让我向您介绍我目前的项目(这显然会产生我面临的问题,因此我在这里发布)。我正在写一个所谓的"编译器"对于一种简单的语言。我已经构建了一个VM来运行生成的字节码,关联的Lexer(所有这个项目都是一个可选的赋值)。我的问题在于表达式解析位。我使用Dijkstra的Shunting Yard算法将我的后缀表达式转换为相应的AST结构,如果 - 并且只是 - 我尝试实现函数调用和数组下标,我无法正确调整算法以生成正确的AST。 / p>
这里是算法实现的一个示例(我相信一切都是不言自明的)
func (p *parser) shuntingyard(input token.TQueue) *ast.Node {
var operands ast.NStack
var operators *token.TStack
operands = make(ast.NStack, 0)
operators = token.TokenStack()
for tok := input.Dequeue(); tok.Sym != "EOF"; tok = input.Dequeue() {
switch tok.Kind {
case "LParen":
operators.Push(tok)
case "RParen":
for {
// pop item ("(" or operator) from stack
if operators.Empty() {
p.errorf("Unmatched parenthesis on line %d, expected '(' to match closing parenthesis in expression", p.lno)
}
op := operators.Pop()
if op.Sym == "(" {
break // discard "("
}
if isUnary(op.Sym) {
node := ast.MakeNode(*op)
node.AddChild(operands.Pop())
operands.Push(node)
break
}
RHS := operands.Pop()
LHS := operands.Pop()
operands.Push(ast.MakeParentNode(*op, RHS, LHS))
}
default:
if o1, isOp := prOps[tok.Sym]; isOp {
// token is an operator
for !operators.Empty() {
// consider top item on stack
op := operators.PeekTop()
if o2, isOp := prOps[op.Sym]; !isOp || o1.prec > o2.prec || o1.prec == o2.prec && o1.rAssoc {
break
}
// top item is an operator that needs to come off
op = operators.Pop()
if isUnary(op.Sym) {
node := ast.MakeNode(*op)
node.AddChild(operands.Pop())
operands.Push(node)
break
}
RHS := operands.Pop()
LHS := operands.Pop()
operands.Push(ast.MakeParentNode(*op, RHS, LHS))
}
// push operator (the new one) to stack
operators.Push(tok)
} else {
operands.Push(ast.MakeNode(*tok))
}
}
}
// drain stack to result
for !operators.Empty() {
if operators.PeekTop().Sym == "(" {
p.errorf("Unmatched parenthesis on line %d, expected ')' to match previous parenthesis in expression", p.lno)
}
RHS := operands.Pop()
LHS := operands.Pop()
operands.Push(ast.MakeParentNode(*operators.Pop(), RHS, LHS))
}
result := operands.Pop()
for !operands.Empty() {
result.AddSibling(operands.Pop())
}
return result
}
当您遇到来自运算符堆栈的二元运算符时,这个想法非常简单,您可以从操作数堆栈中弹出两个节点,这两个节点设置为运算符遇到的子节点(如果运算符是一元的话,则为一个节点)。然后将结果节点推送到操作数堆栈上。
例如,这个输入:
c = 0;
a = 1 << 3 + 2;
导致(有效)AST:
┣━ Assign =
┃ ┣━ Identifier 'a'
┃ ┗━ Lshift <<
┃ ┣━ Number 1
┃ ┗━ Plus +
┃ ┣━ Number 3
┃ ┗━ Number 2
┗━ Assign =
┣━ Identifier 'c'
┗━ Number 0
但是,每当我尝试嵌套函数调用时输出都是错误的:
foo(bar(0))
结果(显然)不正确:
┣━ Number 0
┣━ Function bar
┗━ Function foo
我什么时候应该:
┗━ Function foo
┗━ Function bar
┗━ Number 0
我的第一个问题是:我需要对我的实现进行哪些修改以支持函数调用的AST生成?因为我在互联网上找到的关于SY算法的所有内容总是生成一个RPN string ...
另一件事是输入如:
-i++;
生成有效输出:
┗━ UnaryMinus -u
┗━ PlusPlus ++
┗━ Identifier 'i'
但是(-i++);
报告和不平衡的括号表达式?
以前与运营商合作的map
是:
var prOps = map[string]struct {
prec int
rAssoc bool
}{
"++" : {50, false}, "--" : {50, false},
"." : {40, false}, "[" : {40, false},
"!" : {30, true}, "~" : {30, true},
"-u" : {29, true}, "--u" : {29, true}, "++u": {29, true},
"**" : {28, true},
"*" : {27, false}, "/" : {27, false}, "%" : {27, false},
"+" : {26, false}, "-" : {26, false},
">>" : {25, false}, "<<" : {25, false},
">" : {24, false}, ">=" : {24, false}, "<" : {24, false}, "<=" : {24, false},
"==" : {23, false}, "!=" : {23, false},
"&" : {22, false},
"^" : {21, false},
"|" : {20, false},
"&&" : {19, false},
"||" : {18, false},
"=" : {10, true}, "+=" : {10, true}, "-=" : {10, true}, "*=" : {10, true},
"/=" : {10, true}, "**=" : {10, true}, "^=" : {10, true}, "~=" : {10, true},
"|=" : {10, true}, "&=" : {10, true}, "%=" : {10, true}, "<<=" : {10, true},
">>=": {10, true},
"," : {9, false},
}
其中-u
,++u
,--u
是有意义的运算符的实例,但不是真的。
另一方面,Shunting Yard算法是我真正需要的吗?我的意思是,如果使用此算法is feasable解析面向对象的表达式,那么这是我的最佳选择吗?
我还在更广泛的意义上阅读(很多)解析器。我相信,从我读到的,写一个递归下降解析器是我在合理的时间内实现我的目标的最好机会。我读了一些源代码,但解析器往往不适合读者。 关于解析器和AST生成,我应该为这个项目保留什么指南?
请告诉我这个帖子是否有任何问题:)请问我是否错过链接/提供所需的任何资源!
希望得到一些有用的答案。