自作言語のお時間です。
どうも、新型MacをかってM1を試してみたいdangomushiです。
今回は、自作言語に「スコープ」の実装を施しました。また、機械語のように自分で読んでいてわからなくなっては元も子もないので、可読性も重視しました。
この記事は、前回の自作言語でHelloWorldの続きです。まだご覧になってない方は、ぜひご覧ください。
どうやったのか
前述のとおり、引数の追加を行いました。仕掛けは簡単です。変数用と関数用とで辞書を分け、変数用は関数がcallされたときにリセットしただけです。
サンプルコード
まず最初は、「書き込んだファイルを仮想機械語に変換」するコード。
#usr/bin/env python3
import sys, os, re, glob
#TODO:Main / カーネル
class Main:
def __init__(self):
pass
#TODO:data = すべてのデータ
def run(self, vald, lis):
valts = []
i = 2
case = 0
arg = ""
name = sys.argv[1]
file = open(name, encoding="utf-8")
self.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]
self.start(valts, i)
def start(self, valts, i):
for data in valts:
data = data.replace("\n", "")
if data.endswith("{") and data.startswith("fn"):
data1 = data.split(" ")[1].replace(")", "").split("(")[0]
arg = data.split("(")[1].split(")")[0]
if data1 == "main():":
pass
else:
data = data1.replace("{", "") + "({}):".format(arg)
self.conf.write("\n{}".format(data))
else:
data = data.strip(" ").replace(";", "")
data = data.split("//")[0]
if data.startswith("if"):
if data.split(" ")[2] == "more_than":
self.conf.write("\n jne {}, {}, _L{}".format(data.split(" ")[1], data.split(" ")[3],i))
elif data.split(" ")[2] == "less_than":
self.conf.write("\n ja {}, {}, _L{}".format(data.split(" ")[1], data.split(" ")[3],i))
self.conf.write("\n ret")
self.conf.write("\n_L{}:".format(i))
i += 1
else:
if data.startswith("ret"):
rfun = data.split(" ")[1]
try:
rarg = data.split(" ")[2]
self.conf.write("\n mov {}, {}".format(rfun, rarg))
except IndexError:
pass
self.conf.write("\n ret")
elif data.startswith("use"):
try:
self.conf.write("\n call {}{}".format(data.split(" ")[1].split("(")[0]+"(", data.split("(")[1]))
except KeyError:
self.conf.write("\n call {}{}".format(data.split(" ")[1].split("(")[0]))
elif "open" in data:
self.conf.write("\n open:{}".format(data.split(":")[1]))#, data.split("=")[1]))
elif "make" in data:
self.conf.write("\n {}, make".format(data.split(" ")[1]))
elif data.endswith("close"):
self.conf.write("\n {}, close".format(data.split(" ")[0]))
elif "write" in data:
try:
self.conf.write("\n write:{}".format(vald["".join(data.split(":")[1:])]))
except KeyError:
self.conf.write("\n write:{}".format("".join(data.split(":")[1:])))
elif data.startswith("consolp:"):
self.conf.write("\n msg {}".format(data.split("consolp:")[1]))
elif data.startswith("int"):
if "," in data:
datav = data.split("=")[0].split("int ")[1].replace(" ", "").split(",")
datanum = int(data.split("=")[1].replace(" ", "").split(","))
valdl = dict(zip(datav, datanum))
for datav in datav:
self.conf.write("\n mov {}, {}".format(datav, valdl[datav]))
else:
datav = data.split(" ")[1]
datanum = int(data.split(" ")[3])
self.conf.write("\n mov {}, {}".format(datav, datanum))
elif data.startswith("str"):
if "," in data:
datav = data.split("=")[0].split("int ")[1].replace(" ", "").split(",")
datanum = data.split("=")[1].replace(" ", "").split(",")
valdl2 = dict(zip(datav, datanum))
for datav in datav:
self.conf.write("\n mov {}, {}".format(datav, valdl[datav]))
else:
datav = data.split(" ")[1]
datanum = data.split(" ")[3]
self.conf.write("\n mov {}, {}".format(datav, datanum))
try:
valdl.update(valdl2)
except UnboundLocalError:
pass
#elif data.startswith("}"):
# self.conf.write("\n mov end")
elif data.startswith("while"):
c = data.split(" ")[1]
wc = 0
cou = 0
self.conf.write("\n jmp _L{}, {}".format(i, c))
self.conf.write("\n ret")
self.conf.write("\n_L{}, {}:".format(i, c))
wc = 2
if ">" in data or "<" in data:
self.conf.write("\n cmp {}, {}".format(c, cou))
self.conf.write("\n add {}, {}, 1".format("cou", "cou"))
wc = 1
i += 1
# add x, 10, 2
elif data.startswith("~") is False:
if "+" in data:
data = data.replace(" ", "")#.split(":")[1]
val = data.split("=")[0]
data2 = data.split("=")[1].split("+")[0]
data3 = data.split("=")[1].split("+")[1]
self.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]
self.conf.write("\n sub {}, {}, {}".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]
self.conf.write("\n mul {}, {}, {}".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]
self.conf.write("\n div {}, {}, {}".format(val, data2, data3))
self.conf.write("\ncall main()")
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()
vald = {}
lis = []
omega = Main()
omega.run(vald, lis)
できるだけ実行時の速度を速めたかったので、次のコードは短めです。と言いたいところですが、実はこっちのコードは長くなりました。辞書の処理などがあるからですね。
次は、仮想機械を立ち上げて機械語を実行する
部分のコード。
import sys
class Run:
def __init__(self):
self.wc = 0
def run(self, i):
file = open(sys.argv[1], encoding="utf-8")
data2 = file.readlines()
dick = {}
lis = []
lisf = []
lisw = []
wc = 0
ji = 0
vald = {}
for data in data2:
data = data.replace("\n", "")
if data.startswith(" "):
if wc == 1:
lisw.append(data.replace(" ", "") + ";")
else:
lis.append("{};".format(data.replace(" ", "")))
elif data.startswith(" ret"):
break
elif data.endswith(":"):
if data.startswith("_"):
try:
try:
ji = int(data.split(" ")[1].replace(":", ""))
except IndexError:
pass
except ValueError:
ji = "True"
func = data.replace(":", "")
lisf.append(func)
elif data.startswith("call"):
dick2 = dict(zip(lisf, "".join(lis).split("ret;")))
dick.update(dick2)
self.Main(dick, vald, data.split(" ")[1], i, ji)
file.close()
def Main(self, dick, vald, f, i, ji):
lis = str(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(")") and data.startswith("call") is False:
val = data.split("msg ")[1].split("(")[0]+"("+data.split("(")[1]
if '"' in val:
print(val.replace('"', ""))
else:
self.Main(dick, vald, val, i, ji)
print(dick[val])
else:
data = str(data)
if data.startswith("mov"):
if data.split(" ")[1] == "end":
break
else:
val = data.split(", ")[1]
arg = data.split(", ")[0].split(" ")[1]
vald[arg] = val
elif data.startswith("strf"):
dick[data.split(", ")[0].split(" ")[1]] = data.split(", ")[1]
elif "open" in data:
name = "".join(data.split(":")[1:]).replace(" ", "").split(",")
opf = open(dick[name[0]], name[1], encoding=name[2])
elif data.endswith("close"):
opf.close()
elif data.endswith("make"):
file = data.split(" ")[1]
with open(file.replace(",", ""), "w", encoding="utf_8") as opf:
pass
elif "write" in data:
opf.write("".join(data.split(":")[1]))
elif data.startswith("call"):
func = data.split(" ")[1]
if func.endswith("()"):
pass
else:
argn = func.split("(")[1].replace(")", "")
arg = vald[argn]
vald = {}
vald[argn] = arg
self.Main(dick, vald, func, i, ji)
elif data.startswith("msg"):
if ' "' in data.split("msg")[1]:
print(data.split("msg")[1].replace(' "', "").replace('"', ""))
else:
dick2 = {}
try:
dick2["1"] = vald[data.split("msg ")[1]]
except KeyError:
print("変数名'{}'が見つかりません。終了。".format(data.split("msg ")[1]))
sys.exit()
self.Main(dick2, vald, "1", i, ji)
if str(vald[data.split("msg ")[1]]).startswith("open"):
pass
else:
print(vald[data.split("msg ")[1]])
elif data.startswith("add"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
try:
vald[val] = int(vald[formula]) + int(vald[formula2])
except KeyError:
try:
vald[val] = int(vald[formula2]) + int(formula)
except KeyError:
vald[val] = int(formula) + int(formula2)
elif data.startswith("sub"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
try:
vald[val] = int(vald[formula]) - int(vald[formula2])
except KeyError:
try:
vald[val] = int(vald[formula]) - int(formula2)
except KeyError:
vald[val] = int(formula) - int(formula2)
elif data.startswith("mul"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
try:
vald[val] = int(vald[formula]) * int(vald[formula2])
except KeyError:
try:
vald[val] = int(formula) * int(vald[formula2])
except KeyError:
vald[val] = int(formula) * int(formula2)
elif data.startswith("div"):
formula = data.split(",")[1].replace(" ", "")
formula2 = data.split(",")[2].replace(" ", "")
val = data.split(",")[0].split(" ")[1]
try:
vald[val] = int(vald[formula]) / int(vald[formula2])
except KeyError:
try:
vald[val] = int(formula) / int(vald[formula])
except KeyError:
vald[val] = int(formula) / int(formula2)
elif data == "break":
break
elif data.startswith("jmp"):
jmp = data.replace("jmp ", "")
try:
if ji == "True":
while True:
self.Main(dick, vald, jmp, i, "True")
else:
for dan in range(ji):
self.Main(dick, vald, jmp, i, 0)
break
except KeyError:
pass
elif data.startswith("jne"):
data = data.replace(",", "").split(" ")
func = data[3]
try:
if int(vald[data[1]]) > int(vald[data[2]]):
self.Main(dick, vald, func, i, ji)
else:
break
except KeyError:
try:
if int(vald[data[1]]) > int(data[2]):
self.Main(dick, vald, func, i, ji)
else:
break
except KeyError:#TODO
if int(data[1]) > int(vald[data[2]]):
self.Main(dick, vald, func, i, ji)
else:
break
elif data.startswith("ja"):
data = data.replace(",", "").split(" ")
func = data[3]
try:
if int(vald[data[1]]) < int(vald[data[2]]):
self.Main(dick, func, i, ji)
else:
break
except KeyError:
try:
if int(vald[data[1]]) < int(data[2]):
self.Main(dick, vald, func, i, ji)
else:
break
except KeyError:
if int(data[1]) < int(data[2]):
self.Main(dick, vald, func, i, ji)
else:
break
if __name__ == '__main__':
Run().run(0)
サンプルコード
例えば、lapislazuliでは次のようなコードを実行できます。
fn iffunc(x) {
if x more_than 3 {
x = x + 1;
}
ret func(x) x;
}
fn main() {
int x = 6;
use iffunc(x);
consolp:x;
ret main() 0;
}
lasバイトコードがこちら。
iffunc(x):
jne x, 3, _L2
ret
_L2:
add x, x, 1
mov func(x), x
ret
main():
mov x, 6
call iffunc(x)
msg x
mov main(), 0
ret
call main()
7
英語の勉強がしたかったので、>とか<を使わずにmore_thanとless_tahanを使いました。(more_than=> / less_than=<)
関数定義は、Rustにあこがれて「fn」です。けっこうRustの構文とか好きなんですよ。私。
前回に引き続き、最初に呼び出される(callされる)のがmain()関数なので、Cとかそのあたりと同じで最初の処理はそこに書きます。解説はそのくらいですかね。また何かありましたら私のついったまでお来しくださいませ。
ご連絡
ただいま私dangomushiは、自作言語などで情報の共有ができる「仲間」を探しております。もし興味があって情報共有したいよ~って方がいらっしゃいましたら遠慮なく連絡してください。
まとめ
今回は、たった2つのファイルで「if文,while文, 関数/変数定義、スコープ」ができる言語を作りました。スコープと引数を実装した際、辞書型がこんがらがったのが印象的でした。
課題としては、
- if文を実行するともれなく不定期でバグ
- 実行速度が遅い
- 関数の実行の仕方が一通り
の三つかと思います。次は改善していこうとおもいます。
それでは、次の記事でお会いしましょう!!
さようなら!