Java/GradleでANTLRを使い、DDLからテーブル定義書を作成、その3
リスナー方式に変更
どうもコメントを拾うには、これまでのやり方ではだめのようだ。リスナーを使う必要があるらしい。そこで、以下のように変更してみる。
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import 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.toUpperCase()));
// 字句解析の結果を構文解析する
CommonTokenStream stream = new CommonTokenStream(lexer);
MySqlParser parser = new MySqlParser(stream);
// リスナーを作成
MySqlParserBaseListener listener = new MySqlParserBaseListener() {
@Override public void enterSqlStatement(MySqlParser.SqlStatementContext ctx) {
System.out.println("" + ctx.getText());
}
};
// ウォーカーで構文木をたどっていき、リスナーで構文要素を得る
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(listener, parser.root());
}
}
出力結果は以下になる。
SELECTCOUNT(*)FROMTBLBOOK
INSERTINTOTBLBOOK(A,B)VALUES(1,2)
コメントを拾う
で、以下の本の「12.1 Broadcasting Tokens on Different Channels」を見てみる。
つまり、単にコメントやら空白やらをスキップすると、本体のChannel 0とは別のChannel 1に入っていくのだが、特にコメントを保持したい場合には、さらに空白とは別のチャンネルに入れる。
それが上の図の後者の状況だ。しかし、この場合でもトークンインデックスは同じになっている。つまり、本体の「void」のインデックスは2であり、コメントを得たい場合には、コメントチャンネルでvoidの左側のインデックスを見れば良いということになる。
そこで、次のように変更してみる。
import java.util.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import mysqlparser.*;
public class TestMain {
static String SQL =
"/*! this is test */ \n" +
"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()));
// 字句解析の結果を構文解析する
CommonTokenStream stream = new CommonTokenStream(lexer);
MySqlParser parser = new MySqlParser(stream);
// リスナーを作成
MySqlParserBaseListener listener = new MyListener(stream);
// ウォーカーで構文木をたどっていき、リスナーで構文要素を得る
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(listener, parser.root());
}
static class MyListener extends MySqlParserBaseListener {
final BufferedTokenStream tokens;
MyListener(BufferedTokenStream tokens) {
this.tokens = tokens;
}
@Override public void enterSqlStatement(MySqlParser.SqlStatementContext ctx) {
Token sqlToken = ctx.getStart();
int sqlTokenIndex = sqlToken.getTokenIndex();
List<Token>hiddenTokens =
tokens.getHiddenTokensToLeft(sqlTokenIndex, MySqlLexer.MYSQLCOMMENT);
if (hiddenTokens != null && hiddenTokens.size() > 0) {
System.out.println("mysqlcomment[" + hiddenTokens.get(0).getText() + "]");
}
System.out.println("" + ctx.getText());
}
}
}
入力にはMYSQLコメント「/*! this is test */」がSQLステート麺との前に付けられている。そして、SQLステートメントがやってきたら、コメントチャンネルを見に行き「左」のものを見る。
結果は以下になった。これでOKだ。
mysqlcomment[/*! THIS IS TEST */]
SELECTCOUNT(*)FROMTBLBOOK
INSERTINTOTBLBOOK(A,B)VALUES(1,2)
コメント字句解析の定義を変更する。
以下になっているのを、
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);
以下に変更する
channels { MYCOMMENT, ERRORCHANNEL }
// SKIP
SPACE: [ \t\r\n]+ -> channel(HIDDEN);
SPEC_MYSQL_COMMENT: '/*!' .+? '*/' -> channel(MYCOMMENT);
COMMENT_INPUT: '/*' .*? '*/' -> channel(MYCOMMENT);
LINE_COMMENT: (
('-- ' | '#') ~[\r\n]* ('\r'? '\n' | EOF)
| '--' ('\r'? '\n' | EOF)
) -> channel(MYCOMMENT);
次のように変更する。
import java.util.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import mysqlparser.*;
public class TestMain {
static String SQL =
"/* this is test */ \n" +
"select count(*) from tblbook;\n" +
"-- this is comment\n" +
"insert into tblbook (a, b) values(1, 2);" +
"/*! MySql comment */ SELECT * FROM SAMPLE";
public static void main(String[] args) {
// 字句解析を行う
MySqlLexer lexer = new MySqlLexer(CharStreams.fromString(SQL.toUpperCase()));
// 字句解析の結果を構文解析する
CommonTokenStream stream = new CommonTokenStream(lexer);
MySqlParser parser = new MySqlParser(stream);
// リスナーを作成
MySqlParserBaseListener listener = new MyListener(stream);
// ウォーカーで構文木をたどっていき、リスナーで構文要素を得る
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(listener, parser.root());
}
static class MyListener extends MySqlParserBaseListener {
final BufferedTokenStream tokens;
MyListener(BufferedTokenStream tokens) {
this.tokens = tokens;
}
@Override public void enterSqlStatement(MySqlParser.SqlStatementContext ctx) {
Token sqlToken = ctx.getStart();
int sqlTokenIndex = sqlToken.getTokenIndex();
List<Token>hiddenTokens =
tokens.getHiddenTokensToLeft(sqlTokenIndex, MySqlLexer.MYCOMMENT);
if (hiddenTokens != null && hiddenTokens.size() > 0) {
System.out.println("comment[" + hiddenTokens.get(0).getText() + "]");
}
System.out.println("" + ctx.getText());
}
}
}
結果は以下になる。
comment[/* THIS IS TEST */]
SELECTCOUNT(*)FROMTBLBOOK
comment[-- THIS IS COMMENT
]
INSERTINTOTBLBOOK(A,B)VALUES(1,2)
comment[/*! MYSQL COMMENT */]
SELECT*FROMSAMPLE