ANTLR 4解析规则

1 解析规则

Java应用通过调用生成的规则函数使用。

规则间使用分号分隔,选项间使用中竖线分隔。

选项为空,表示整个规则可不匹配。

1
2
3
4
superClass
: 'extends' ID
| // empty means other alternative(s) are optional
;

2 选项标签

用于生成详细的解析树监听事件。

使用#标记,要么全部标记,要么全不标记。

标签名不能与规则名相同。

标签不必在行尾。

1
2
3
4
5
6
7
8
grammar T;
stat: 'return' e ';' # Return
| 'break' ';' # Break
;
e : e '*' e # Mult
| e '+' e # Add
| INT # Int
;

以下为生成的规则上下文类:

1
2
3
4
5
6
7
8
9
10
11
12
public interface AListener extends ParseTreeListener {
void enterReturn(AParser.ReturnContext ctx);
void exitReturn(AParser.ReturnContext ctx);
void enterBreak(AParser.BreakContext ctx);
void exitBreak(AParser.BreakContext ctx);
void enterMult(AParser.MultContext ctx);
void exitMult(AParser.MultContext ctx);
void enterAdd(AParser.AddContext ctx);
void exitAdd(AParser.AddContext ctx);
void enterInt(AParser.IntContext ctx);
void exitInt(AParser.IntContext ctx);
}

可以重复使用标签,意味着触发相同的事件。

3 规则上下文对象

对应规则的解析树节点。

1
inc : e '++' ;

对应的上下文对象如下:

1
2
3
4
public static class IncContext extends ParserRuleContext {
public EContext e() { ... } // return context object associated with e
...
}

当规则被引用多次时,将生成通过索引访问应用上下文的方法:

1
field : e '.' e ;

生成方法如下:

1
2
3
4
5
public static class FieldContext extends ParserRuleContext {
public EContext e(int i) { ... } // get ith e context
public List<EContext> e() { ... } // return ALL e contexts
...
}

其他的规则可在内嵌动作中访问匹配的上下文列表:

1
2
3
4
5
6
7
// 规则s引用了field.
s : field
{
List<EContext> x = $field.ctx.e();
...
}
;

监听器和访问器也可以通过形如f.e()的方式访问。

4 规则元素标记

使用=添加字段到规则对象中,+=添加字段列表到规则对象中。

1
2
3
4
5
6
7
8
9
10
// 返回值标记
stat: 'return' value=e ';' # Return
| 'break' ';' # Break
;

// token标记
array : '{' el+=INT (',' el+=INT)* '}' ;

// 上下文对象标记
elist : exprs+=e (',' exprs+=e)* ;

生成字段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class ReturnContext extends StatContext {
public EContext value;
...
}

public static class ArrayContext extends ParserRuleContext {
public List<Token> el = new ArrayList<Token>();
...
}

public static class ElistContext extends ParserRuleContext {
public List<EContext> exprs = new ArrayList<EContext>();
...
}

5 规则元素

指定parser在某一时刻的特定行为。

Syntax Description
T token, 大写开头
’literal’ 字面量,固定值
r 规则,小写开头
r [«args»] 带参规则,参数按逗号分隔,按目标语言表示
{«action»} Execute an action immediately after the preceding alternative element and immediately before the following alternative element. The action conforms to the syntax of the target language. ANTLR copies the action code to the generated class verbatim, except for substituting attribute and token references such as $x and $x.y.在前后规则选项间立即执行动作。按照目标语言表示。代码逐字拷贝进生成的类文件,除了附属的属性和标识x和x.y
{«p»}? Evaluate semantic predicate «p». Do not continue parsing past a predicate if «p» evaluates to false at runtime. Predicates encountered during prediction, when ANTLR distinguishes between alternatives, enable or disable the alternative(s) surrounding the predicate(s).
. 通配符

此外,~表示非,如~INT表示除了INT外的标识。

6 子规则

如同括号包围的没有名称的规则,不能定义本地属性。

通常有以下4类子规则:

Syntax Description
img (x\ y\ z). Match any alternative within the subrule exactly once. Example: `returnType : (type ‘void’) ;`
img (x\ y\ z)? Match nothing or any alternative within subrule. Example: classDeclaration : 'class' ID (typeParameters)? ('extends' type)? ('implements' typeList)? classBody ;
img (x\ y\ z) Match an alternative within subrule zero or more times. Example: `annotationName : ID (‘.’ ID) ;`
img (x\ y\ z)+ Match an alternative within subrule one or more times. Example: annotations : (annotation)+ ;

其中,?也可以用于取消贪婪模式,如??只用一个子规则时,可以省略括号。如ID+表示(ID)+

7 异常捕获

每条规则都被try/catch/finally包装。

1
2
3
4
5
6
7
8
9
10
11
12
void r() throws RecognitionException {
try {
rule-body
}
catch (RecognitionException re) {
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
exitRule();
}
}

可以使用策略对象修改全局行为,也可以对一条规则单独定义。

1
2
3
4
5
r : ...
;
catch[FailedPredicateException fpe] { ... }
catch[RecognitionException e] { ... }
finally { System.out.println("exit rule r"); }

全部异常如下:

Exception name Description
RecognitionException RuntimeException的子类,其他异常的父类
NoViableAltException 无法决定解析路径
LexerNoViableAltException 仅用于词法解析
InputMismatchException 当前token与解析器期望不一致
FailedPredicateException A semantic predicate that evaluates to false during prediction renders the surrounding alternative nonviable. Prediction occurs when a rule is predicting which alternative to take. If all viable paths disappear, parser will throw NoViableAltException. This predicate gets thrown by the parser when a semantic predicate evaluates to false outside of prediction, during the normal parsing process of matching tokens and calling rules.

8 规则属性定义

规则可以有入参、返回值和本地变量,使用逗号分隔。其定义如下:

1
rulename[args] returns [retvals] locals [localvars] : ... ;

方括号中的变量可以和其他变量一样被引用,如:

1
2
// Return the argument plus the integer value of the INT token
add[int x] returns [int result] : '+=' INT {$result = $x + $INT.int;} ;

可以赋初值。但不要在手动解析泛型文本时过度使用,如ScopeParser

在不同的的语言中略有不同:

C++中使用数组引用来定义整型;Go和Swift需要交换类型和变量名;GO需要使用冒号分隔类型和变量名;Python和JavaScript不需要声明类型。

技术上,以上变量声明方式可以互换,如TestScopeParsing

从语法层面上,可以定义两种规则级别的命名动作:init和after,分别在规则匹配前和后执行。注意after不是finally的一部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
** Derived from rule "row : field (',' field)* '\r'? '\n' ;" */
row[String[] columns]
returns [Map<String,String> values]
locals [int col=0]
@init {
$values = new HashMap<String,String>();
}
@after {
if ($values!=null && $values.size()>0) {
System.out.println("values = "+$values);
}
}
: ...
;

生成的代码如下,其中动作代码直接拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CSVParser extends Parser {
...
public static class RowContext extends ParserRuleContext {
public String [] columns;
public Map<String,String> values;
public int col=0;
...
}
...
public final RowContext row(String [] columns) throws RecognitionException {
RowContext _localctx = new RowContext(_ctx, 4, columns);
enterRule(_localctx, RULE_row);
...
}
...
}

9 启动规则和EOF

启动规则是解析器首先使用的规则,是被语言应用程序调用的规则。

任何规则都可以作为启动规则。启动规则不必处理所有的输入,按需匹配。

如:

1
2
3
4
s : ID
| ID '+'
| ID '+' INT
;

按照规则匹配,其余部分删除。如a+3匹配第三个选项,结果为a+3;a+b匹配第二个选项,结果为a+;a b匹配第一个选项,结果为a

注意:描述整个输入文件的规则需要使用特殊的标识EOF。否则,对于不匹配的内容提前返回且不会抛出异常。

1
2
3
4
config : element*; // can "match" even with invalid input.


file : element* EOF; // don't stop early. must match all input

参考资料