ANTLR4ことはじめ
目次
- 1. 概要
- 2. この文書作ろうと思ったきっかけ
- 3. 関連文書
- 4. 似たツール
- 5. Ubuntu20.04でANTLR4をインストール
- 6. 簡易電卓の作成
- 7. 簡易プログラム言語の作成その1
- 8. 今後
- 9. この文書のチェンジログ
1 概要
- 機能豊富なパーサジェネレータ
- インストール
- 簡易電卓作成(普通のプログラム言語のHello Worldにあたるもの)
- 簡易プログラム言語作成(計算機能、変数利用と、print命令のみ)
2 この文書作ろうと思ったきっかけ
- 構文解析をPythonでやろうと色々調べたところ、どうやら、ANTLR4が抜けてるというのをみつけて、これやってみよとなりました。
3 関連文書
- 公式サイトhttps://www.antlr.org/index.html
- 公式サイト文書https://github.com/antlr/antlr4/blob/master/doc/index.md
- GitHub https://github.com/antlr/antlr4
- 各種言語や規約用のパーサーソース https://github.com/antlr/grammars-v4
- ダウンロード https://www.antlr.org/download/
- Getting Started https://github.com/antlr/antlr4/blob/master/doc/getting-started.md
- ANATLR4 の Python (2 and 3)関連文書 https://github.com/antlr/antlr4/blob/master/doc/python-target.md
4 似たツール
- Python
- C++
- 古典?
- flex
- bison
- lex
- yacc
5 Ubuntu20.04でANTLR4をインストール
- 公式サイトの文書のやつをやってみる
5.1 以下の操作を行っている動画
5.2 Ubuntu20.04でANTLR4をインストール する手順
sudo apt install antlr4
- 環境変数をセットしないと、上手く動作しなかった(必要なものが見つからないってエラー)
- 以下を実行してから実行するのが良い
- ホームディレクトリの.bashrcとかに追記しておくと、いちいち実行しなくても良くなる
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH}
5.3 動作確認
5.3.1 ソースファイルHello.g4を用意
- https://github.com/antlr/antlr4/blob/master/doc/getting-started.mdの A First Example をやってみる
- Hello.g4を上のサイトの書いてある内容で作成
5.3.2 ソースファイルHello.g4から生成
antlr4 Hello.g4
- これで、必要なファイルが生成される
HelloLexer.interp HelloLexer.java HelloLexer.tokens Hello.interp HelloParser.java HelloListener.java HelloBaseListener.java Hello.tokens
5.3.3 生成したjavaファイルをコンパイル
javac Hello*.java
5.3.4 生成したものを動作確認
/usr/share/antlr4/grun Hello r -tree or /usr/share/antlr4/grun Hello r -gui
5.4 この章のまとめ
- antlr4をUbuntu20.04にインストール
- 公式サイトのGetting StartedのHello.g4を利用してコンパイル実行してみた
6 簡易電卓の作成
6.1 自然数を解釈する
6.1.1 以下の操作を行っている動画
6.1.2 自然数を解釈する 手順
- 0だけを解釈するものを作る
- 以下の内容でCalc.g4というファイルを作成
- Parser rule(パーサールール)名は小文字で開始
- Token(識別子)名は大文字で開始
grammar Calc; expr : N; N : '0'; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH} antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
- 0と1を解釈するものを作る
- 以下の内容にCalc.g4を修正
- 亀の子括弧[]は中身のどれかに該当する一文字にマッチする正規表現
- 例1 [01]は0か1にマッチ
- 例2 [012]は0か1か2にマッチ
- 例3 [abc]はaかbかcにマッチ
- 正規表現は言語やツールによって多少方言あり
grammar Calc; expr : N; N : [01]; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
- 0から9までを解釈するものを作る
- 以下の内容にCalc.g4を修正
grammar Calc; expr : N; N : [0123456789]; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
- 0から9までを解釈するものを作る(その2)
- 以下の内容にCalc.g4を修正
- 亀の子括弧[]は中身のどれかに該当する一文字にマッチする正規表現
- 例1 [0-5]は0か1か2か3か4か5にマッチ
- (文字もコンピューター内部では数字で処理されていて、この数字ならこの文字の事ということで処理されている。通常半角英数の場合,アスキーコードという文字と数字の対応の規約が使われていて、それだと0から9まで1つずつ増える数字で扱っている)
- 例2 [0-2]は0か1か2にマッチ
- 例3 [a-z]はaからzまでの26文字の1文字にマッチ
- 今回の[0-9]は0,1,2,3,4,5,6,7,8,9の1文字にマッチする正規表現
- 例1 [0-5]は0か1か2か3か4か5にマッチ
grammar Calc; expr : N; N : [0-9]; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
- 二桁以上の数字に対応(その1)
- 以下の内容にCalc.g4を修正
- +は正規表現で、直前のものを1度以上繰り返ししているものにマッチ
- 例えば 1+なら、1が1文字以上にマッチ、1,11,111,1111,….にマッチする
- 今回の[0-9]+だと、0から9までの文字が1文字以上繰り返されるものにマッチ
- *は正規表現で、直前のものを0回以上繰り返ししているものにマッチ
- 例1 0* なら 文字無し、0,00,000,0000,0000,….にマッチ
- 例2 [0-9]* なら 数字が0回以上繰り返されるものにマッチ、 文字なし、0,12,359,…等など
grammar Calc; expr : N; N : [0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
- 二桁以上の数字に対応(その2)
- 以下の内容にCalc.g4を修正
grammar Calc; expr : N; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- [0-9] | [1-9][0-9]+ : 0から9のひと桁の数字あるいは、1から9ではじまる2桁以上の数字にマッチする表現
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc expr -gui
6.1.3 この節のまとめ
- 自然数を解釈するパーサーの作成
6.2 足し算表現、引き算表現の解釈処理追加
6.2.1 以下の操作を行っている動画
6.2.2 足し算表現、引き算表現の解釈処理追加 手順
- まず足し算表現を追加
- 前の章で作成したCalc.g4を以下に修正
- 注意書き、正規表現マッチの多くの場合、特別な意味を持つ文字(+,*,.,?等)はバックスラッシュでエスケープすると、特殊な意味ではなく、その文字自体を意味するものとして扱うことが多いが、ANTLERの文法記述ではこれではなく、半角シングルクオートでかこむようです。
- 例えば+文字自体を扱いたい時
- ‘+’と記述
- 例えば+文字自体を扱いたい時
- 他の主要な正規表現で使う文字
- .(半角ピリオド文字) : 任意の1文字にマッチ
- “no viable alternative at input ‘<EOF>'” を防ぐ為に、parseルールを追加
grammar Calc; parse : expr EOF; expr : expr '+' expr | N; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH} antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc parse -gui
- 引き算表現も追加
- 以下の内容にCalc.g4を修正
grammar Calc; parse : expr EOF; expr : expr '+' expr | expr '-' expr | N; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc parse -gui
- 数字の前に’-‘がある場合の処理追加
- 以下の内容にCalc.g4を修正
grammar Calc; parse : expr EOF; expr : expr '+' expr | expr '-' expr | N | '-' N; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc parse -gui
6.2.3 この節のまとめ
- 足し算引き算表現も解釈出来るように修正
- 数字の前のマイナス記号も解釈出来るように修正
6.3 掛け算表現、割り算表現の解釈処理追加
6.3.1 以下の操作を行っている動画
6.3.2 掛け算表現、割り算表現の解釈処理追加 手順
- 前の章で作成したCalc.g4を以下に修正
grammar Calc; parse : expr EOF; expr : expr '*' expr | expr '/' expr | expr '+' expr | expr '-' expr | N | '-' N; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH} antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc parse -gui
6.3.3 この節のまとめ
- 掛け算、割り算表現も解釈出来るように修正
6.4 括弧でくるんだ部分を優先して処理するように修正
6.4.1 以下の操作を行っている動画
6.4.2 括弧でくるんだ部分を優先して処理するように修正 する手順
- 前の章で作成したCalc.g4を以下に修正
- 計算式を複数入れれるように修正
- 区切り文字としては、EOF,改行,セミコロン(;)
grammar Calc; parse : expr EOF; expr : '(' expr ')' | expr '*' expr | expr '/' expr | expr '+' expr | expr '-' expr | N | '-' N | '-' '(' expr ')' ; N : [0-9] | [1-9][0-9]+; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- コンパイル
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH} antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc parse -gui
6.4.3 この節のまとめ
- 括弧でくるんだ部分を優先して処理するように修正
6.5 複数数式入力に対応する修正
6.5.1 以下の操作を行っている動画
6.5.2 複数数式入力に対応する修正 する手順
- 前の章で作成したCalc.g4を以下に修正
- 計算式を複数入れれるように修正
- 区切り文字としては、EOF,改行,セミコロン(;)
grammar Calc; lines: | equation | lines equation | EOF; equation : expr CR | expr EOF; expr : '(' expr ')' | expr '*' expr | expr '/' expr | expr '+' expr | expr '-' expr | N | '-' N | '-' '(' expr ')' ; N : [0-9] | [1-9][0-9]+; CR : [\n;]; // newlines, semicolons WS : [ \t]+ -> skip ; // skip spaces, tabs
- コンパイル
export CLASSPATH=.:/usr/share/java/antlr4.jar:/usr/share/java/antlr4-runtime.jar:/usr/share/java:${CLASSPATH} antlr4 Calc.g4 javac Calc*java
- 以下で確認する(色々入力して、最後にCtrl-Dを押す。WindowsならCtrl-Zかも)
/usr/share/antlr4/grun Calc lines -gui
6.5.3 この節のまとめ
- 複数数式入力に対応できるようになった
6.6 現時点で最新バージョンのAntlr4をインストール(2021/1/25現在最新の4.9.1をインストール)
- 色々やっていると以下の不具合があったので、最新のものを入れ直すことにしたhttps://www.antlr.org/download/
- Ubuntu20.04でPython用のAntlr4パッケージがデフォルトで用意されていない
- pipでPython用のAntlr4ランタイムを入れると、Ubuntu20.04でパッケージ化されているAntlr4はバージョンがあわない
6.6.1 以下の操作を行っている動画
6.6.2 現時点で最新バージョンのAntlr4をインストール(2021/1/25現在最新の4.9.1をインストール) する手順
- antlr-4.9.1-complete.jar ダウンロード
wget -m -l 1 https://www.antlr.org/download/antlr-4.9.1-complete.jar ln -s www.antlr.org/download/antlr-4.9.1-complete.jar .
- 設定を行う
- 設定の方法についてはhttps://github.com/antlr/antlr4/blob/master/doc/getting-started.mdのInstallationのUnixのところがわかりやすい。そこの方法を、カレントディレクトリのファイルを使うように、また、バージョンを4.9.1を使うように修正したのが以下となります。
- これを利用しているシェルで実行すればOK
export CLASSPATH=".:./antlr-4.9.1-complete.jar:$CLASSPATH" alias antlr4='java -Xmx500M -cp "./antlr-4.9.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' alias grun='java -Xmx500M -cp "./antlr-4.9.1-complete.jar:$CLASSPATH" org.antlr.v4.gui.TestRig'
- 私はset.shというファイル名でこの命令を入れておいた。(また使う時にやり方忘れる為)
- ファイルに入れている場合(set.sh)環境変数や、aliasを設定するには
. set.sh
- あるいは、
source set.sh
- python用のパッケージをインストール
- デバッグ用のipdbと、ANTLR 4.9.1 runtime for Python 3 をインストール
- pip3はpipになる環境もある。環境にあわせてPython用のパッケージインストールコマンドを使って下さい。
- virtualenv下に入れた方が良いかも。私はvirtualenv環境にこれらのパッケージを入れている
- 例えばvirtualenvの環境名をYoutubeToolsにする場合
virtualenv YoutubeTools
- 作った環境に入るには
source YoutubeTools/bin/activate
- その後に以下を実行。pip3ではなく、pipを使うべき環境なら、そちらを使ってください。Ubuntu20.04ならpip3を使います。
pip3 install ipdb pip3 install antlr4-python3-runtime
6.6.3 この節のまとめ
- 現時点で最新バージョンのAntlr4をインストール(2021/1/25現在最新の4.9.1をインストール)
- Python3で利用するのに必要なパッケージインストール
- Python3用のデバッガーパッケージインストール
6.7 アクション用のラベル追加, Python用のコード生成
6.7.1 以下の操作を行っている動画
6.7.2 アクション用のラベル追加, Python用のコード生成 する手順
- 前の章で作成したCalc.g4を以下に修正
grammar Calc; lines: | equation | lines equation | EOF; equation : expr CR | expr EOF; expr : '(' expr ')' #myBr | expr '*' expr #myMul | expr '/' expr #myDiv | expr '+' expr #myAdd | expr '-' expr #mySub | N #myNum | '-' N #myMinusNum | '-' '(' expr ')' #myMinus ; N : [0-9] | [1-9][0-9]+; CR : [\n;]; // newlines, semicolons WS : [ \t]+ -> skip ; // skip spaces, tabs
- コンパイル
antlr4 -Dlanguage=Python3 Calc.g4
6.7.3 この節のまとめ
- アクション用のラベル追加し、Python3用のコードを自動生成した。
6.8 Python用のコード作成(Listenerメソッドが動くように修正)
6.8.1 関連文書
- ANATLR4 の Python (2 and 3)関連文書 https://github.com/antlr/antlr4/blob/master/doc/python-target.md の “How do I create and run a custom listener?”の部分が一番関係している
6.8.2 以下の操作を行っている動画
6.8.3 Python用のコード作成(Listenerメソッドが動くように修正) の手順
- main.pyを以下の内容で作成
- ANATLR4 の Python (2 and 3)関連文書 https://github.com/antlr/antlr4/blob/master/doc/python-target.md の “How do I create and run a custom listener?”の部分をベースに、今回作成した構文の名前に変更した
- ipdbを使って動作確認しやすくなるコードも追加してある
- ipdbを使わない場合は、ipdbが含まれいている行を削除するか、#でコメントにしておけばOK
import ipdb import sys from antlr4 import * from CalcLexer import CalcLexer from CalcParser import CalcParser from CalcListener import CalcListener class KeyPrinter(CalcListener): def exitKey(self, ctx): pass def main(argv): input = InputStream(argv[1]) lexer = CalcLexer(input) stream = CommonTokenStream(lexer) parser = CalcParser(stream) tree = parser.lines() #print(tree.getChild(0)) #print(tree.getChild(0).getText()) ##print(tree.getChild(0).toStringTree()) #ipdb.set_trace() printer = KeyPrinter() walker = ParseTreeWalker() walker.walk(printer, tree) if __name__ == '__main__': main(sys.argv)
6.8.4 前の節で自動生成した、CalcListener.pyを以下のように修正し、どこが、どの順で呼び出されるかを確認
- 主要な解釈のexit部分に、print文追加
- print(ctx.getText()) で該当の部分を表示
- 今回は省略していますが、enterの方も表示してみるとよりわかりやすくなります。
# Generated from Calc.g4 by ANTLR 4.9.1 from antlr4 import * if __name__ is not None and "." in __name__: from .CalcParser import CalcParser else: from CalcParser import CalcParser # This class defines a complete listener for a parse tree produced by CalcParser. class CalcListener(ParseTreeListener): # Enter a parse tree produced by CalcParser#lines. def enterLines(self, ctx:CalcParser.LinesContext): pass # Exit a parse tree produced by CalcParser#lines. def exitLines(self, ctx:CalcParser.LinesContext): print("exitLines") pass # Enter a parse tree produced by CalcParser#equation. def enterEquation(self, ctx:CalcParser.EquationContext): pass # Exit a parse tree produced by CalcParser#equation. def exitEquation(self, ctx:CalcParser.EquationContext): print("exitEquation") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myMul. def enterMyMul(self, ctx:CalcParser.MyMulContext): pass # Exit a parse tree produced by CalcParser#myMul. def exitMyMul(self, ctx:CalcParser.MyMulContext): print("exitMyMul") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myNum. def enterMyNum(self, ctx:CalcParser.MyNumContext): pass # Exit a parse tree produced by CalcParser#myNum. def exitMyNum(self, ctx:CalcParser.MyNumContext): print("exitMyNum") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myMinusNum. def enterMyMinusNum(self, ctx:CalcParser.MyMinusNumContext): pass # Exit a parse tree produced by CalcParser#myMinusNum. def exitMyMinusNum(self, ctx:CalcParser.MyMinusNumContext): print("exitMyMinusNum") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myMinus. def enterMyMinus(self, ctx:CalcParser.MyMinusContext): pass # Exit a parse tree produced by CalcParser#myMinus. def exitMyMinus(self, ctx:CalcParser.MyMinusContext): print("exitMyMinus") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#mySub. def enterMySub(self, ctx:CalcParser.MySubContext): pass # Exit a parse tree produced by CalcParser#mySub. def exitMySub(self, ctx:CalcParser.MySubContext): print("exitMySub") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myDiv. def enterMyDiv(self, ctx:CalcParser.MyDivContext): pass # Exit a parse tree produced by CalcParser#myDiv. def exitMyDiv(self, ctx:CalcParser.MyDivContext): print("exitMyDiv") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myAdd. def enterMyAdd(self, ctx:CalcParser.MyAddContext): pass # Exit a parse tree produced by CalcParser#myAdd. def exitMyAdd(self, ctx:CalcParser.MyAddContext): print("exitMyAdd") print(ctx.getText()) pass # Enter a parse tree produced by CalcParser#myBr. def enterMyBr(self, ctx:CalcParser.MyBrContext): pass # Exit a parse tree produced by CalcParser#myBr. def exitMyBr(self, ctx:CalcParser.MyBrContext): pass del CalcParser
6.8.5 テスト
- テスト1 100 の場合
python main.py "100"
- 上記の場合の出力
exitLines exitMyNum 100 exitEquation 100<EOF> exitLines
- テスト2 -100 の場合
python main.py "-100"
- 上記の場合の出力
exitLines exitMyMinusNum -100 exitEquation -100<EOF> exitLines
- テスト3 -100 の場合
python main.py "-100"
- 上記の場合の出力
exitLines exitMyMinusNum -100 exitEquation -100<EOF> exitLines
- テスト3 1+3 の場合
python main.py "1+3"
- 上記の場合の出力
exitLines exitMyNum 1 exitMyNum 3 exitMyAdd 1+3 exitEquation 1+3<EOF> exitLines
- テスト3 1+3 の場合
python main.py "(3-1)*5"
- 上記の場合の出力
exitLines exitMyNum 3 exitMyNum 1 exitMySub 3-1 exitMyNum 5 exitMyMul (3-1)*5 exitEquation (3-1)*5<EOF> exitLines
6.8.6 この節のまとめ
- Python用のコード作成(Listenerメソッドが動くように修正)
- print文を多量に埋め込むことで、どのように動作しているかを確認
6.9 電卓として動作するように処理を書く(Python向け)
6.9.1 以下の操作を行っている動画
6.9.2 電卓として動作するように処理を書く手順
6.9.3 前の節で自動生成した、CalcListener.pyを以下のように修正
# Generated from Calc.g4 by ANTLR 4.9.1 from antlr4 import * from collections import deque if __name__ is not None and "." in __name__: from .CalcParser import CalcParser else: from CalcParser import CalcParser # This class defines a complete listener for a parse tree produced by CalcParser. class CalcListener(ParseTreeListener): s = deque([]) # Enter a parse tree produced by CalcParser#lines. def enterLines(self, ctx:CalcParser.LinesContext): pass # Exit a parse tree produced by CalcParser#lines. def exitLines(self, ctx:CalcParser.LinesContext): #print("exitLines") pass # Enter a parse tree produced by CalcParser#equation. def enterEquation(self, ctx:CalcParser.EquationContext): pass # Exit a parse tree produced by CalcParser#equation. def exitEquation(self, ctx:CalcParser.EquationContext): print("***exitEquation") print(ctx.getText()) if(len(self.s)>0): print(self.s.pop()) pass # Enter a parse tree produced by CalcParser#myMul. def enterMyMul(self, ctx:CalcParser.MyMulContext): pass # Exit a parse tree produced by CalcParser#myMul. def exitMyMul(self, ctx:CalcParser.MyMulContext): #print("exitMyMul") #print(ctx.getText()) a=self.s.pop() b=self.s.pop() self.s.append(a*b) pass # Enter a parse tree produced by CalcParser#myNum. def enterMyNum(self, ctx:CalcParser.MyNumContext): pass # Exit a parse tree produced by CalcParser#myNum. def exitMyNum(self, ctx:CalcParser.MyNumContext): #print("exitMyNum") #print(ctx.getText()) self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by CalcParser#myMinusNum. def enterMyMinusNum(self, ctx:CalcParser.MyMinusNumContext): pass # Exit a parse tree produced by CalcParser#myMinusNum. def exitMyMinusNum(self, ctx:CalcParser.MyMinusNumContext): #print("exitMyMinusNum") #print(ctx.getText()) self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by CalcParser#myMinus. def enterMyMinus(self, ctx:CalcParser.MyMinusContext): pass # Exit a parse tree produced by CalcParser#myMinus. def exitMyMinus(self, ctx:CalcParser.MyMinusContext): #print("exitMyMinus") #print(ctx.getText()) a=self.s.pop() self.s.append(-a) pass # Enter a parse tree produced by CalcParser#mySub. def enterMySub(self, ctx:CalcParser.MySubContext): pass # Exit a parse tree produced by CalcParser#mySub. def exitMySub(self, ctx:CalcParser.MySubContext): #print("exitMySub") #print(ctx.getText()) a=self.s.pop() b=self.s.pop() self.s.append(b-a) pass # Enter a parse tree produced by CalcParser#myDiv. def enterMyDiv(self, ctx:CalcParser.MyDivContext): pass # Exit a parse tree produced by CalcParser#myDiv. def exitMyDiv(self, ctx:CalcParser.MyDivContext): a=self.s.pop() b=self.s.pop() self.s.append(b/a) pass # Enter a parse tree produced by CalcParser#myAdd. def enterMyAdd(self, ctx:CalcParser.MyAddContext): pass # Exit a parse tree produced by CalcParser#myAdd. def exitMyAdd(self, ctx:CalcParser.MyAddContext): #print("exitMyAdd") #print(ctx.getText()) a=self.s.pop() b=self.s.pop() self.s.append(a+b) pass # Enter a parse tree produced by CalcParser#myBr. def enterMyBr(self, ctx:CalcParser.MyBrContext): pass # Exit a parse tree produced by CalcParser#myBr. def exitMyBr(self, ctx:CalcParser.MyBrContext): pass del CalcParser
6.9.4 動作確認
python main.py "(1+3)*4;"
python main.py "(1+3)*4;5*4;8*3;-1+2*3;1+1;5-3;5/3;-6/3;-(1+3)*5+3;(1+3)/2;-4/(1+1)"
6.9.5 この節のまとめ
- 文法チェック、エラー処理無しだが、電卓として計算できるところまでもっていった
- (バグをみつけたら、教えて下さい。お願い致します。)
6.9.6 この節の追記
- 文法チェックを特別にコーディングしなくても、デフォである程度のエラー管理してくれるようで、文法間違ってたら、メッセージがでた。ただし、文法エラーという表示ではなかった。
7 簡易プログラム言語の作成その1
- 簡易電卓に以下の機能を追加したプログラム言語を作成する
- 変数に結果を代入可能
- 変数をprint命令で表示可能
- 直接計算結果をprint命令で表示可能
- 文字列もprint命令で表示可能
- 計算式にも変数を使える
- コメント文を入れれる
7.1 parserと、lexerのファイル分離
- ファイル分離しない方法で、文字列を扱う方法がわからなかったので、ファイルを2つに分ける
- ファイルを分けると、parser側で、’+’等の記法でトークンを処理できなくなるようだったので、全ての特別な扱いをする記号に名前をつけて、Lexer側に追記した。
7.1.1 以下の操作を行っている動画
7.2 parserと、lexerのファイル分離 する手順
- 前の章のCalc.g4をベースに MyLangLexer.g4 MyLangParser.g4 を作成する。MyLangLexer.g4 がトークンを抽出するルールで、MyLangParser.g4 がパースするルールを記述している。
- 色々ためして、数式をパース出来るものが以下
- 数式の終わりはセミコロンのみに変更(前の章では改行もOKだったが)
- MyLangLexer.g4 は以下の内容で作成する。作成は使い慣れたテキストエディタでOK
lexer grammar MyLangLexer; N : [0-9] | [1-9][0-9]+; ADD : '+'; SUB : '-'; MUL : '*'; DIV : '/'; SB : '='; LBR : '('; RBR : ')'; EL : [;]; // semicolons WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
- MyLangParser.g4 は以下の内容で作成する。同じく、作成は使い慣れたテキストエディタでOK
parser grammar MyLangParser; options { tokenVocab=MyLangLexer; } lines: | command | lines command | EOF; command : expr EL; expr : LBR expr RBR #myBr | expr MUL expr #myMul | expr DIV expr #myDiv | expr ADD expr #myAdd | expr SUB expr #mySub | N #myNum | SUB N #myMinusNum | SUB LBR expr RBR #myMinus ;
7.2.1 コンパイルして、動作確認
antlr4 MyLangParser.g4 MyLangLexer.g4 javac MyLang*java grun MyLang lines -gui
7.2.2 この節のまとめ
- parserルール記述ファイルと、lexer記述ファイルを分離した
- 分離したものをコンパイルして、試す方法を説明した
7.3 print命令、変数、文字列も扱えるように修正
- 関係する本家の文書https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md の”Lexer Commands”の”mode(), pushMode(), popMode, and more”に文字列を処理する方法の例があり、これをルールに追加している
- モード切替をおこなわないと、文字列のデータの中まで通常のトークン探す処理をしてしまうので、モード切替を行い、文字列の中は、別処理にする記述をおこなっている
7.3.1 以下の操作を行っている動画
7.4 する手順
- 前の節の2つのg4ファイルを以下に修正する
- MyLangLexer.g4 は以下の内容で作成する。作成は使い慣れたテキストエディタでOK
- ~[ほにゃらら]になっていると、ほにゃららを含まない文字列にマッチ
- 変数名の自由度を高くしていたが、数式等が変数名と解釈されたりしたので、変数名の制限を強くした
- 文字列の記述方法が[https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md]] の”Lexer Commands”の”mode(), pushMode(), popMode, and more”に文字列を処理する方法の例があり、これをそのまま使っている。ただし、このままだと、””でかこんだ中でバックスラッシュでエスケープは出来ない。そのルールにするには、より記述が複雑になるため、今回の例ではやっていない。
lexer grammar MyLangLexer; N : [0-9] | [1-9][0-9]+; ADD : '+'; SUB : '-'; MUL : '*'; DIV : '/'; SB : '='; LBR : '('; RBR : ')'; PRINT : 'print'; // VARIABLE : [A-Z]~[=; "'.+\-*/()\t\r\n]*; VARIABLE : [a-zA-Z_][a-zA-Z0-9_]*; EL : [;]; // semicolons WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines // String LQUOTE : '"' -> more, mode(STR) ; mode STR; STRING : '"' -> mode(DEFAULT_MODE) ; // token we want parser to see TEXT : . -> more ; // collect more text for string
- MyLangParser.g4 は以下の内容で作成する。作成は使い慣れたテキストエディタでOK
- 変数に数式処理の結果を代入可能にするルール追加
- 変数に文字列をを代入可能にするルール追加
- print命令を追加(数式処理結果,文字列,変数の中身を出力可能)
- 数式中にも変数を使えるようにルール追加
parser grammar MyLangParser; options { tokenVocab=MyLangLexer; } lines: | command | lines command | EOF; command : VARIABLE SB expr EL #mySubstitutionExpr | VARIABLE SB STRING EL #mySubstitutionStr | printout #myPrintout; printout :PRINT expr EL #myPrintOutExpr |PRINT STRING EL #myPrintOutString |PRINT VARIABLE EL #myPrintOutVariable; expr : LBR expr RBR #myBr | expr MUL expr #myMul | expr DIV expr #myDiv | expr ADD expr #myAdd | expr SUB expr #mySub | N #myNum | VARIABLE #myVal | SUB N #myMinusNum | SUB VARIABLE #myMinusVal | SUB LBR expr RBR #myMinus ;
7.4.1 コンパイルして、動作確認
antlr4 MyLangParser.g4 MyLangLexer.g4 javac MyLang*java grun MyLang lines -gui
7.4.2 この節のまとめ
- 文字列を扱うルール追加( 本家の文書https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md の”Lexer Commands”の”mode(), pushMode(), popMode, and more”に文字列を処理する方法の例を利用 )
- print命令、変数、文字列も扱えるように修正
7.5 コメント文も扱えるように修正
- 関係する本家の文書https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md の”Lexer Commands”の”channel()”に文字列を処理する方法の例があり、これをルールに追加している
7.5.1 以下の操作を行っている動画
- MyLangLexer.g4 を以下の内容で作成する。作成は使い慣れたテキストエディタでOK。 MyLangParser.g4は前の章と同じ
- 関係する本家の文書https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md の”Lexer Commands”の”channel()”に文字列を処理する方法の例があり、これをそのままルールに追加している
lexer grammar MyLangLexer; N : [0-9] | [1-9][0-9]+; ADD : '+'; SUB : '-'; MUL : '*'; DIV : '/'; SB : '='; LBR : '('; RBR : ')'; PRINT : 'print'; // VARIABLE : [A-Z]~[=; "'.+\-*/()\t\r\n]*; VARIABLE : [a-zA-Z_][a-zA-Z0-9_]*; EL : [;]; // semicolons BLOCK_COMMENT : '/*' .*? '*/' -> channel(HIDDEN) ; LINE_COMMENT : '//' ~[\r\n]* -> channel(HIDDEN) ; WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines // String LQUOTE : '"' -> more, mode(STR) ; mode STR; STRING : '"' -> mode(DEFAULT_MODE) ; // token we want parser to see TEXT : . -> more ; // collect more text for string
7.5.2 コンパイルして、動作確認
antlr4 MyLangParser.g4 MyLangLexer.g4 javac MyLang*java grun MyLang lines -gui
7.5.3 この節のまとめ
- 本家の文書https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md の”Lexer Commands”の”channel()”に文字列を処理する方法の例があり、これをそのままルールに追加し、コメント文をスキップしてパースすることが出来るようになった。
7.6 数式計算と、print 計算式; の動作部分のみ、Listenerメソッド追加
- Pythonでリスナー関数が呼び出されるようにして、インタプリタとして動作するようにコードを追加する
- 以下の操作を行っている動画
- 数式計算と、print 計算式; の動作部分のみ、Listenerメソッド追加 する手順
- 前節の MyLangLexer.g4 MyLangParser.g4 利用して、Pythonのコードを自動生成する
antlr4 -Dlanguage=Python3 MyLangLexer.g4 MyLangParser.g4
- MyLangLexer.py MyLangParser.py MyLangParserListener.py が生成される
- MyLangParserListener.py に それぞれの処理を追加する。
- MyLangParserListener.py の修正例
- 未作成今後作成予定
- main.py の作成例
- ANATLR4 の Python (2 and 3)関連文書 https://github.com/antlr/antlr4/blob/master/doc/python-target.md がmain部分作成の時便利
import ipdb import sys from antlr4 import * from MyLangLexer import MyLangLexer from MyLangParser import MyLangParser from MyLangParserListener import MyLangParserListener class KeyPrinter(MyLangParserListener): def exitKey(self, ctx): pass def main(argv): input = InputStream(argv[1]) lexer = MyLangLexer(input) stream = CommonTokenStream(lexer) parser = MyLangParser(stream) tree = parser.lines() #print(tree.getChild(0)) #print(tree.getChild(0).getText()) ##print(tree.getChild(0).toStringTree()) #ipdb.set_trace() printer = KeyPrinter() walker = ParseTreeWalker() walker.walk(printer, tree) if __name__ == '__main__': main(sys.argv)
- MyLangParserListener.py を以下に修正
- 前章の計算する部分と、print 計算式の部分だけコード追加
# Generated from MyLangParser.g4 by ANTLR 4.9.1 from antlr4 import * from collections import deque if __name__ is not None and "." in __name__: from .MyLangParser import MyLangParser else: from MyLangParser import MyLangParser # This class defines a complete listener for a parse tree produced by MyLangParser. # Generated from MyLangParser.g4 by ANTLR 4.9.1 from antlr4 import * from collections import deque if __name__ is not None and "." in __name__: from .MyLangParser import MyLangParser else: from MyLangParser import MyLangParser # This class defines a complete listener for a parse tree produced by MyLangParser. class MyLangParserListener(ParseTreeListener): s = deque([]) # Enter a parse tree produced by MyLangParser#lines. def enterLines(self, ctx:MyLangParser.LinesContext): pass # Exit a parse tree produced by MyLangParser#lines. def exitLines(self, ctx:MyLangParser.LinesContext): pass # Enter a parse tree produced by MyLangParser#mySubstitutionExpr. def enterMySubstitutionExpr(self, ctx:MyLangParser.MySubstitutionExprContext): pass # Exit a parse tree produced by MyLangParser#mySubstitutionExpr. def exitMySubstitutionExpr(self, ctx:MyLangParser.MySubstitutionExprContext): pass # Enter a parse tree produced by MyLangParser#mySubstitutionStr. def enterMySubstitutionStr(self, ctx:MyLangParser.MySubstitutionStrContext): pass # Exit a parse tree produced by MyLangParser#mySubstitutionStr. def exitMySubstitutionStr(self, ctx:MyLangParser.MySubstitutionStrContext): pass # Enter a parse tree produced by MyLangParser#myPrintout. def enterMyPrintout(self, ctx:MyLangParser.MyPrintoutContext): pass # Exit a parse tree produced by MyLangParser#myPrintout. def exitMyPrintout(self, ctx:MyLangParser.MyPrintoutContext): pass # Enter a parse tree produced by MyLangParser#myPrintOutExpr. def enterMyPrintOutExpr(self, ctx:MyLangParser.MyPrintOutExprContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutExpr. def exitMyPrintOutExpr(self, ctx:MyLangParser.MyPrintOutExprContext): #print(ctx.getText()) if(len(self.s)>0): print(self.s.pop()) pass # Enter a parse tree produced by MyLangParser#myPrintOutString. def enterMyPrintOutString(self, ctx:MyLangParser.MyPrintOutStringContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutString. def exitMyPrintOutString(self, ctx:MyLangParser.MyPrintOutStringContext): pass # Enter a parse tree produced by MyLangParser#myPrintOutVariable. def enterMyPrintOutVariable(self, ctx:MyLangParser.MyPrintOutVariableContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutVariable. def exitMyPrintOutVariable(self, ctx:MyLangParser.MyPrintOutVariableContext): pass # Enter a parse tree produced by MyLangParser#myMul. def enterMyMul(self, ctx:MyLangParser.MyMulContext): pass # Exit a parse tree produced by MyLangParser#myMul. def exitMyMul(self, ctx:MyLangParser.MyMulContext): a=self.s.pop() b=self.s.pop() self.s.append(a*b) pass # Enter a parse tree produced by MyLangParser#myNum. def enterMyNum(self, ctx:MyLangParser.MyNumContext): pass # Exit a parse tree produced by MyLangParser#myNum. def exitMyNum(self, ctx:MyLangParser.MyNumContext): self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by MyLangParser#myMinusNum. def enterMyMinusNum(self, ctx:MyLangParser.MyMinusNumContext): pass # Exit a parse tree produced by MyLangParser#myMinusNum. def exitMyMinusNum(self, ctx:MyLangParser.MyMinusNumContext): self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by MyLangParser#myMinus. def enterMyMinus(self, ctx:MyLangParser.MyMinusContext): pass # Exit a parse tree produced by MyLangParser#myMinus. def exitMyMinus(self, ctx:MyLangParser.MyMinusContext): a=self.s.pop() self.s.append(-a) pass # Enter a parse tree produced by MyLangParser#myMinusVal. def enterMyMinusVal(self, ctx:MyLangParser.MyMinusValContext): pass # Exit a parse tree produced by MyLangParser#myMinusVal. def exitMyMinusVal(self, ctx:MyLangParser.MyMinusValContext): pass # Enter a parse tree produced by MyLangParser#mySub. def enterMySub(self, ctx:MyLangParser.MySubContext): pass # Exit a parse tree produced by MyLangParser#mySub. def exitMySub(self, ctx:MyLangParser.MySubContext): a=self.s.pop() b=self.s.pop() self.s.append(b-a) pass # Enter a parse tree produced by MyLangParser#myVal. def enterMyVal(self, ctx:MyLangParser.MyValContext): pass # Exit a parse tree produced by MyLangParser#myVal. def exitMyVal(self, ctx:MyLangParser.MyValContext): pass # Enter a parse tree produced by MyLangParser#myDiv. def enterMyDiv(self, ctx:MyLangParser.MyDivContext): pass # Exit a parse tree produced by MyLangParser#myDiv. def exitMyDiv(self, ctx:MyLangParser.MyDivContext): a=self.s.pop() b=self.s.pop() self.s.append(b/a) pass # Enter a parse tree produced by MyLangParser#myAdd. def enterMyAdd(self, ctx:MyLangParser.MyAddContext): pass # Exit a parse tree produced by MyLangParser#myAdd. def exitMyAdd(self, ctx:MyLangParser.MyAddContext): a=self.s.pop() b=self.s.pop() self.s.append(a+b) pass # Enter a parse tree produced by MyLangParser#myBr. def enterMyBr(self, ctx:MyLangParser.MyBrContext): pass # Exit a parse tree produced by MyLangParser#myBr. def exitMyBr(self, ctx:MyLangParser.MyBrContext): pass del MyLangParser
7.6.1 動作確認
python main.py "print (1+3)*4+5*3;print -(1-5)*3;print 4/2;"
- 出力
31 12 2.0
7.6.2 この節のまとめ
- print 計算式; が上手く動作するように修正
7.7 残りのListener部分処理追加
- 変数処理や、文字列操作等の処理を追加
- まだ作って動作確認はしてない。作りしだい、文書に追記します。
- ここまでの復習でやってもらっても良いかも
7.7.1 MyLangParserListener.py を以下に修正
- 前章の計算する部分と、print 計算式の部分だけコード追加
- vdicという連想配列をメンバ変数として用意して、変数関係に利用してます。
- 内容で重要な部分
- ctx.getText() でそのリスナー関数が呼ばれたところ全体の文字列を取得できる。
- ctx.getChild(0).getText() で複数の要素があるところのリスナー関数は、1番目の要素の文字列取り出せる。
- ctx.getChild(1).getText() で複数の要素があるところのリスナー関数は、2番目の要素の文字列取り出せる。要素数が1の時はエラーになる。
- ctx.getChild(3).getText() で複数の要素があるところのリスナー関数は、3番目の要素の文字列取り出せる。要素数が2以下の時はエラーになる。
# Generated from MyLangParser.g4 by ANTLR 4.9.1 from antlr4 import * from collections import deque if __name__ is not None and "." in __name__: from .MyLangParser import MyLangParser else: from MyLangParser import MyLangParser # This class defines a complete listener for a parse tree produced by MyLangParser. class MyLangParserListener(ParseTreeListener): s = deque([]) vdic = {} # Enter a parse tree produced by MyLangParser#lines. def enterLines(self, ctx:MyLangParser.LinesContext): pass # Exit a parse tree produced by MyLangParser#lines. def exitLines(self, ctx:MyLangParser.LinesContext): pass # Enter a parse tree produced by MyLangParser#mySubstitutionExpr. def enterMySubstitutionExpr(self, ctx:MyLangParser.MySubstitutionExprContext): pass # Exit a parse tree produced by MyLangParser#mySubstitutionExpr. def exitMySubstitutionExpr(self, ctx:MyLangParser.MySubstitutionExprContext): #print("exitMySubstitutionExpr") self.vdic[ctx.getChild(0).getText()]=self.s.pop(); #print(self.vdic[ctx.getChild(0).getText()]) #print(ctx.getText()) pass # Enter a parse tree produced by MyLangParser#mySubstitutionStr. def enterMySubstitutionStr(self, ctx:MyLangParser.MySubstitutionStrContext): pass # Exit a parse tree produced by MyLangParser#mySubstitutionStr. def exitMySubstitutionStr(self, ctx:MyLangParser.MySubstitutionStrContext): #print("exitMySubstitutionStr") self.vdic[ctx.getChild(0).getText()]=ctx.getChild(2).getText()[1:-1] #print(self.vdic[ctx.getChild(0).getText()]) #print(ctx.getText()) pass # Enter a parse tree produced by MyLangParser#myPrintout. def enterMyPrintout(self, ctx:MyLangParser.MyPrintoutContext): pass # Exit a parse tree produced by MyLangParser#myPrintout. def exitMyPrintout(self, ctx:MyLangParser.MyPrintoutContext): pass # Enter a parse tree produced by MyLangParser#myPrintOutExpr. def enterMyPrintOutExpr(self, ctx:MyLangParser.MyPrintOutExprContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutExpr. def exitMyPrintOutExpr(self, ctx:MyLangParser.MyPrintOutExprContext): #print(ctx.getText()) if(len(self.s)>0): print(self.s.pop()) pass # Enter a parse tree produced by MyLangParser#myPrintOutString. def enterMyPrintOutString(self, ctx:MyLangParser.MyPrintOutStringContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutString. def exitMyPrintOutString(self, ctx:MyLangParser.MyPrintOutStringContext): #str=ctx.getText() #print(ctx.getChild(0).getText()) #print(ctx.getChild(1).getText()) #print(str) #str2=str[6:-2] print(ctx.getChild(1).getText()[1:-1]) pass # Enter a parse tree produced by MyLangParser#myPrintOutVariable. def enterMyPrintOutVariable(self, ctx:MyLangParser.MyPrintOutVariableContext): pass # Exit a parse tree produced by MyLangParser#myPrintOutVariable. def exitMyPrintOutVariable(self, ctx:MyLangParser.MyPrintOutVariableContext): print(self.vdic[ctx.getChild(1).getText()]) pass # Enter a parse tree produced by MyLangParser#myMul. def enterMyMul(self, ctx:MyLangParser.MyMulContext): pass # Exit a parse tree produced by MyLangParser#myMul. def exitMyMul(self, ctx:MyLangParser.MyMulContext): a=self.s.pop() b=self.s.pop() self.s.append(a*b) pass # Enter a parse tree produced by MyLangParser#myNum. def enterMyNum(self, ctx:MyLangParser.MyNumContext): pass # Exit a parse tree produced by MyLangParser#myNum. def exitMyNum(self, ctx:MyLangParser.MyNumContext): self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by MyLangParser#myMinusNum. def enterMyMinusNum(self, ctx:MyLangParser.MyMinusNumContext): pass # Exit a parse tree produced by MyLangParser#myMinusNum. def exitMyMinusNum(self, ctx:MyLangParser.MyMinusNumContext): self.s.append(int(ctx.getText())) pass # Enter a parse tree produced by MyLangParser#myMinus. def enterMyMinus(self, ctx:MyLangParser.MyMinusContext): pass # Exit a parse tree produced by MyLangParser#myMinus. def exitMyMinus(self, ctx:MyLangParser.MyMinusContext): a=self.s.pop() self.s.append(-a) pass # Enter a parse tree produced by MyLangParser#myMinusVal. def enterMyMinusVal(self, ctx:MyLangParser.MyMinusValContext): pass # Exit a parse tree produced by MyLangParser#myMinusVal. def exitMyMinusVal(self, ctx:MyLangParser.MyMinusValContext): #print("exitMyMinusVal") #print(ctx.getChild(1).getText()) self.s.append(-self.vdic[ctx.getChild(1).getText()]) pass # Enter a parse tree produced by MyLangParser#mySub. def enterMySub(self, ctx:MyLangParser.MySubContext): pass # Exit a parse tree produced by MyLangParser#mySub. def exitMySub(self, ctx:MyLangParser.MySubContext): a=self.s.pop() b=self.s.pop() self.s.append(b-a) pass # Enter a parse tree produced by MyLangParser#myVal. def enterMyVal(self, ctx:MyLangParser.MyValContext): pass # Exit a parse tree produced by MyLangParser#myVal. def exitMyVal(self, ctx:MyLangParser.MyValContext): self.s.append(self.vdic[ctx.getText()]) pass # Enter a parse tree produced by MyLangParser#myDiv. def enterMyDiv(self, ctx:MyLangParser.MyDivContext): pass # Exit a parse tree produced by MyLangParser#myDiv. def exitMyDiv(self, ctx:MyLangParser.MyDivContext): a=self.s.pop() b=self.s.pop() self.s.append(b/a) pass # Enter a parse tree produced by MyLangParser#myAdd. def enterMyAdd(self, ctx:MyLangParser.MyAddContext): pass # Exit a parse tree produced by MyLangParser#myAdd. def exitMyAdd(self, ctx:MyLangParser.MyAddContext): a=self.s.pop() b=self.s.pop() self.s.append(a+b) pass # Enter a parse tree produced by MyLangParser#myBr. def enterMyBr(self, ctx:MyLangParser.MyBrContext): pass # Exit a parse tree produced by MyLangParser#myBr. def exitMyBr(self, ctx:MyLangParser.MyBrContext): pass del MyLangParser
7.7.2 動作確認
python main.py "print 1+1;print \"hello\";X=\"sss\";print X;Y=-(5-1)*5;print Y;Z=5*Y;print -Z;print Z;"
- 出力
2 hello sss -20 100 -100
7.7.3 この節のまとめ
- 変数がらみの処理追加し、変数を利用可能で、文字列も変数に入れて処理できる簡易プログラム言語を作成した
- ループ、関数定義、if文は現状使えない簡易言語を作れれた。他の機能追加も、多分作れそうな感じまできた。
- ここまでに利用した機能はANTLR4のほんの一部と思われるが、もっと複雑なプログラム言語、独自の設定ファイルや、変換ツール、フィルターも、ここまでのテクだけ、あるいはちょっとプラスアルファで作れそう。
- 動作確認はちょっとしか行ってないので、バグやおかしな動作があるかもしれません。みつけたら教えてください。不具合を修正するのも練習に良いと思うので、みつけたら、修正していただいた方が良いと思います。
7.8 ファイル指定して実行できるバージョンのmain2.py作成
- コマンドラインの引数として命令を直接与えていたけど、ファイルで命令を与えれるようにしたmain2.pyを作成
7.8.1 main2.py を以下で作成
- main.pyをベースに作成
import ipdb import sys from antlr4 import * from MyLangLexer import MyLangLexer from MyLangParser import MyLangParser from MyLangParserListener import MyLangParserListener class KeyPrinter(MyLangParserListener): def exitKey(self, ctx): pass def main(argv): with open(argv[1]) as f: input = InputStream(f.read()) lexer = MyLangLexer(input) stream = CommonTokenStream(lexer) parser = MyLangParser(stream) tree = parser.lines() #print(tree.getChild(0)) #print(tree.getChild(0).getText()) ##print(tree.getChild(0).toStringTree()) #ipdb.set_trace() printer = KeyPrinter() walker = ParseTreeWalker() walker.walk(printer, tree) if __name__ == '__main__': main(sys.argv)
- test01.mylangファイルを以下の内容で作成
/* 複数行のコメント */ // 一行のコメント X=12; print 3*X; print -X*5; S="Hello"; print S; print "World!"; print -(5-1)*5; print -(5+1)*5;
7.8.2 動作確認
python3 main2.py test01.mylang or python main2.py test01.mylang
- その時の出力
7.8.3 この節のまとめ
- ファイルからプログラムを読み込めるmain2.pyを作成
7.9 この章のまとめ
- 以下の処理を追加して、簡易プログラム言語を作ってみた。
- 変数処理
- 文字列処理
- 計算式に変数を利用可能に改良
- print命令の実装
- 今後
- もしかしたら、ループとか、関数定義、関数利用、if文等の処理を追加した、簡易言語パート2を作るかもしれない。
- もともと別の用途として、Antler4を使いだしたので、他の用途の簡易言語を作るかもしれない。
- いちおう簡易言語作れるようになったので、ここで、この文書や、動画シリーズ終わるかも
8 今後
- 今後も文書追加していきます。
9 この文書のチェンジログ
- 2021/01/17 初版
- 2021/01/18 自然数を解釈する の章, 足し算表現、引き算表現の解釈処理追加 の章, 掛け算表現、割り算表現の解釈処理追加, 括弧でくるんだ部分を優先して処理するように修正 追加
- 2021/01/25 括弧でくるんだ部分を優先して処理するように修正 修正
- 2021/01/25 複数数式入力に対応する修正, 現時点で最新バージョンのAntlr4をインストール(2021/1/25現在最新の4.9.1をインストール), アクション用のラベル追加, Python用のコード生成, Python用のコード作成(Listenerメソッドが動くように修正), 電卓として動作するように処理を書く(Python向け) 追加
- 2021/01/27 簡易プログラム言語の作成その1 の章追加
- 2021/01/28 簡易プログラム言語の作成その1 の章の def exitMyMinusVal(self, ctx:MyLangParser.MyMinusValContext): にバグがあったので修正(デバッグ用print文もコメントアウト)
- 2021/01/31 簡易プログラム言語の作成その1 の章の 「ファイル指定して実行できるバージョンのmain2.py作成」の節追加
Created: 2021-01-31 日 15:11