はじめに
「毎月1つは新しい技術に触れよう!」と志した結果、1月のテーマは「ANTLR4」になりました(なんとなく)
- 多分、1年くらいかけて、PL/0 インタプリタを開発するんだと思います(完成するかな?)
- なつかしいですね。20年前作りましたね。
- 上手くいったら、これLLVMに繋げたいなあ……
ANTLR4とは?
ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It's widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface (or visitor) that makes it easy to respond to the recognition of phrases of interest.
(邦訳)ANTLR(ANother Tool for Language Recognition)は、強力なparser生成器です。構造化された文章やバイナリファイルに対する、読み込み、処理、実行、翻訳に使えます。言語、ツール、フレームワークに広く使われています。grammerから、ANTLRはparserを生成します、oarserがoarse treeを生成し、listener interface(もしくはvisitor)を生成します。これによって、興味のあるフレーズの認識が容易に実現できます。
非常にわかりやすい参考文献
-
ANTLRで数式を逆ポーランド記法に変換してみた
- こちらを参考にしつつ、試行錯誤でやっています。
環境構築
環境構築手段はこのあたりを参考に。
$ cd /usr/local/lib
$ curl -O https://www.antlr.org/download/antlr-4.9-complete.jar
export CLASSPATH=".:/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH"
alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
alias grun='java -Xmx500M -cp "/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH" org.antlr.v4.gui.TestRig'
# Step0. 入力された数字を表示する
grammerファイルを作る。
- grammerは、ファイル名を合わせる事(ここでは‘Cal'を使っている)
- あとでsum表示に使うつもりなのでsum
grammar Cal;
sum : exp ;
exp : NUM ;
NUM : [0-9]+ ;
WS : [\t\r\n]+ -> skip;
コンパイルする
Makefileを使ってサクッとコンパイルする。なお、コンパイルするときはjavacに全部のjavaファイルを同時に喰わせた方がいいっぽい(そうでないとundefinedなsymbolと怒られる)。
export CLASSPATH=".:/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH"
ANTLR4=java -Xmx500M -cp "/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH" org.antlr.v4.Tool
all: Cal.tokens
javac *.java
Cal.tokens : Cal.g4
$(ANTLR4) $<
clean:
rm -rf *.class
動作確認
grun Cal sum -gui
で動作確認できる。なお、入力が終わったら改行してCtrl+D
しないと反映されない。(ここだと、123"改行""Ctrl+D"までやって、画面表示される)。
Step1. "1+1"に対応してみる。
grammerを見直して、"1+1"にも対応させてみよう。
expは、NUM
だけでなく、NUM + NUM
も食べれますよ、と。
grammar Cal;
sum : exp ;
exp : NUM |
exp PLUS exp;
PLUS: '+' ;
NUM : [0-9]+ ;
WS : [\t\r\n]+ -> skip;
対応する関数を実装
- exitExp()は、exp要素から抜けるときに実行される関数。
- つまり、"123 + 456"の場合…3回呼ばれる。
- 123のexpから抜ける場合
- 456のexpから抜ける場合
- ”123+456"のexpから抜ける場合、
- テストコードの振る舞い
- 数値のexpから抜けるときには、Stackに数字を積む(push)
- PLUS含むexpから抜けるときは、Stackから2つ数字を抜き(pop)、加算して数字を積む(push)
- exitSum()が呼ばれるので、Stackに残った値を表示する。
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;
import java.util.*;
public class CalInterfaceListener extends CalBaseListener {
CalParser parser;
Stack<Integer> mStack;
public CalInterfaceListener(CalParser parser) {
this.parser = parser;
mStack = new Stack<Integer>();
}
@Override
public void exitExp(CalParser.ExpContext ctx) {
if( ctx.PLUS() != null ){
int v1 = Integer.parseInt(mStack.pop().toString() );
int v2 = Integer.parseInt(mStack.pop().toString() );
Integer v3 = v1 + v2;
mStack.push ( v3 );
}
if( ctx.NUM() != null ){
mStack.push( Integer.parseInt( ctx.NUM().toString() ) );
}
}
@Override
public void exitSum(CalParser.SumContext ctx) {
System.out.println( mStack.pop() );
}
}
つみのこし・・・
ここに、PL/0のgrammerがあるのではあるが… うん、"*"を使った構文のあたりのやり方が分からない!
if( ctx.XXX() != null ){
が0個のときもTRUEになるっぽい。ここらへんは2月の宿題ということで。
expression
: ('+' | '-')? term (('+' | '-') term)*
;
term
: factor (('*' | '/') factor)*
;
factor
: ident
| number
| '(' expression ')'
;
ident
: STRING
;
number
: NUMBER
;