基础知识
正则表达式基础
1 | *:匹配前面的子表达式零次或多次。 |
ANTLR语法基础
grammar
名称和文件名要一致。
antlr4的写法
比如:定义词法文件为Hello.g4
文件开头要声明为如下:1
grammar Hello;
antlr3的写法1
lexer grammar Hello;
Parser规则
即non-terminal以小写字母开始。
Lexer规则
即terminal以大写字母开始。
Lexer 规则无论写在哪里都会被重排到Parser规则之后。
规则先后顺序
规则中若有冲突,先出现的规则优先匹配
字符串
使用单引号引出字符串。
例如:’string’
|符号的时用
|用于分隔两个产生式,(a|b) 括号用于指定子产生式。
例如:sparksql的SqlBase.g4文件1
2
3
4
5statement
: query #statementDefault
| ctes? dmlStatementNoWith #dmlStatement
| USE db=identifier #use
;
@members
表示在antlr配置文件中内嵌java代码。
例如:sparksql部分的antlr配置SqlBase.g4文件嵌入如下代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 {
/**
* When false, INTERSECT is given the greater precedence over the other set
* operations (UNION, EXCEPT and MINUS) as per the SQL standard.
*/
public boolean legacy_setops_precedence_enbled = false;
/**
* Verify whether current token is a valid decimal token (which contains dot).
* Returns true if the character that follows the token is not a digit or letter or underscore.
*
* For example:
* For char stream "2.3", "2." is not a valid decimal token, because it is followed by digit '3'.
* For char stream "2.3_", "2.3" is not a valid decimal token, because it is followed by '_'.
* For char stream "2.3W", "2.3" is not a valid decimal token, because it is followed by 'W'.
* For char stream "12.0D 34.E2+0.12 " 12.0D is a valid decimal token because it is followed
* by a space. 34.E2 is a valid decimal token because it is followed by symbol '+'
* which is not a digit or letter or underscore.
*/
public boolean isValidDecimal() {
int nextChar = _input.LA(1);
if (nextChar >= 'A' && nextChar <= 'Z' || nextChar >= '0' && nextChar <= '9' ||
nextChar == '_') {
return false;
} else {
return true;
}
}
/**
* When true, ANSI SQL parsing mode is enabled.
*/
public boolean ansi = false;
}
fragment
相当于私有变量,不希望外部访问的变量,用于其他(函数或变量)去调用的。
fragment仅仅是为了把长词法规则分解为短词法规则以提高可读性。
声明为fragment可以告诉antlr,该规则本身不是一个词法符号,它只会被其它的词法规则使用,这意味着我们不能在文法规则中引用DIGIT。
例如:sparksql的SqlBase.g4配置文件中1
2
3fragment LETTER
: [A-Z]
;
@header
运行antlr脚本,生成的类中自动带上指定的这个包路径。1
package org.spark.spark.sql; } {
#label
在产生式后面 # label 可以给某条产生式命名,在生成的代码中即可根据标签分辨不同产生式。
规则:1
2
3要么所有的产生式都有标签
要么所有的产生式都没有标签
两个产生式可以使用相同的标签
例如:sparksql的SqlBase.g4文件1
2
3
4
5
6insertInto
: INSERT OVERWRITE TABLE tableIdentifier (partitionSpec (IF NOT EXISTS)?)? #insertOverwriteTable
| INSERT INTO TABLE? tableIdentifier partitionSpec? #insertIntoTable
| INSERT OVERWRITE LOCAL? DIRECTORY path=STRING rowFormat? createFileFormat? #insertOverwriteHiveDir
| INSERT OVERWRITE LOCAL? DIRECTORY (path=STRING)? tableProvider (OPTIONS options=tablePropertyList)? #insertOverwriteDir
;
自定义通道规范
只有词法分析器语法才能包含自定义通道规范。
skip
参考:https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md
skip命令告诉词法分析器获取另一个token并丢弃当前文本。
例如:1
2
3
4ID : [a-zA-Z]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace(抛弃空格)
channel
将词法符号送入不同的通道。
背景:如果我们不想让空白字符和注释在语法中到处都是,我们必须让词法分析器丢弃它们。不幸的是,这意味着程序中将完全无法访问空白字符和注释,也无法对它们进一步处理。
解决:
忽略却保留注释和空白字符的方法是将这些词法符号送入一个隐藏通道。
使用方法:
案例来自sparksql 的SqlBase.g4文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 匹配sql单行注释
SIMPLE_COMMENT
: '--' ~[\r\n]* '\r'? '\n'? -> channel(HIDDEN)
;
// 匹配sql多行注释,/*xxxxx*/样式的注释
BRACKETED_EMPTY_COMMENT
: '/**/' -> channel(HIDDEN)
;
/*
* 匹配sql多行注释,/*
* *
* */
* 类型的注释
*/
BRACKETED_COMMENT
: '/*' ~[+] .*? '*/' -> channel(HIDDEN)
;
规则结束
规则以分号终结。
例如:1
2
3createTableHeader
: CREATE TEMPORARY? EXTERNAL? TABLE (IF NOT EXISTS)? multipartIdentifier
;
注释
多行注释
1 | /** comment */ |
单行注释
1 | // |
递归
可以处理直接的左递归,不能处理间接的左递归。
MUL: ‘*’
如果用 MUL: ‘*’; 指定了某个字符串的名字,在程序里面就能用这个名字了。
常量操作符
在配置文件定义操作符常量,可以在visitor中以常量的形式引用这些符号。
例如:sparksql的SqlBase.g4配置文件中如下内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21ADD: 'ADD';
AFTER: 'AFTER';
ALL: 'ALL';
ALTER: 'ALTER';
ANALYZE: 'ANALYZE';
AND: 'AND';
ANTI: 'ANTI';
ANY: 'ANY';
ARCHIVE: 'ARCHIVE';
ARRAY: 'ARRAY';
AS: 'AS';
ASC: 'ASC';
AT: 'AT';
AUTHORIZATION: 'AUTHORIZATION';
BETWEEN: 'BETWEEN';
BOTH: 'BOTH';
BUCKET: 'BUCKET';
BUCKETS: 'BUCKETS';
BY: 'BY';
CACHE: 'CACHE';
CASCADE: 'CASCADE';