编译知识
词法分析(lexing): 词法分析就是将文本分解成token,token就是具有特殊含义的原子单位, 如果语言的保留字、标点符号、数字、字符串等.
语法分析(paring):语法分析器使用词法分析从输入中分离出一个个的token,并将token流作为其输入
根据某种给定的
形式文法
对由输入的 token 进行分析并确定其语法结构的过程
- 自顶向下分析, 对应LL分析器
- 自底向上分析, 对应LR分析器
javacc
使用递归下降语法解析,LL(k)
- 第一个L表示从左到右扫描输入
- 第二个L表示每次都进行最左推导(在推导语法树的过程中每次都替换句型中最左的非终结符为终结符)
- k表示每次向前探索(lookahead) k个终结符
- LOOKAHEAD: 设置在解析过程中面临 choice point 可以look ahead的token数量, 缺省的值是1
- 调大 K 可以消除二义性, 但会减慢解析速度
- 比如 LOOKAHEAD(2) 就表示要看两个 Token 才能决定下一步的动作
基本结构
1 | options { |
Option块和class声明块
1 | /* adder.jj Adding up numbers */ |
- STATIC默认是true,这里要将其修改为false,使得生成的函数不是static 的。
- ARSER_BEGIN(XXX)……PARSER_END(XXX)块,这里定义了一个名为 Adder的类
词法描述器
1 | // 忽略的字符 |
语法分析器
语法介绍
java代码块用
{}
声明1
2
3
4
5
6
7
8
9// 定义java代码块
void javaCodeDemo():
{}
{
{
int i = 0;
System.out.println(i);
}
}java函数
需要使用JAVACODE声明1
2
3JAVACODE void print(Token t){
System.out.println(t);
}条件
if语句 []
1
2
3
4
5
6
7
8
9
10
11
12
13
14// if语句
void ifExpr():
{}
{
[
<SELECT>
{
System.out.println("if select");
}
]
// 循环,出现一次
(<SELECT>)?
}if else 语句 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// if - else
void ifElseExpr():
{}
{
(
<SELECT> {System.out.println("if else select");}
|
<UPDATE> {System.out.println("if else update");}
|
<DELETE> {System.out.println("if else delete");}
|
{
System.out.println("other");
}
)
}循环
while 0~n
1
2
3
4
5
6// while 0~n
void whileExpr():
{}
{
(<SELECT>)*
}
1 | void Start() : |
例子
1 | options { |
介绍下生成的一些类:
- Adder 是语法分析器
- TokenMgrError 是一个简单的定义错误的类,它是Throwable类的子类,用于定义在词法分析阶段检测到的错误。
- ParseException是另一个定义错误的类。它是Exception 和Throwable的子类,用于定义在语法分析阶段检测到的错误。
- Token类是一个用于表示token的类。我们在.jj文件中定义的每一个token(PLUS, NUMBER, or EOF),在Token类中都有对应的一个整数属性来表示,此外每一个token都有名为image的string类型的属性,用来表示token所代表的从输入中获取到的真实值。
- SimpleCharStream是一个转接器类,用于把字符传递给语法分析器。
AdderConstants是一个接口,里面定义了一些词法分析器和语法分析器中都会用到的常量。AdderTokenManager 是词法分析器。
一个更复杂的例子
1 | PARSER_BEGIN(Calculator) |
Calcite
基础
1 | graph LR; |
- SqlNode: 抽象语法树(AST), 树状结构.
- RelNode: 逻辑执行计划节点, 如TableScan, Project, Sort, Join等, 树状结构.
- 继承自 RelOptNode, 代表能被优化器进行优化
RelTraitSet#getTraitSet();
用来定义逻辑表的物理相关属性(分布/排序)
- 继承自 RelOptNode, 代表能被优化器进行优化
SQL 解析阶段(SQL–>SqlNode)
Calcite 使用 JavaCC 做 SQL 解析,JavaCC 根据 Calcite 中定义的 Parser.jj 文件,生成一系列的 java 代码,生成的 Java 代码会把 SQL 转换成 AST 的数据结构(这里是 SqlNode 类型)
与 Javacc 相似的有 ANTLR,JavaCC 中的 jj 文件跟 ANTLR 中的 G4文件类似,Apache Spark 中使用ANTLR
1 | String sql = "select * from emps where id = 1 group by locationid order by empno limit 100"; |
源码分析
1 | public SqlNode parseQuery() throws SqlParseException { |
这里的 parser 就是 javacc 文件生成的解析类:
生成代码
1 | //org.apache.calcite.sql.parser.impl.SqlParserImpl |
javacc 定义
1 | /** |
生成逻辑执行计划(SqlNode->RelNode)
SqlToRelConverter 中的 convertQuery()
将 SqlNode 转换为 RelRoot, 其实现如下:
1 | /** |
最后生成的逻辑执行计划
1 | SQL: |