Skip to content
This repository was archived by the owner on Jan 4, 2025. It is now read-only.

Commit d9ef37a

Browse files
committed
skeleton repl that can eval self-evaling things, quote, and if.
1 parent 690d60b commit d9ef37a

File tree

4 files changed

+197
-3
lines changed

4 files changed

+197
-3
lines changed

eval.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Write this in pseudo-Lark. We have to take a different strategy from
2+
# Arc because we're not on top of a Lisp or Scheme, so roots comes first instead of in arc.arc.
3+
from roots import *
4+
from sexpr import str2sexpr
5+
from symbol import *
6+
from types import *
7+
8+
xcar = lambda x: car(x) if acons(x) else None
9+
10+
# Let's not let them poke at raw Python variables without explicitly
11+
# them.
12+
13+
arc_globals = {nil: nil, ac_global_name(t): t}
14+
15+
# Evaluate an Arc expression, represented as an s-expression.
16+
# Env is a dictionary of lexically-bound variables (symbol->value).
17+
def ac_eval(s, env):
18+
if isinstance(s, String):
19+
return s
20+
elif literal(s):
21+
return s
22+
elif s is nil:
23+
return s
24+
elif isSymbol(s):
25+
return ac_var_ref(s, env)
26+
elif xcar(s) == Symbol('quote'):
27+
return cadr(s)
28+
elif xcar(s) == Symbol('if'):
29+
return ac_if(cdr(s), env)
30+
# elif xcar(s) == Symbol('fn'):
31+
# return ac_fn(cadr(s), cddr(s), env)
32+
# elif xcar(s) == Symbol('assign'):
33+
# return ac_set(cdr(s), env)
34+
# elif acons(s):
35+
# return ac_call(car(s), cdr(s), env)
36+
else:
37+
raise Exception('Bad object in expression %s (type %s)' % (s, type(s)))
38+
39+
40+
def literal(x):
41+
return isinstance(x, bool) or isinstance(x, int) or isinstance(x, float)
42+
43+
44+
def ac_global_name(s):
45+
return Symbol('_' + str(s))
46+
47+
48+
def ac_var_ref(s, env):
49+
if s in env:
50+
return env[s]
51+
return arc_globals[ac_global_name(s)]
52+
53+
54+
# Should False Python objects be false eventually?
55+
# We're not even close to exposing Python right now, I think.
56+
def ar_false(x):
57+
return x is nil or x == False
58+
59+
def ac_if(s, env):
60+
while s is not nil:
61+
if cdr(s) is nil:
62+
return ac_eval(car(s), env)
63+
elif not ar_false(ac_eval(car(s), env)):
64+
return ac_eval(cadr(s), env)
65+
s = cddr(s)
66+
67+
return nil
68+
69+
def tle():
70+
while True:
71+
print 'Lark> ',
72+
expr = str2sexpr(raw_input())[0]
73+
print ac_eval(expr, {})

roots.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
'''Roots of Lisp primitives in Python.
22
Let's see how far we can get embedding an Arc-like Lisp in Python.
33
'''
4+
from symbol import *
45

56
# We can't use tuples here because we need mutable cons cells.
67
# Objects are big and slow.
78
cons = lambda x, y: [x,y]
89
car = lambda x: x[0]
910
cdr = lambda x: x[1]
1011

11-
nil = None
12+
caar = lambda x: x[0][0]
13+
cadr = lambda x: x[1][0] # car of cdr.
14+
cdar = lambda x: x[0][1]
15+
cddr = lambda x: x[1][1]
16+
17+
pylist = list
18+
1219
# Atom is a problem: we're going to expose Python 2-tuples in our Lisp
1320
# eventually, and those are probably atoms. Let's just get it working
1421
# and then we'll worry about the distinction; the associated car/cdr
1522
# implementations are bad too.
16-
atom = lambda x: not (isinstance(x, tuple) and len(x) == 2)
23+
acons = lambda x: isinstance(x, pylist) and len(x) == 2
24+
atom = lambda x: not acons(x)
1725

1826
# Readable types:
1927
# - lists
@@ -23,7 +31,7 @@
2331

2432
def _topylist(L):
2533
acc = []
26-
while L is not None:
34+
while L is not nil:
2735
acc.append(car(L))
2836
L = cdr(L)
2937
return acc

symbol.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""A simple "symbol" class for Python. Symbols are like strings,
2+
except that symbols should only be comparable to other symbols. This
3+
module is designed so that one should be able to do a "from symbol
4+
import *" on this.
5+
6+
To create a new Symbol, use the Symbol() function, like this:
7+
8+
hello = Symbol("hello")
9+
10+
All symbols are interned so that comparison using 'is' will work. For
11+
example,
12+
13+
Symbol("hello") is Symbol("h" + "ello")
14+
15+
should work.
16+
17+
By default, all symbols are case insensitive, but if the module
18+
variable CASE_SENSITIVE is set to a true value, symbols will become
19+
case sensitive.
20+
21+
Also, we use the UserString to make sure that symbols are of a
22+
different "class" than Strings. isSymbol() depends on this
23+
representation, so be careful about changing it.
24+
25+
26+
Ripped off from PyScheme by Scott Wolchok.
27+
"""
28+
29+
__license__ = "MIT License"
30+
31+
32+
from UserString import UserString as __UserString
33+
import weakref
34+
35+
__all__ = ['Symbol', 'isSymbol', 'nil', 't']
36+
37+
38+
"""By default, symbols are case-insensitive."""
39+
CASE_SENSITIVE = 0
40+
41+
42+
class __Symbol(__UserString):
43+
"""Support class for symbols."""
44+
def __eq__(self, other):
45+
"""Comparison should only be possible between symbols. Since all
46+
Symbols are interned, this is equivalent to an 'is' check."""
47+
return self is other
48+
49+
50+
"""A global dictionary that contains all known symbols.
51+
__interned_symbols: strings -> symbols
52+
53+
The dictionary uses weak references to reduce the chance of symbol abuse.
54+
"""
55+
__interned_symbols = weakref.WeakValueDictionary({})
56+
57+
58+
def Symbol(s):
59+
"""Generates a new Symbol that we guarantee can be compared in
60+
constant time to any other symbol."""
61+
global __interned_symbols
62+
63+
## Defensive programming measure:
64+
assert not isinstance(s, __Symbol), ("%s already a symbol!" % s)
65+
66+
if not CASE_SENSITIVE: s = s.lower()
67+
if not __interned_symbols.has_key(s):
68+
## Subtle note: we have to use a local variable here to store the newSymbol.
69+
## If we had tried something shorter like:
70+
##
71+
## __interned_symbol[s] = __Symbol(s)
72+
##
73+
## then the new Symbol will immediately evaporate since there
74+
## aren't any hard references! Weak references can be tricky.
75+
newSymbol = __Symbol(s)
76+
__interned_symbols[s] = newSymbol
77+
return __interned_symbols[s]
78+
79+
80+
81+
82+
"""Here are definitions of symbols that we should know about."""
83+
false = Symbol("#f")
84+
true = Symbol("#t")
85+
nil = Symbol('nil')
86+
t = Symbol('t')
87+
__empty_symbol = Symbol("")
88+
89+
90+
def isSymbol(x):
91+
"""Returns True if x is already a symbol."""
92+
return type(x) == type(__empty_symbol)
93+
94+
95+
96+
def makeUniqueTemporary(_counter = [0]):
97+
"""Constructs a symbol that does not collide with any other
98+
symbol. I'll use this to help with the macro-expansion.
99+
100+
NOTE/FIXME: we do this by making an "illegal" symbol which starts
101+
with a number and contains a space. The parser module doesn't
102+
allow such symbols to exist... although it's possible to subvert this
103+
by calling STRING->SYMBOL. So this mechanism is not perfect.
104+
"""
105+
while ('%d*** temporary' % _counter[0]) in __interned_symbols:
106+
_counter[0] += 1
107+
return Symbol('%d*** temporary' % _counter[0])

types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import UserString
2+
3+
__all__ = ['String']
4+
# XXX: something more efficient, perhaps?
5+
class String(UserString.MutableString):
6+
pass

0 commit comments

Comments
 (0)