# 产生式和语法动作

在impl块内部将产生式和语义动作结合起来，以函数的形式编写代码，一个例子如下：

```rust
#[rule(Expr -> Sub Expr)]
#[prec(UMinus)] // 可选
fn expr_neg(s: Token, r: Expr<'p>) -> Expr<'p> {
  mk_expr(s.loc(), Unary { op: UnOp::Neg, r: Box::new(r) }.into())
}
```

以下几点需要注意：

1. 这个生命周期`'p`是在impl处提供的，**p是硬编码的**，这意味着你把所有的`'p`都换成`'a`或者别的什么的，是不能work的。当然，我可以在过程宏中更加精细地提取代码中的这些信息，但是我想了一下，感觉收益明显小于付出，不多费事了
2. 它虽然看起来是一个函数，但是其实**不是**，事实上它的函数体被提取出来当成代码段直接嵌在一个大函数中，**所以不要在里面写return之类可以改变控制流的语句**
3. lalr1会对参数进行类型检查，保证一个非终结符只能对应于一个类型，保证一个终结符一定对应一个Token类型，但是这检查完全是基于**字符串比较**的，并不会考虑任何rust层面的语义信息(如类型别名)，所以误判可能会给你带来一些困扰。编写类型也可能带来相当程度的代码冗余，但是我认为这是有必要的，这是为了最大程度的保证生成器生成出来的代码可以被rustc编译。如果把生成器看成一个编译器，而把rustc的运行看成它的结果的运行，这就是在尝试把错误在编译期检查出来

关于第三点还可以补充一些。java框架中采用`SemValue`作为lr分析中value stack的内容，`SemValue`就是一堆value stack中可能出现的类型组成的一个class，除了依赖于文档(不存在的)或者观察+记忆，使用者没办法知道哪个成员在这个产生式中是有效的，哪个成员是需要写入的，这带来了很大的不便，编译器也不能利用类型信息来检查代码。lalr1中的value stack是所有可能出现的类型的一个enum，在传递给语法动作前，会先取出栈中对应类型的成员(如果类型不对就`panic!()`)，使用者可以很好地利用编译器的检查和ide的提示。我认为这个好处完全足以弥补编写类型带来的麻烦。

此外，对于产生式还可以添加一个额外的配置，`#[prec(Term)]`，这是指定这条产生式的优先级与终结符`Term`相同。具体的会在"解决冲突"一节描述。
