1 """This module implements the primitives of the Scheme language."""
6 from scheme_reader import Pair, nil
11 print("warning: could not import the turtle module.", file=sys.stderr)
13 class SchemeError(Exception):
14 """Exception indicating an error in a Scheme program."""
17 """Signifies an undefined value."""
21 okay = okay() # Assignment hides the okay class; there is only one instance
24 def __init__(self, body, env):
30 return "(delay {0})".format(str(self.body))
33 return "Promise({0}, {1})".format(repr(self.body), repr(self.env))
35 class LambdaProcedure:
36 """A procedure defined by a lambda expression or the complex define form."""
38 def __init__(self, formals, body, env):
39 """A procedure whose formal parameter list is FORMALS (a Scheme list),
40 whose body is the Scheme list BODY, and whose parent
41 environment is the Frame ENV. A lambda expression containing multiple
42 expressions, such as (lambda (x) (display x) (+ x 1)) can be handled by
43 using (begin (display x) (+ x 1)) as the body."""
44 self.formals = formals
49 return "(lambda {0} {1})".format(str(self.formals), ' '.join(str(b) for b in self.body))
52 args = (self.formals, self.body, self.env)
53 return "LambdaProcedure({0}, {1}, {2})".format(*(repr(a) for a in args))
56 """A procedure defined by a mu expression, which has dynamic scope.
67 def __init__(self, formals, body):
68 """A procedure whose formal parameter list is FORMALS (a Scheme list),
69 whose body is the single Scheme expression BODY. A mu expression
70 containing multiple expressions, such as (mu (x) (display x) (+ x 1))
71 can be handled by using (begin (display x) (+ x 1)) as the body."""
72 self.formals = formals
76 return "(mu {0} {1})".format(str(self.formals), str(self.body))
79 args = (self.formals, self.body)
80 return "MuProcedure({0}, {1})".format(*(repr(a) for a in args))
82 ########################
83 # Primitive Operations #
84 ########################
86 class PrimitiveProcedure:
87 """A Scheme procedure defined as a Python function."""
89 def __init__(self, fn, use_env=False):
91 self.use_env = use_env
96 return '#[primitive ' + self.name + ']'
101 def primitive(*names):
102 """An annotation to convert a Python function into a PrimitiveProcedure."""
104 proc = PrimitiveProcedure(fn)
107 _PRIMITIVES.append((name,proc))
111 def add_primitives(frame):
112 """Enter bindings in _PRIMITIVES into FRAME, an environment frame."""
113 for name, proc in _PRIMITIVES:
114 frame.define(name, proc)
116 def check_type(val, predicate, k, name):
117 """Returns VAL. Raises a SchemeError if not PREDICATE(VAL)
118 using "argument K of NAME" to describe the offending value."""
119 if not predicate(val):
120 msg = "argument {0} of {1} has wrong type ({2})"
121 raise SchemeError(msg.format(k, name, type(val).__name__))
124 @primitive("boolean?")
125 def scheme_booleanp(x):
126 return x is True or x is False
128 def scheme_true(val):
129 """All values in Scheme are true except False."""
130 return val is not False
132 def scheme_false(val):
133 """Only False is false in Scheme."""
138 return not scheme_true(x)
141 def scheme_eqp(x, y):
142 # This is a bit sloppy, we ought to be using 'is' for everything *except* numbers.
143 # But representing symbols as Python strings makes that a bit fuzzier.
144 if scheme_pairp(x) and scheme_pairp(y):
149 def scheme_equalp(x, y):
154 return isinstance(x, Pair)
162 """Return whether x is a well-formed list. Assumes no cycles."""
164 if not isinstance(x, Pair):
170 def scheme_length(x):
173 check_type(x, scheme_listp, 0, 'length')
177 def scheme_cons(x, y):
180 @primitive("car", "stream-car")
182 check_type(x, scheme_pairp, 0, 'car')
187 check_type(x, scheme_pairp, 0, 'cdr')
190 @primitive("promise?")
191 def scheme_promisep(x):
192 return isinstance(x, Promise)
196 check_type(x, scheme_promisep, 0, 'force')
199 @primitive("stream-cdr")
200 def scheme_stream_cdr(x):
201 return scheme_force(scheme_cdr(x))
205 def scheme_list(*vals):
207 for i in range(len(vals)-1, -1, -1):
208 result = Pair(vals[i], result)
212 def scheme_append(*vals):
216 for i in range(len(vals)-2, -1, -1):
219 check_type(v, scheme_pairp, i, "append")
220 r = p = Pair(v.first, result)
222 while scheme_pairp(v):
223 p.second = Pair(v.first, result)
229 @primitive("string?")
230 def scheme_stringp(x):
231 return isinstance(x, str) and x.startswith('"')
233 @primitive("symbol?")
234 def scheme_symbolp(x):
235 return isinstance(x, str) and not scheme_stringp(x)
237 @primitive("number?")
238 def scheme_numberp(x):
239 return isinstance(x, int) or isinstance(x, float)
241 @primitive("integer?")
242 def scheme_integerp(x):
243 return isinstance(x, int) or (scheme_numberp(x) and round(x) == x)
245 @primitive("procedure?")
246 def scheme_procedurep(x):
247 return isinstance(x, LambdaProcedure) or isinstance(x, PrimitiveProcedure)
249 def _check_nums(*vals):
250 """Check that all arguments in VALS are numbers."""
251 for i, v in enumerate(vals):
252 if not scheme_numberp(v):
253 msg = "operand {0} ({1}) is not a number"
254 raise SchemeError(msg.format(i, v))
256 def _arith(fn, init, vals):
257 """Perform the fn fneration on the number values of VALS, with INIT as
258 the value when VALS is empty. Returns the result as a Scheme value."""
268 def scheme_add(*vals):
269 return _arith(operator.add, 0, vals)
272 def scheme_sub(val0, *vals):
275 return _arith(operator.sub, val0, vals)
278 def scheme_mul(*vals):
279 return _arith(operator.mul, 1, vals)
282 def scheme_div(val0, val1):
284 return _arith(operator.truediv, val0, [val1])
285 except ZeroDivisionError as err:
286 raise SchemeError(err)
288 @primitive("quotient")
289 def scheme_quo(val0, val1):
291 return _arith(operator.floordiv, val0, [val1])
292 except ZeroDivisionError as err:
293 raise SchemeError(err)
295 @primitive("remainder")
296 def scheme_modulo(val0, val1):
298 return _arith(operator.mod, val0, [val1])
299 except ZeroDivisionError as err:
300 raise SchemeError(err)
303 def scheme_floor(val):
305 return math.floor(val)
308 def scheme_ceil(val):
310 return math.ceil(val)
312 def _numcomp(op, x, y):
318 return _numcomp(operator.eq, x, y)
322 return _numcomp(operator.lt, x, y)
326 return _numcomp(operator.gt, x, y)
330 return _numcomp(operator.le, x, y)
334 return _numcomp(operator.ge, x, y)
357 if scheme_booleanp(x):
359 if scheme_numberp(x):
361 if scheme_symbolp(x):
367 @primitive("display")
368 def scheme_display(val):
369 if scheme_stringp(val):
371 print(str(val), end="")
375 def scheme_print(val):
379 @primitive("newline")
380 def scheme_newline():
386 def scheme_error(msg = None):
387 msg = "" if msg is None else str(msg)
388 raise SchemeError(msg)