能否用ANTLR4定义起始与结束标记相同的可嵌套块语法?
问题描述
我希望定义一种语法,其中每个块的起始标记是以%开头的标签名,起始标记与结束标记的区别在于前者带有括号包裹的参数,后者没有。示例如下:
%table(parameter-list) table,data,goes,here %table
我原本编写的ANTLR产生式如下:
table: '%table' paramlist .*? '%table' ; paramlist: '(' param? (',' param)* ')' ;
但该语法存在问题:若第一个块缺失结束标记,会错误识别后续块的起始标记。比如下面的示例:
%table() stuff,goes,here %table() second,table %table
由于我需要块支持嵌套,上述示例存在歧义——第二个块的起始标记%table()可能被当作第一个块的结束标记,但实际上因为括号的存在,它并不是结束标记。
请问能否在ANTLR中定义结束标记为%table且后面不跟(?或者是否必须使用不同的结束标签?
我尝试过要求结束标记单独占一行,但这种强制换行的要求不合理:
table: '%table' paramlist .*? '%table' '\n' ;
是否有更简洁合理的实现方式?
解决方案
完全可以在ANTLR中定义结束标记为%table且后面不跟(,不需要强制换行或改用不同结束标签,核心思路是利用ANTLR的语义谓词区分标记,并结合词法模式管理嵌套。
1. 优化词法规则
先在词法分析器中明确区分起始标记和结束标记,同时处理嵌套逻辑:
lexer grammar BlockLexer; // 起始标记:%标签名(参数),匹配后进入块内容模式 START_TAG: '%' ID '(' (PARAM (',' PARAM)*)? ')' -> pushMode(BLOCK_CONTENT); // 结束标记:%标签名,且后面不能紧跟( END_TAG: '%' ID {_input.LA(1) != '('}?; // 块内容模式:处理块内文本和嵌套块 mode BLOCK_CONTENT; // 块内文本:匹配除了可能触发标签的%ID之外的所有内容 BLOCK_TEXT: (~[%] | '%' {_input.LA(1) != ID}?)+; // 嵌套的起始标记,再次推入模式 NESTED_START: '%' ID '(' (PARAM (',' PARAM)*)? ')' -> pushMode(BLOCK_CONTENT); // 嵌套的结束标记,弹出当前模式 NESTED_END: '%' ID {_input.LA(1) != '('}? -> popMode; // 辅助片段规则 fragment ID: [a-zA-Z_][a-zA-Z0-9_]*; fragment PARAM: ~[(),\r\n]+;
2. 对应的语法规则
在语法分析器中可以简洁定义支持嵌套的块结构:
parser grammar BlockParser; options { tokenVocab=BlockLexer; } // 顶层规则 top: block* EOF; // 块规则:起始标记 + 块内容/嵌套块 + 结束标记 block: START_TAG (BLOCK_TEXT | block)* END_TAG;
原理说明
- 语义谓词
{_input.LA(1) != '('}?确保END_TAG只会匹配后面没有括号的%ID,彻底区分起始和结束标记。 - 词法模式
pushMode和popMode天然支持嵌套:进入块内容模式后,遇到新的起始标记会再次推入模式,遇到结束标记则弹出模式,不会出现跨块误匹配。 BLOCK_TEXT的规则避免了块内普通字符被误识别为标签,保证内容解析的准确性。
针对原问题示例的效果
对于你给出的有歧义的输入,这个语法会正确识别第一个%table()是最外层起始标记,第二个%table()是嵌套块的起始标记,最后的%table是最外层的结束标记,完全符合预期。
内容的提问来源于stack exchange,提问作者Dov
相关产品推荐
相关产品推荐

