本章有5个例子

4.1 匹配算数表达式语言

算数表达式如下所示:

193
a = 5
b = 6
a+b*2
(1+2)*3

语法文件:

grammar Expr;

prog:	stat+ ;
stat:	expr NEWLINE
		|	ID '=' expr NEWLINE
		|	NEWLINE
		;

expr:	expr ('*'|'/') expr
		|	expr ('+'|'-') expr
		|	INT
		|	ID
		|	'(' expr ')'
		;

ID:		[a-zA-Z]+ ;
INT:	[0-9]+ ;
NEWLINE: '\r'? '\n' ;
WS:	[ \t]+ -> skip ;
  • 语法包括一些文法规则。有文法结构的规则比如statexpr,也有表示此法单元的规则比如IDINT
  • 小写字母开头的是语法解析器规则。
  • 大写字母开头的是词法规则,一般全部大写。
  • 用竖线|表示多种可能的规则,也可以用括号表示子规则,比如('*'|'/')

ANTLR可以处理大多数的左递归文法。

分离词法和语法文件:

注意用lexer grammer

lexer grammar CommonLexerRules; // note "lexer grammar"
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip ;

导入语法文件:

grammar LibExpr; // Rename to distinguish from original
import CommonLexerRules; // includes all rules from CommonLexerRules.g4

prog: stat+ ;

stat: expr NEWLINE
		| ID '=' expr NEWLINE
		| NEWLINE
		; 

expr: expr ('*'|'/') expr
		| expr ('+'|'-') expr
		| INT
		| ID
		| '(' expr ')'
		; 

4.2 用visitor构造计算器

带标签的规则

grammar LabeledExpr; // rename to distinguish from Expr.g4

prog:   stat+ ;

stat:   expr NEWLINE                # printExpr
    |   ID '=' expr NEWLINE         # assign
    |   NEWLINE                     # blank
    ;

expr:   expr op=('*'|'/') expr      # MulDiv
    |   expr op=('+'|'-') expr      # AddSub
    |   INT                         # int
    |   ID                          # id
    |   '(' expr ')'                # parens
    ;

MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

普通规则的标签写在#后面,子规则的标签用tagName=()

用Listener构造翻译器

要求解析文件:

import java.util.List;
import java.util.Map;
public class Demo {
    void f(int x, String y) { }
    int[ ] g(/*no args*/) { return null; }
	List<Map<String, Integer>>[] h() { return null; } 
}

生成接口文件:

interface IDemo {
    void f(int x, String y);
    int[ ] g(/*no args*/);
    List<Map<String, Integer>>[] h();
}

用Listener:

import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;

public class ExtractInterfaceListener extends JavaBaseListener {
    JavaParser parser;
    public ExtractInterfaceListener(JavaParser parser) {this.parser = parser;}
    /** Listen to matches of classDeclaration */
    @Override
    public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx){
        System.out.println("interface I"+ctx.Identifier()+" {");
    }
    @Override
    public void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
        System.out.println("}");
    }

    /** Listen to matches of methodDeclaration */
    @Override
    public void enterMethodDeclaration(
        JavaParser.MethodDeclarationContext ctx
    )
    {
        // need parser to get tokens
        TokenStream tokens = parser.getTokenStream();
        String type = "void";
        if ( ctx.type()!=null ) {
            type = tokens.getText(ctx.type());
        }
        String args = tokens.getText(ctx.formalParameters());
        System.out.println("\t"+type+" "+ctx.Id=entifier()+args+";");
    }
}

在语法中嵌入任意动作

需要处理的文件:

parrt Terence Parr 101
tombu Tom Burns 020
bke Kevin Edgar 008

简易的语法文件:

file : (row NL)+ ; // NL is newline token: '\r'? '\n'
row : STUFF+ ;

完整的语法文件:

grammar Rows;

@parser::members { // add members to generated RowsParser
    int col;
    public RowsParser(TokenStream input, int col) { // custom constructor
        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 ;   // match but don't pass to the parser
NL   :  '\r'? '\n' ;     // match and pass to the parser
STUFF:  ~[\t\r\n]+ ;     // match any chars except tab, newline

用语义谓词更改解析逻辑

假设输入:

2 9 10 3 1 2 3

需要把输入的整数分组,比如:

(group 2 (sequence 9 10)) (group 3 (sequence 1 2 3))

用以下语法文件:

grammar Data;

file : group+ ;

group: INT sequence[$INT.int] ;

sequence[int n]
locals [int i = 1;]
     : ( {$i<=$n}? INT {$i++;} )* // match n integers
     ;
     
INT :   [0-9]+ ;             // match integers
WS  :   [ \t\n\r]+ -> skip ; // toss out all whitespace

4.5 酷炫的词法特性

ANTLR有3种token相关的特点

同时解析多种语言的文件

岛屿语法:比如Python文件中嵌入的Django模板

ANTLR提供词法分析特性lexical modes用来处理混合的格式

用于分析XML的语法文件:

lexer grammar XMLLexer;

// Default "mode": Everything OUTSIDE of a tag
OPEN        :   '<'                 -> pushMode(INSIDE) ;
COMMENT     :   '<!--' .*? '-->'    -> skip ;
EntityRef   :   '&' [a-z]+ ';' ;
TEXT        :   ~('<'|'&')+ ;           // match any 16 bit char minus < and &

// ----------------- Everything INSIDE of a tag ---------------------
mode INSIDE;

CLOSE       :   '>'                 -> popMode ; // back to default mode
SLASH_CLOSE :   '/>'                -> popMode ;
EQUALS      :   '=' ;
STRING      :   '"' .*? '"' ;
SlashName   :   '/' Name ;
Name        :   ALPHA (ALPHA|DIGIT)* ;
S           :   [ \t\r\n]           -> skip ;

fragment
ALPHA       :   [a-zA-Z] ;

fragment
DIGIT       :   [0-9] ;

XML文件:

<tools>
	<tool name="ANTLR">A parser generator</tool>
</tools>

重写输入流

发送token到不同的通道