一个完整的例子

下面我们简单讲解一下一个解析四则运算表达式的parser,我们会使用除了#[use_unsafe]之外的所有选项。

struct Parser; // 用户希望成为parser的struct

#[lalr1(Expr)]
#[verbose("verbose.txt")]
#[log_token]
#[log_reduce]
#[expand]
#[show_fsm("fsm.dot")]
#[show_dfa("dfa.dot")]
#[lex(r#"
# 描述终结符的优先级和结合性,越靠后优先级越高;结合性分为left,right和no_assoc
priority = [
  { assoc = 'left', terms = ['Add', 'Sub'] },
  { assoc = 'left', terms = ['Mul', 'Div', 'Mod'] },
  { assoc = 'no_assoc', terms = ['UMinus'] },
  { assoc = 'no_assoc', terms = ['RPar'] },
]

# 描述识别出终结符的正则表达式
[lexical]
'\(' = 'LPar'
'\)' = 'RPar'
'\+' = 'Add'
'-' = 'Sub'
'\*' = 'Mul'
'/' = 'Div'
'%' = 'Mod'
'\d+' = 'IntLit'
'\s+' = '_Eps'
"#)]
impl Parser {
  // 为了简单起见,这里都没有实现错误处理
  // 任何一个非终结符的类型必须在整个parser中是统一的,例如这里的Expr是i32类型
  // 任何一个终结符必须具有Token类型
  // 函数的名字其实是可以随便取的,最终的代码中并不会被保留,不过为了可读性还是最好符合本条规则的含义
  #[rule(Expr -> Expr Add Expr)]
  fn expr_add(l: i32, _op: Token, r: i32) -> i32 { l + r }
  #[rule(Expr -> Expr Sub Expr)]
  fn expr_sub(l: i32, _op: Token, r: i32) -> i32 { l - r }
  #[rule(Expr -> Expr Mul Expr)]
  fn expr_mul(l: i32, _op: Token, r: i32) -> i32 { l * r }
  #[rule(Expr -> Expr Div Expr)]
  fn expr_div(l: i32, _op: Token, r: i32) -> i32 { l / r }
  #[rule(Expr -> Expr Mod Expr)]
  fn expr_mod(l: i32, _op: Token, r: i32) -> i32 { l % r }
  #[rule(Expr -> Sub Expr)]
  #[prec(UMinus)] // 本条产生式与UMinus相同,比二元运算符都高
  fn expr_neg(_op: Token, r: i32) -> i32 { -r }
  #[rule(Expr -> LPar Expr RPar)]
  fn expr_paren(_l: Token, i: i32, _r: Token) -> i32 { i }
  #[rule(Expr -> IntLit)]
  fn expr_int(i: Token) -> i32 { std::str::from_utf8(i.piece).unwrap().parse().unwrap() }
}

利用#[expand],得到过程宏输出的代码如下(为了美观,代码经过了格式化):

来跑一个实际的例子:

结果显然是正确的,可以看一下#[log_token]#[log_reduce]的输出:

比较枯燥,其实也没啥看的必要,只有出错的时候用于调试可能才有意义。

最后看一下那几个文件中都输出了什么内容。先看最简单的dfa的图形,大概是这样的:

dfa

再看一个巨大的lr fsm(美观起见,我把Add等终结符换成了对应的符号,_Eof替换成了#):

fsm

值得注意的是这个lr fsm是没有经过解决冲突的处理的,例如图中的这一处:

conflict

显然是有移进-规约冲突的。

但是文本表示的verbose信息中体现了冲突的解决,这个片段对应于verbose.txt中的:

可见这里所有冲突选择都被优先级和结合性的约束给消除了。

Last updated

Was this helpful?