代码之家  ›  专栏  ›  技术社区  ›  Geff

为什么ANTLR生成的解析器重用上下文对象?

  •  0
  • Geff  · 技术社区  · 6 年前

    我想添加递归的特性。

    FunctionCallContext 为他们准备了一本字典。我可以成功地使用它们一次。同样,当我再次从自身(递归地)调用同一个函数时,解析器为新函数调用创建一个新的上下文对象,正如我所期望的那样。

        grammar BatshG;
    /*
     * Parser Rules
     */
    compileUnit: ( (statement) | functionDef)+;
    statement:      print  ';'
                |   println ';'
                |   assignment ';'
                |   loopWhile
                |   branch 
                |   returnStatement ';'
                |   functionCall ';'
    ;
    
    branch:
              'if' '(' condition=booleanexpression ')' 
                    trueBranch=block 
                ('else' falseBranch=block)?;
    loopWhile:
              'while' '(' condition=booleanexpression ')' 
                    whileBody=block 
    ;
    
    block: 
                    statement
                |   '{' statement* '}';
    
    numericexpression:      
                    MINUS onepart=numericexpression               #UnaryMinus
                |   left=numericexpression op=('*'|'/') right=numericexpression  #MultOrDiv
                |   left=numericexpression op=('+'|'-') right=numericexpression  #PlusOrMinus
                |   number=NUMERIC                                      #Number
                |   variableD                                       #NumVariable
    ;
    stringexpression: 
                    left=stringexpression PLUSPLUS right=stringexpression #Concat   
                |   string=STRING #String
                |   variableD                                       #StrVariable
                |   numericexpression #NumberToString
    ;
    booleanexpression:
                            left=numericexpression relationalOperator=('<' | '>' | '>=' | '<=' | '==' | '!=' ) right=numericexpression #RelationalOperation
                        |   booleanliteral #Boolean
                        |   numericexpression #NumberToBoolean
    ;
    booleanliteral: trueConst | falseConst ;
    trueConst :  'true'          ;
    falseConst :  'false'                ;
    
    assignment : varName=IDENTIFIER EQUAL  right=expression;
    expression: numericexpression | stringexpression | functionCall | booleanexpression;
    println: 'println' '(' argument=expression ')'; 
    print: 'print' '(' argument=expression ')';
    
    functionDef: 'function' funcName= IDENTIFIER
                            '(' 
                                (functionParameters=parameterList)?
                            ')'
                                '{' 
                                    statements=statementPart?
                                '}' 
    ;
    
    statementPart:  statement* ;
    returnStatement: ('return' returnValue=expression );
    parameterList : paramName=IDENTIFIER (',' paramName=IDENTIFIER)*;
    
    functionCall: funcName=IDENTIFIER '(' 
                (functionArguments=argumentList)?
    ')';
    argumentList: expression (',' expression)*;
    variableD: varName=IDENTIFIER;
    ///*
    // * Lexer Rules
    // */
    NUMERIC: (FLOAT | INTEGER);
    PLUSPLUS: '++';
    MINUS: '-';
    IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
    
    EQUAL  :            '='              ;
    
    STRING : '"' (~["\r\n] | '""')* '"'          ;
    
    INTEGER: [0-9] [0-9]*;
    DIGIT : [0-9]                       ;
    FRAC : '.' DIGIT+                   ;
    EXP : [eE] [-+]? DIGIT+  ;
    FLOAT : DIGIT* FRAC EXP?             ;
    WS: [ \n\t\r]+ -> channel(HIDDEN);
    
        ///*
        // * Lexer Rules
        // */
        NUMERIC: (FLOAT | INTEGER);
        PLUSPLUS: '++';
        MINUS: '-';
        IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
        EQUAL  :            '='              ;
        STRING : '"' (~["\r\n] | '""')* '"'          ;
        INTEGER: [0-9] [0-9]*;
        DIGIT : [0-9]                       ;
        FRAC : '.' DIGIT+                   ;
        EXP : [eE] [-+]? DIGIT+  ;
        FLOAT : DIGIT* FRAC EXP?             ;
        WS: [ \n\t\r]+ -> channel(HIDDEN);
    

    我编写的解析器的部分类(不是生成的部分):

        public partial class BatshGParser
    {
        //"extensions" for contexts:
        public partial class FunctionCallContext
        {
            private Dictionary<string, object> localVariables = new Dictionary<string, object>();
            private bool isFunctionReturning;
            public FunctionCallContext()
            {
               localVariables = new Dictionary<string, object>();
               isFunctionReturning = false;
            }
    
            public Dictionary<string, object> LocalVariables { get => localVariables; set => localVariables = value; }
            public bool IsFunctionReturning { get => isFunctionReturning; set => isFunctionReturning = value; }
        }
    
        public partial class FunctionDefContext
        {
            private List<string> parameterNames;
    
            public FunctionDefContext()
            {
                parameterNames = new List<string>();
            }
            public List<string> ParameterNames { get => parameterNames; set => parameterNames = value; }
        }
    }
    

             public class BatshGVisitor : BatshGBaseVisitor<ResultValue>
        {
            public ResultValue Result { get; set; }
            public StringBuilder OutputForPrint { get; set; }
            private Dictionary<string, object> globalVariables = new Dictionary<string, object>();
            //string = function name
            //object = parameter list
            //object =  return value
            private Dictionary<string, Func<List<object>, object>> globalFunctions = new Dictionary<string, Func<List<object>, object>>();
            private Stack<BatshGParser.FunctionCallContext> actualFunctions = new Stack<BatshGParser.FunctionCallContext>();
    
            public override ResultValue VisitCompileUnit([NotNull] BatshGParser.CompileUnitContext context)
            {
                OutputForPrint = new StringBuilder("");
    
                isSearchingForFunctionDefinitions = true;
                var resultvalue = VisitChildren(context);
                isSearchingForFunctionDefinitions = false;
                resultvalue = VisitChildren(context);
                Result = new ResultValue() { ExpType = "string", ExpValue = resultvalue.ExpValue ?? null };
    
                return Result;
            }
            public override ResultValue VisitChildren([NotNull] IRuleNode node)
            {
                if (this.isSearchingForFunctionDefinitions)
                {
                    for (int i = 0; i < node.ChildCount; i++)
                    {
                        if (node.GetChild(i) is BatshGParser.FunctionDefContext)
                        {
                            Visit(node.GetChild(i));
                        }
                    }
                }
                return base.VisitChildren(node);
            }
            protected override bool ShouldVisitNextChild([NotNull] IRuleNode node, ResultValue currentResult)
            {
                if (isSearchingForFunctionDefinitions)
                {
                    if (node is BatshGParser.FunctionDefContext)
                    {
                        return true;
                    }
                    else
                        return false;
                }
                else
                {
                    if (node is BatshGParser.FunctionDefContext)
                    {
                        return false;
                    }
                    else
                        return base.ShouldVisitNextChild(node, currentResult);
                }
            }
    
            public override ResultValue VisitFunctionDef([NotNull] BatshGParser.FunctionDefContext context)
            {
    
                string functionName = null;
                functionName = context.funcName.Text;
                if (context.functionParameters != null)
                {
                    List<string> plist = CollectParamNames(context.functionParameters);
                    context.ParameterNames = plist;
                }
                if (isSearchingForFunctionDefinitions)
                    globalFunctions.Add(functionName,
                    (
                    delegate(List<object> args)
                        {
                            var currentMethod = (args[0] as BatshGParser.FunctionCallContext);
                            this.actualFunctions.Push(currentMethod);
                            //args[0] is the context
                            for (int i = 1; i < args.Count; i++)
                            {
    
                                currentMethod.LocalVariables.Add(context.ParameterNames[i - 1],
                                    (args[i] as ResultValue).ExpValue
                                    );
                            }
    
                        ResultValue retval = null;
                            retval = this.VisitStatementPart(context.statements);
    
                            this.actualFunctions.Peek().IsFunctionReturning = false;
                            actualFunctions.Pop();
                            return retval;
                        }
                     )
                );
                return new ResultValue()
                {
    
                };
            }       
    
            public override ResultValue VisitStatementPart([NotNull] BatshGParser.StatementPartContext context)
            {
                if (!this.actualFunctions.Peek().IsFunctionReturning)
                {
                    return VisitChildren(context);
                }
                else
                {
                    return null;
                }
            }
    
            public override ResultValue VisitReturnStatement([NotNull] BatshGParser.ReturnStatementContext context)
            {
                this.actualFunctions.Peek().IsFunctionReturning = true;
                ResultValue retval = null;
                if (context.returnValue != null)
                {
                    retval = Visit(context.returnValue);
                }
    
                return retval;
            }
    
                    public override ResultValue VisitArgumentList([NotNull] BatshGParser.ArgumentListContext context)
            {
                List<ResultValue> argumentList = new List<ResultValue>();
                foreach (var item in context.children)
                {
                    var tt = item.GetText();
                    if (item.GetText() != ",")
                    {
                        ResultValue rv = Visit(item);
                        argumentList.Add(rv);
                    }
                }
                return
                    new ResultValue()
                    {
                        ExpType = "list",
                        ExpValue = argumentList ?? null
                    };
            }
    
            public override ResultValue VisitFunctionCall([NotNull] BatshGParser.FunctionCallContext context)
            {
                string functionName = context.funcName.Text;
                int hashcodeOfContext = context.GetHashCode();
                object functRetVal = null;
                List<object> argumentList = new List<object>()
                {
                    context
                    //here come the actual parameters later
                };
    
                ResultValue argObjects = null;
                if (context.functionArguments != null)
                {
                    argObjects = VisitArgumentList(context.functionArguments);
                }
    
                if (argObjects != null )
                {
                    if (argObjects.ExpValue is List<ResultValue>)
                    {
                        var argresults = (argObjects.ExpValue as List<ResultValue>) ?? null;
                        foreach (var arg in argresults)
                        {
                            argumentList.Add(arg);
                        }
                    }
                }
    
                if (globalFunctions.ContainsKey(functionName))
                {
                    {
                        functRetVal = globalFunctions[functionName]( argumentList );
                    }
                }
    
                return new ResultValue()
                {
                    ExpType = ((ResultValue)functRetVal).ExpType,
                    ExpValue = ((ResultValue)functRetVal).ExpValue
                };
            }
    
            public override ResultValue VisitVariableD([NotNull] BatshGParser.VariableDContext context)
            {
    
                object variable;
                string variableName = context.GetChild(0).ToString();
                string typename = "";
    
                Dictionary<string, object> variables = null;
                if (actualFunctions.Count > 0)
                {
                    Dictionary<string, object> localVariables = 
                        actualFunctions.Peek().LocalVariables;
                    if (localVariables.ContainsKey(variableName))
                    {
                        variables = localVariables;
                    }
                }
                else
                {
                    variables = globalVariables;
                }
    
                if (variables.ContainsKey(variableName))
                {
                    variable = variables[variableName];
    
                    typename = charpTypesToBatshTypes[variable.GetType()];
                }
                else
                {
    
                    Type parentContextType = contextTypes[context.parent.GetType()];
                    typename = charpTypesToBatshTypes[parentContextType];
                    variable = new object();
    
                    if (typename.Equals("string"))
                    {
                        variable = string.Empty;
                    }
                    else
                    {
                        variable = 0d;
                    }
                }
    
                return new ResultValue()
                {
                    ExpType = typename,
                    ExpValue = variable
                };
            }           
            public override ResultValue VisitAssignment([NotNull] BatshGParser.AssignmentContext context)
            {
                string varname = context.varName.Text;
                ResultValue varAsResultValue = Visit(context.right);
                Dictionary<string, object> localVariables = null;
    
                if (this.actualFunctions.Count > 0)
                {
                    localVariables = 
                        actualFunctions.Peek().LocalVariables;
                    if (localVariables.ContainsKey(varname))
                    {
                        localVariables[varname] = varAsResultValue.ExpValue;
                    }
                    else
                    if (globalVariables.ContainsKey(varname))
                    {
                        globalVariables[varname] = varAsResultValue.ExpValue;
                    }
                    else
                    {
                        localVariables.Add(varname, varAsResultValue.ExpValue);
                    }
                }
                else
                {
                    if (globalVariables.ContainsKey(varname))
                    {
                        globalVariables[varname] = varAsResultValue.ExpValue;
                    }
                    else
                    {
                        globalVariables.Add(varname, varAsResultValue.ExpValue);
                    }
                }
                return varAsResultValue;
            }
    }
    

    1 回复  |  直到 6 年前
        1
  •  3
  •   sepp2k    5 年前

    FunctionCallContext 对象,这些对象将是唯一的。即使对于两个完全相同的函数调用,它们也必须是相同的,因为它们还包含元数据,比如函数调用出现在源代码中的什么位置——这显然会在调用之间有所不同,即使其他所有调用都是相同的。

    function f(x) {
      return f(x);
    }
    print(f(x));
    

    这将创建一个正好包含两个元素的树 函数调用上下文 对象-一个用于第2行,一个用于第4行。它们都是不同的-它们都有引用函数名的子节点 f 争论呢 x

    什么会导致问题?

    事实上,您多次看到同一个节点只是因为您多次访问树的同一部分。对于您的用例来说,这是一件非常正常的事情,但是在您的用例中,这会导致一个问题,因为您在对象中存储了可变数据,假设您得到了一个新的 FunctionCall 对象,而不是每次函数调用出现在源代码中时。

    这不是解析树的工作方式(它们表示源代码的结构,而不是运行时可能发生的调用序列),因此您不能使用 对象来存储有关特定运行时函数调用的信息。一般来说,我认为把可变状态放入上下文对象是个坏主意。

    相反,您应该将可变状态放入访问者对象中。对于您的特定问题,这意味着有一个包含每个运行时函数调用的局部变量的调用堆栈。每次函数开始执行时,都可以将一个帧推送到堆栈上,每次函数退出时,都可以将其弹出。这样,堆栈顶部将始终包含当前正在执行的函数的局部变量。


    PS:这与你的问题无关,但算术表达式中通常的优先规则是, + - * 具有与相同的优先级 / / * 以及 高于 . 这意味着,例如 9 * 5 / 3 5 ,应该是什么时候 15 (假设整数运算的常规规则)。

    要解决这个问题 + - 以及 * / 应该是同一规则的一部分,因此它们具有相同的优先级:

    | left=numericexpression op=('*'|'/') right=numericexpression #MulOrDiv
    | left=numericexpression op=('+'|'-') right=numericexpression #PlusOrMinus