トイ言語実験日記6(toigでeval)
文字列型作ったらこれで
eval()
が作れるんじゃねって気がしてきた
というわけで
とりあえずとりあえず
class TestEval(BaseTest):
def test_eval(self):
self.go("""
eval := func (expr) do expr end
""")
assert self.go("eval(None)") == None
assert self.go("eval(True)") == True
assert self.go("eval(False)") == False
assert self.go("eval(5)") == 5
困るまで全部このメソッドの中でやっていこう
evalは何度書いても楽しい
ifを作る
型を判定する組み込み関数がほしい
ToigStr
で文字列を表すことになったので、isinstance(..., str)
では正しく判定できなくなっている
_builtins = {
...
"is_bool": lambda _, s: s.append(type(s.pop()) is bool),
"is_int": lambda _, s: s.append(type(s.pop()) is int),
"is_str": lambda _, s: s.append(type(s.pop()) is ToigStr),
"is_name": lambda _, s: s.append(type(s.pop()) is str),
...
}
_builtins = {
...
"is_bool": lambda args: type(args[0]) is bool,
"is_int": lambda args: type(args[0]) is int,
"is_str": lambda args: type(args[0]) is ToigStr,
"is_name": lambda args: type(args[0]) is str,
...
}
Parser
では、ToigStr
を先に処理しておかないといけなかった
そうでないとToigStr("If")
でもcase "if":
にマッチしてしまう
普通にバグでしたすみません
def _primary(self):
match self._current_token:
case ToigStr(s):
return self._advance()
...
case "if":
self._advance(); return self._if()
...
return self._advance()
本体
matchも分割代入もないのでちょっと泥臭いが普通に書ける
self.go("""
eval := func (expr) do
if expr == None then
None
elif is_bool(expr) or is_int(expr) or is_str(expr) then
expr
elif expr[0] == 'if' then
if eval(expr[1]) then eval(expr[2]) else eval(expr[3]) end
else
error('Unexpected expression: ', expr)
end
end
""")
...
assert self.go("eval(['if', True, 5, 6])") == 5
assert self.go("eval(['if', False, 5, 6])") == 6
with pytest.raises(AssertionError):
self.go("eval(['unexpected case'])")
defineとgetを作るよ
辞書がないので配列使って辞書もどきを作るよ
とりあえずスコープはなしで
self.go("""
define := runc(env, name, val) do
for p in env do
if p[0] == name then p[1] = val; return(val) end
end;
append(env, [name, val]);
return(val)
end
""")
self.go("""
get := runc(env, name) do
for p in env do
if p[0] == name then return(p[1]) end
end;
error('Not found:', name)
end
""")
配列に破壊的に要素を付け足す関数がほしいけどなかった
pushで破壊的に追加しようかなあと思ったけど
突然Pythonのやり方を尊重してappendを破壊的変更に使うことに変えた
pushとpopは非破壊的に
_builtins = {
...
"append": lambda args: args[0].append(args[1]),
...
def _append(_, s):
arr = s.pop(); val = s.pop()
arr.append(val)
s.append(None)
_builtins = {
...
"append": _append,
...
}
self._go("push := func (l, a) do l + [a] end")
self._go("pop := func (l) do l[:-1] end")
あとzipを作ったりruncをstdlibに格上げしたりしている
unfoldを知ったので使ってみたいお年ごろ > zip
self._go("""
zip := func (l1, l2) do
unfoldl(
[l1, l2],
func (s) do first(s) == [] or last(s) == [] end,
func (s) do [first(first(s)), first(last(s))] end,
func (s) do [rest(first(s)), rest(last(s))] end
)
end
""")
self._go("""
defmacro _runc (params, body) do quasiquote
func (unquote_splicing(rest(params))) do
letcc return do unquote(body) end
end
end end
#rule [runc, _runc, PARAMS, do, EXPR, end]
""")
これだけお膳立てを整えてやっとeval
self.go("""
_eval := func (expr, env) do
print('eval', expr);
if expr == None then
None
elif is_bool(expr) or is_int(expr) then
expr
elif is_str(expr) then
get(env, expr)
elif expr[0] == 'define' then
define(env, expr[1], _eval(expr[2], env))
elif expr[0] == 'if' then
if _eval(expr[1], env) then
_eval(expr[2], env)
else
_eval(expr[3], env)
end
else
error('Unexpected expression: ', expr)
end
end
""")
self.go("""
global_env := [];
eval := func (expr) do _eval(expr, global_env) end
""")
assert self.go("eval(['define', 'a', 5])") == 5
assert self.go("eval('a')") == 5
assert self.go("eval(['define', 'b', 6])") == 6
assert self.go("eval('b')") == 6
assert self.go("eval(['define', 'b', 7])") == 7
assert self.go("eval('b')") == 7
with pytest.raises(AssertionError):
self.go("eval('c')")
テストメソッドを分割した
class TestEval(BaseTest):
@pytest.fixture(autouse=True)
def setup_env(self):
self.go("""
define := runc (env, name, val) do
...
end
""")
self.go("""
get := runc (env, name) do
...
end
""")
self.go("""
_eval := func (expr, env) do
...
end
""")
self.go("""
global_env := [];
eval := func (expr) do _eval(expr, global_env) end
""")
def test_primary(self):
assert self.go("eval(None)") == None
...
def test_if(self):
assert self.go("eval(['if', True, 5, 6])") == 5
...
def test_define(self):
assert self.go("eval(['define', 'a', 5])") == 5
...
setup_env()
の大部分は1度だけ実行すればいいんだけれども
scope="class"
にするとself.i
にアクセスする方法が見つからなかったので
テストごとに実行するようにしている
なんとかならないか
scope="class"にするとself.iにアクセスする方法が見つからなかった
このへんが我ながらうさんくさいんだよなー
これでちゃんと動くのか少々心配だけど動いてるって状態
class BaseTest:
i: ICI | STM
@pytest.fixture(autouse=True)
def set_interpreter(self, request):
request.cls.i = request.param()
def parsed(self, src):
return self.i.parse(src)
メソッドごとにInterpreter
を生成するならこれでいいんだよな
class BaseTest:
@pytest.fixture(autouse=True)
def set_interpreter(self, request):
self.i = request.param()
def parsed(self, src):
return self.i.parse(src)
なんでこうしなかったんだっけ?
Interpreter
を毎回つくるのがもったいないと思ったのかな?
うさんくさいところもないしこっちでいくか
普通にテスト通るし
組み込み関数
global_envで組み込み関数を入れておきelseは全部関数として扱う
def setup_eval(self):
self.go("""
_eval := func (expr, env) do
...
else
op_val := _eval(expr[0], env);
args_val := map(expr[1:], func (arg) do _eval(arg, env) end);
op_val(args_val)
end
end
""")
self.go("""
global_env := [
['add', func (args) do args[0] + args[1] end],
['sub', func (args) do args[0] - args[1] end],
['equal', func (args) do args[0] == args[1] end]
];
eval := func (expr) do _eval(expr, global_env) end
""")
def test_builtins(self):
assert self.go("eval(['add', 5, 6])") == 11
assert self.go("eval(['sub', 11, 6])") == 5
assert self.go("eval(['equal', 5, 5])") == True
assert self.go("eval(['equal', 5, 6])") == False
じゃユーザ定義関数
環境にスコープを導入しないと
[親環境, [[名前, 値], [名前, 値], ...]]
という形で表すことにする
global_env
は親環境をNone
としてこう
global_env := [None, [
['add', ['primitive', func (args) do args[0] + args[1] end]],
['sub', ['primitive', func (args) do args[0] - args[1] end]],
['equal', ['primitive', func (args) do args[0] == args[1] end]]
]];
eval := func (expr) do _eval(expr, global_env) end
そうそう
組み込み関数をis_callable
で見分ける、というわけにいかないので
目印をつけて['primitive', 関数本体]
と表すことにした
define
はenv[1]
を見るように変えて、
define := runc (env, name, val) do
for p in env[1] do
if p[0] == name then p[1] = val; return(val) end
end;
append(env[1], [name, val]);
return(val)
end
get
はenv[1]
に名前が見つからなかったらenv[0]
で探すようにする
env[0]
がNone
だったら見つからなかったということでエラー
get := runc (env, name) do
for p in env[1] do
if p[0] == name then return(p[1]) end
end;
if env[0] != None then
get(env[0], name)
else
error('Not found:', name)
end
end
func
を作るところとapply
を呼ぶところ
_eval := func (expr, env) do
...
elif expr[0] == 'func' then
['closure', expr[1], expr[2], env]
...
else
op_val := _eval(expr[0], env);
args_val := map(expr[1:], func (arg) do _eval(arg, env) end);
apply(op_val, args_val)
end
end
なんか投稿しようとしたらWAFが文句言ってきたので二つに分けた
apply
やってることはいつもと同じ
[op_val[3], []]
で新しいスコープを作っている
body := op_val[3]
とかってやりながら書いた方が可読性が上がるけどまあいいやという気になっている
matchや分割代入があればそれでやるんだけど
apply := func (op_val, args_val) do
if op_val[0] == 'primitive' then
op_val[1](args_val)
else
env := [op_val[3], []];
for name_val in zip(op_val[1], args_val) do
define(env, name_val[0], name_val[1])
end;
_eval(op_val[2], env)
end
end
テスト
def test_function(self):
assert self.go("eval([['func', ['n'], ['add', 'n', 5]], 6])") == 11
def test_fib(self):
self.go("""eval(
['define', 'fib', ['func', ['n'],
['if', ['equal', 'n', 0], 0,
['if', ['equal', 'n', 1], 1,
['add', ['fib', ['sub', 'n', 1]], ['fib', ['sub', 'n', 2]]]]]]]
)""")
assert self.go("eval(['fib', 0])") == 0
assert self.go("eval(['fib', 1])") == 1
assert self.go("eval(['fib', 2])") == 1
assert self.go("eval(['fib', 3])") == 2
assert self.go("eval(['fib', 10])") == 55
def test_adder(self):
self.go("""eval(
['define', 'make_adder', ['func', ['n'],
['func', ['m'], ['add', 'n', 'm']]
]]
)""")
assert self.go("eval([['make_adder', 5], 6])") == 11
おっそ!
fib
のテストだけで4秒かかっている
iciのfib
で1秒、stm
のfib
で4秒て感じ
['fib', 6]
でがまんしとこ
これなら合わせて1秒
これくらいでもevalが実装できました、て言えそうな気もするけどカウンター作れるところまでやっとこ
assign
とseq
を作って
assign := runc (env, name, val) do
for p in env[1] do
if p[0] == name then p[1] = val; return(val) end
end;
if env[0] != None then
assign(env[0], name, val)
else
error('Not found:', name)
end
end
_eval := func (expr, env) do
...
elif expr[0] == 'assign' then
assign(env, expr[1], _eval(expr[2], env))
elif expr[0] == 'seq' then
val := None;
for e in expr[1:] do
val = _eval(e, env)
end;
val
...
end
カウンター
def test_counter(self):
self.go("""eval(['seq',
['define', 'make_counter', ['func', [], ['seq',
['define', 'c', 0],
['func', [], ['assign', 'c', ['add', 'c', 1]]]
]]],
['define', 'counter1', ['make_counter']],
['define', 'counter2', ['make_counter']]
])""")
assert self.go("eval(['counter1'])") == 1
assert self.go("eval(['counter1'])") == 2
assert self.go("eval(['counter2'])") == 1
assert self.go("eval(['counter2'])") == 2
assert self.go("eval(['counter1'])") == 3
assert self.go("eval(['counter2'])") == 3
ここらへんまで動けばstmもiciもだいたいちゃんと動くって言っちゃってよいのでは
文字列と配列をちょっと扱えるようにするとeval
が作れるわけだから
最小eval
にもうちょっと機能を付け加えるだけでeval
でeval
が書けそうだな
いや書けるに決まっているんだけど
現実味というか
構文解析をつける直前の main くらいから始めるとちょうどいまじっそうしたくらいのところ
これに必要な機能だけ追加していこう
ここまでやってきたことをなぞりながら進めればいいだろう
i.go(["define", "eval", ["func", ["expr"], "expr"]])
assert i.go(["eval", None]) == None
assert i.go(["eval", True]) == True
assert i.go(["eval", False]) == False
assert i.go(["eval", 5]) == 5
次はifを作る
まず親evalで文字列と配列が扱えるようにしないと
文字列を評価するところ
class ToigStr(str):
pass
class Evaluator:
def eval(self, expr, env):
match expr:
...
case bool(val) | int(val) | ToigStr(val):
return val
...
文字列・配列を扱うための組み込み関数を追加
たぶんこれくらいで足りるはず
忘れてたら都度追加する
class Evaluator:
def _apply(self, f_val, args_val):
if callable(f_val):
return f_val(args_val)
# Builtin Functions
def _set_at(args):
args[0][args[1]] = args[2]
return args[2]
def _slice(args):
arr, start, end, step = args
return arr[slice(start, end, step)]
def _set_slice(args):
arr, start, end, step, val = args
arr[start:end:step] = val
return val
def _error(args):
assert False, f"{' '.join(map(str, args))}"
_builtins = {
"__builtins__": None,
"add": lambda args: args[0] + args[1],
"sub": lambda args: args[0] - args[1],
"equal": lambda args: args[0] == args[1],
"array": lambda args: args,
"get_at": lambda args: args[0][args[1]],
"set_at": _set_at,
"append": lambda args: args[0].append(args[1]),
"slice": _slice,
"set_slice": _set_slice,
"is_bool": lambda args: type(args[0]) is bool,
"is_int": lambda args: type(args[0]) is int,
"is_str": lambda args: type(args[0]) is ToigStr,
"is_array": lambda args: type(args[0]) is list,
"print": lambda args: print(*args),
"error": lambda args: _error(args)
}
class BuiltIns:
@staticmethod
def load(env):
for name, func in _builtins.items():
env.define(name, func)
class Interpreter:
def __init__(self):
self._env = Environment()
BuiltIns.load(self._env)
self._env = Environment(self._env)
新しいeval()
expr
の型を判断して処理する
if
だけでなくquote
も追加
i.go(["define", "eval", ["func", ["expr"],
["if", ["equal", "expr", None], None,
["if", ["is_bool", "expr"], "expr",
["if", ["is_int", "expr"], "expr",
["if", ["is_str", "expr"], "expr",
["seq",
["define", "op", ["get_at", "expr", 0]],
["if", ["equal", "op", ToigStr("quote")],
["eval", ["get_at", "expr", 1]],
["if", ["equal", "op", ToigStr("if")],
["if", ["eval", ["get_at", "expr", 1]],
["eval", ["get_at", "expr", 2]],
["eval", ["get_at", "expr", 3]]],
["error", "expr"]]]
]]]]]
]])
型の判断ではor
がないのでひとつずつif
で確認している
quote
はなぜ必要になるかというと
["eval", ["if", True, 5, 6]]
と書くと、["if", True, 5, 6]
を親evalが評価して5
にしてから"eval"
に渡してしまうため
ここは評価しないでそのままにしておいて、という意味で["eval", ["quote", ["if", True, 5, 6]]]
と書く
assert i.go(["eval", None]) == None
とかではNone
を親evalが評価仕様がしなかろうがNone
はNone
だったから
そのへんも含めて書き直し&if
のテスト
def eval(expr):
return i.go(["eval", ["quote", expr]])
assert eval(None) == None
assert eval(True) == True
assert eval(False) == False
assert eval(5) == 5
assert eval(ToigStr("hello")) == "hello"
assert eval(["if", True, 5, 6]) == 5
assert eval(["if", False, 5, 6]) == 6
assert eval(["if", ["if", True, True, True], 5, 6]) == 5
assert eval(["if", True, ["if", True, 5, 6], 7]) == 5
assert eval(["if", False, 5, ["if", False, 6, 7]]) == 7
次は変数
ところで"define"
をdefineできるのか?(お前が聞くな
えーと、
書いてから考えよう
for
がないので自前で再帰する
i.go(["define", "define_", ["func", ["env", "name", "val"], ["seq",
["define", "_define_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["seq",
["append", ["get_at", "env", 1], ["array", "name", "val"]],
"val"],
["seq",
["define", "name_val", ["get_at", "pairs", 0]],
["if", ["equal", ["get_at", "name_val", 0], "name"],
["set_at", "name_val", 1, "val"],
["_define_", ["slice", "pairs", 1, None, None]]]]]]],
["_define_", ["get_at", "env", 1]]
]]])
i.go(["define", "assign_", ["func", ["env", "name", "val"], ["seq",
["define", "_assign_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["assign_", ["get_at", "env", 0], "name", "val"],
["seq",
["define", "name_val", ["get_at", "pairs", 0]],
["if", ["equal", ["get_at", "name_val", 0], "name"],
["set_at", "name_val", 1, "val"],
["_assign_", ["slice", "pairs", 1, None, None]]]]]]],
["if", ["equal", "env", None],
["error", "name"],
["_assign_", ["get_at", "env", 1]]]]]])
i.go(["define", "get_", ["func", ["env", "name"], ["seq",
["define", "_get_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["get_", ["get_at", "env", 0], "name"],
["seq",
["define", "name_val", ["get_at", "pairs", 0]],
["if", ["equal", ["get_at", "name_val", 0], "name"],
["get_at", "name_val", 1],
["_get_", ["slice", "pairs", 1, None, None]]]]]]],
["if", ["equal", "env", None],
["error", "name"],
["_get_", ["get_at", "env", 1]]]]]])
関数名変えときゃいけそう
配列の操作をいちいちget_at
、set_at
、slice
で書くのもけっこうだな
文字列型作ったらこれでeval()が作れるんじゃねって気がしてきた
ただの錯覚でした
ほんとうにありがとうございました
本当に顧客が必要としていたもの:quote
というわけでclass ToigStr
は抹殺
で、is_name
はマクロがないと定義できないので、「配列でなくてNoneでもboolでもintでもないもの」を名前と判断する
あとはdefine
とassign
の処理を入れて
i.go(["define", "eval", ["func", ["expr", "env"],
["if", ["not", ["is_array", "expr"]],
["if", ["equal", "expr", None], None,
["if", ["is_bool", "expr"], "expr",
["if", ["is_int", "expr"], "expr",
["get_", "env", "expr"]]]],
["seq",
["define", "op", ["get_at", "expr", 0]],
["if", ["equal", "op", ["quote","quote"]],
["get_at", "expr", 1],
["if", ["equal", "op", ["quote", "define"]],
["define_", "env",
["get_at", "expr", 1],
["eval", ["get_at", "expr", 2], "env"]],
["if", ["equal", "op", ["quote", "assign"]],
["assign_", "env",
["get_at", "expr", 1],
["eval", ["get_at", "expr", 2], "env"]],
["if", ["equal", "op", ["quote", "if"]],
["if", ["eval", ["get_at", "expr", 1], "env"],
["eval", ["get_at", "expr", 2], "env"],
["eval", ["get_at", "expr", 3], "env"]],
["error", "expr"]]]]]
]
]
]])
テスト
スコープのテストもいまやっておく
i.go(["define", "global_env", ["array", None, ["array"]]])
def eval(expr):
return i.go(["eval", ["quote", expr], "global_env"])
def fails(expr):
try: eval(expr)
except AssertionError: return True
else: return False
assert eval(None) == None
assert eval(True) == True
assert eval(False) == False
assert eval(5) == 5
assert eval(["if", True, 5, 6]) == 5
assert eval(["if", False, 5, 6]) == 6
assert eval(["if", ["if", True, True, True], 5, 6]) == 5
assert eval(["if", True, ["if", True, 5, 6], 7]) == 5
assert eval(["if", False, 5, ["if", False, 6, 7]]) == 7
assert eval(["define", "a", 5]) == 5
assert eval(["define", "b", 6]) == 6
assert eval("a") == 5
assert eval("b") == 6
assert eval(["define", "b", 7]) == 7
assert eval("a") == 5
assert eval("b") == 7
i.go(["define", "global_env", ["array", "global_env", ["array"]]])
assert eval(["define", "a", 8]) == 8
assert eval("a") == 8
assert eval("b") == 7
assert eval(["assign", "a", 9]) == 9
assert eval("a") == 9
assert eval("b") == 7
assert eval(["assign", "b", 10]) == 10
assert eval("a") == 9
assert eval("b") == 10
assert fails(["assign", "c", 11])
assert fails("c")
i.go(["define", "global_env", ["get_at", "global_env", 0]])
assert eval("a") == 5
assert eval("b") == 10
組み込み関数・ユーザ関数・seq
を一気に実装
まるごと
i.go(["define", "first", ["func", ["l"], ["get_at", "l", 0]]])
i.go(["define", "last", ["func", ["l"], ["get_at", "l", -1]]])
i.go(["define", "rest", ["func", ["l"], ["slice", "l", 1, None, None]]])
i.go(["define", "map", ["func", ["l", "f"],
["if", ["equal", "l", ["array"]],
["array"],
["add",
["array", ["f", ["first", "l"]]],
["map", ["slice", "l", 1, None, None], "f"]]]]])
i.go(["define", "define_", ["func", ["env", "name", "val"], ["seq",
["define", "_define_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["seq",
["append", ["last", "env"], ["array", "name", "val"]],
"val"],
["seq",
["define", "name_val", ["first", "pairs"]],
["if", ["equal", ["first", "name_val"], "name"],
["set_at", "name_val", 1, "val"],
["_define_", ["slice", "pairs", 1, None, None]]]]]]],
["_define_", ["last", "env", 1]]]]])
i.go(["define", "assign_", ["func", ["env", "name", "val"], ["seq",
["define", "_assign_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["assign_", ["first", "env"], "name", "val"],
["seq",
["define", "name_val", ["first", "pairs"]],
["if", ["equal", ["first", "name_val"], "name"],
["set_at", "name_val", 1, "val"],
["_assign_", ["rest", "pairs"]]]]]]],
["if", ["equal", "env", None],
["error", "name"],
["_assign_", ["last", "env"]]]]]])
i.go(["define", "get_", ["func", ["env", "name"], ["seq",
["define", "_get_", ["func", ["pairs"],
["if", ["equal", "pairs", ["array"]],
["get_", ["first", "env"], "name"],
["seq",
["define", "name_val", ["first", "pairs"]],
["if", ["equal", ["first", "name_val"], "name"],
["last", "name_val"],
["_get_", ["rest", "pairs"]]]]]]],
["if", ["equal", "env", None],
["error", "name"],
["_get_", ["last", "env"]]]]]])
i.go(["define", "eval", ["func", ["expr", "env"],
["if", ["not", ["is_array", "expr"]],
["if", ["equal", "expr", None], None,
["if", ["is_bool", "expr"], "expr",
["if", ["is_int", "expr"], "expr",
["get_", "env", "expr"]]]],
["seq",
["define", "op", ["first", "expr"]],
["if", ["equal", "op", ["quote","func"]],
["array", ["quote", "closure"],
["get_at", "expr", 1], ["get_at", "expr", 2], "env"],
["if", ["equal", "op", ["quote", "quote"]],
["get_at", "expr", 1],
["if", ["equal", "op", ["quote", "define"]],
["define_", "env",
["get_at", "expr", 1],
["eval", ["get_at", "expr", 2], "env"]],
["if", ["equal", "op", ["quote", "assign"]],
["assign_", "env",
["get_at", "expr", 1],
["eval", ["get_at", "expr", 2], "env"]],
["if", ["equal", "op", ["quote", "seq"]],
["eval_seq", ["rest", "expr"], "env", None],
["if", ["equal", "op", ["quote", "if"]],
["if", ["eval", ["get_at", "expr", 1], "env"],
["eval", ["get_at", "expr", 2], "env"],
["eval", ["get_at", "expr", 3], "env"]],
["seq",
["define", "op_val", ["eval", "op", "env"]],
["define", "args_val", ["map",
["rest", "expr"],
["func", ["arg"], ["eval", "arg", "env"]]]],
["apply", "op_val", "args_val"]]]]]]]]]]]])
i.go(["define", "eval_seq", ["func", ["exprs", "env", "result"],
["if", ["equal", "exprs", ["array"]],
"result",
["eval_seq", ["rest", "exprs"], "env",
["eval", ["first", "exprs"], "env"]]]]])
i.go(["define", "apply", ["func", ["op_val", "args_val"],
["if", ["equal", ["first", "op_val"], ["quote", "primitive"]],
[["last", "op_val"], "args_val"],
["seq",
["define", "env", ["array", ["get_at", "op_val", 3], ["array"]]],
["set_params", "env", ["get_at", "op_val", 1], "args_val"],
["eval", ["get_at", "op_val", 2], "env"]]]]])
i.go(["define", "set_params", ["func", ["env", "params", "args"],
["if", ["equal", "params", ["array"]], None,
["seq",
["define_", "env", ["first", "params"], ["first", "args"]],
["set_params", "env", ["rest", "params"], ["rest", "args"]]]]]])
i.go(["define", "global_env", ["array", None, ["array",
["array", ["quote", "add"], ["array", ["quote", "primitive"],
["func", ["args"], ["add",
["get_at", "args", 0], ["get_at", "args", 1]]]]],
["array", ["quote", "sub"], ["array", ["quote", "primitive"],
["func", ["args"], ["sub",
["get_at", "args", 0], ["get_at", "args", 1]]]]],
["array", ["quote", "equal"], ["array", ["quote", "primitive"],
["func", ["args"], ["equal",
["get_at", "args", 0], ["get_at", "args", 1]]]]],
]]])
いつものひとたちが動いた
eval(["define", "fib", ["func", ["n"],
["if", ["equal", "n", 0], 0,
["if", ["equal", "n", 1], 1,
["add", ["fib", ["sub", "n", 1]], ["fib", ["sub", "n", 2]]]]]]])
assert eval(["fib", 0]) == 0
assert eval(["fib", 1]) == 1
assert eval(["fib", 2]) == 1
assert eval(["fib", 3]) == 2
assert eval(["fib", 6]) == 8
eval(["define", "make_adder",
["func", ["n"], ["func", ["m"], ["add", "n", "m"]]]])
assert eval([["make_adder", 5], 6]) == 11
eval(["seq",
["define", "make_counter", ["func", [], ["seq",
["define", "c", 0],
["func", [], ["assign", "c", ["add", "c", 1]]]
]]],
["define", "counter1", ["make_counter"]],
["define", "counter2", ["make_counter"]]
])
assert eval(["counter1"]) == 1
assert eval(["counter1"]) == 2
assert eval(["counter2"]) == 1
assert eval(["counter2"]) == 2
assert eval(["counter1"]) == 3
assert eval(["counter2"]) == 3
便宜上いままでひとつずつ実行していたeval
を定義する式をseq
で全部ひとつにまとめる
DEFINE_EVAL = ["seq",
["define", "first", ["func", ["l"], ["get_at", "l", 0]]],
["define", "last", ["func", ["l"], ["get_at", "l", -1]]],
...
["define", "eval", ["func", ["expr"], ["_eval", "expr", "global_env"]]]
]
それを使ってeval
を定義する
i.go(DEFINE_EVAL)
def eval(expr):
return i.go(["eval", ["quote", expr]])
assert eval(None) == None
assert eval(True) == True
...
そのeval
とeval
を定義する式を使ってeval
を定義する
なにを言ってるかわからねーと思うが(以下略
eval(DEFINE_EVAL)
eval
で動かしたeval
でfib
を動かす
eval(["eval", ["quote",
["define", "fib", ["func", ["n"],
["if", ["equal", "n", 0], 0,
["if", ["equal", "n", 1], 1,
["add", ["fib", ["sub", "n", 1]], ["fib", ["sub", "n", 2]]]]]]]]])
assert eval(["eval", ["quote",["fib", 0]]]) == 0
assert eval(["eval", ["quote",["fib", 1]]]) == 1
assert eval(["eval", ["quote",["fib", 2]]]) == 1
assert eval(["eval", ["quote",["fib", 3]]]) == 2
動いた!
といっても超遅くてこれだけで10秒くらいかかる
fib(6)
とかまったくやる気しない
その気になればいくらでもeval
を重ねていくこともできるはずけど同様
遅いけどevalでevalが書けたというのは自分の中では大きいマイルストーン
おめでとう俺
なんて書くとさらっと動いたように見えるかもしれないけれども艱難辛苦を乗り越えてここまでやってきました(大げさ
デバッグを支援してくれるような機能がほぼないし変数の値を見ようにも
デバッガでenv
の中を見てenv
を探しその中で目的の変数を探す、みたいなことをやらなきゃいけなくて大変
print
だけがお友達
だけどprint
もちゃんと動くか保証の限りではない
という状況で、引数ひとつ書き忘れてたとかくらいでも難航
なにを言ってるかわからねーと思うが(以下略
あらためてまとめ
pythonでevalを書く
pythonで書いたevalでevalを書く
pythonで書いたevalで書いたevalでevalを実行
Evaluator().eval(["eval", ["quote", ["eval", ["quote",["fib", 3]]]]])
が動くようになりました、って書いたらわかりやすいかな(そうでもない
Evaluator().eval(["eval", ["quote", ["eval", ["quote",["fib", 3]]]]])
---------------- ----- ------
Pythonで書いたeval ↑ ↑
Pythonで書いたevalで書いたeval
Pythonで書いたevalで書いたevalで書いたeval
ふたつめとみっつめは全く同じevalってところがミソ
いろんなテストにもなっているという安心感
toigでevalを書き、evalでevalを書いたら次は
toigでtoigを書く、かなあ
最後までやるとマクロやらカスタムルールやら継続やら入ってくるし道のりは長そうだが
救いはtoigで書いたしくみを流用できるかもというところ
やってみるか
文字列関連でもう少し組み込み関数がほしくなるかな?どうかな?
test_eval.pyの名前をtest_toig_on_toig.pyに変えてと
classで表現してるところはどうしようか
いつぞややってみたようにクロージャで書くかな
class BaseToigOnToigTest(BaseTest):
@pytest.fixture(autouse=True)
def setup_eval(self):
# Utilities
self.go("""
EOL := '
';
is_space := func (c) do c == ' ' or c == EOL end;
is_digit := func (c) do '0' <= c and c <= '9' end;
is_alphabet := func (c) do
('A' <= c and c <= 'Z') or ('a' <= c and c <= 'z')
end
""")
self.go("""
is_name_first := func (c) do is_alphabet(c) or c == '_' end;
is_name_rest := func (c) do
is_name_first(c) or is_digit(c)
end
""")
self.go("""
contains := runc (a, l) do
for e in l do
if a == e then return(True) end
end;
False
end
""")
# Scanner
self.go("""
scan := func (src) do
pos := 0;
token := '';
advance := func () do pos = pos + 1 end;
current_char := func () do
if pos < len(src) then src[pos] else '$EOF' end
end;
append_char := func () do
token = token + current_char();
advance()
end;
word := func (is_rest) do
append_char();
while is_rest(current_char()) do append_char() end
end;
name := func () do
word(is_name_rest);
if token == 'None' then None
elif token == 'True' then True
elif token == 'False' then False
else token end
end;
get_token := func () do
token = '';
while is_space(current_char()) do advance() end;
if current_char() == '$EOF' then '$EOF'
elif is_name_first(current_char()) then
name()
elif is_digit(current_char()) then
word(is_digit);
to_int(token)
else error(c) end
end;
tokens := [];
while True do
token :=get_token();
append(tokens, token);
if token == '$EOF' then break() end
end;
tokens
end
""")
# Parser
self.go("""
parse := func (tokens) do
pos := 0;
current_token := func () do tokens[pos] end;
advance := func() do
pos = pos + 1;
tokens[pos - 1]
end;
primary := func () do
advance()
end;
expression := func () do
primary()
end;
expr := expression();
if current_token() != '$EOF' then
error(current_token())
end;
expr
end
""")
# Interpreter
self.go("""
go := func (src) do eval(parse(scan(src))) end
""")
文字列関連でもう少し組み込み関数がほしくなるかな?どうかな?
to_int()
が必要になったのでstm、iciとも追加した
これだけのために意外とたくさん書かないといけなかった
ヘルパー関数とかも多いからまあそんなもんか
class TestInterpreter(BaseToigOnToigTest):
def test_primary(self):
assert self.go("go('None')") == None
assert self.go("go('True')") == True
assert self.go("go('False')") == False
assert self.go("go('5')") == 5