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

时间:2018-12-11 13:09:40

标签: c# object scope antlr interpreter

我正在尝试使用ANTLR为一种简单的编程语言创建解释器。 我想添加递归功能。

到目前为止,我已经实现了定义和调用函数,并且可以使用多个return语句以及局部变量。为了获得局部变量,我为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 个答案:

答案 0 :(得分:2)

  

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

不是。源代码中的每个函数调用将仅对应一个FunctionCallContext对象,并且这些对象将是唯一的。即使对于两个完全相同的函数调用,它们也必须如此,因为它们还包含元数据,例如函数调用在源代码中出现的位置-并且即使其他所有条件都相同,两次调用之间也显然会有所不同。 / p>

为说明这一点,请考虑以下源代码:

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

这将创建一棵仅包含两个FunctionCallContext对象的树-一个用于第2行,一个用于第4行。它们都将是不同的-它们都将具有引用函数名称{{1}的子节点}和参数f,但它们将具有不同的位置信息和不同的哈希码-子节点也一样。这里什么都没有重复使用。

  

什么可能导致问题?

您多次看到同一节点的事实仅仅是由于您多次访问真实的同一部分。对于您的用例来说,这是完全正常的事情,但是在您的情况下,这会引起问题,因为您将可变数据存储在对象中,并假设每次函数调用发生时都会得到一个新的x对象在运行时-而不是每次函数调用都出现在源代码中。

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

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


PS:这与您的问题无关,但是算术表达式中的通常优先级规则使得FunctionCallContext的优先级与+相同,而-的优先级相同为*。在您的语法中,/的优先级大于/的优先级,而*的优先级高于-。这意味着,例如+在应该为9 * 5 / 3时将求值到5(假定整数算术的常用规则)。

要修复此15+以及-*应该是同一规则的一部分,因此它们具有相同的优先级:

/