Bison语法解析:带分号表达式的子节点属性继承问题
分号敏感表达式解析问题解决方案
需求背景
开发自定义语言语法时,要求表达式末尾有无分号触发不同解析逻辑:
- 无分号表达式(如
1+2)使用OpAdd、OpMult等节点类型 - 带分号表达式(如
1+2;)使用OpAdd2、OpMult2等节点类型 - 嵌套表达式(如
1+2*3;)的所有子表达式需要继承根表达式的分号属性,即2*3应使用OpMult2而非默认的OpMult
现有语法规则(问题版本)
%% program : stmt ; stmt : assignment | expression | expression_with_semicolon | str ; assignment : KW_LET id OP_ASSIGN expression { $$ = Node::add<ast::OpAssignLiteral>($2, $4); } | KW_LET id OP_COLON type OP_ASSIGN expression { $$ = Node::add<ast::OpAssignTypeAndLiteral>($2, $4, $6); } | KW_LET id OP_COLON type { $$ = Node::add<ast::OpAssignType>($2, $4); } ; id : IDENTIFIER { $$ = Node::add<ast::Identifier>(curtoken); } ; expression : expression OP_PLUS expression { $$ = Node::add<ast::OpAdd>($1, $3); } | expression OP_MINUS expression { $$ = Node::add<ast::OpSub>($1, $3); } | term ; expression_with_semicolon : expression OP_PLUS expression OP_SEMICOLON { $$ = Node::add<ast::OpAdd2>($1, $3); } | expression OP_MINUS expression OP_SEMICOLON { $$ = Node::add<ast::OpSub2>($1, $3); } | term_with_semicolon ; term : term OP_MULT term { $$ = Node::add<ast::OpMult>($1, $3); } | term OP_DIVF term { $$ = Node::add<ast::OpDivF>($1, $3); } | factor ; term_with_semicolon : term OP_MULT term OP_SEMICOLON { $$ = Node::add<ast::OpMult2>($1, $3); } | term OP_DIVF term OP_SEMICOLON { $$ = Node::add<ast::OpDivF2>($1, $3); } | factor OP_SEMICOLON { $$ = $1; } ; factor : posneg | OP_LPAREN expression OP_RPAREN { $$ = $2; } ; posneg : L_INTEGER { $$ = Node::add<ast::Integer>(curtoken); } | OP_PLUS factor { $$ = Node::add<ast::SignedNode>(OP_PLUS, $2); } | OP_MINUS factor { $$ = Node::add<ast::SignedNode>(OP_MINUS, $2); } ; type : TYPE { $$ = Node::add<ast::Type>(curtoken); } ; str : SSTRING OP_SEMICOLON { $$ = Node::add<ast::String>(curtoken); } ; %%
当前问题
仅能处理无分号和非嵌套带分号表达式,嵌套场景失效:比如1+2*3;中的2*3会被解析为OpMult,而非期望的OpMult2。
尝试过的无效方案
- 全局布尔标记:试图通过标记区分带分号上下文,但无法在解析规则中修改全局变量
- 新增内部规则:添加
expression_with_semicolon_inner等规则,但与现有expression规则产生冲突
可行解决方案
核心思路是通过带参数的语法规则传递分号上下文标记,让所有子表达式继承根表达式的分号属性。
修改后的语法规则
%% program : stmt ; stmt : assignment | expression(false) // 无分号上下文的表达式 | expression(true) OP_SEMICOLON // 带分号上下文的表达式,最后匹配分号 | str ; assignment : KW_LET id OP_ASSIGN expression(false) { $$ = Node::add<ast::OpAssignLiteral>($2, $4); } | KW_LET id OP_COLON type OP_ASSIGN expression(false) { $$ = Node::add<ast::OpAssignTypeAndLiteral>($2, $4, $6); } | KW_LET id OP_COLON type { $$ = Node::add<ast::OpAssignType>($2, $4); } ; id : IDENTIFIER { $$ = Node::add<ast::Identifier>(curtoken); } ; // 带上下文参数的表达式规则:$has_semicolon为布尔标记,标记当前是否处于带分号上下文 expression[has_semicolon] : expression[has_semicolon] OP_PLUS expression[has_semicolon] { // 根据上下文选择对应的节点类型 $$ = $has_semicolon ? Node::add<ast::OpAdd2>($1, $3) : Node::add<ast::OpAdd>($1, $3); } | expression[has_semicolon] OP_MINUS expression[has_semicolon] { $$ = $has_semicolon ? Node::add<ast::OpSub2>($1, $3) : Node::add<ast::OpSub>($1, $3); } | term[has_semicolon] ; term[has_semicolon] : term[has_semicolon] OP_MULT term[has_semicolon] { $$ = $has_semicolon ? Node::add<ast::OpMult2>($1, $3) : Node::add<ast::OpMult>($1, $3); } | term[has_semicolon] OP_DIVF term[has_semicolon] { $$ = $has_semicolon ? Node::add<ast::OpDivF2>($1, $3) : Node::add<ast::OpDivF>($1, $3); } | factor[has_semicolon] ; factor[has_semicolon] : posneg | OP_LPAREN expression[has_semicolon] OP_RPAREN { $$ = $2; } ; posneg : L_INTEGER { $$ = Node::add<ast::Integer>(curtoken); } | OP_PLUS factor[false] { $$ = Node::add<ast::SignedNode>(OP_PLUS, $2); } | OP_MINUS factor[false] { $$ = Node::add<ast::SignedNode>(OP_MINUS, $2); } ; type : TYPE { $$ = Node::add<ast::Type>(curtoken); } ; str : SSTRING OP_SEMICOLON { $$ = Node::add<ast::String>(curtoken); } ; %%
方案说明
- 上下文参数传递:为
expression、term、factor规则添加[has_semicolon]布尔参数,标记当前是否处于带分号的根表达式上下文 - 统一规则入口:在
stmt中,带分号表达式通过expression(true) OP_SEMICOLON触发,无分号则用expression(false) - 子表达式继承上下文:所有嵌套的子表达式会自动继承上层的
has_semicolon标记,确保整个表达式树的节点类型统一 - 语义动作分支:每个运算符规则的语义动作根据
has_semicolon的值,选择对应的节点类型(如OpAdd或OpAdd2)
该方案避免了重复定义规则,解决了上下文传递问题,能正确处理嵌套表达式的分号属性继承。
内容的提问来源于stack exchange,提问作者Jjcv Vih
相关产品推荐
相关产品推荐

