⇦
2022-04-29
The smallest readable interpreter I could come up with that handles integers, assignments, lexical scope, first class functions.
class Symbol(str): ...
Node = int | Symbol | list["Node"]
def is_whitespace(c: str) -> bool: return c in " \n"
def is_number(c: str) -> bool: return c.isdigit()
def is_symbol(c: str) -> bool: return c not in " \n()"
i = 0
def parse(source: str) -> Node:
global i
def gobble(f: Callable[[str], bool]) -> str:
global i
value = ""
while f(source[i]):
value += source[i]
i += 1
return value
gobble(is_whitespace)
if is_number(source[i]):
return int(gobble(is_number))
if is_symbol(source[i]):
return Symbol(gobble(is_symbol))
if source[i] == "(":
i += 1 # gobble (
items = []
while source[i] != ")":
items.append(parse(source))
gobble(is_whitespace)
i += 1 # gobble )
return items
raise RuntimeError("Unknown character")
from typing import Callable
Value = int | Callable[..., int]
Scope = dict[Symbol, Value]
def block(scope: Scope, *nodes: Node) -> Value:
local_scope = scope.copy()
values = [interpret(local_scope, child) for child in nodes]
return values[-1] # blocks evaluate to the final value
def assign(scope: Scope, *nodes: Node) -> Value:
symbol, node = nodes
value = interpret(scope, node)
scope[symbol] = value
return value
def function(scope: Scope, *nodes: Node) -> Value:
local_scope = scope.copy()
*vars, node = nodes
return lambda *args: interpret(local_scope | dict(zip(vars, args)), node)
macros = {
Symbol("{"): block,
Symbol("="): assign,
Symbol("=>"): function,
}
builtins = {
Symbol("print"): lambda *args: print(*args),
Symbol("-"): lambda a: -a,
Symbol("*"): lambda a, b: a * b,
Symbol("+"): lambda a, b: a + b,
}
def interpret(scope: Scope, node: Node) -> Value:
if isinstance(node, int):
return node
if isinstance(node, Symbol):
return scope[node]
f, *args = node
if f in macros:
return macros[f](scope, *args)
f, *args = [interpret(scope, child) for child in node]
return f(*args)
Let's run it:
source = """
({
(= make-adder
(=> a (=> b (+ a b))))
(= add-one (make-adder 1))
(print (add-one 3))
(= x 5)
(= f
(=> a b ({
(= c (+ a b))
(* (- c) x)
))
)
(print (f 1 3))
)
"""
interpret(builtins, parse(source))
Should give you:
4
-20
Heads up - the typing is a bit all over the shop and doesn't actually typecheck..