ANTLR4でPL/0インタプリタを作りたい！(2回目 苦悩編 ～え？LLVM Builder禁止縛りですか？～)

# 前回までのあらすじ
- [ANTLR4でPL/0インタプリタを作りたい！(1回目 環境構築編)](https://qiita.com/hon_no_mushi/items/b4f186af68b2f93b00b2)
    - antlr4環境を構築した
    - .g4で文法かけるようになった
    - JAVAからantlr4用の独自実装が動作する事の確認ができた
- これで、最低限のスタートラインまで立てた！ということで、pl0コンパイラ開発へ進めていくのだった…

# 今回のオチ
- antlr4とllvm builderが共存……できない……
    - llvm-builder「-fno-exceptions指定でお願いします」
    - antlr4「-fexceptionsをお願いしますー」
    - 板挟みになったプログラマ「どうしろと…」
- PL/0構文厄介すぎる
    - 変数のスコープがぁ……
- よし、PL/0フルセットサポートをあきらめよう！

# 1. pl0のgrammerを使おう

https://github.com/antlr/grammars-v4/blob/master/pl0/pl0.g4

こちらに、BSD licenseのpl0 grammerがある。こちらをベースに検討していきたい。

# 2. c++開発環境への移行

さて、LLVM でコンパイルできる LLVM IRを生成するためには、LLVM Builderを使うのが一番普通そう。

[LLVM APIを使ってみよう！ 〜 Brainf**kコンパイラをIRBuilderで書き直してみた 〜](https://itchyny.hatenablog.com/entry/2017/03/06/100000)

LLVM Builderを使える開発言語は、https://github.com/llvm/llvm-project/tree/main/llvm/bindings を見る限りだとこのあたりが選択肢になる。

- C++
- go
- python
- ocaml

よし、C++で作るか！　（これが後々の悲劇を招くことを今の我々は知る由もなかったのだ…）

## 2.1  C言語用のコードを生成

前回では、antlr4を使ったjavaのコードを生成した。オプションを変更する事で、C++用のコードも生成できる。

https://github.com/antlr/antlr4/blob/master/doc/cpp-target.md

` $ antlr4 -Dlanguage=Cpp MyGrammar.g4 `

これを試す場合には、http://www.antlr.org から cpp 用のruntimeもダウンロードし、インストールまでする必要がある。

今回はこのようなフォルダ構成でテストをしている。

```
- pl0.g4
- main.cpp ( listenerの実装もひとまずここ)
- antlr4-runtime (pl0.g4から生成したファイルを置く)
- testdata
```

この環境ために、Makefileを雑に書いた。cmake使った方がいいかなーと思いつつ、まあ動けばいい！！

```Makefile
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

# CXX=clang++
CXX=g++

all : a.out

.PHONY: antlr4/pl0.tokens
antlr4/pl0.tokens : pl0.g4
        $(ANTLR4) -Dlanguage=Cpp pl0.g4 -o antlr4-runtime

OBJS = \
    main.o \
    antlr4-runtime/pl0BaseListener.o \
    antlr4-runtime/pl0Lexer.o \
    antlr4-runtime/pl0Listener.o \
    antlr4-runtime/pl0Parser.o \

.SUFFIXES: .cpp .o
.cpp.o:
        $(CXX) \
                -pg -g \
                -c \
                "-I/usr/local/include/antlr4-runtime/" \
                $< \
                -o $@

a.out : ${OBJS}
        $(CXX) \
                -pg -g \
                -I/usr/local/include/antlr4-runtime/ \
                ${OBJS} \
                /usr/local/lib/libantlr4-runtime.a \
                -o a.out

clean :
        rm -f ${OBJS}
        rm -f a.out
```

# 3. それではLLVM Builderとつなご…　つながらない！

## 3.1 LLVM Builderとantlr4が仲良くしてくれない

- [LLVM APIを使ってみよう！ 〜 Brainf**kコンパイラをIRBuilderで書き直してみた 〜](https://itchyny.hatenablog.com/entry/2017/03/06/100000)

こちらの記事を参考にしてくっつけてみよう！と思った。

とりあえず、makefileを修正して、llvm-config指定してコンパイルですね。

```
.SUFFIXES: .cpp .o
.cpp.o:
        $(CXX) \
                -pg -g \
                -c \
                `llvm-config --cxxflags --ldflags --libs --system-libs` \
                "-I/usr/local/include/antlr4-runtime/" \
                $< \
                -o $@
```

ところが、"exception handling disabled" と怒られた。おや？

```bash
g++ \
        -pg -g \
        -c \
        `llvm-config --cxxflags --ldflags --libs --system-libs` \
                "-I/usr/local/include/antlr4-runtime/" \
        main.cpp \
        -o main.o
In file included from /usr/local/include/antlr4-runtime/Lexer.h:8,
                 from /usr/local/include/antlr4-runtime/antlr4-runtime.h:31,
                 from main.cpp:5:
/usr/local/include/antlr4-runtime/Recognizer.h: 
In member function ‘virtual const std::vector<short unsigned int> antlr4::Recognizer::getSerializedATN() const ’:
/usr/local/include/antlr4-runtime/Recognizer.h:69:13: 
error: exception handling disabled, 
use ‘-fexceptions’ to enable
   69 |       throw "there is no serialized ATN";
      |
```

llvm-configさん、やはりあなたでしたか……

```
$ llvm-config --cxxflags --ldflags --libs --system-libs
-I/usr/lib/llvm-11/include -std=c++14   -fno-exceptions -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-L/usr/lib/llvm-11/lib
-lLLVM-11
```

ぐぬぅ・・・そうしたら、llvm-configの後ろにつけたらどうだ！

```
g++ \
        -pg -g \
        -c \
        `llvm-config --cxxflags --ldflags --libs --system-libs` \
        -fexceptions \
                "-I/usr/local/include/antlr4-runtime/" \
        main.cpp \
        -o main.o
g++ \
        -pg -g \
        -I/usr/local/include/antlr4-runtime/ \
        main.o antlr4-runtime/pl0BaseListener.o antlr4-runtime/pl0Lexer.o antlr4-runtime/pl0Listener.o antlr4-runtime/pl0Parser.o  \
        /usr/local/lib/libantlr4-runtime.a \
        -o a.out
/usr/bin/ld: main.o:(.data.rel+0x0): undefined reference to `llvm::DisableABIBreakingChecks'
collect2: error: ld returned 1 exit status
make: *** [Makefile:32: a.out] エラー 1
```

終わった…… antlr4とLLVM Builderを使ってPL/0コンパイラを作ろうはここで終了……

## 3.2 ということで

パンが無ければ ~~パンツ~~ ケーキ を食べればいいじゃない！の精神で、LLVM IRを手書きする方針に変更します。

ということで、２月はここまで。うん、なかなかのスローペースだな。

# 4. 現代らしからぬ制約が必要になりそう

PL/0構文のためのLLVM IRを手書きするプログラムを作り始めたわけだが、なかなかに「いやらしい」。

## 4.1. 関数内関数内関数内関数内関数内関数内...

PL/0の構文としては、以下のように、block内でProcedure内を定義できる、が、更にProcedure内でblockも定義できる。

https://ja.wikipedia.org/wiki/PL/0

```
program = block "." .

block = [ "const" ident "=" number {"," ident "=" number} ";"]
        [ "var" ident {"," ident} ";"]
        { "procedure" ident ";" block ";" } statement .
```

つまり、関数内に関数が置ける、更にその下にも関数が置ける……

```plaintext
VAR x;

PROCEDURE fa ;
  VAR x;

  PROCEDURE fb ;
    VAR x;
  ;
;
.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/f77aae8b-8913-9eee-cb63-c4247c02d01f.png)

この場合、PROCEDURE fb内でxというidentが使われたら、スコープとして一番内側にあるfb::x を意味すると解釈しなければならない。

# ということで、今回は・・・

ある程度テストプログラム側に制約を持たせて逃げます！

- programで定義された変数は、globalとして扱う。
- procedureで定義された変数も、globalとして扱う。
- それぞれの変数のスコープについては、とりあえず考えずに作る。
- 「一つ上の変数を参照する」みたいなことは非サポートとする。
- 変数の二重定義については、とりあえず考えずに作る。

とりあえず、ここまで……