Hive-技术补充-ANTLR实现一个小项目

本文介绍了如何利用ANTLR工具,从Java代码的数组初始化语句出发,将其转换为Unicode字符序列,涉及词法分析、语法解析以及ANTLR的使用和自定义监听器的实现。
摘要由CSDN通过智能技术生成

一、说明

阅读完<Hive-技术补充-初识ANTLR>和<Hive-技术补充-ANTLR词法语法分析>后我们对ANTLR已经有了一个初步认识,下面我们就来做一个简单的小项目来感受下它的整个过程。

static short [] data = {1,2,3} ;

小项目目标是把上面的代码转换成下面的Unicode字符

static String data = "\u0001\u0002\u0003" ;

Unicode是一种用于字符编码的国际标准,它定义了世界上所有字符的唯一编号。字符集中的每一个字符分配一个唯一的数字,Unicode使用4个16进制数字来表示一个16位的字符。

我们先编写下上面代码,并查看下java编译器将它编译成class的样子

vi Test.java

public class Test{
    static short [] data = {1,2,3} ;
}

javac Test.java

javap -c Test

字节码说明:  

public Test();
    Code:
       0: aload_0    //将局部变量表中第 0 个位置的引用类型(对象)压入操作数栈顶
       1: invokespecial #1 // Method java/lang/Object."<init>":()V //调用类的私有方法,构造函数初始化方法以及超类方法。
       4: return

  static {};
    Code:
       0: iconst_3    //将整型 3 推送至栈顶
       1: newarray       short //分配数据成员类型为 short 基本数据类型的新数组
       3: dup        //复制栈顶的元素然后压入栈
       4: iconst_0    //将整型 0 推送至栈顶
       5: iconst_1    //将整型 1 推送至栈顶
       6: sastore    //用于将 short 值存储到数组中。它是将一个 short 值存储到局部变量表中指定的 short 数组的索引位置
       7: dup        //复制栈顶的元素然后压入栈
       8: iconst_1    //将整型 1 推送至栈顶    
       9: iconst_2    //将整型 2 推送至栈顶
      10: sastore    //用于将 short 值存储到数组中。它是将一个 short 值存储到局部变量表中指定的 short 数组的索引位置
      11: dup        //复制栈顶的元素然后压入栈
      12: iconst_2    //将整型 2 推送至栈顶
      13: iconst_3    //将整型 3 推送至栈顶
      14: sastore    //用于将 short 值存储到数组中。它是将一个 short 值存储到局部变量表中指定的 short 数组的索引位置
      15: putstatic     #2                  // Field data:[S 设置类中静态字段的值
      18: return

}

可以发现编译后的字节码已经限制了数组长度为 3 ,如果是一个字符串,java编译器会把它存储为连续的short序列从而不受长度约束。

将数组的初始化语句修改成字符串可以得到更紧凑的class文件,避免了java对初始化方法的长度限制。

下面我们通过ANTLR来实现它。

二、ANTLR目录结构

我们用jd-gui打开下载的 ANTLR jar包(<Hive-技术补充-初识ANTLR>有下载方式)

ANTLR工具是用 org.antlr.v4.Tool 来处理编写好的语法文件,并生成一些代码(词法分析器、语法分析器)。词法分析器将输入的字符流分解为词法符号序列,然后将它们传递给能进行语法检查的语法分析器。

ANTLR运行库是由若干类和方法组成的库,这些类和方法是自动生成的代码(如:Parser、Lexer和Token)运行所必须的。

因此我们完成这个小项目的步骤为:

        1、创建语法(即合法语句结构的集合)

        2、对语法文件运行ANTLR

        3、将生成的代码与jar包中的运行库一起编译

        4、运行

三、编写语法文件

vi ArrayInit.g4

//语法通常以 grammar 关键字开头
//这是一个名为ArrayInit的语法,它必须与文件名 ArrayInit.g4 相匹配
grammar ArrayInit;

//一条名为 init 的规则 ,它包括一对花括号中的、逗号分割的value
//必须至少匹配一个value
init : '{' value (',' value)* '}';

//一个value可以是嵌套的花括号结构,也可以是一个简单的整数,即 INT 词法符号
value :init
    |   INT
    ;
//语法分析器的规则必须以小写字母开头,词法分析器的规则必须用大写字母开头
INT : [0-9]+ ; //定义词法符号 INT ,它由一个或多个数字组成
WS : [ \t\r\n]+ -> skip ; //定义词法规则 ‘空白符号’ 丢弃

四、对语法文件运行ANTLR

antlr4 ArrayInit.g4

ANTLR为我们自动生成了这些文件,正常情况下这些都需要我们自己编写的,下面看看这些文件的作用。

1、ArrayInitParser.java

该文件包含一个语法分析器类的定义,这个语法分析器专门用来识别我们的目标语言,也就是上面声明的数组。该类每条规则都有对应的方法,还有一些辅助代码。

2、ArrayInitLexer.java

该文件包含一个词法分析器类的定义,ANTLR根据语法文件中词法规则 INT 和 WS ,以及语法中的字面值 '{' ',' '}' 生成。

3、ArrayInit.tokens

ANTLR会给我们定义的词法符号指定一个数字形式的类型,然后将它们的对应关系存储到此文件。有时我们需要将一个大型语法切割成很多小语法,此时,该文件就非常有用了。通过它,ANTLR可以在多个小语法文件中同步全部的词法类型。

4、ArrayInitListener.java

默认情况下,ANTLR生成的语法分析器可以将输入文本转换成一棵语法分析树。在遍历语法分析树时,遍历器能够触发一系列事件,并通知我们提供的监听器对象。ArrayInitListener给出了这些回调方法的定义,我们可以实现它来自定义功能。ArrayInitBaseListener是对它的默认实现,其中的方法都是空的,我们直接修改即可。

此外,通过指定 -visitor 命令行参数,ANTLR可以为我们生成语法分析树的访问器,通过访问者模式来遍历语法分析树。

接下来,我们将使用监听器将short数组初始化语句转换为字符串对象。

五、编译

javac *.java

六、测试

grun ArrayInit init -tokens

我们输入 {99,3,567}

按Ctrl + D

每行输出代表一个词法符号

也可以使用 -tree 参数来查看语法分析树

grun ArrayInit init -tree

也可以使用 -gui 参数来生成一个可视化的对话框,我们用对话框来处理下嵌套结构(用crt远程连接弹不出对话框,需要在本机桌面才可以)

grun ArrayInit init -gui

七、java程序集成语法分析器

vi TestArrayInit.java

//导入ANTLR的运行库
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;

public class TestArrayInit {
	public static void main(String [] args) throws Exception{
		//新建一个 CharStream,从标准输入读取数据
		ANTLRInputStream input = new ANTLRInputStream(System.in);
		//新建一个词法分析器,处理输入的 CharStream
		ArrayInitLexer lexer = new ArrayInitLexer(input);
		//新建一个词法符号缓冲区,用于存储词法分析器将生成的词法符号
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		//新建一个语法分析器,处理词法符号缓冲区中的内容
		ArrayInitParser parser = new ArrayInitParser(tokens);
		
		//针对init语法开始语法分析
		ParseTree tree = parser.init();
		//用LISP风格打印生成的语法分析树
		System.out.println(tree.toStringTree(parser));
	}
}

编译并运行

javac  ArrayInit*.java TestArrayInit.java

java TestArrayInit

八、实现小项目

我们继承ArrayInitBaseListener来实现自己的监听器,覆盖其中部分方法,在遍历器遍历语法分析树时,调用我们自己写的监听器来实现字符的处理。

我们先来手动的走下这个过程,输入数据为:

short 数组:{99,3,451}

翻译后的结果为:

String形式:"\u0063 \u0003 \u01c3"

{ ----> "

数字 ----> 4位的16进制形式并加前缀 \u

} -----> "

下面我们编写监听类

vi ShortToUnicodeString.java

//将类似{1,2,3}的short数组初始化语句翻译为"\u0001\u0002\u0003"
public class ShortToUnicodeString extends ArrayInitBaseListener {
	//{  ---->  "
	@Override 
	public void enterInit(ArrayInitParser.InitContext ctx) { 
		System.out.print('"');
	}
	//}  ---->  "
	@Override 
	public void exitInit(ArrayInitParser.InitContext ctx) { 
		System.out.print('"');
	}
	//整数 ----> 前缀\u + 4位16进制形式
	@Override 
	public void enterValue(ArrayInitParser.ValueContext ctx) { 
		//先假定不存在嵌套结构
		int value = Integer.valueOf(ctx.INT().getText());
		System.out.printf("\\u%04x",value);
	} 
}

vi Translate.java

//导入ANTLR的运行库
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;

public class Translate {
	public static void main(String [] args) throws Exception{
		//新建一个 CharStream,从标准输入读取数据
		ANTLRInputStream input = new ANTLRInputStream(System.in);
		//新建一个词法分析器,处理输入的 CharStream
		ArrayInitLexer lexer = new ArrayInitLexer(input);
		//新建一个词法符号缓冲区,用于存储词法分析器将生成的词法符号
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		//新建一个语法分析器,处理词法符号缓冲区中的内容
		ArrayInitParser parser = new ArrayInitParser(tokens);	
		//针对init语法开始语法分析
		ParseTree tree = parser.init();
		//新建一个通用的,能够触发回调函数的语法分析树遍历器
		ParseTreeWalker walker = new ParseTreeWalker();
		//遍历语法分析过程中生成的语法分析树,触发回调
		walker.walk(new ShortToUnicodeString(),tree);
		//用LISP风格打印生成的语法分析树
		System.out.println();
	}
}

编译测试

javac *.java

java Translate

输入{99,3,451}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值