antlr-基础-左递归规则
Left-recursive rules
最自然的表述是左递归的,像C语言的声明及算数表达式;
然后左递归的算数表达式经常模棱两可;
stat: expr '=' expr ';' // e.g., x=y; or x=f(x);
    | expr ';'          // e.g., f(x); or f(g(x));
    ;
expr: expr '*' expr
    | expr '+' expr
    | expr '(' expr ')' // f(x)
    | id
    ;
1+2*3模糊的,但是可以使用语义表述
expr[int pr] : id
               ( {4 >= $pr}? '*' expr[5]
               | {3 >= $pr}? '+' expr[4]
               | {2 >= $pr}? '(' expr[0] ')'
               )*
             ;
expr[pr]只能执行那些pr已经执行过的
正式的规则
e : e '*' e
  | e '+' e
  |<assoc=right> e '?' e ':' e
  |<assoc=right> e '=' e
  | INT
  ;
Actions and Attributes
Actions使用目标语言写的语句,并用大括号扩起来;
识别器根据位置在语法中触发它们
下面代码会在parser后输出语句
decl: type ID ';' {System.out.println("found a decl");} ;
type: 'int' | 'float' ;
actions能访问tokens的属性和rule的引用
decl: type ID ';'
      {System.out.println("var "+$ID.text+":"+$type.text+";");}
    | t=ID id=ID ';'
      {System.out.println("var "+$id.text+":"+$t.text+";");}
    ;
Token Attributes
所有的tokens有一个预定义的、只读的属性集合;
包括有用的token属性,比如:类型、text
Actions可以通过$label.attribute访问这些属性,label引用了一个lable的实例;
r : INT {int x = $INT.line;}
    ( ID {if ($INT.line == $ID.line) ...;} )?
    a=FLOAT b=FLOAT {if ($a.line == $b.line) ...;}
  ;
在(...)?中的action子类型
在两个里面都可以引用$ID,不使用label
r : ... ID {System.out.println($ID.text);}
| ... ID {System.out.println($ID.text);}
;
为了访问匹配的literals,必须使用label
stat: r='return' expr ';' {System.out.println("line="+$r.line);} ;
有时需要访问属性,有时需要访问Token 对象本身,因为它聚合了所有的属性,此外可以测试一个子规则是否匹配token
stat: 'if' expr 'then' stat (el='else' stat)?
	{if ( $el!=null ) System.out.println("found an else");}
	| ...
	;
全部的类型如下:
| 属性 | 类型 | 描述 | 
|---|---|---|
| text | String | 匹配token的text,翻译为getText,例子:$ID.text. | 
| type | int | token类型,非0正数,翻译为getType,例子:$ID.type | 
| line | int | token出现的行号,从1开始,翻译为getLine,例子:$ID.line | 
| pos | The | 字符在行中的位置,翻译为togetCharPositionInLine,例子:$ID.pos | 
| index | int | 全部token stream中的索引,从0开始getTokenIndex,例子:$ID.index | 
| channel | int | channel号,忽略不在channel的token,默认getChannel,例子:$ID.channel | 
| int | int | integer text value的值,valueOf(text-of-token)$INT.int | 
解析规则属性
解析规则预定义一系列只读属性,解析规则可以在actions中引用;
returnStat : 'return' expr {System.out.println("matched "+$expr.text);} ;
如下使用rule label
returnStat : 'return' e=expr {System.out.println("matched "+e.text);} ;
| 属性 | 类型 | 描述 | 
|---|---|---|
| text | String | 匹配规则的text | 
| start | Token | 规则可能匹配的第一个内容,永不隐藏,可以在rule中被任何action使用 | 
| stop | Token | 最后一个非隐藏的通道token,匹配规则,只能在after和finally actions使用 | 
| ctx | ParserRuleContext | 关联一个规则调用,其他的规则都是从这里引出比如$ctx.start | 
Dynamically-Scoped Attributes
可以通过传参和返回值与rule传递信息,比如如下代码
h函数无法调用f中的变量,这是通过大括号进行词法划界
void f() {
    int x = 0;
    g();
}
void g() {
    h();
}
void h() {
    int y = x; // INVALID reference to f's local variable x
}
所以,java使用了lexical scoping;
如果能访问之前其他函数定义的本地变量,就是使用了dynamic scoping
这样变量的值就取决调用的方法;
这种情况,不同的方法有时需要互相通信,提供在调用链中提供context信息;
使用$r::x
r是rule名字
x是rule里的属性
需要由程序员指定,r就是上一个Rule,如果不是会抛异常
grammar DynScope;
prog: block ;
block
    /* List of symbols defined within this block */
    locals [
    List<String> symbols = new ArrayList<String>()
    ]
    : '{' decl* stat+ '}'
    // print out all symbols found in block
    // $block::symbols evaluates to a List as defined in scope
    {System.out.println("symbols="+$symbols);}
    ;
/** Match a declaration and add identifier name to list of symbols */
decl: 'int' ID {$block::symbols.add($ID.text);} ';' ;
/** Match an assignment then test list of symbols to verify
 * that it contains the variable on the left side of the assignment.
 * Method contains() is List.contains() because $block::symbols
 * is a List.
 */
stat: ID '=' INT ';'
    {
    if ( !$block::symbols.contains($ID.text) ) {
    System.err.println("undefined variable: "+$ID.text);
    }
    }
    | block
    ;
ID : [a-z]+ ;
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
一个简单的构建和测试
$ antlr4 DynScope.g4
$ javac DynScope*.java
$ grun DynScope prog
=>  {
=>  int i;
=>  i = 0;
=>  j = 3;
=>  }
=>  EOF
<=  undefined variable: j
    symbols=[i]
简单的域声明@members action和dynamic scoping有个很大的不同;
symbols是一个本地变量,规则块调用都有个copy,
下面的嵌套块重新定义了i,内层需要隐藏外层的内容;
{
    int i;
    int j;
    i = 0;
    {
        int i;
        int x;
        x = 5;
    }
    x = 3;
}
下面是输出
$ grun DynScope prog nested-input
symbols=[i, x]
undefined variable: x
symbols=[i, j]
 
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号