Skip to content

Instantly share code, notes, and snippets.

@vollmerm
Created June 23, 2025 11:26
Show Gist options
  • Save vollmerm/9c6005b66400ba8c2b504d2f1ee09ba3 to your computer and use it in GitHub Desktop.
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
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