火影推荐程序连载15-用Java写编译器(1)- 词法和语法分析

ANTLR全称ANother Tool for Languate Recognition,是基于LL(*)算法实现的语法分析器生成器和词法分析器生成器,由旧金山大学的Terence Parr博士等人于1989年开始使用java编写。截止到目前,ANTLR已经支持生成适用于Ada95、C、C#、JavaScript、Objective-C、Perl、Python、Ruby、C++和Standard ML等多种编程语言的词法和语法分析器了。

ANTLR安装

$ cd /usr/local/lib
$ wget https://www.antlr.org/download/antlr-4.7.1-complete.jar
$ export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH"
$ alias antlr4='java -jar /usr/local/lib/antlr-4.7.1-complete.jar'
$ alias grun='java org.antlr.v4.gui.TestRig'

词法分析和Antlr词法定义

词法分析就是讲字符序列转换为单词序列的过程。单词是构成源代码的最小单位,它由一个或多个连续字符组成。比如对代码int year=2018进行词法分析,源代码将被转换成由5个单词组成的序列:int,\s(空格),year,=2018

antlr的词法定义比较简单,大部分的词法都可以使用简单的正则表达式表示。我们来看一个简单的例子DemoLexer.g4

lexer grammer Demo;
PLUS:'+';
MINUS:'-';
MULTIPLE:'*';
DIV:'/';
LPAREN:'(';
RPAREN:')';
NUMBER:[0-9]+;

第1行声明这是一个词法定义文件,第2-7定义4个运算符关键字和左右两个括号,第8行则是正整数的定义。

语法分析和antlr语法定义

语法分析就是根据特定的形式文法对由单词序列构成的输入进行分析并确定其语法结构的过程。比如int,\s(空格),year,=2018这5个单词序列按顺序输入到语法分析器,语法分析器就能识别出这是一条变量声明和初始化的语句。

antlr的语法定义和词法定义类似,列出所有可能的单词组合情况。我们还是从一个简单的四则运算语法定义作为例子。

DemoParser.g4

parser grammar DemoParser;
options { tokenVocab=DemoLexer; }
expr:
    '(' expr ')'
    | NUMBER ( MULTIPLE | DIV ) NUMBER
    | NUMBER ( PLUS | MINUS ) NUMBER
;

第1行声明这是一个语法定义文件,第2行设置词法选项,说明使用哪个词法,第3-7行是运算表达式的文法定义。这里简单介绍一下,expr为我们为文法取的名称,此文法有三种情况:由括号包起来的表达式、乘除表达式和加减表达式。需要注意的是乘除表达式和加减表达式定义的顺序不能随便调换,不然会影响运算符的优先级,得到错误的语法解析树。高优先级的表达式需要先定义。

生成词法分析器和语法分析器

上面只是antlr词法和语法的定义,无法在java里直接使用,因为它不是合法的java源代码,我们需要将其转换成java代码,这个转换工作还是由antlr为我们完成。在终端运行如下命令:

antlr4 DemoLexer.g4 -package demo.antlr
antlr4 DemoParser.g4 -package demo.antlr -visitor

命令执行后,将会自动生成如下文件:

DemoLexer.java
DemoLexer.tokens
DemoParser.java
DemoParser.tokens
DemoParserBaseListener.java
DemoParserBaseVisitor.java
DemoParserListener.java
DemoParserVisitor.java
注:想要CMD执行npm开头的命令,需要先安装Nodejs。

  string e =www.qiaoheibpt.com "abc";
  
  //这里e还是使用字符串的留存性,且使用的还是a的地址。证明c分配的内存引用并没有放入常量池替换
  
  Assert.True(www.yachengyl.cn string.ReferenceEquals(www.letianhuanchao.cn a, e));
  
  Assert.False(www.feihongyul.cn string.ReferenceEquals(www.fengmingpt.com, e));
  
  string f www.baihuayl7.cn= "abc" + "abc";
  
  string g www.jintianxuesha.com= a +www.xinxingyulep.cn b;
  
  string h =www.jujinyule.com "abcabc";
  
  void testDeleteFileDir3(www.zhuyngyule.cn) throws IOException {
  
  Path path =www.baichuangyule.cn Paths.get(www.shentuylzc.cn"D:\\data\\test1");
  
  //如果文件不存在,返回false,表示删除失败(文件不存在)
  
  //如果文件夹里面包含文件,抛出DirectoryNotEmptyException
  
  void testDeleteFileDir4(www. jinmazx.cn) throws IOException {
  
  Path path = Paths.get(www.bhylzc.cn"D:\\data\\test1");
  
  boolean result = Files.deleteIfExists(path);
  
  System.out.println(result);
  
  //IsInterned 表示从常量池中获取对应的字符串,获取失败返回null
  
  //a+b实际上是发生了字符串组合运算,内部重新new了一个新的字符串,所以f,g引用地址不同
  
  Assert.False(string.ReferenceEquals(www.xinxingyulep.cn, g));
  
  Assert.True(string.ReferenceEquals(www.baihua178.cn string.IsInterned(www.lecaixuangj.cn), h));
  
  Assert.True(string.ReferenceEquals(www.yixinpt2.cn f,www.pinguo2yl.com h));

这些java文件就是我们能在程序里直接调用的词法分析器和语法分析器。

词法分析器和语法分析器的使用

生成的词法分析器和语法分析器拷贝到源码目录后就可以直接使用了,我们来看一下最常用的调用方法:

String source = "1+2+3";//我们需要解析的表达式
CharStream charStream = new ANTLRInputStream(source);
DemoLexer lexer = new DemoLexer(charStrem);//词法分析器
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
DemoParser parser = new DemoParser(tokenStream);//语法分析器
ExprContext exprContext = parser.expr();//以expr规则为起始规矩开始解析,得到语法解析树

通过以上步骤,表达式字符串最终被转换成了语法解析树,有了语法解析树,对表达式求值就变得很简单了。首先,我们先定义一个解析树访问(遍历)器MainDemoParserVisitor.java:

public MainDemoParserVisitor extends DemoParserBaseVisitor {
    @override
    public Integer visitExpr(ExprContext ctx) {
        if (ctx.MULTIPLE()!=null) {//乘法
            return Integer.parseInt(ctx.NUMBER(0)) * Integer.parseInt(ctx.NUMBER(1));
        } else if (ctx.DIV()!=null) {//除法
            return Integer.parseInt(ctx.NUMBER(0)) / Integer.parseInt(ctx.NUMBER(1));
        } else if (ctx.PLUS()!=null) {//加法
            return Integer.parseInt(ctx.NUMBER(0)) + Integer.parseInt(ctx.NUMBER(1));
        } else if (ctx.MINUS()!=null) {//减法
            return Integer.parseInt(ctx.NUMBER(0)) - Integer.parseInt(ctx.NUMBER(1));
        } else {//对括号内表达式求值
            return visitExpr(ctx.expr());
        }
    }

}

使用访问器对解析树进行遍历并求值:

MainDemoParserVisitor visitor = new MainDemoParserVisitor();//创建访问者
Integer result = (Integer) vistor.visit(exprContext);//开始遍历语法解析树对表达式求值
System.out.println(result);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值