HIVE的sql解析通过鼎鼎大名的ANTLR完成,具体细节可以在http://www.antlr.org/详细了解,这里只整理一些自己的小心得。
1. 终结符首字母大写,非终结符首字母小写
2. Antlr的LL文法不支持左递归,需要消除文法中的左递归。
3. 代码优先级采用highOpExpr: lowOpExpr op lowOpExpr的方式解决。
具体实例:
1.算子优先级问题解决:
解析如下算数表达式: 5+3*4
NUM:'0'..'9'('0'..'9')*;
LEFT:'(';
RIGHT:')';
ADD:'+';
MULTI:'*';
statement:
addStatement;
addStatement:
multiStatement ADD multiStatement;
multiStatement:
NUM
| NUM MULTI NUM;
这样就解决了算子优先级问题。
2. 嵌套问题解决
解析如下算数表达式
3+(5*(6+2)+2*(4+8))*(6+2)
括号优先级比乘法算子高,乘法优先级比加法高,而括号中有嵌套了加法和乘法表达式。
NUM:'0'..'9'('0'..'9')*;
LEFTBRACKET:'(';
RIGHTBRACKET:')';
ADD:'+';
MULTI:'*';
WS : (' ' |'\t' |'\n' |'\r' )+ {skip();} ;
start :
addStatement;
addStatement:
multiStatement ( ADD^ multiStatement )*;
multiStatement:
expr ( MULTI^ expr )*;
expr:
NUM|
LEFTBRACKET! addStatement RIGHTBRACKET!;
在子句中引用上层元素,达到了实现嵌套语句目的的同时也消除了左递归问题.
整理LL(1)文法编译器简单过程描述。
1. 通过FIRST几个何FOLLOW集合构建预测分支表。
2. 构建辅助栈Stack
3. 将最终的非终结符S入栈Stack
4. 从栈Stack弹出一个元素e。
5. 读取token数据流,得到一个tokenA。
6. 弹出的元素e如果为非终结符NotTerminal,从预测分支表中以NotTerminal和tokenA为主键查询此NoTerminal的构成式并从右向左依次压入堆栈,如果栈顶还是非终结符,弹出该非终结符继续从预测分支表中查询构成式,并从右向左依次压入堆栈,循环此逻辑直到栈顶为终结符,则弹出栈顶元素与元素e比对,如果不同则保错退出。
7. 如果弹出的元素e为终结符,和tokenA进行对比,如果不相等则保错。
8. 继续步骤4,直到栈Stack为空。
LL文法主要过程就是从最终的非终结符开始,不断将最左边也就是栈顶的非终结符用产生式进行替换,针对LL(k)的文法过程中一个非终结符+一个token可能会出现多个产生式,需要采取回溯的方式找到最合适的匹配。
而针对大部分SQL语句,基本上LL(1)文法就可以描述,因为每个语句元素,通过SQL语句中的第一个词就可以判断出该子句具体是什么操作,比如 SELECT GROUP BY WHERE JOIN等。