我正在设计一个使用递归下降的解释器,现在我已经开始实现内置方法。
[我正在实现的方法的一个示例是print()
方法,它输出到控制台,就像python的print()
方法和Java的System.out.println()
。
然而,引起我注意的是,有多种方法可以实现这些内置方法。我敢肯定还有很多,但是我已经确定了两种可行的方法来实现这一目标,我正在尝试确定哪种方法是最佳实践。下面的上下文是我在解释器中使用的不同层,这些层大致基于https://www.geeksforgeeks.org/introduction-of-compiler-design/和我遇到的其他教程。
1。为每个内置方法创建一个AST节点。
此方法需要对解析器进行编程,以为每个单独的方法生成一个节点。这意味着每种方法都将存在一个唯一节点。例如:
当在词法分析器中找到TPRINT
标记时,解析器将寻找生成一个节点。
print : TPRINT TLPAREN expr TRPAREN {$$ = new Print($3);}
;
这是打印类的外观。
class Print : public Node {
public:
virtual VariableValue visit_Semantic(SemanticAnalyzer* analyzer) override;
virtual VariableValue visit_Interpreter(Interpreter* interpreter) override;
Node* value;
Print(Node* cvalue) {
value = cvalue;
}
}
[我从那里定义visit_Semantic
和visit_interpreter
方法,并使用从顶部节点的递归访问它们。
我可以想到使用此方法的一些优点/缺点:
优点
Print
节点的visit_interpreter
方法时,它可以直接将响应编程为它的访问方法,直接执行响应。缺点
2。为方法调用节点创建通用AST节点,然后使用查找表确定正在调用的方法。
这涉及创建一个通用节点MethodCall
和语法,以确定是否已调用方法,并带有一些唯一的identifier,例如所引用方法的字符串。然后,当调用MethodCall
的visit_Interpreter
或visit_Semantic
方法时,它将在要执行代码的表中查找。
methcall : TIDENTIFIER TLPAREN call_params TRPAREN {$$ = new MethodCall($1->c_str(), $3);}
;
MethodCall
节点。这里的唯一标识符是std::string methodName
:
class MethodCall : public Node {
public:
virtual VariableValue visit_Semantic(SemanticAnalyzer* analyzer) override;
virtual VariableValue visit_Interpreter(Interpreter* interpreter) override;
std::string methodName;
ExprList *params;
MethodCall(std::string cmethodName, ExprList *cparams) {
params = cparams;
methodName = cmethodName;
}
};
优点:
缺点:
std::string methodName
必须在查找表中进行比较以确定对此的响应。这不像直接编程来响应Node的访问方法那样有效。哪种实践是在编译器/解释器中处理方法的最佳方法?是否总有一些更好的不同做法,或者我还缺少其他缺点/缺点?
我对编译器/解释器设计还很陌生,所以请问我是否弄错了某些术语。
您绝对应该使用表查找。它使您的工作变得容易得多。另外,请考虑用户定义的功能!然后,您肯定需要一张桌子。