谓词是布尔表达式,效果是动态地减少语法分析器的可选规则,用{...}?表示。

作用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),会同时匹配declexpr两条语法规则,在解析时会匹配第一条符合的规则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的算术表达式。