自作言語のお時間です。
どうも、もっと冬休みを伸ばしてほしいdangomushiです。
またまたまたまた作りました。(仮想機械言語の説明)
何回作るんでしょう。何回このくだりやるんでしょう。
それはさておき、また作りました。今回は、前回の反省を生かして、実行速度を速めてみました。具体的には、VMの仮想言語の設計に力を入れた感じです。ちなみに、ほかのバイトコード等と区別するため、自分ではこの言語を「lasバイトコード」とか言ってます。
名前をラピスラズリ(lapis lazuli)といいます。拡張子は「~.la」なんでかは、、文法とか昨今の自作言語の名前つけブームから察してください、、、。一説には、筆者の誕生石とも関係があるとか、、、。
あと、またもや12/30~1/4までの五日間で仕上げましたので、コードがスパゲッティを通り越して糸こんにゃく状態です(個人的な裏話ですが、他人に発表できるような言語を何日で自作できるかの挑戦でもあったためです。筆者は中学生のため、きれいさ等は大目に見ていただけると幸いです)。
時間があれば、次の記事でプログラム本体の説明もしたいと思っています。
「dangomushi」とコンソールに表示
println_dangomushi
これを機械語と呼んでいいのかっ!?って感じですけど、その時は「アセンブリ」とかちんぷんかんぷんだったんです。で、今回のがこれ。
main:
msg "dangomushi"
mov eax, 0
ret
call main
一気にアセンブリらしくなりましたね(笑)。では、解説です。
一行目、「main:」
これは、「ここからmain関数だよ」ということを示しています。早い話がアセンブリでのラベルです。
二行目、「msg」
これは、「コンソールに出力するよ」ということを示しています。本来であれば、別のラベルだったり色々するのですが筆者の場合「はぁ?!なんでだよ!理解できん!」となったので、まとめました。
三行目、「mov eax 0」
これは、アセンブラやらない勢からすると最も不可解であろう部分です。解説すると、ここではreturnの処理をしています。普通であれば「return 0」の部分です。「eax」をつかっているのは、おそらくアセンブラ大好き勢の皆さんでも不可解と思いますが、私にもわかりません。多分、その時の筆者は疲れていたんだろうと思います。
ということで、eaxはreturnされた値を格納するところを指しています。要はreturnされる値の変数です。
四行目、「ret」
これは、「main関数がおわるよ」ということを示しています。まぁあんま説明することないですね。次行きましょ、次。
五行目、最後「call main」
ここは、「main関数を呼び出すよ」ということを示しています。ここが、最も筆者が悩んだところであり、最もアセンブラとかけ離れている個所です。個人的には、自動的にMainが呼び出されて欲しかったのですがなぜかうまくいかず、同じところをぐるぐる回りつづけるというさんざんな目にあったので諦めました。
そんで、やっと本題。
読者の皆様、お待たせいたしました。本題のHelloworldでございます。
まずは、コードから。
ちなみに、gitohabにも同じものを用意しています。おそらく「lapislazuli」的な名前だと思われます。
工程
今回自作した言語は、二回のステップで実行されます。
1、コンパイル
書いたlaファイルをlasバイトコードに変換します。簡単でした。
2、実行
コンパイルしたlasバイトコードを専用の実行プログラム(仮想機械)上で実行します。
1のコード(main.py, 83行)
import sys, os, re, glob
#TODO:Main / カーネル
class Main:
def __init__(self):
pass
#TODO:data = すべてのデータ
def run(self, dick, lis):
valts = []
i = 0
case = 0
name = sys.argv[1]
file = open(name, encoding="utf-8")
conf = open(name.split(".")[0]+".las", "a", encoding="utf_8")
data = file.readlines()
file.close()
valts.append(data)
valts = [e for inner_list in valts for e in inner_list]
for data in valts:
data = data.replace("\n", "")
if data.endswith(":") and data.startswith(" while") is False:
data = data.split(" ")[1].replace("()", "")
if data == "main:":
pass
else:
data = data.replace(":", "") + "():"
conf.write("\n{}".format(data))
elif data.startswith(" "):
data = data.replace(" ", "").replace(";", "")
if data.startswith("return"):
conf.write("\n mov {}, {}".format("eax", data.split(" ")[1]))
conf.write("\n ret\n")
elif data.startswith("use"):
conf.write("\n call {}".format(data.split(" ")[1]))
elif "put" in data:
conf.write("\n msg {}".format(data.split(":")[1]))
elif data.startswith("int"):
datav = data.split(" ")[1]
datanum = data.split(" ")[3]
conf.write("\n mov {}, {}".format(datav, datanum))
elif "+" in data:
data = data.replace(" ", "")
val = data.split("=")[0]
data2 = data.split("=")[1].split("+")[0]
data3 = data.split("=")[1].split("+")[1]
conf.write("\n add {}, {}, {}".format(val, data2, data3))
elif "-" in data:
data = data.replace(" ", "")
val = data.split("=")[0]
data2 = data.split("=")[1].split("-")[0]
data3 = data.split("=")[1].split("-")[1]
conf.write("\n sub {}, {}, {}".format(val, data2, data3))
conf.write("\ncall main")
conf.close()
if __name__ == '__main__':
file_list = glob.glob("*las")
for file in file_list:
os.remove(file)
f = open(sys.argv[1].split(".")[0]+".las", "x", encoding="utf_8")
f.close()
dick = {}
lis = []
omega = Main()
omega.run(dick, lis)
できることが少ないので、コードも激少です。
次に、仮想機械のコードです。
2のコード(run.py, 75行)
import sys
class Run:
def run(self):
file = open(sys.argv[1], encoding="utf-8")
data = file.readlines()
dick = {}
lis = []
lisf = []
i = 0
for data in data:
data = data.replace("\n", "")
if data.startswith(" "):
lis.append("{};".format(data.replace(" ", "")))
elif data.endswith(":") and data.startswith("while") is False:
func = data.replace(":", "")
lisf.append(func)
elif data.startswith("call"):
dick = dict(zip(lisf, "".join(lis).split("ret;")))
self.test(dick, func, i)
file.close()
def test(self, dick, f, i):
lis = dick[f].split(";")
lisf = f
for data in lis:
data = data.split(";")
data = [a for a in data if a != '']
for data in data:
if data.endswith("()"):
self.test(dick, data.split(" ")[1], i)
else:
if data.startswith("mov"):
data1 = data.split(", ")
datas = data1[0].split(" ")[1]
if datas == "eax":
dick[lisf] = "".join(data1[1:])
else:
dick[datas] = data1[1]
elif data.startswith("call"):
func = data.split(" ")[1]
self.test(dick, func, i)
elif data.startswith("msg"):
if ' "' in data.split("msg")[1]:
print(data.split("msg")[1].replace(' "', "").replace('"', ""))
else:
print(dick[data.split("msg")[1]])
elif data.startswith("add"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
dick[val] = int(dick[formula]) + int(dick[formula2])
elif data.startswith("sub"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
dick[val] = int(dick[formula]) - int(dick[formula2])
if __name__ == '__main__':
Run().run()
こちらのコードも、処理系と思えば激少ですね。
急な宣伝
Githubでは、このような説明とmain.py / run.pyのほかにも、サンプルコードもついてきます。ぜひぜひGithubのほうにもおいでください。リンクはこちら
まとめ
今回は、pythonのみを利用して自作言語の処理系&仮想機械の解説を行いました。
感想としては、ちょっとできることが少なすぎますね。もっと増やしていきたいです。
今後の課題としては、
- 前述のとおりできることが少ない。
- 静的型付け言語でない(筆者の好み)。
- オブジェクト指向でない。
- 足し算引き算しかできない。
があげられます。次の記事では、この課題を改善しつつプログラム本体の解説もしていきたいと思います。
それでは、また次の記事でお会いしましょう。
さようなら!