Created
June 23, 2025 11:26
-
-
Save vollmerm/9c6005b66400ba8c2b504d2f1ee09ba3 to your computer and use it in GitHub Desktop.
An example of a CPS interpreter that supports exceptions via throw and catch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Closure: | |
def __init__(self, params, body, env): | |
self.params = params | |
self.body = body | |
self.env = env | |
def __repr__(self): | |
return f"<Closure {self.params}>" | |
class Primitive: | |
def __init__(self, name, func): | |
self.name = name | |
self.func = func | |
def __repr__(self): | |
return f"<Primitive {self.name}>" | |
class Env: | |
def __init__(self, parent=None): | |
self.parent = parent | |
self.bindings = {} | |
def extend(self, names, values): | |
new_env = Env(self) | |
for name, value in zip(names, values): | |
new_env.bindings[name] = value | |
return new_env | |
def lookup(self, name): | |
if name in self.bindings: | |
return self.bindings[name] | |
elif self.parent: | |
return self.parent.lookup(name) | |
else: | |
raise NameError(f"Variable not found: {name}") | |
def eval(expr, env, cont, exn_cont): | |
if isinstance(expr, (int, bool)): | |
cont(expr, exn_cont) | |
elif isinstance(expr, str): | |
try: | |
value = env.lookup(expr) | |
cont(value, exn_cont) | |
except NameError as e: | |
exn_cont(str(e)) | |
elif isinstance(expr, list): | |
if expr[0] == 'lambda': | |
params = expr[1] | |
body = expr[2] | |
cont(Closure(params, body, env), exn_cont) | |
elif expr[0] == 'if': | |
test_expr = expr[1] | |
then_expr = expr[2] | |
else_expr = expr[3] | |
eval(test_expr, env, | |
lambda test_val, exn_cont1: | |
eval(then_expr if test_val else else_expr, env, cont, exn_cont1), | |
exn_cont) | |
elif expr[0] == 'throw': | |
eval(expr[1], env, | |
lambda exc_val, exn_cont1: exn_cont1(exc_val), | |
exn_cont) | |
elif expr[0] == 'catch': | |
try_expr = expr[1] | |
var = expr[3][0] | |
handler_expr = expr[4] | |
def new_exn_cont(exc_val): | |
new_env = env.extend([var], [exc_val]) | |
eval(handler_expr, new_env, cont, exn_cont) | |
eval(try_expr, env, cont, new_exn_cont) | |
else: | |
eval_application(expr, env, cont, exn_cont) | |
else: | |
exn_cont(f"Invalid expression: {expr}") | |
def eval_application(expr_list, env, cont, exn_cont): | |
def eval_func(func, func_exn_cont): | |
def eval_args(args, args_exn_cont): | |
apply_function(func, args, cont, args_exn_cont) | |
eval_expr_list(expr_list[1:], env, eval_args, func_exn_cont) | |
eval(expr_list[0], env, eval_func, exn_cont) | |
def eval_expr_list(exprs, env, cont, exn_cont): | |
if not exprs: | |
cont([], exn_cont) | |
else: | |
def eval_rest(first_val, exn_cont1): | |
def handle_rest(rest_vals, exn_cont2): | |
cont([first_val] + rest_vals, exn_cont2) | |
eval_expr_list(exprs[1:], env, handle_rest, exn_cont1) | |
eval(exprs[0], env, eval_rest, exn_cont) | |
def apply_function(func, args, cont, exn_cont): | |
if isinstance(func, Closure): | |
if len(func.params) != len(args): | |
exn_cont(f"Argument count mismatch: expected {len(func.params)}, got {len(args)}") | |
else: | |
new_env = func.env.extend(func.params, args) | |
eval(func.body, new_env, cont, exn_cont) | |
elif isinstance(func, Primitive): | |
try: | |
result = func.func(*args) | |
cont(result, exn_cont) | |
except Exception as e: | |
exn_cont(str(e)) | |
else: | |
exn_cont(f"Not callable: {func}") | |
def initial_env(): | |
env = Env() | |
primitives = { | |
'+': lambda x, y: x + y, | |
'-': lambda x, y: x - y, | |
'*': lambda x, y: x * y, | |
'/': lambda x, y: x / y, | |
'=': lambda x, y: x == y, | |
'<': lambda x, y: x < y, | |
'>': lambda x, y: x > y, | |
'and': lambda x, y: x and y, | |
'or': lambda x, y: x or y, | |
'not': lambda x: not x | |
} | |
for name, func in primitives.items(): | |
env.bindings[name] = Primitive(name, func) | |
return env | |
def initial_cont(result, exn_cont): | |
print("Result:", result) | |
def initial_exn_cont(error): | |
print("Uncaught exception:", error) | |
def interpret(program): | |
env = initial_env() | |
eval(program, env, initial_cont, initial_exn_cont) | |
# Example Usage | |
if __name__ == "__main__": | |
# Example 1: Simple arithmetic | |
interpret(['*', 3, ['+', 2, 4]]) | |
# Example 2: Conditional with boolean | |
interpret(['if', ['and', True, ['<', 3, 5]], 10, 20]) | |
# Example 3: Lambda application | |
interpret([['lambda', ['x', 'y'], ['+', 'x', 'y']], 5, 7]) | |
# Example 4: Exception handling | |
interpret([ | |
'catch', | |
['throw', 42], | |
'with', ['e'], | |
['*', 'e', 2] | |
]) | |
# Example 5: Nested exceptions | |
interpret([ | |
'catch', | |
['+', 1, | |
['catch', | |
['throw', 10], | |
'with', ['x'], | |
['throw', ['*', 'x', 2]] | |
] | |
], | |
'with', ['y'], | |
['+', 'y', 100] | |
]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment