You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写适配PEST prec climber的无左递归Solidity表达式PEG文法

方案选型

优先使用PEST内置的prec climber(优先级爬升器)处理表达式左递归问题,不推荐手动按优先级分层编写规则消除左递归,核心原因如下:

  • 手动分层需要为每一级优先级单独定义非终结符,Solidity表达式共有17个优先级层级,加上前缀、后缀、调用、索引等逻辑,文法规模会膨胀3倍以上,后续调整运算符、修改优先级需要改动多处规则,维护成本极高,且很容易因规则调用顺序错误重新引入左递归或优先级逻辑错误。
  • PEST原生提供的prec climber不需要手动改写文法消除左递归,只需要定义基础原子表达式、前缀/后缀/中缀算子的优先级与结合性,解析阶段会自动按优先级规则构造语法树,规则简洁,调整优先级只需要修改对应算子的优先级数值即可,出错概率极低。
文法调整步骤

调整核心思路是把原规则中所有规则开头直接递归调用expression的左递归结构拆分,将原子表达式、后缀操作单独抽离,所有中缀、前缀、三元、赋值运算交给prec climber处理。

1. 定义基础原子表达式

原子表达式是所有表达式的最小匹配单元,规则开头不能递归调用expression,把原规则中无左递归的基础项归入此类,注意补充原规则缺失的字面量匹配:

// 基础原子表达式
primary = {
    Payable ~ callArgumentList
    | Type ~ LParen ~ typeName ~ RParen
    | New ~ typeName
    | tupleExpression
    | inlineArrayExpression
    | identifier
    | literal // 需自行补充:包含整数、布尔值、字符串、地址、十六进制字面量等基础值
}

2. 定义后缀操作

后缀操作优先级高于所有前缀、中缀运算,左结合,匹配所有跟在原子表达式后面的索引、成员访问、调用、后缀自增自减逻辑:

// 后缀操作
postfix = {
    LBrack ~ expression? ~ RBrack // 数组索引 arr[i]
    | LBrack ~ expression? ~ Colon ~ expression? ~ RBrack // 数组切片 arr[i:j]
    | Period ~ (identifier | Address) // 成员访问 obj.member
    | LBrack ~ (namedArgument ~ (Comma ~ namedArgument)*)? ~ RBrack // 命名参数调用/索引
    | callArgumentList // 普通函数调用 fn(arg1, arg2)
    | Inc | Dec // 后缀自增自减 a++、a--
}

// 绑定所有后缀操作的基础表达式,无左递归
primary_with_postfix = {
    primary ~ postfix*
}

这部分规则不会触发左递归:所有后缀匹配都以LBrack/Period/LParen等token开头,只有匹配完前面的primary内容后才会递归匹配内部的expression,不会出现规则开头直接递归的情况。

3. 定义前缀、中缀算子规则,接入prec climber

按Solidity官方定义的运算符优先级(数值越大优先级越高)、结合性配置算子,核心expression规则直接调用prec climber即可:

// 前缀算子
prefix_op = _{
    (Inc | Dec | Not | BitNot | Delete | Sub)
}

// 中缀/三元/赋值算子,优先级从低到高对应数值0-12
infix_op = _{
    assignOp       > { 0, r } // 赋值运算,右结合
    | Conditional ~ expression ~ Colon > { 1, r } // 三元运算 a?b:c,右结合
    | Or           > { 2, l } // 逻辑或 ||,左结合
    | And          > { 3, l } // 逻辑与 &&,左结合
    | (Equal | NotEqual) > { 4, l } // 相等判断 == !=,左结合
    | (LessThan | GreaterThan | LessThanOrEqual | GreaterThanOrEqual) > {5, l} // 比较运算,左结合
    | BitOr        > { 6, l } // 按位或 |,左结合
    | BitXor       > { 7, l } // 按位异或 ^,左结合
    | BitAnd       > { 8, l } // 按位与 &,左结合
    | (Shl | Sar | Shr) > {9, l} // 移位运算,左结合
    | (Add | Sub)  > {10, l} // 加减 + -,左结合
    | (Mul | Div | Mod) > {11, l} // 乘除模 * / %,左结合
    | Exp          > {12, r} // 幂运算 **,右结合
}

// 核心表达式规则,静默规则(加_前缀)避免生成多余语法树节点
expression = _{
    prec_climb(primary_with_postfix, prefix_op, infix_op)
}
注意事项
  • 词法规则需要和算子严格对齐:比如逻辑与And对应&&、按位与BitAnd对应&,不要混淆单双字符运算符,否则会出现优先级匹配错误。
  • 结合性不要写错:幂运算、赋值、三元运算为右结合,其余中缀运算、后缀运算为左结合,前缀运算为右结合,和Solidity语义保持一致。
  • 如果需要支持更细粒度的语法树处理,可以去掉算子规则前的_静默标记,自定义匹配时的节点生成逻辑。

内容的提问来源于stack exchange,提问作者0xKitsune

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:15:33