前回までのあらすじ
-
ANTLR4でPL/0インタプリタを作りたい!(1回目 環境構築編)
- antlr4環境を構築した
- .g4で文法かけるようになった
- JAVAからantlr4用の独自実装が動作する事の確認ができた
- これで、最低限のスタートラインまで立てた!ということで、pl0コンパイラ開発へ進めていくのだった…
今回のオチ
- antlr4とllvm builderが共存……できない……
- llvm-builder「-fno-exceptions指定でお願いします」
- antlr4「-fexceptionsをお願いしますー」
- 板挟みになったプログラマ「どうしろと…」
- PL/0構文厄介すぎる
- 変数のスコープがぁ……
- よし、PL/0フルセットサポートをあきらめよう!
1. pl0のgrammerを使おう
こちらに、BSD licenseのpl0 grammerがある。こちらをベースに検討していきたい。
2. c++開発環境への移行
さて、LLVM でコンパイルできる LLVM IRを生成するためには、LLVM Builderを使うのが一番普通そう。
LLVM APIを使ってみよう! 〜 Brainf**kコンパイラをIRBuilderで書き直してみた 〜
LLVM Builderを使える開発言語は、https://github.com/llvm/llvm-project/tree/main/llvm/bindings を見る限りだとこのあたりが選択肢になる。
- C++
- go
- python
- ocaml
よし、C++で作るか! (これが後々の悲劇を招くことを今の我々は知る由もなかったのだ…)
2.1 C言語用のコードを生成
前回では、antlr4を使ったjavaのコードを生成した。オプションを変更する事で、C++用のコードも生成できる。
$ antlr4 -Dlanguage=Cpp MyGrammar.g4
これを試す場合には、http://www.antlr.org から cpp 用のruntimeもダウンロードし、インストールまでする必要がある。
今回はこのようなフォルダ構成でテストをしている。
- pl0.g4
- main.cpp ( listenerの実装もひとまずここ)
- antlr4-runtime (pl0.g4から生成したファイルを置く)
- testdata
この環境ために、Makefileを雑に書いた。cmake使った方がいいかなーと思いつつ、まあ動けばいい!!
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が仲良くしてくれない
こちらの記事を参考にしてくっつけてみよう!と思った。
とりあえず、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" と怒られた。おや?
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を手書きする方針に変更します。
ということで、2月はここまで。うん、なかなかのスローペースだな。
4. 現代らしからぬ制約が必要になりそう
PL/0構文のためのLLVM IRを手書きするプログラムを作り始めたわけだが、なかなかに「いやらしい」。
4.1. 関数内関数内関数内関数内関数内関数内...
PL/0の構文としては、以下のように、block内でProcedure内を定義できる、が、更にProcedure内でblockも定義できる。
program = block "." .
block = [ "const" ident "=" number {"," ident "=" number} ";"]
[ "var" ident {"," ident} ";"]
{ "procedure" ident ";" block ";" } statement .
つまり、関数内に関数が置ける、更にその下にも関数が置ける……
VAR x;
PROCEDURE fa ;
VAR x;
PROCEDURE fb ;
VAR x;
;
;
.
この場合、PROCEDURE fb内でxというidentが使われたら、スコープとして一番内側にあるfb::x を意味すると解釈しなければならない。
ということで、今回は・・・
ある程度テストプログラム側に制約を持たせて逃げます!
- programで定義された変数は、globalとして扱う。
- procedureで定義された変数も、globalとして扱う。
- それぞれの変数のスコープについては、とりあえず考えずに作る。
- 「一つ上の変数を参照する」みたいなことは非サポートとする。
- 変数の二重定義については、とりあえず考えずに作る。
とりあえず、ここまで……