所以我一直在阅读有关词法分析器,解析器,解释器甚至编译的内容。
对于我试图实现的语言,我选择了一种Recrusive Descent Parser。由于该语言的原始语法具有左递归,我不得不稍微重写它。
这是我的语法的简化版本(请注意,它不是任何标准格式语法,但有点伪,我想,这就是我在文档中找到它的方式):
expr:
-----
expr + expr
expr - expr
expr * expr
expr / expr
( expr )
integer
identifier
为了摆脱左递归,我把它变成了这个(注意添加了NOT运算符):
expr:
-----
expr_term {+ expr}
expr_term {- expr}
expr_term {* expr}
expr_term {/ expr}
expr_term:
----------
! expr_term
( expr )
integer
identifier
然后使用以下子例程(简化的伪代码ish)遍历我的标记:
public string Expression()
{
string term = ExpressionTerm();
if (term != null)
{
while (PeekToken() == OperatorToken)
{
term += ReadToken() + Expression();
}
}
return term;
}
public string ExpressionTerm()
{
//PeekToken and ReadToken accordingly, otherwise return null
}
这个有效!调用Expression
后的结果总是等于给出的输入。
这让我想知道:如果我在这些子程序中创建AST节点而不是字符串,并使用中缀评估器(也记住运算符的关联性和优先级等)来评估AST,我不会得到同样的结果?
如果我这样做,那么为什么有这么多的主题涵盖“修复左递归,记住关联性和什么不是”当它实际上“死简单”解决或甚至看似无问题?或者它真的是由此产生的AST人员关注的结构(而不是它的评估结果)? ,是否有人可以解决问题,我可能也会把它弄错,哈哈!
答案 0 :(得分:1)
AST 的形状很重要,因为a+(b*3)
通常与(a+b)*3
不一样,并且可以合理地期望解析器指出哪些{{1}意味着。
通常,AST实际上会删除括号。 (解析树不会,但预计AST会抽象出语法噪音。)所以a+b*3
的AST看起来应该是这样的:
a+(b*3)
如果您的语言遵循通常的数学符号约定,那么 Sum
|
+---+---+
| |
Var Prod
| |
a +---+---+
| |
Var Const
| |
b 3
的AST也将遵循。
"中缀评估员" - 或者我想象你指的是 - 只是另一个解析器。所以,是的,如果您以后很乐意解析,那么您现在不必解析。
顺便说一句,表明你可以按照你阅读它们的顺序将令牌放回到一起并不能真正证明解析器的功能。只需回显标记生成器的输出,就可以做到这一点。
答案 1 :(得分:0)
处理表达式(数学或其他)的标准和最简单方法是使用反映预期关联和运算符优先级的规则层次结构:
expre = sum
sum = addend '+' sum | addend
addend = term '*' addend | term
term = '(' expre ')' | '-' integer | '+' integer | integer
这样的语法使解析或抽象树可以直接评估。您可以扩展规则层次结构以包括幂和位运算符,或者使其成为and
or
和比较的逻辑表达式的层次结构的一部分。