某小伙的Antlr4学习笔记

6 篇文章 0 订阅
6 篇文章 0 订阅

概览

作为一款语言识别工具,

它可以解析(自定义)规则的语句,生成执行树

分有几个阶段

1.词法分析阶段 (lexical analysis)

根据我们定义的词法

解析出我们对应的关键词出来

2.解析阶段

根据我们定义的语法

对解析出来的词进行构建,生成一个语法树

应用场景

1.定制特定领域语言(DSL)

类似hibernate中的HQL,用DSL来定义要执行操作的高层语法,这种语法接近人可理解的语言,由DSL到计算机语言的翻译则通过ANTLR来做,可在ANTLR的结构语言中定义DSL命令具体要执行何种操作。

spark,hive中的解析语法也是用的antlr4

2.文本解析 可利用ANTLR解析JSON,HTML,XML,EDIFACT,或自定义的报文格式。解析出来的信息需要做什么处理也可以在结构文件中定义。

安装

antlr4在使用的时候需要自己定义一个xx.g4文件,然后通过antlr4程序对其进行代码自动生成(SimpleTemplate),当然也可以自定义

代码生成出几个文件

XXXBaseListener.java

XXXLexer

XXXListener(监听模式)

XXXParser

以及一些语法文件

根据需要还可以生成XXXBaseVisitor.java(访问者模式)

这里介绍两种途径进行生成代码

  1. 依赖idea的插件进行配置生成
  2. 通过antlr4的jar包(https://www.antlr.org/download/)antlr-4.0-complete.jar

使用idea的插件的方法的话,对应的生成的代码的版本受到idea本身插件兼容的版本的限制,想要自己生成指定版本的代码并运行比较麻烦

而antlr-4.0-complete.jar 可以根据自己需要的版本进行下载然后构建比较灵活

IDEA安装antlr

在这里插入图片描述

由于我已经安装好了,安装后重启,就会发现下标栏出现了对应的工具

在这里插入图片描述

新建maven项目,引入jar包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>it.luke</groupId>
    <artifactId>Antlr_pro</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4-runtime</artifactId>
            <version>4.8</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>antlr</id>
                        <goals>
                            <goal>antlr4</goal>
                        </goals>
                        <phase>none</phase>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>src/test/java</outputDirectory>
                    <listener>true</listener>
                    <treatWarningsAsErrors>true</treatWarningsAsErrors>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

新建一个g4文件,并编写自己的规则,这里引用一个四则运算的规则

grammar Demo;

//parser
prog:stat
;
stat:expr|NEWLINE
;

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

//lexer
ID:('a'..'z'|'A'..'Z')+;
INT:'0'..'9'+;
NEWLINE:'\r'?'\n';
WS:(' '|'\t'|'\n'|'\r')+{skip();};

编写成功后可以通过安装antlr的插件进行规则预览

选择语法文件,对其中某个语法规则进行测试,可以看到有视图弹出

在这里插入图片描述

在这里插入图片描述

便可以测试你的规则的效果了

待你认为规则没问题后,你便可以进行配置,然后生成代码了,主要就算指定一些代码的生成路径,包名,解析成那种程序语言,默认java

在这里插入图片描述

在这里插入图片描述

antlr工具包生成

到上方的网址选择自己想要的antlr版本之后,根据自己的g4文件开始构建

在这里插入图片描述

java -jar antlr-4.8-complete.jar

可以看到他提供了很多配置参数可以用

在这里插入图片描述

这里我们默认执行

java -jar antlr-4.8-complete.jar Demo.g4

在这里插入图片描述

可以看到它在默认的当前目录下生成了文件,这个时候你可以选择在当前目录构建按一个java项目,也可以将这些文件拷贝到现有的java项目之下,然后选择的antlr4-runtime包需要和你的complete的包的版本一致,不然会导致一些编译问题

使用方法

g4文件的构造

规则识别文件在antlr4的github官网里面有很多别人的样例可供学习,你也可以定制一套属于自己的规则

g4构造分有几个部分

  1. grammar Name (包括了词法和语法的声明写法,有的为了重用性和解耦,可以将词法和语法进行分开,也就是 lexer grammar Name和parser grammar Name 进行声明)

  2. options (通过这个声明一些配置信息,可选)

  3. import (如果你不是合并式的写法,词法和语法是分开写的,就可以在语法声明文件中通过import,导入对应的词法文件)

  4. actionName (用来定义一些文件头…之类的)

  5. rule (主要的语法规则,可以是多个)

    这是核心,表示规则,以 “:” 开始, “;” 结束, 多规则以 “|” 分隔。

    ID : [a-zA-Z0-9|'_']+ ;    //数字 
    STR:'\'' ('\'\'' | ~('\''))* '\''; 
    WS: [ \t\n\r]+ -> skip ; // 系统级规则 ,即忽略换行与空格
    
    sqlStatement
        : ddlStatement 
        | dmlStatement     | transactionStatement
        | replicationStatement     | preparedStatement
        | administrationStatement     | utilityStatement
        ;
    

和前文说的一样,g4文件主要处理了两件事,

一个是解析出你要的词法:

​ 比如我定义了规则里面的INT就只能是数字

INT:'0'..'9'+;

​ 比如我定义了ID只能是英文

ID:('a'..'z'|'A'..'Z')+;

一个是根据你的词法指定的规则

​ 比如我要指定元素之间的加减乘除

prog:stat
;
stat:expr|NEWLINE
;

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

同一个语法里面可以通过|进行多个结果的匹配,相当于或

语法树的遍历使用

语法树的遍历使用分有两种,一种是监听者模式,一种是访问者模式

使用监听者模式,主要借助了ParseTreeWalker 这样一个类,相当于是一个hook

每经过一个树的节点,便会触发对应节点的方法.好处就算是比较方便,但是灵活性不够,不能够自主性的调用任意节点进行使用

使用访问者模式,将每个数据的节点类型高度抽象出来够,根据你传入的上下文类型来判断你想要访问的是哪个节点,触发对应的方法

这边通过对刚刚的四则运算语法进行简单的测试

grammar Demo;

//parser
prog:stat
;
stat:expr|NEWLINE
;

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

//lexer
ID:('a'..'z'|'A'..'Z')+;
INT:'0'..'9'+;
NEWLINE:'\r'?'\n';
WS:(' '|'\t'|'\n'|'\r')+{skip();};

测试的规则语法树如图:(a+b)+2*3

在这里插入图片描述

监听者模式

在这里插入图片描述

在生成的代码结构里面,新建一个Main类进行调用测试

public static void runListener() throws Exception{

        //对每一个输入的字符串,构造一个 ANTLRStringStream 流 in
        ANTLRInputStream in = new ANTLRInputStream("(a+b)+2*3\n");

        //用 in 构造词法分析器 lexer,词法分析的作用是产生记号
        DemoLexer lexer = new DemoLexer(in);

        //用词法分析器 lexer 构造一个记号流 tokens
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        //再使用 tokens 构造语法分析器 parser,至此已经完成词法分析和语法分析的准备工作
        DemoParser parser = new DemoParser(tokens);
        DemoParser.StatContext stat = parser.stat();

        //调用lister
        DemoBaseListener demoBaseListener = new DemoBaseListener();
		
        ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
        parseTreeWalker.walk(demoBaseListener,stat );

        System.out.println("end");
    }

官网说的监听器的遍历规则是从左往右的深度优先遍历,眼见为虚,实践为实,这边重写一下生成的DemoBaseListener,简单的打印当前节点的信息,观察一下是否符合

// Generated from D:/GitPro/My_pro/Antlr_pro/src/test/antlr\Demo.g4 by ANTLR 4.8
package Demo;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;


public class DemoBaseListener implements DemoListener {

	@Override public void enterProg(DemoParser.ProgContext ctx) {
		System.out.println("enterProg:"+ctx.getText());
	}

	@Override public void exitProg(DemoParser.ProgContext ctx) {
//		System.out.println("exitProg: " +ctx.getText());
	}

	@Override public void enterStat(DemoParser.StatContext ctx) {
		System.out.println("enterStat:"+ctx.getText());
	}

	@Override public void exitStat(DemoParser.StatContext ctx) {
//		System.out.println("exitStat:"+ctx.getText());
	}

	@Override public void enterExpr(DemoParser.ExprContext ctx) {
		System.out.println("enterExpr:"+ctx.getText());
	}

	@Override public void exitExpr(DemoParser.ExprContext ctx) {
//		System.out.println("exitExpr: "+ctx.getText());
	}

	@Override public void enterMultExpr(DemoParser.MultExprContext ctx) {
		System.out.println("enterMultExpr:"+ctx.getText());
	}

	@Override public void exitMultExpr(DemoParser.MultExprContext ctx) {
//		System.out.println("exitMultExpr:"+ctx.getText());
	}

	@Override public void enterAtom(DemoParser.AtomContext ctx) {
		System.out.println("enterAtom:"+ctx.getText());
	}

	@Override public void exitAtom(DemoParser.AtomContext ctx) {
//		System.out.println("exitAtom:"+ctx.getText());
	}


	@Override public void enterEveryRule(ParserRuleContext ctx) {
//		System.out.println("enterEveryRule:"+ctx.getText());
	}

	@Override public void exitEveryRule(ParserRuleContext ctx) {
//		System.out.println("exitEveryRule:"+ctx.getText());
	}

	@Override public void visitTerminal(TerminalNode node) {
		System.out.println("visitTerminal:"+node.getText());
	}

	@Override public void visitErrorNode(ErrorNode node) {
		System.out.println("visitErrorNode:"+node.getText());
	}
}

执行调用我们的main方法

在这里插入图片描述

对比我们解析的语法树,验证了这个遍历顺序

访问者模式

新写一个访问者的方法

public static void runVistor(){

        //新建输入流
        ANTLRInputStream in = new ANTLRInputStream("(a+b)+2*3\n");

        //新建词法解析器
        DemoLexer vistorLexer = new DemoLexer(in);
        //对解析token进行缓存
        CommonTokenStream vistorToken = new CommonTokenStream(vistorLexer);
        //新建解析器进行解析
        DemoParser vistorParser = new DemoParser(vistorToken);

        //通过访问者的模式进行访问
        DemoBaseVisitor<String> DemoBaseVisitor = new DemoBaseVisitor<String>();

        //传入需要解析的节点
        DemoBaseVisitor.visit(vistorParser.prog());
        DemoBaseVisitor.visit(vistorParser.stat());
        DemoBaseVisitor.visit(vistorParser.multExpr());


    }
    public static void main(String[] args) throws Exception{
//        runListener();
        runVistor();
    }

本次我们可以通过传入我们需要访问的节点:prog,stat,multExpr

并重写访问者的对应节点的代码

@Override public T visitProg(DemoParser.ProgContext ctx) {
		System.out.println("visitProg:"+ctx.getText());
		return visitChildren(ctx);
	}
...
@Override public T visitStat(DemoParser.StatContext ctx) {
		System.out.println("visitStat:"+ctx.getText());
		return visitChildren(ctx); }
...

@Override public T visitMultExpr(DemoParser.MultExprContext ctx) {
		System.out.println("visitMultExpr:"+ctx.getText());
		return visitChildren(ctx); }

查看结果确实可以根据你的需要访问到对应的节点,如果多个节点的名字相同,则会按照从左到右,深度优先的顺序依次触发

在这里插入图片描述

样例

在这个基础上,我们尝试着解析一下sql的语法,为了简单测试,只测试查询语法,这边在git上拉了一份g4文件用来测试

 grammar MysqlQuery;
 
// @header{package com.antlr.mysql.query;}
    
AS                              : A S;
SELECT                       : S E L E C T;
FROM                        : F R O M;
TABLE                        : T A B L E;
MAX                         : M A X;
SUM                         : S U M;
AVG                          : A V G;
MIN                          : M I N;
COUNT                     : C O U N T;
ALL                            : A L L;
DISTINCT                  : D I S T I N C T;
WHERE                     : W H E R E;
GROUP                    : G R O U P;
BY                             : B Y ;
ORDER                     : O R D E R; 
HAVING                   : H A V I N G;
NOT                          : N O T;
IS                               :  I S ;
TRUE                         : T R U E;
FALSE                        : F A L S E;
UNKNOWN               : U N K N O W N;
 BETWEEN                  : B E T W E E N;
 AND                           :  A N D;
 IN                                :   I N;
 NULL                           : N U L L;
 OR                             : O R ;
 ASC                          : A S C;
 DESC                       : D E S C;
 LIMIT                      : L I M I T ;
 OFFSET                    : O F F S E T;    
     
fragment A      : [aA];
fragment B      : [bB];
fragment C      : [cC];
fragment D      : [dD];
fragment E      : [eE];
fragment F      : [fF];
fragment G      : [gG];
fragment H      : [hH];
fragment I      : [iI];
fragment J      : [jJ];
fragment K      : [kK];
fragment L      : [lL];
fragment M      : [mM];
fragment N      : [nN];
fragment O      : [oO];
fragment P      : [pP];
fragment Q      : [qQ];
fragment R      : [rR];
fragment S      : [sS];
fragment T      : [tT];
fragment U      : [uU];
fragment V      : [vV];
fragment W      : [wW];
fragment X      : [xX];
fragment Y      : [yY];
fragment Z      : [zZ];
fragment HEX_DIGIT:                  [0-9A-F];
fragment DEC_DIGIT:                  [0-9];
fragment LETTER:                         [a-zA-Z];

 
ID:    ( 'A'..'Z' | 'a'..'z' | '_' | '$') ( 'A'..'Z' | 'a'..'z' | '_' | '$' | '0'..'9' )*;
TEXT_STRING :    (  '\'' ( ('\\' '\\') | ('\'' '\'') | ('\\' '\'') | ~('\'') )* '\''  );
ID_LITERAL:   '*'|('@'|'_'|LETTER)(LETTER|DEC_DIGIT|'_')*; 
REVERSE_QUOTE_ID :   '`' ~'`'+ '`';
DECIMAL_LITERAL:     DEC_DIGIT+; 

    
tableName            : tmpName=ID;
column_name            :ID;
function_name            : tmpName=ID ;
 
 selectStatement:
       SELECT        
        selectElements            
    ( 
        FROM tableSources         
        ( whereClause )? 
        ( groupByCaluse )?
        ( havingCaluse )?
    ) ?    
    ( orderByClause )?
    ( limitClause )?
;  

 
 selectElements
    : (star='*' | selectElement ) (',' selectElement)*
    ;  
    
  
tableSources
    : tableName (',' tableName)*
    ;
      
whereClause 
    : WHERE    logicExpression
    ;
    
 logicExpression
     : logicExpression logicalOperator logicExpression
     | fullColumnName comparisonOperator value
     | fullColumnName BETWEEN value AND value
     | fullColumnName NOT? IN '(' value (',' value)*  ')' 
     | '(' logicExpression ')'
     ;
      

groupByCaluse
    :   GROUP BY   groupByItem (',' groupByItem)*   
    ;
havingCaluse
    :    HAVING  logicExpression
   ;
   
 orderByClause
    : ORDER BY orderByExpression (',' orderByExpression)*
    ;       
    
 limitClause
    : LIMIT
    (
      (offset=decimalLiteral ',')? limit=decimalLiteral
      | limit=decimalLiteral OFFSET offset=decimalLiteral
    )
    ;
    
orderByExpression
    : fullColumnName order=(ASC | DESC)?
    ; 

    
groupByItem
    : fullColumnName order=(ASC | DESC)?
    ;
    
logicalOperator
    : AND | '&' '&'  | OR | '|' '|'
    ;    
    
comparisonOperator
    : '=' | '>' | '<' | '<' '=' | '>' '='
    | '<' '>' | '!' '=' | '<' '=' '>'
    ;  
    
    
value 
    : uid
    | textLiteral
    | decimalLiteral 
    ;
 
decimalLiteral
    : DECIMAL_LITERAL
    ;  
textLiteral
    : TEXT_STRING
    ;      
 
selectElement
    : fullColumnName (AS? uid)?      #selectColumnElement
    | functionCall (AS? uid)?               #selectFunctionElement   
    ; 

fullColumnName
    : column_name  
    ;
    
functionCall
   :  aggregateWindowedFunction     #aggregateFunctionCall  
    ;   

aggregateWindowedFunction
    : (AVG | MAX | MIN | SUM) '(' functionArg ')'
    | COUNT '(' (starArg='*' |  functionArg?) ')'
    | COUNT '(' aggregator=DISTINCT functionArgs ')'  
    ;

functionArg
    :  column_name 
    ;

functionArgs 
    : column_name (',' column_name)*
    ;

uid
    : ID  
    ;


 WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines

可以看到,当是简单的查询就涉及到这么多的词法和语法了,所以这个语法规则的编写能拿现成就拿现成的吧

同样的先对规则进行测试,查看能否正常生成语法树

在这里插入图片描述

然后在通过插件生成代码,进行测试

在这里插入图片描述

public class mysqlMain {
    public static void listenerRun(){

        //新建流
        ANTLRInputStream input = new ANTLRInputStream("SELECT column1,column2,column3 from tableC where column1 = 1");

        //新建词法解析
        MysqlQueryLexer mysqlQueryLexer = new MysqlQueryLexer(input);

        //缓存词法解析token
        CommonTokenStream token = new CommonTokenStream(mysqlQueryLexer);

        //解析语法
        MysqlQueryParser parser = new MysqlQueryParser(token);

        //通过监听器的方式
        ParseTreeWalker parserWalk = new ParseTreeWalker();
        //重写部分监听操作,输出列名和表名
        parserWalk.walk(new MysqlQueryBaseListener(),parser.selectStatement() );

    }

同样的,重写一部分的监听代码,方便我们查看

执行

在这里插入图片描述

成功识别到每个节点的数据

同样的还有大数据架构里面的hive和spark,同样使用了antlr

如果你搭建了spark的源码环境,就可以看到它的g4文件

在这里插入图片描述

或者去它的git仓库看一下

https://github.com/apache/spark/tree/master/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ANTLR是一款强大的语法分析器生成工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。它被广泛应用于学术领域和工业生产实践,是众多语言、工具和框架的基石。Twitter搜索使用ANTLR进行语法分析,每天处理超过20亿次查询;Hadoop生态系统中的Hive、Pig、数据仓库和分析系统所使用的语言都用到了ANTLR;Lex Machina将ANTLR用于分析法律文本;Oracle公司在SQL开发者IDE和迁移工具中使用了ANTLR;NetBeans公司的IDE使用ANTLR来解析C++;Hibernate对象-关系映射框架(ORM)使用ANTLR来处理HQL语言。   除了这些鼎鼎大名的项目之外,还可以利用ANTLR构建各种各样的实用工具,如配置文件读取器、遗留代码转换器、维基文本渲染器,以及JSON解析器。我编写了一些工具,用于创建数据库的对象-关系映射、描述三维可视化以及在Java源代码中插入性能监控代码。我甚至为一次演讲编写了一个简单的DNA模式匹配程序。   一门语言的正式描述称为语法(grammar),ANTLR能够为该语言生成一个语法分析器,并自动建立语法分析树——一种描述语法与输入文本匹配关系的数据结构。ANTLR也能够自动生成树的遍历器,这样你就可以访问树中的节点,执行自定义的业务逻辑代码。   本书既是ANTLR 4的参考手册,也是解决语言识别问题的指南。你会学到如下知识:   识别语言样例和参考手册中的语法模式,从而编写自定义的语法。   循序渐进地为从简单的JSON到复杂的R语言编写语法。同时还能学会解决XML和Python中棘手的识别问题。   基于语法,通过遍历自动生成的语法分析树,实现自己的语言类应用程序。   在特定的应用领域中,自定义识别过程的错误处理机制和错误报告机制。   通过在语法中嵌入Java动作(action),对语法分析过程进行完全的掌控。   本书并非教科书,所有的讨论都是基于实例的,旨在令你巩固所学的知识,并提供语言类应用程序的基本范例。   本书的读者对象本书尤其适用于对数据读取器、语言解释器和翻译器感兴趣的开发者。虽然本书主要利用ANTLR来完成这些工作,你仍然可以学到很多有关词法分析器和语法分析器的知识。初学者和专家都需要本书来高效地使用ANTLR 4。如果希望学习第三部分中的高级特性,你需要先了解之前章节中的ANTLR基础知识。此外,读者还需要具备一定的Java功底。   Honey Badger版本ANTLR 4的版本代号是“Honey Badger”,这个名字来源于一段著名的YouTube短片The Crazy Nastyass Honey Badger(网址为:http://www.youtube.com/watch?v=4r7wHMg5Yjg)中的勇敢无畏的主角——一只蜜獾。它敢吃你给它的任何东西,根本不在乎那是什么!   ANTLR 4有哪些神奇之处ANTLR 4引入了一些新功能,降低了入门门槛,使得语法和语言类应用程序的开发更加容易。最重要的新特性在于,ANTLR 4几乎能够处理任何语法(除了间接左递归,稍后会提到)。在ANTLR将你的语法转换成可执行的、人类可读的语法分析代码的过程中,语法冲突或者歧义性警告不会再出现。   无论多复杂的语法,只要你提供给ANTLR自动生成的语法分析器的输入是合法的,该语法分析器就能够自动识别之。当然,你需要自行保证该语法能够准确地描述目标语言。   ANTLR语法分析器使用了一种名为自适应LL(*)或者ALL(*)(读作“all star”)的新技术,它是由我和Sam Harwell一起开发的。ALL(*)是ANTLR 3中的LL(*)的扩展,在实际生成的语法分析器执行前,它能够在运行时以动态方式对语法执行分析,而非先前的静态方式。由于ALL(*)语法分析器能够访问实际的输入文本,通过反复分析语法的方式,它最终能够决定如何识别输入文本。相比之下,静态分析必须考虑所有可行的(无限长的)输入序列。   在实践中,拥有ALL(*)意味着你无须像在其他语法分析器生成工具(包括ANTLR 3)中那样,扭曲语法以适应底层的语法分析策略。如果你曾经为ANTLR 3的歧义性警告和yacc的归约/归约冲突(reduce/reduce conflict)而抓狂,ANTLR 4就是你的不二之选!   另外一个强大的新功能是ANTLR 4极大地简化了匹配某些句法结构(如编程语言中的算术表达式)所需的语法规则。长久以来,处理表达式都是ANTLR语法(以及手工编写的递归下降语法分析器)的难题。识别表达式最自然的语法对于传统的自顶向下的语法分析器生成器(如ANTLR 3)是无效的。现在,利用ANTLR 4,你可以通过如下规则匹配表达式:   类似expr的自引用规则是递归的,更准确地说,是左递归(left recursive)的,因为它的至少一个备选分支直接引用了它自己。   ANTLR 4自动将类似expr的左递归规则重写成了等价的非左递归形式。唯一的约束是左递归必须是直接的,也就是说规则直接引用自身。一条规则不能引用另外一条规则,如果后者的备选分支之一在左侧直接引用了前者(而没有匹配一个词法符号)。详见5.4节。   除了上述两项与语法相关的改进,ANTLR 4还使得编写语言类应用程序更加容易。ANTLR生成的语法分析器能够自动建立名为语法分析树(parse tree)的视图,其他程序可以遍历此树,并在所需处理的结构处触发回调函数。在先前的ANTLR 3中,用户需要补充语法来创建树。除了自动建立树结构之外,ANTLR 4还能自动生成语法分析树遍历器的实现:监听器(listener)或者访问器(visitor)。监听器与在XML文档的解析过程中响应SAX事件的处理器相似。   由于拥有以下几点ANTLR 3所不具备的新特性,ANTLR 4显得非常容易上手:   最大的改变是ANTLR 4降低了语法中内嵌动作(代码)的重要性,取而代之的是监听器和访问器。新机制将语法和应用的逻辑代码解耦,使得应用程序本身被封装起来,而非散落在语法的各处。在没有内嵌动作的情况下,你可以在多个程序中复用同一份语法,甚至都无须重新编译生成的语法分析器。虽然ANTLR仍然允许内嵌动作的存在,但是在ANTLR 4中,它们更像是一种进阶用法。这样的行为能够最大程度地掌控语法分析过程,但其代价是语法复用性的丧失。   由于ANTLR能够自动生成语法分析树和树的遍历器,在ANTLR 4中,你无须再编写树语法。取而代之的是一些广为人知的设计模式,如访问者模式。这意味着,在学会了ANTLR语法之后,你就可以重回自己熟悉的Java领域来实现真正的语言类应用程序。   ANTLR 3的LL(*)语法分析策略不如ANTLR 4的ALL(*)强大,所以ANTLR 3为了能够正确识别输入的文本,有时候不得不进行回溯。回溯的存在使得语法的调试格外困难,因为生成的语法分析器会对同样的输入进行(递归的)多趟语法分析。回溯也为语法分析器在面对非法输入时给出错误消息设置了重重障碍。   ANTLR 4是25年前我读研究生时所走的一小段弯路的成果。我想,我也许会稍微改变我曾经的座右铭。   为什么不花5天时间编程,来使你25年的生活自动化呢?ANTLR 4正是我所期望的语法分析器生成器,现在,我终于能够回头去研究我原先在20世纪80年代试图解决的问题——假如我还记得它的话。   本书的主要内容本书是你所能找到的有关ANTLR 4的信息源中最好、最完整的。免费的在线文档提供了足够多有关基础语法的句法和语义的资料,不过没有详细解释ANTLR的相关概念。在本书中,识别语言的语法模式和将其表述为ANTLR语法的内容是独一无二的。贯穿全书的示例能够在构建语言类应用程序方面助你一臂之力。本书可帮助你融会贯通,成为ANTLR专家。   本书由四部分组成。   第一部分介绍了ANTLR,提供了一些与语言相关的背景知识,并展示了ANTLR的一些简单应用。在这一部分中,你会了解ANTLR的句法以及主要用途。   第二部分是一部有关设计语法和使用语法来构建语言类应用程序的“百科全书”。   第三部分展示了自定义ANTLR生成的语法分析器的错误处理机制的方法。随后,你会学到在语法中嵌入动作的方法——在某些场景下,这样做比建立树并遍历之更简单,也更有效率。此外,你还将学会使用语义判定(semantic predicate)来修改语法分析器的行为,以便解决一些充满挑战的识别难题。   本部分的最后一章解决了一些充满挑战的识别难题,例如识别XML和Python中的上下文相关的换行符。   第四部分是参考章节,详细列出了ANTLR语法元语言的所有规则和ANTLR运行库的用法。   完全不了解语法和语言识别工具的读者请务必从头开始阅读。具备ANTLR 3使用经验的用户可从第4章开始阅读以学习ANTLR 4的新功能。   有关ANTLR的更多在线学习资料在http://www.antlr.org上,你可以找到ANTLR、ANTLRWorks2图形界面开发环境、文档、预制的语法、示例、文章,以及文件共享区。技术支持邮件组是一个对初学者十分友好的公开讨论组。   Terence Parr2012年11月于旧金山大学致  谢Acknowledgements大约25年前,我开始致力于ANTLR的相关工作。那时,在许多人的帮助下,ANTLR工具的句法和功能逐渐成形,在此,我向他们致以由衷的感谢。要特别感谢的是Sam Harwell,他是ANTLR 4的另一位开发者。他不仅帮助我完成了此软件,而且在ALL(*)语法分析算法上做出了突出的贡献。Sam也是ANTLRWorks2语法IDE的开发者。   感谢以下人员对本书进行了技术审阅:Oliver Ziegermann、Sam Rose、Kyle Ferrio、Maik Schmidt、Colin Yates、Ian Dees、Tim Ottinger、Kevin Gisi、Charley Stran、Jerry Kuch、Aaron Kalair、Michael Bevilacqua-Linn、Javier Collado、Stephen Wolff以及Bernard Kaiflin。同时,我还要感谢那些在本书和ANTLR 4软件处于beta版本时报告问题的热心读者。尤其要感谢的是Kim Shrier和Graham Wideman,他们二位的审阅格外认真。Graham的审阅报告之仔细、翔实和广博,令我不知是该紧握他的手予以感谢,还是该为自己的疏漏羞愧难当。   最后,我还要感谢编辑Susannah Davidson Pfalzer,她一如既往地支持我完成了三本书的创作。她提出的宝贵建议和对本书内容的精雕细琢使本书更加完美。   The Translator's Words译 者 序四年前,我在读研究生时曾经参考龙书编写过一个简单的编译器前端。经过一个星期的实践后,我意识到,从头实现一个编译器前端的难度远远超出了一般开发者的能力。编写编译器前端所需要的理论基础、技术功底和精力都远非普通软件可比。   幸运的是,ANTLR的出现使这个过程变得易如反掌。ANTLR能够根据用户定义的语法文件自动生成词法分析器和语法分析器,并将输入文本处理为(可视化的)语法分析树。这一切都是自动进行的,所需的仅仅是一份描述该语言的语法文件。   一年前,我在为淘宝的一个内部数据分析系统设计DSL时,第一次接触到了ANTLR。使用ANTLR之后,我在一天之内就完成了整个编译器前端的开发工作,从而能够迅速开始处理真正的业务逻辑。从那时起,我就被它强大的功能所深深吸引。简而言之,ANTLR能够解决别的工具无法解决的问题。   软件改变了世界。数十年来,信息化的浪潮在全球颠覆着一个又一个的行业。然而,整个世界的信息化程度还远未达到合理的高度,还有大量传统行业的生产力可以被信息化所解放。在这种看似矛盾的情形背后存在着一条鸿沟:大量从事传统行业的人员拥有在本行业中无与伦比的业务知识和经验,却苦于跟不上现代软件发展的脚步。解决这个问题的根本方法就是DSL(Domain Specific Language),让传统行业的人员能够用严谨的方式与计算机对话。其实,本质上任何编程语言都是一种DSL,殊途同归。   而实现DSL的主要困难就在编译器前端。编译器被称为软件工程皇冠上的明珠。一直以来,对于普通的开发者而言,编译器的设计与实现都如同诗中描述的那样:“白云在青天,可望不可即。”   ANTLR改变了这一切。ANTLR自动生成的编译器前端高效、准确,能够将开发者从繁杂的编译理论中解放出来,集中精力处理自己的业务逻辑。ANTLR 4引入的自动语法分析树创建与遍历机制,极大地提高了语言识别程序的开发效率。   时至今日,ANTLR仍然是Java世界中实现编译器的不二之选,同时,它对其他编程语言也提供了不同程度的支持。在开始学习ANTLR时,我发现国内有关ANTLR的资料较为贫乏,这催生了我翻译本书的念头。我期望通过本书的翻译,让更多的开发者能够更加自如地解决职业生涯中碰到的难题。   本书没有冗长的理论,而是从一些具体的需求出发,由浅入深地介绍了语言的背景知识、ANTLR语法的设计方法以及基于ANTLR 4实现语言识别程序的详细步骤。它尤其适用于对语言识别程序的开发感兴趣的开发者。不过,假如你现在没有这样的需求,我仍然建议你阅读本书,因为它能够开拓你的眼界,让你深入实现层面加深对编程语言的理解。
ANTLR4是一种强大的语法解析器生成器,它可以用于多种编程语言,包括C。使用ANTLR4,您可以定义自己的语法规则,并生成用于解析和分析C代码的解析器。ANTLR4生成的解析器能够将C代码转换为语法树,从而方便进行各种静态分析、代码转换和代码生成等操作。 使用ANTLR4来解析C代码的基本步骤如下: 1. 定义C语法规则:使用ANTLR4的语法规则定义语法规范,可以参考C语言的语法规范以及相关文档和资料。 2. 生成解析器代码:使用ANTLR4命令行工具将定义好的语法规则文件(通常以.g4为扩展名)作为输入,生成相应的解析器代码。 3. 编写应用程序:使用生成的解析器代码编写应用程序。您可以使用解析器来处理C代码,执行相应的操作,如静态分析、源码转换等。 4. 构建和运行应用程序:将应用程序编译并运行,开始对C代码进行解析和分析。 下面是一些相关问题: 相关问题: 1. 如何定义C语法规则文件? 2. ANTLR4支持哪些其他编程语言? 3. 使用ANTLR4解析C代码时,如何处理错误和异常情况? 4. 可以在ANTLR4中定义和使用自定义标记吗? 5. ANTLR4生成的解析器代码如何集成到现有的C项目中? 6. ANTLR4有哪些高级特性和扩展功能可用于解析C代码? 7. 如何使用ANTLR4生成C代码的抽象语法树(AST)? 8. ANTLR4的性能如何?是否有优化选项可用? 请注意,由于回答的长度限制,我可能无法一次回答所有

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值