ANTLR4事始め





ANTLR4ことはじめ

目次

1 概要

  • 機能豊富なパーサジェネレータ
  • インストール
  • 簡易電卓作成(普通のプログラム言語のHello Worldにあたるもの)
  • 簡易プログラム言語作成(計算機能、変数利用と、print命令のみ)

2 この文書作ろうと思ったきっかけ

  • 構文解析をPythonでやろうと色々調べたところ、どうやら、ANTLR4が抜けてるというのをみつけて、これやってみよとなりました。

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を用意

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 自然数を解釈する 手順

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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文字にマッチする正規表現
    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
    
  5. 二桁以上の数字に対応(その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
    
  6. 二桁以上の数字に対応(その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 足し算表現、引き算表現の解釈処理追加 手順

  1. まず足し算表現を追加
    • 前の章で作成した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
    
  2. 引き算表現も追加
    • 以下の内容に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
    
  3. 数字の前に’-‘がある場合の処理追加
    • 以下の内容に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をインストール) する手順

  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 .
    
  2. 設定を行う
    • 設定の方法については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
    
  3. 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 関連文書

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. テスト1 100 の場合
    python main.py "100"
    
    • 上記の場合の出力
    exitLines
    exitMyNum
    100
    exitEquation
    100<EOF>
    exitLines
    
  2. テスト2 -100 の場合
    python main.py "-100"
    
    • 上記の場合の出力
    exitLines
    exitMyMinusNum
    -100
    exitEquation
    -100<EOF>
    exitLines
    
  3. テスト3 -100 の場合
    python main.py "-100"
    
    • 上記の場合の出力
    exitLines
    exitMyMinusNum
    -100
    exitEquation
    -100<EOF>
    exitLines
    
  4. テスト3 1+3 の場合
    python main.py "1+3"
    
    • 上記の場合の出力
    exitLines
    exitMyNum
    1
    exitMyNum
    3
    exitMyAdd
    1+3
    exitEquation
    1+3<EOF>
    exitLines
    
  5. テスト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 この節のまとめ

7.5 コメント文も扱えるように修正

7.5.1 以下の操作を行っている動画


  • MyLangLexer.g4 を以下の内容で作成する。作成は使い慣れたテキストエディタでOK。 MyLangParser.g4は前の章と同じ
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 この節のまとめ

7.6 数式計算と、print 計算式; の動作部分のみ、Listenerメソッド追加

  • Pythonでリスナー関数が呼び出されるようにして、インタプリタとして動作するようにコードを追加する
  1. 以下の操作を行っている動画

  2. 数式計算と、print 計算式; の動作部分のみ、Listenerメソッド追加 する手順
    • 前節の MyLangLexer.g4 MyLangParser.g4 利用して、Pythonのコードを自動生成する
    antlr4 -Dlanguage=Python3 MyLangLexer.g4 MyLangParser.g4
    
    • MyLangLexer.py MyLangParser.py MyLangParserListener.py が生成される
    • MyLangParserListener.py に それぞれの処理を追加する。
  3. MyLangParserListener.py の修正例
    • 未作成今後作成予定
    
    
  4. 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):
    	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)
    
    
  5. 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部分処理追加

  • 変数処理や、文字列操作等の処理を追加
  • まだ作って動作確認はしてない。作りしだい、文書に追記します。
  • ここまでの復習でやってもらっても良いかも
  1. 以下の操作を行っている動画

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を作成
  1. 以下の操作を行っている動画

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作成」の節追加

著者: NM Max

Created: 2021-01-31 日 15:11

Validate