1 通过文法中嵌入的操作打印指定列文本
4.4节第一个例子介绍打印指定列的文本来介绍如何在文法文件中嵌入特殊操作。
1.1 目标
通过文法中嵌入操作的这个demo,我们可以在文法中嵌入操作,指定变量,来根据条件对单词或符号串进行操作。对@parser::members,locals[ int i = 0],$i的使用进行了简介,并学会简单使用。
1.2 分析
该节介绍了如何在文法中嵌入操作。书中以按列打印文本为例介绍了该功能。书中给出的输入样例如下。每一行有若干单词组成,单词通过制表符\t分割,行与行之间通过换行\n分割。
parrt Terence Parr 101
tombu Tom Burns 020
bke Kevin Edgar 008
上述文本的文法定义如下所示。通过@parser::members增加了一个自定义的解析器构造器。该构造词会额外接收一个col参数表示列号。这里我们将为文法文件命名为Demo,那么生成器生成的解析器名称就为DemoParser。STUFF定义可以看到,非换行、制表的符号构成一列上展示的符号。实际上演示中为了文本统一描述,列上展示的符号通过制表符来分隔。
grammar Demo;
@parser::members {
int col;
public DemoParser(TokenStream input, int col) {
this(input);
this.col = col;
}
}
file: (row NL)+;
row
locals[ int i = 0]
: ( STUFF {
$i++;
if($i == col) System.out.println($STUFF.text);
}
)+
;
TAB : '\t' -> skip;
NL : '\r'? '\n';
STUFF: ~[\t\r\n]+;
我们打开生成的DemoParser.java文件可以看到自动生成的构造函数。如果构造的DemoParser对象col传入1,就只打印第一列。传3就打印第3列。
int col;
public DemoParser(TokenStream input, int col) {
this(input);
this.col = col;
}
try {
enterOuterAlt(_localctx, 1);
{
setState(13);
_errHandler.sync(this);
_la = _input.LA(1);
do {
{
{
setState(11);
((RowContext)_localctx).STUFF = match(STUFF);
_localctx.i++;
if(_localctx.i == col) System.out.println((((RowContext)_localctx).STUFF!=null?((RowContext)_localctx).STUFF.getText():null));
}
}
setState(15);
_errHandler.sync(this);
_la = _input.LA(1);
} while ( _la==STUFF );
}
}
文法文件中的STUFF中嵌入了对单词的操作。i是一个局部变量,表示读到了一行中的第几个单词。当每读到一个单词时,i自增。如果i等于构造parser时的列号,那么打印该列单词。
locals[ int i = 0]
: ( STUFF {
$i++;
if($i == col) System.out.println($STUFF.text);
}
)+
;
补充一个主程序例子:
public class AntlrTest {
public static void main(String[] args) throws Exception{
String exp = "a\tb\tc\na\tb\tc\n";
CharStream input = CharStreams.fromString(exp);
DemoLexer demoLexer = new DemoLexer(input);
CommonTokenStream tokens = new CommonTokenStream(demoLexer);
// 打印第一列字母a
DemoParser parser = new DemoParser(tokens,1);
parser.setBuildParseTree(false);
parser.file();
}
}
2 对输入数字字符串进行分组
4.4节中第二个例子介绍了如何通过在文法中加入语义谓词来对文本进行分组。对于2 9 10 3 1 2 3字符串,第一个2表示这个分组后会跟两个数字,那么2,(9,10) 构成第一组。后续的3表示随后读入的3个数字字符构成一个组(3,(1,2,3)。
2.1 目标
通过使用语义谓词({$ i<= $n}?)来增强语法分析能力。
2.2 分析
对这个2 9 10 3 1 2 3进行分组,分组方式已经在2中说明。该字符串的文法如下。
这里的核心是{$i <= $n}。当满足条件,表达式为 true时,那么后续的数字将作为分组的一部分,否则作为下一个分组中的一部分。定义sequence时,指定了变量n,其值为sequence前面数字值。
file : group+;
group: INT sequence[$INT.int];
sequence[int n]
locals [int i = 1;]
: ( {$i <= $n}? INT {$i++;} )*
;
INT : [0-9]+;
WS : [ \t\n\r]+ -> skip;
增加上述语义谓词后,会在语法分析器中生成如下代码。
while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
if ( _alt==1 ) {
{
{
setState(14);
if (!(_localctx.i <= _localctx.n)) throw new FailedPredicateException(this, "$i <= $n");
setState(15);
match(INT);
_localctx.i++;
}
}
}
setState(21);
_errHandler.sync(this);
_alt = getInterpreter().adaptivePredict(_input,1,_ctx);
}
}
打印语法书的代码如下
public class AntlrTest {
public static void main(String[] args) throws Exception{
String input = "2 9 10 3 1 2 3";
CharStream charStream = CharStreams.fromString(input);
DemoLexer demoLexer = new DemoLexer(charStream);
CommonTokenStream commonTokenStream = new CommonTokenStream(demoLexer);
DemoParser demoParser = new DemoParser(commonTokenStream);
ParseTree parseTree = demoParser.file();
System.out.println(parseTree.toStringTree(demoParser));
}
}
最终生成的分组输出为
(file (group 2 (sequence 9 10)) (group 3 (sequence 1 2 3)))