Skip to content

Commit 167e11c

Browse files
committed
Update interpreter
1 parent 5f70820 commit 167e11c

File tree

2 files changed

+68
-81
lines changed

2 files changed

+68
-81
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
<li>Sudoku <a href="constraint-sudoku.html">solving</a> using a custom backtracking constraint solver</li>
7777
<li><a href="lambda-calculus.html">Lambda calculus</a> in modern JavaScript</li>
7878
<li>Reach OO/closure <a href="poor-mans-object.html">nirvana</a></li>
79-
<li>87 line <a href="interpreter.html">interpreter</a> with integers, lexical scope, first class functions</li>
79+
<li>80 line <a href="interpreter.html">interpreter</a> with integers, lexical scope, first class functions</li>
8080
<li>Weenie <a href="weenie-lisp.html">LISP</a> parser</li>
8181
<li>Then a smol <a href="pratt-example.html">Pratt parser</a></li>
8282
</ul>

interpreter.html

Lines changed: 67 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -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) -&gt; <span class="hljs-keyword">Node</span><span class="hljs-title">:
62-
global</span> i
63-
def gobble(f: Callable[[str], bool]) -&gt; 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">) -&gt; 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">) -&gt; 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">) -&gt; 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">"=&gt;"</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>) -&gt; 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&#39;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">#=&gt; (a) (#=&gt; (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">#=&gt; (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

Comments
 (0)