《ANTLR 4权威指南》笔记11 - 语义谓词
谓词是布尔表达式,效果是动态地减少语法分析器的可选规则,用{...}?
表示。
作用1:处理同一种语言的多个版本,比如不同数据库的SQL的语言,GCC的特殊语法。
可以用语法规则谓词处理不同的Java语言版本:
grammar Enum;
@parser::members {public static boolean java5;}
prog: ( stat
| enumDecl
)+
;
stat: id '=' expr ';' {System.out.println($id.text+"="+$expr.text);} ;
expr
: id
| INT
;
enumDecl
: {java5}? 'enum' name=id '{' id (',' id)* '}'
{System.out.println("enum "+$name.text);}
;
id : ID
| {!java5}? 'enum'
;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
或者用词法规则谓词处理更好:
grammar Enum2;
@lexer::members {public static boolean java5 = false;}
prog: ( stat
| enumDecl
)+
;
stat: ID '=' expr ';' {System.out.println($ID.text+"="+$expr.text);} ;
expr: ID
| INT
;
// No predicate needed here because 'enum' token undefined if !java5
enumDecl
: 'enum' name=ID '{' ID (',' ID)* '}'
{System.out.println("enum "+$name.text);}
;
ENUM: 'enum' {java5}? ; // must be before ID
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
谓词会降低性能,粗暴的做法是认为enum总是ID,在action中修改token类型:
tokens { ENUM }
ID : [a-zA-Z]+
{if (java5 && getText().equals("enum")) setType(Enum2Parser.ENUM);}
;
作用2:解析语法歧义。
我们需要谓词根据上下文选择歧义的规则,如果谓词成功地解析了某个短语地语法歧义,就称这个短语是上下文相关的。
例:解析C++语法中的T(i)
如果不用语义谓词:
grammar CppStat;
stat: decl ';' {System.out.println("decl "+$decl.text);}
| expr ';' {System.out.println("expr "+$expr.text);}
;
decl: ID ID // E.g., "Point p"
| ID '(' ID ')' // E.g., "Point (p)", same as ID ID
;
expr: INT // integer literal
| ID // identifier
| ID '(' expr ')' // function call
;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
WS : [ \t\n\r]+ -> skip ;
这个语法是有歧义的,如果输入decl f(i)
,会同时匹配decl
和expr
两条语法规则,在解析时会匹配第一条符合的规则decl
。如果运行grun CppStat stat -diagnostics
,则会打印出歧义的信息。经过修改,添加一个方法istype()
,使用谓词{istype()}?
:
grammar PredCppStat;
@parser::header {
import java.util.*;
}
@parser::members {
Set<String> types = new HashSet<String>() {{add("T");}};
boolean istype() { return types.contains(getCurrentToken().getText()); }
}
stat: decl ';' {System.out.println("decl "+$decl.text);}
| expr ';' {System.out.println("expr "+$expr.text);}
;
decl: ID ID // E.g., "Point p"
| {istype()}? ID '(' ID ')' // E.g., "Point (p)", same as ID ID
;
expr: INT // integer literal
| ID // identifier
| {!istype()}? ID '(' expr ')' // function call
;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
WS : [ \t\n\r]+ -> skip ;
**注意:**这个歧义可以用语义谓词解决,但是有一些歧义是无法用谓词解决的!
例:解析C++语法中的 T(i)[5]
,可以解析为T(i),[5]
和T (i)[5]
,有歧义,但是无法用谓词解决。
例:C++中无法得知T
是函数名还是变量名,直到解析器遇到T(i)
之类的语句。这种情况只能对输入进行多趟解析,或者使用中间表示形式,比如解析树。
一般语言的歧义只有类似于1+2*3
的算术表达式。