@@ -35,114 +35,101 @@ <h1>Smol interpreter</h1>
3535
3636
3737< p > The smallest readable interpreter I could come up with that handles integers, assignments, lexical scope, first class functions.</ p >
38- < pre > < code class ="lang-python "> from __future__ import annotations
39- from dataclasses import dataclass
40- from typing import Callable, Union
41-
42- @dataclass(< span class ="hljs-attr "> frozen=</ span > < span class ="hljs-literal "> True</ span > )
43- class Symbol:
44- value: str
45-
46- @dataclass
47- class List:
48- is_macro: bool
49- values: list[< span class ="hljs-keyword "> Node</ span > < span class ="hljs-title "> ]
50-
51- Atom</ span > = int
52- < span class ="hljs-keyword "> Node</ span > < span class ="hljs-title "> = Union</ span > [Atom, Symbol, List]
53- Value = Union[int, < span class ="hljs-keyword "> Node</ span > < span class ="hljs-title "> , Callable</ span > [..., int], None]
54- Scope = dict[Symbol, Value]
38+ < pre > < code class ="lang-python "> class Symbol(str): ...
39+ Node = int | Symbol | list["Node"]
5540
56- is_whitespace = lambda c: c < span class =" hljs-keyword " > in </ span > < span class =" hljs-string " > " \n"</ span >
57- is_number = lambda c: c.isdigit()
58- is_symbol = lambda c: c not < span class =" hljs-keyword " > in </ span > < span class =" hljs-string " > " \n()"</ span >
41+ def is_whitespace(c: str) - > bool: return c in " \n"
42+ def is_number(c: str) - > bool: return c.isdigit()
43+ def is_symbol(c: str) - > bool: return c not in " \n()"
5944
60- i = < span class =" hljs-number " > 0 </ span >
61- def parse(source: str) -> < span class =" hljs-keyword " > Node </ span > < span class =" hljs-title " > :
62- global</ span > i
63- def gobble(f: Callable[[str], bool]) -> str:
45+ i = 0
46+ def parse(source: str) -> Node :
47+ global i
48+ def gobble(f: Callable[[str], bool]) -> str:
6449 global i
65- value = < span class =" hljs-string " > "" </ span >
50+ value = ""
6651 while f(source[i]):
6752 value += source[i]
68- i += < span class =" hljs-number " > 1 </ span >
53+ i += 1
6954 return value
7055
7156 gobble(is_whitespace)
7257 if is_number(source[i]):
7358 return int(gobble(is_number))
7459 if is_symbol(source[i]):
7560 return Symbol(gobble(is_symbol))
76- if source[i] == < span class ="hljs-string "> "("</ span > :
77- i += < span class ="hljs-number "> 1</ span > < span class ="hljs-comment "> # gobble (</ span >
78- is_macro = source[i] == < span class ="hljs-string "> "#"</ span >
79- if is_macro:
80- i += < span class ="hljs-number "> 1</ span > < span class ="hljs-comment "> # gobble #</ span >
81- items = List(is_macro, [])
82- while source[i] != < span class ="hljs-string "> ")"</ span > :
83- items.values.append(parse(source))
61+ if source[i] == "(":
62+ i += 1 # gobble (
63+ items = []
64+ while source[i] != ")":
65+ items.append(parse(source))
8466 gobble(is_whitespace)
85- i += < span class =" hljs-number " > 1 </ span > < span class =" hljs-comment " > # gobble )</ span >
67+ i += 1 # gobble )
8668 return items
87- raise RuntimeError(< span class ="hljs-string "> "Unknown character"</ span > )
69+ raise RuntimeError("Unknown character")
70+
71+ from typing import Callable
72+ Value = int | Callable[..., int]
73+ Scope = dict[Symbol, Value]
8874
89- def block(scope: Scope, *nodes: < span class =" hljs-keyword " > Node</ span > < span class =" hljs-title " > ) -> Value</ span > :
75+ def block(scope: Scope, *nodes: Node) - > Value:
9076 local_scope = scope.copy()
91- values = [interpret(local_scope, child) for child < span class =" hljs-keyword " > in </ span > nodes]
92- return values[-< span class =" hljs-number " > 1 </ span > ] < span class =" hljs-comment " > # blocks evaluate to the final value</ span >
77+ values = [interpret(local_scope, child) for child in nodes]
78+ return values[-1 ] # blocks evaluate to the final value
9379
94- def assign(scope: Scope, *nodes: < span class ="hljs-keyword "> Node</ span > < span class ="hljs-title "> ) -> Value</ span > :
95- symbol, < span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> = nodes</ span >
96- scope[symbol] = interpret(scope, < span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> )
97- return</ span > None
80+ def assign(scope: Scope, *nodes: Node) -> Value:
81+ symbol, node = nodes
82+ value = interpret(scope, node)
83+ scope[symbol] = value
84+ return value
9885
99- def function(scope: Scope, *nodes: < span class =" hljs-keyword " > Node</ span > < span class =" hljs-title " > ) -> Value</ span > :
86+ def function(scope: Scope, *nodes: Node) - > Value:
10087 local_scope = scope.copy()
101- vars, < span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> = nodes</ span >
102- return lambda *args: interpret(local_scope | dict(zip(vars.values, args)), < span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> )
103-
104- builtins</ span > = {
105- Symbol(< span class ="hljs-string "> "{"</ span > ): block,
106- Symbol(< span class ="hljs-string "> "="</ span > ): assign,
107- Symbol(< span class ="hljs-string "> "=>"</ span > ): function,
108- Symbol(< span class ="hljs-string "> "print"</ span > ): lambda *args: print(*args),
109- Symbol(< span class ="hljs-string "> "-"</ span > ): lambda a: -a,
110- Symbol(< span class ="hljs-string "> "*"</ span > ): lambda a, b: a * b,
111- Symbol(< span class ="hljs-string "> "+"</ span > ): lambda a, b: a + b,
88+ *vars, node = nodes
89+ return lambda *args: interpret(local_scope | dict(zip(vars, args)), node)
90+
91+ macros = {
92+ Symbol("{"): block,
93+ Symbol("="): assign,
94+ Symbol("=> "): function,
95+ }
96+ builtins = {
97+ Symbol("print"): lambda *args: print(*args),
98+ Symbol("-"): lambda a: -a,
99+ Symbol("*"): lambda a, b: a * b,
100+ Symbol("+"): lambda a, b: a + b,
112101}
113102
114- def interpret(scope: Scope, < span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> : Node</ span > ) -> Value:
115- if isinstance(< span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> , int</ span > ):
116- return < span class ="hljs-keyword "> node</ span >
117- < span class ="hljs-title "> if</ span > isinstance(< span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> , Symbol</ span > ):
118- return scope[< span class ="hljs-keyword "> node</ span > < span class ="hljs-title "> ]
119- if</ span > < span class ="hljs-keyword "> node</ span > .is_macro:< span class ="hljs-title ">
120- f</ span > , *args = < span class ="hljs-keyword "> node</ span > .< span class ="hljs-title "> values</ span >
121- f = interpret(scope, f)
122- return f(scope, *args)
123- f, *args = [interpret(scope, child) for child < span class ="hljs-keyword "> in</ span > < span class ="hljs-keyword "> node</ span > .< span class ="hljs-title "> values</ span > ]
103+ def interpret(scope: Scope, node: Node) -> Value:
104+ if isinstance(node, int):
105+ return node
106+ if isinstance(node, Symbol):
107+ return scope[node]
108+ f, *args = node
109+ if f in macros:
110+ return macros[f](scope, *args)
111+ f, *args = [interpret(scope, child) for child in node]
124112 return f(*args)
125113</ code > </ pre >
126114< p > Let's run it:</ p >
127- < pre > < code class ="lang-python "> source = < span class =" hljs-string " > "" </ span > < span class =" hljs-string " > "
128- (< span class =" hljs-subst " > # {
129- (< span class =" hljs-comment " > #= make-adder</ span >
130- (< span class =" hljs-comment " > #=> (a) (#=> (b) (+ a b))))</ span >
131- (< span class =" hljs-comment " > #= add-one (make-adder 1))</ span >
132- (print (add-one < span class =" hljs-number " > 3 </ span > ))
133-
134- (< span class =" hljs-comment " > #= x 5)</ span >
135- (< span class =" hljs-comment " > #= f </ span >
136- (< span class =" hljs-comment " > #=> ( a b) (#{ </ span >
137- (< span class =" hljs-comment " > #= c (+ a b))</ span >
115+ < pre > < code class ="lang-python "> source = "" "
116+ ({
117+ (= make-adder
118+ (= > a (= > b (+ a b))))
119+ (= add-one (make-adder 1))
120+ (print (add-one 3 ))
121+
122+ (= x 5)
123+ (= f
124+ (= > a b ({
125+ (= c (+ a b))
138126 (* (- c) x)
139127 ))
140128 )
141- (print (f < span class =" hljs-number " > 1 </ span > < span class =" hljs-number " > 3 </ span > ))
129+ (print (f 1 3 ))
142130)
143- < span class ="hljs-string "> ""</ span > < span class ="hljs-string "> "
144- interpret(builtins, parse(source))</ span > </ span > </ span >
145- </ code > </ pre >
131+ """
132+ interpret(builtins, parse(source))</ code > </ pre >
146133< p > Should give you:</ p >
147134< pre > < code > < span class ="hljs-number "> 4</ span >
148135< span class ="hljs-number "> -20</ span >
0 commit comments