ANTLR 實戰 SQL 詞法/語法分析
關於 詞法/語法分析 和 ANTLR 語法 的詳細內容,可參考我的另一篇博客:https://blog.csdn.net/pentiumCM/article/details/106076655。
本篇博客爲實戰入門速食篇,主要提供demo實戰,不做太多內容展開。
ANTLR 是語言識別的一個工具 (ANother Tool for Language Recognition ) ,它提供了一個框架,可以通過包含 Java, C++, 或 C# 動作(action)的語法描述來構造語言識別器,編譯器和解釋器。
一、準備工具
- 安裝 ANTLR 環境:
推薦使用 ANTLR-4.8版本
參考資料:https://www.cnblogs.com/wynjauu/articles/9872822.html
本篇博客主要介紹在 java 環境下面使用 ANTLR,所有有兩種方式:
- 方式一:直接基於 java 的 jdk 環境,不使用 java的 IDE中來使用 ANTLR,全程操作在CMD中通過命令
- 方式二:基於 java 的 IDE 來使用 ANTLR,操作比較方便
兩種方式皆可,如果電腦中有 ide 編譯器,本人建議可使用 ide 安裝 ANTLR 的插件來使用 ANTLR。
二、實戰環節
1. 方式1:不借助外部 IDE
- 準備 ANTLR 的 文法文件(g4後綴)
可以從 ANTLR 官方提供的 demo 中挑選自己需要的,ANTLR 官方demo:https://github.com/antlr/grammars-v4
爲了方便,我直接貼出我已經選好的文法,如下:
MysqlQuery.g4:
MysqlQuery.g4 內容如下:
// 1. 定義一個名爲 MysqlQuery 的語法
grammar MysqlQuery;
// 2. rule - 這是核心,表示規則,以 “:” 開始, “;” 結束, 多規則以 "|" 分隔。
// 2.1 lexer - 詞法(符號(Token)名大寫開頭 - 詞法)
AS : A S;
SELECT : S E L E C T;
FROM : F R O M;
TABLE : T A B L E;
MAX : M A X;
SUM : S U M;
AVG : A V G;
MIN : M I N;
COUNT : C O U N T;
ALL : A L L;
DISTINCT : D I S T I N C T;
WHERE : W H E R E;
GROUP : G R O U P;
BY : B Y ;
ORDER : O R D E R;
HAVING : H A V I N G;
NOT : N O T;
IS : I S ;
TRUE : T R U E;
FALSE : F A L S E;
UNKNOWN : U N K N O W N;
BETWEEN : B E T W E E N;
AND : A N D;
IN : I N;
NULL : N U L L;
OR : O R ;
ASC : A S C;
DESC : D E S C;
LIMIT : L I M I T ;
OFFSET : O F F S E T;
fragment A : [aA];
fragment B : [bB];
fragment C : [cC];
fragment D : [dD];
fragment E : [eE];
fragment F : [fF];
fragment G : [gG];
fragment H : [hH];
fragment I : [iI];
fragment J : [jJ];
fragment K : [kK];
fragment L : [lL];
fragment M : [mM];
fragment N : [nN];
fragment O : [oO];
fragment P : [pP];
fragment Q : [qQ];
fragment R : [rR];
fragment S : [sS];
fragment T : [tT];
fragment U : [uU];
fragment V : [vV];
fragment W : [wW];
fragment X : [xX];
fragment Y : [yY];
fragment Z : [zZ];
fragment HEX_DIGIT: [0-9A-F];
fragment DEC_DIGIT: [0-9];
fragment LETTER: [a-zA-Z];
ID: ( 'A'..'Z' | 'a'..'z' | '_' | '$') ( 'A'..'Z' | 'a'..'z' | '_' | '$' | '0'..'9' )*;
TEXT_STRING : ( '\'' ( ('\\' '\\') | ('\'' '\'') | ('\\' '\'') | ~('\'') )* '\'' );
ID_LITERAL: '*'|('@'|'_'|LETTER)(LETTER|DEC_DIGIT|'_')*;
REVERSE_QUOTE_ID : '`' ~'`'+ '`';
DECIMAL_LITERAL: DEC_DIGIT+;
// 2.2 parser - 語法
//解析規則(Parser rule)名小寫開頭,後面可以跟字母、數字、下劃線 - 語法
tableName : tmpName=ID;
column_name :ID;
function_name : tmpName=ID ;
selectStatement:
SELECT
selectElements
(
FROM tableSources
( whereClause )?
( groupByCaluse )?
( havingCaluse )?
) ?
( orderByClause )?
( limitClause )?
;
selectElements
: (star='*' | selectElement ) (',' selectElement)*
;
tableSources
: tableName (',' tableName)*
;
whereClause
: WHERE logicExpression
;
logicExpression
: logicExpression logicalOperator logicExpression
| fullColumnName comparisonOperator value
| fullColumnName BETWEEN value AND value
| fullColumnName NOT? IN '(' value (',' value)* ')'
| '(' logicExpression ')'
;
groupByCaluse
: GROUP BY groupByItem (',' groupByItem)*
;
havingCaluse
: HAVING logicExpression
;
orderByClause
: ORDER BY orderByExpression (',' orderByExpression)*
;
limitClause
: LIMIT
(
(offset=decimalLiteral ',')? limit=decimalLiteral
| limit=decimalLiteral OFFSET offset=decimalLiteral
)
;
orderByExpression
: fullColumnName order=(ASC | DESC)?
;
groupByItem
: fullColumnName order=(ASC | DESC)?
;
logicalOperator
: AND | '&' '&' | OR | '|' '|'
;
comparisonOperator
: '=' | '>' | '<' | '<' '=' | '>' '='
| '<' '>' | '!' '=' | '<' '=' '>'
;
value
: uid
| textLiteral
| decimalLiteral
;
decimalLiteral
: DECIMAL_LITERAL
;
textLiteral
: TEXT_STRING
;
selectElement
: fullColumnName (AS? uid)? #selectColumnElement
| functionCall (AS? uid)? #selectFunctionElement
;
fullColumnName
: column_name
;
functionCall
: aggregateWindowedFunction #aggregateFunctionCall
;
aggregateWindowedFunction
: (AVG | MAX | MIN | SUM) '(' functionArg ')'
| COUNT '(' (starArg='*' | functionArg?) ')'
| COUNT '(' aggregator=DISTINCT functionArgs ')'
;
functionArg
: column_name
;
functionArgs
: column_name (',' column_name)*
;
uid
: ID
;
// 在進行解析的過程中,忽略掉空格,換行
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- 運行命令生成相關java文件與token文件:
在 文件MysqlQuery.g4 所在的目錄打開CMD窗口,執行如下的命令:
antlr4 MysqlQuery.g4
如圖,箭頭1是我們自己定義好的 詞法/語法 規則,箭頭2是 antlr4 生成命令,箭頭3是生成出來的文件。
- 編譯java文件
繼續在 cmd 窗口中執行命令:
javac ./*.java
- 分析語法樹
輸入grun命令回車,在命令行輸入你要測試的語法,再回車,按Ctrl+z 後回車。
例如,我們需要測試 MysqlQuery.g4 文件中的 selectStatement:
我們在當前的 CMD中輸入:
grun MysqlQuery selectStatement -gui
回車之後,即可輸入我們需要測試的語句,這時候我們輸入:
select a from b where c = 1
繼續回車,這時候按 Ctrl+z(結束符)後再按回車,便可得到我們需要的語法樹了,並且以 GUI 的形式呈現:
語法樹的 GUI 窗口:
2. 方式2:藉助 idea
-
IDEA 集成 ANTLR:
參考資料: https://blog.csdn.net/qq_21383435/article/details/80814618 -
在 idea 中新建 maven 項目,在項目中新建 .g4 的文法文件
MysqlQuery.g4 文件內容爲方案一中所提供的內容
-
文法可視化
在 idea 中集成使用 ANTLR 的好處是,當你編輯好 .g4的文法文件之後,不需要像方案一中生成 java 等文件在編譯 進行測試,直接可以通過 ANTLR Preview進行查看效果。- 在 ide 中調出 ANTLR Preview:
- 在 .g4 的文法文件中選擇需要測試的語法:
如我們需要測試 selectStatement,鼠標點到該語法處,然後右鍵 Text Rule selectStatement
在左側輸入待測試的序列,右側會自動生成語法樹結構,十分方便
- 在 ide 中調出 ANTLR Preview:
參考資料
https://blog.csdn.net/qq_39158142/article/details/86437919
https://blog.csdn.net/sherrywong1220/article/details/53697737?utm_source=blogxgwz4