我最近对 LALR 的了解足以编写一个LALR 生成器,并且我正在尝试为它构建一个 java 或 c# 样式的语法(这里指定了它的开头)。
我知道编写解析器生成器需要付出额外的努力,比如重新发明轮子(为什么不使用 Antlr?),但我的目标是引导一个爱好操作系统,它可以在不依赖第三方工具链的情况下自行编译。我的问题不在于生成器,而在于语法。
我遇到了减少/减少语句和表达式的歧义。
我知道如何解决某些类型的歧义,例如 dangling-else,但是这几个对我来说并不直观,他们让我难过。
解决这些问题的最佳方法是什么?另外,是否有可以用来帮助可视化解决方案的原型制作工具?或者,我应该回到第一方并尝试为语法实现 GLR 解析器生成器吗?
这些声明是合法的:
Generic.List<int> myVar1 = x + 4, myVar2; // stmt -> var-decl ;
// var-decl -> type-name var-decl-list
t = 99; // simple-stmt -> assign
i++; // simple-stmt -> incr-decr
// incr-decr -> primary-expr ++
json.deserialize<list<int>>(obj); // simple-stmt -> call
// call -> primary-expr ( params )
// ... -> primary-expr . basic-name ( params )
// ... -> basic-name . basic-name ( params )
这是它的设置方式:
basic-name : ident < type-list >
| ident
nested-name : nested-name . basic-name
| basic-name
basic-type : int | bool | ...
type-name : nested-name
| basic-type
stmt : var-decl ;
| simple-stmt ;
| ...
var-decl : type-name var-decl-list
var-decl-list : var-decl-list , var-init
| var-init
var-init : ident assign-op expression
| ident
simple-stmt : assign
| call
| incr-decr
expr : assign-expr
assign-expr : assign
| cond-expr
assign : unary-expr assign-op expr
...
rel-expr : rel-expr < shift-expr
...
| shift-expr
...
unary-expr : unary-op primary-expr
| primary-expr
unary-op : + - ! ~ ++ -- // Prefix operators
| ( type-name ) // Conversion operator
primary-expr : call
| primary-expr . basic-name
| primary-expr ++
| ( expr )
...
| basic-name
call : primary-expr ( params )
incr-decr : primary-expr ++
| -- primary-expr
| ...
因此,当解析器期待一个语句时,*LR(k) 项集内核是method-body -> { * stmts-opt }
并且状态的完整项集看起来像这样:
method-body -> { * stmts-opt }
stmts-opt -> * stmts
stmts-opt -> *
stmts -> * stmts stmt
stmt -> * var-decl ;
stmt -> * simple-stmt ;
var-decl -> * type-name var-decl-list
simple-stmt -> * assign
simple-stmt -> * call
simple-stmt -> * incr-decr
type-name -> * nested-name
type-name -> * basic-type
nested-name -> * nested-name . basic-name
nested-name -> * basic-name
basic-name -> * ident < type-list >
basic-name -> * ident
assign -> * unary-expr assign-op expr
unary-expr -> * unary-op primary-expr
unary-expr -> * primary-expr
unary-op -> * ( typename )
unary-op -> * ! | ~ | ...
primary-expr -> * call
primary-expr -> * primary-expr . basic-name
primary-expr -> * primary-expr ++
primary-expr -> * basic-name
primary-expr -> * ( expr )
call -> * primary-expr ( params )
incr-decr -> * primary-expr ++
...
当标识符移动时,下一个状态是:
basic-name -> ident *
basic-name -> ident * < type-list >
它被解析或减少,并将下一个状态带到:
nested-name -> basic-name *
primary-expr -> basic-name *
潜在的冲突。在父状态中,前瞻没有帮助,因为nested-name
and中有一个点primary-expr
。哦,天哪,让我们尝试通过嵌套名称减少:
type-name -> nested-name *
nested-name -> nested-name * . basic-name
这里没什么可看的......现在,改为减少如何primary-expr
:
unary-expr -> primary-expr *
primary-expr -> primary-expr * . basic-name
primary-expr -> primary-expr * ++
call -> primary-expr * ( params )
incr-decr -> primary-expr * ++
...
现在当我们 shift ++ 时,我们得到:
primary-expr -> primary-expr ++ *
incr-decr -> primary-expr ++ *
...另一个减少 - 减少冲突。
让我们尝试改变(
而不是ident
:
primary-expr -> ( * expr )
unary-op -> ( * type-name )
expr -> * assign-expr
assign-expr -> * assign
assign-expr -> * cond-expr
assign -> * unary-expr assign-op expr
unary-expr -> * unary-op primary-expr
unary-expr -> * primary-expr
unary-op -> * ( typename )
unary-op -> * ! | ~ | ...
primary-expr -> * call
primary-expr -> * primary-expr . basic-name
primary-expr -> * primary-expr ++
primary-expr -> * basic-name
primary-expr -> * ( expr )
call -> * primary-expr ( params )
cond-expr -> * ...
...
rel-expr -> * rel-expr < shift-expr
rel-expr -> * shift-expr
...
type-name -> * nested-name
type-name -> * basic-type
nested-name -> * nested-name . basic-name
nested-name -> * basic-name
basic-name -> * ident < type-list >
basic-name -> * ident
ident
将or(
移到堆栈上时也会出现同样的问题。
这些只是我到目前为止遇到的那些。由于basic-name
优先于rel-expr
,我假设类似的东西x < n
会被解释为basic-name -> ident < type-list *
,如果它实际上是一个关系表达式,则会出错。
我的大脑已经到了可以真正需要一些帮助的地步。