我正在尝试使用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;
}
}
是什么原因引起的?谢谢!
答案 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
和+
以及-
和*
应该是同一规则的一部分,因此它们具有相同的优先级:
/