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

能否用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 16:46:06