在递归规则中组合类似的结构

时间:2017-06-29 07:43:42

标签: bison jison

这适用于Jison的解析器,但我想这同样适用于Bison。

我有一个规则,它有一个表达式的定义。

expr
    : NUMBER -> { type: "number", value: $1 }
    | "(" expr ")" -> $2
    | expr "+" expr -> { type: "+", left: $1, right: $3 }
    | expr "-" expr -> { type: "-", left: $1, right: $3 }
    | expr "*" expr -> { type: "*", left: $1, right: $3 }
    | expr "/" expr -> { type: "/", left: $1, right: $3 }
    ;

我的语法相同我也有一个"过滤表达式的规则"这也支持"参数"。

filterExpr
    : NUMBER -> { type: "number", value: $1 }
    | PARAM -> { type: "param", name: $1 } /* parameter */
    | "(" filterExpr ")" -> $2
    | filterExpr "+" filterExpr -> { type: "+", left: $1, right: $3 }
    | filterExpr "-" filterExpr -> { type: "-", left: $1, right: $3 }
    | filterExpr "*" filterExpr -> { type: "*", left: $1, right: $3 }
    | filterExpr "/" filterExpr -> { type: "/", left: $1, right: $3 }
    ;

这有效,但当我添加运算符时,我必须更改这两个定义。有没有办法结合两者的共同部分" expr"和" filterExpr"在语法中?

1 个答案:

答案 0 :(得分:2)

Javascript本身(官方ECMAScript,由ECMA-262定义)使用BNF的扩展进行描述,该扩展允许使用布尔限定符(标准语言中的“参数”)来扩充规则。这正是您正在寻找的效果,它清楚地简化了语言有些错综复杂的语法的表达。有关BNF扩展的完整说明可以在标准的section 5.1.5 (Grammar Notation)中找到;总之,参数可以从左侧传递到右侧的非终端,也可以为RHS终端明确设置或取消设置;此外,它们可用于根据参数的存在与否来过滤可能的产品。 (这篇文章末尾有一个例子。)

这个特殊的BNF扩展不会给BNF增加任何生成能力;通过简单列举可能性,可以机械地消除它的所有用途。遗憾的是,我知道没有实现这种形式的语法生成器(尽管某些Javascript实现肯定可能包含自定义解析器生成器)。

出于您的目的,可以很容易地预处理您的jison语法以实现非常相似的东西。实际上,对bison语法文件进行预处理会相对容易,但使用jison会更容易,因为您可以通过编程方式计算语法并将其作为JSON对象传递给jison。此功能没有详细记录,但jison手册包含足够的示例,应该可以直接使用。例如,请参阅CommonJS section

正如所承诺的,这里是ECMA-262语法的摘录,显示了这个BNF扩展的使用:

IdentifierReference可以使用两个可能的布尔限定符(YieldAwait)进行限定,从而产生四种可能性。它总是Identifier;只有在未使用yield属性或关键字Yield进行限定时,才能使用关键字await才能使用Await进行限定。

IdentifierReference[Yield, Await]:
    Identifier
    [~Yield]yield
    [~Await]await

所以这个单节相当于四个非终端,可以机械生产:

IdentifierReference: Identfier | yield | await
IdentifierReference_Yield: Identifier | await
IdentifierReference_Await: Identifier | yield
IdentifierReference_Yield_Await: Identifier

以下是它的应用方式:Expression可以使用三个属性进行限定,所有属性都通过(?Yield中的)传递给非终端在右边。

Expression[In, Yield, Await]:
    AssignmentExpression[?In, ?Yield, ?Await]
    Expression[?In, ?Yield, ?Await] , AssignmentExpression[?In, ?Yield, ?Await]

yield表达式仅允许AssignmentExpression符合Yield的变体:

AssignmentExpression[In, Yield, Await]:
    ConditionalExpression[?In, ?Yield, ?Await]
    [+Yield]YieldExpression[?In, ?Await]

最后,一个带有显式参数的例子。在GeneratorMethod的制作中,为Yield制作明确指定了PropertyName(这可以防止yield被识别为参数列表中的标识符)和{{1 }}被定义为GeneratorBody FunctionBody(允许Yield表达式并禁止yield作为标识符)且没有yield(不允许Await }表达式,但允许await成为标识符。

await

坚持向后兼容性需要上述复杂性中的大部分:因为为早期JS版本编写的程序可能使用GeneratorMethod[Yield, Await]: * PropertyName[?Yield, ?Await] ( UniqueFormalParameters[+Yield, ~Await] ) { GeneratorBody } GeneratorBody: FunctionBody[+Yield, ~Await] yield作为变量名,因此这些关键字仅保留在语法中早期版本中没有的上下文。 (这是过于简单化,但细节远远超出了这个问题的范围。)