Java/GradleでANTLRを使い、DDLからテーブル定義書を作成、その2

前回の最後で「Positive Technologies MySQL grammar」というものからJavaパーサーを作ってみた。エラーは出ていないのでSQLを解析してみよう。

動かしてみる


import java.util.*; import org.antlr.v4.runtime.*; import mysqlparser.*; import mysqlparser.MySqlParser.*; public class TestMain { static String SQL = "SELECT COUNT(*) FROM TBLBOOK; INSERT INTO TBLBOOK (A, B) VALUES(1, 2)"; public static void main(String[] args) { // 字句解析を行う MySqlLexer lexer = new MySqlLexer(CharStreams.fromString(SQL)); // 字句解析の結果を構文解析する CommonTokenStream stream = new CommonTokenStream(lexer); MySqlParser parser = new MySqlParser(stream); // 構文木を調べてみる List<SqlStatementContext>sqls = parser.root().sqlStatements().sqlStatement(); sqls.stream().forEach(sql->{ System.out.println("" + sql.getText()); }); } }

出力は以下のようになる。一つのSQL文全体を出力すると、スペースが除去される形になるらしい。

SELECTCOUNT(*)FROMTBLBOOK
INSERTINTOTBLBOOK(A,B)VALUES(1,2)

小文字は扱えない

実はこのパーサは小文字が扱えない。

  static String SQL = 
      "select count(*) from tblbook; insert into tblbook (a, b) values(1, 2)";

などとすると、以下のようなエラーになる。

line 1:13 no viable alternative at input '(*'

この件は以下に記述がある。

これは非常に困る。本来は入力を一律に大文字に変換できないのだが(文字列リテラル中の小文字も大文字になってしまう)、しかし今回の目的にはこれでも構わない。

すべてを大文字にして入力することにする。

  static String SQL = 
      "select count(*) from tblbook; insert into tblbook (a, b) values(1, 2)";

  public static void main(String[] args) {

    // 字句解析を行う、その前にすべてを大文字に変換
    MySqlLexer lexer = new MySqlLexer(CharStreams.fromString(SQL.toUpperCase()));

コメントが無視されてしまう

これは当然だろうが、字句解析によってコメントは無視され、コメントを除去した上でのトークンがパーサー側に受け渡されているようだ。これは、MySqlLexer.g4の中の最初に示されている。

lexer grammar MySqlLexer;

channels { MYSQLCOMMENT, ERRORCHANNEL }

// SKIP

SPACE:                               [ \t\r\n]+    -> channel(HIDDEN);
SPEC_MYSQL_COMMENT:                  '/*!' .+? '*/' -> channel(MYSQLCOMMENT);
COMMENT_INPUT:                       '/*' .*? '*/' -> channel(HIDDEN);
LINE_COMMENT:                        (
                                       ('-- ' | '#') ~[\r\n]* ('\r'? '\n' | EOF) 
                                       | '--' ('\r'? '\n' | EOF) 
                                     ) -> channel(HIDDEN);

空白類はHIDDENというチャンネル、コメントはMYSQLCOMMENTというチャンネルに送られるらしい。これを取得する方法は無いものだろうか?これは、以下の本に説明があった。

「4.5 Cool Lexical Features」の「Rewriting the Input Stream」、あるいは「12.1 Broadcasting Tokens on Different Channels」という箇所だ。

要するに、コメントや空白類を保持してしまうと、構文定義にそれらすべてを混ぜなければならない。それらを無視すれば構文定義がすっきりする。これが以前のやり方だったのだが、しかしそれでは必要とするコメントまで抜け落ちてしまう。構文木を解析する上位アプリでは、どんなコメントがあったのかわからないのだ。

そこで、コメントや空白は「別チャンネルに送ることにした」のだという。「保持されるが無視しうる」という状態なのだそうだ。

さて、具体的にどうやってコメントを拾うのかは、この本をもう少し読んでみないとわからない。