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

一、说明

阅读完<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

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

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

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

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}

相关推荐
huaqianzkh1 小时前
了解Hadoop:大数据处理的核心框架
大数据·hadoop·分布式
Kika写代码1 小时前
【Hadoop】【hdfs】【大数据技术基础】实验三 HDFS 基础编程实验
大数据·hadoop·hdfs
我的K84091 小时前
Flink整合Hive、Mysql、Hbase、Kafka
hive·mysql·flink
Java资深爱好者5 小时前
数据湖与数据仓库的区别
大数据·数据仓库·spark
heromps5 小时前
hadoop报错找不到主类
大数据·hadoop·eclipse
静听山水21 小时前
基于ECS实例搭建Hadoop环境
hadoop
三劫散仙1 天前
Hadoop + Hive + Apache Ranger 源码编译记录
hive·hadoop·hbase·ranger
dogplays1 天前
sqoop import将Oracle数据加载至hive,数据量变少,只能导入一个mapper的数据量
hive·oracle·sqoop
zmd-zk1 天前
hive中windows子句的使用
大数据·数据仓库·hive·hadoop·windows·分布式·big data
Natural_yz1 天前
大数据学习09之Hive基础
大数据·hive·学习