Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tail calls to the interpreter #15

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 98 additions & 84 deletions scrapscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,91 +635,105 @@ def match(obj: Object, pattern: Object) -> Optional[Env]:

# pylint: disable=redefined-builtin
def eval_exp(env: Env, exp: Object) -> Object:
logger.debug(exp)
if isinstance(exp, (Int, Bool, String, Bytes, Hole, Closure, NativeFunction)):
return exp
if isinstance(exp, Var):
value = env.get(exp.name)
if value is None:
raise NameError(f"name '{exp.name}' is not defined")
return value
if isinstance(exp, Binop):
handler = BINOP_HANDLERS.get(exp.op)
if handler is None:
raise NotImplementedError(f"no handler for {exp.op}")
return handler(env, exp.left, exp.right)
if isinstance(exp, List):
return List([eval_exp(env, item) for item in exp.items])
if isinstance(exp, Record):
return Record({k: eval_exp(env, exp.data[k]) for k in exp.data})
if isinstance(exp, Assign):
# TODO(max): Rework this. There's something about matching that we need
# to figure out and implement.
assert isinstance(exp.name, Var)
value = eval_exp(env, exp.value)
return EnvObject({**env, exp.name.name: value})
if isinstance(exp, Where):
res_env = eval_exp(env, exp.binding)
assert isinstance(res_env, EnvObject)
new_env = {**env, **res_env.env}
return eval_exp(new_env, exp.body)
if isinstance(exp, Assert):
cond = eval_exp(env, exp.cond)
if cond != Bool(True):
raise AssertionError(f"condition {exp.cond} failed")
return eval_exp(env, exp.value)
if isinstance(exp, Function):
if not isinstance(exp.arg, Var):
raise RuntimeError(f"expected variable in function definition {exp.arg}")
return Closure(env, exp)
if isinstance(exp, MatchFunction):
return Closure(env, exp)
if isinstance(exp, Apply):
if isinstance(exp.func, Var) and exp.func.name == "$$quote":
return exp.arg
callee = eval_exp(env, exp.func)
if isinstance(callee, NativeFunction):
arg = eval_exp(env, exp.arg)
return callee.func(arg)
if not isinstance(callee, Closure):
raise TypeError(f"attempted to apply a non-closure of type {type(callee).__name__}")
arg = eval_exp(env, exp.arg)
if isinstance(callee.func, Function):
assert isinstance(callee.func.arg, Var)
new_env = {**callee.env, callee.func.arg.name: arg}
return eval_exp(new_env, callee.func.body)
elif isinstance(callee.func, MatchFunction):
# TODO(max): Implement MatchClosure
while True:
logger.debug(exp)
if isinstance(exp, (Int, Bool, String, Bytes, Hole, Closure, NativeFunction)):
return exp
if isinstance(exp, Var):
value = env.get(exp.name)
if value is None:
raise NameError(f"name '{exp.name}' is not defined")
return value
if isinstance(exp, Binop):
# TODO(max): Inline these to avoid recursion
handler = BINOP_HANDLERS.get(exp.op)
if handler is None:
raise NotImplementedError(f"no handler for {exp.op}")
return handler(env, exp.left, exp.right)
if isinstance(exp, List):
# TODO(max): Remove recursion
return List([eval_exp(env, item) for item in exp.items])
if isinstance(exp, Record):
# TODO(max): Remove recursion
return Record({k: eval_exp(env, exp.data[k]) for k in exp.data})
if isinstance(exp, Assign):
# TODO(max): Rework this. There's something about matching that we need
# to figure out and implement.
assert isinstance(exp.name, Var)
# TODO(max): Remove recursion
value = eval_exp(env, exp.value)
return EnvObject({**env, exp.name.name: value})
if isinstance(exp, Where):
res_env = eval_exp(env, exp.binding)
assert isinstance(res_env, EnvObject)
new_env = {**env, **res_env.env}
env = new_env
exp = exp.body
continue
if isinstance(exp, Assert):
cond = eval_exp(env, exp.cond)
if cond != Bool(True):
raise AssertionError(f"condition {exp.cond} failed")
exp = exp.value
continue
if isinstance(exp, Function):
if not isinstance(exp.arg, Var):
raise RuntimeError(f"expected variable in function definition {exp.arg}")
return Closure(env, exp)
if isinstance(exp, MatchFunction):
return Closure(env, exp)
if isinstance(exp, Apply):
if isinstance(exp.func, Var) and exp.func.name == "$$quote":
return exp.arg
callee = eval_exp(env, exp.func)
if isinstance(callee, NativeFunction):
arg = eval_exp(env, exp.arg)
return callee.func(arg)
if not isinstance(callee, Closure):
raise TypeError(f"attempted to apply a non-closure of type {type(callee).__name__}")
arg = eval_exp(env, exp.arg)
for case in callee.func.cases:
m = match(arg, case.pattern)
if m is None:
continue
return eval_exp({**env, **m}, case.body)
raise MatchError("no matching cases")
else:
raise TypeError(f"attempted to apply a non-function of type {type(callee.func).__name__}")
if isinstance(exp, Access):
obj = eval_exp(env, exp.obj)
if isinstance(obj, Record):
if not isinstance(exp.at, Var):
raise TypeError(f"cannot access record field using {type(exp.at).__name__}, expected a field name")
if exp.at.name not in obj.data:
raise NameError(f"no assignment to {exp.at.name} found in record")
return obj.data[exp.at.name]
elif isinstance(obj, List):
access_at = eval_exp(env, exp.at)
if not isinstance(access_at, Int):
raise TypeError(f"cannot index into list using type {type(access_at).__name__}, expected integer")
if access_at.value < 0 or access_at.value >= len(obj.items):
raise ValueError(f"index {access_at.value} out of bounds for list")
return obj.items[access_at.value]
raise TypeError(f"attempted to access from type {type(obj).__name__}")
if isinstance(exp, Compose):
clo_inner = eval_exp(env, exp.inner)
clo_outer = eval_exp(env, exp.outer)
return Closure({}, Function(Var("x"), Apply(clo_outer, Apply(clo_inner, Var("x")))))
raise NotImplementedError(f"eval_exp not implemented for {exp}")
if isinstance(callee.func, Function):
assert isinstance(callee.func.arg, Var)
new_env = {**callee.env, callee.func.arg.name: arg}
env = new_env
exp = callee.func.body
continue
elif isinstance(callee.func, MatchFunction):
# TODO(max): Implement MatchClosure
arg = eval_exp(env, exp.arg)
for case in callee.func.cases:
m = match(arg, case.pattern)
if m is None:
continue
env = {**env, **m}
exp = case.body
break
else:
raise MatchError("no matching cases")
continue
else:
raise TypeError(f"attempted to apply a non-function of type {type(callee.func).__name__}")
if isinstance(exp, Access):
obj = eval_exp(env, exp.obj)
if isinstance(obj, Record):
if not isinstance(exp.at, Var):
raise TypeError(f"cannot access record field using {type(exp.at).__name__}, expected a field name")
if exp.at.name not in obj.data:
raise NameError(f"no assignment to {exp.at.name} found in record")
return obj.data[exp.at.name]
elif isinstance(obj, List):
access_at = eval_exp(env, exp.at)
if not isinstance(access_at, Int):
raise TypeError(f"cannot index into list using type {type(access_at).__name__}, expected integer")
if access_at.value < 0 or access_at.value >= len(obj.items):
raise ValueError(f"index {access_at.value} out of bounds for list")
return obj.items[access_at.value]
raise TypeError(f"attempted to access from type {type(obj).__name__}")
if isinstance(exp, Compose):
clo_inner = eval_exp(env, exp.inner)
clo_outer = eval_exp(env, exp.outer)
return Closure({}, Function(Var("x"), Apply(clo_outer, Apply(clo_inner, Var("x")))))
raise NotImplementedError(f"eval_exp not implemented for {exp}")


def bencode(obj: object) -> bytes:
Expand Down