1 """This module implements the core Scheme interpreter functions, including the
2 eval/apply mutual recurrence, environment model, and read-eval-print loop.
5 if __name__ == '__main__':
8 from .scheme_primitives import *
9 from .scheme_reader import *
10 from .ucb import main, trace
18 def scheme_eval(expr, env):
19 """Evaluate Scheme expression EXPR in environment ENV.
21 >>> expr = read_line("(+ 2 2)")
23 Pair('+', Pair(2, Pair(2, nil)))
24 >>> scheme_eval(expr, create_global_frame())
28 raise SchemeError("Cannot evaluate an undefined expression.")
31 if scheme_symbolp(expr):
32 return env.lookup(expr)
33 elif scheme_atomp(expr) or scheme_stringp(expr) or expr is okay:
36 # All non-atomic expressions are lists.
37 if not scheme_listp(expr):
38 raise SchemeError("malformed list: {0}".format(str(expr)))
39 first, rest = expr.first, expr.second
41 procedure = scheme_eval(first, env)
43 # Evaluate Combinations
44 if isinstance(procedure, SpecialForm):
45 return procedure.impl(rest, env)
47 args = rest.map(lambda operand: scheme_eval(operand, env))
48 return scheme_apply(procedure, args, env)
51 def scheme_apply(procedure, args, env):
52 """Apply Scheme PROCEDURE to argument values ARGS in environment ENV."""
53 if isinstance(procedure, PrimitiveProcedure):
54 return apply_primitive(procedure, args, env)
55 elif isinstance(procedure, LambdaProcedure):
56 frame = procedure.env.make_call_frame(procedure.formals, args)
57 return scheme_eval(do_begin_form(procedure.body, frame), frame)
58 elif isinstance(procedure, MuProcedure):
59 raise SchemeError("not part of this project")
61 raise SchemeError("Cannot call {0}".format(str(procedure)))
63 def apply_primitive(procedure, args, env):
64 """Apply PrimitiveProcedure PROCEDURE to a Scheme list of ARGS in ENV.
66 >>> env = create_global_frame()
67 >>> plus = env.bindings["+"]
68 >>> twos = Pair(2, Pair(2, nil))
69 >>> apply_primitive(plus, twos, env)
73 args = itertools.chain(args, (env,))
75 return procedure.fn(*args)
77 raise SchemeError("invalid call to " + (procedure.name or str(procedure)))
84 """An environment frame binds Scheme symbols to Scheme values."""
86 def __init__(self, parent):
87 """An empty frame with a PARENT frame (that may be None)."""
92 if self.parent is None:
93 return "<Global Frame>"
95 s = sorted('{0}: {1}'.format(k,v) for k,v in self.bindings.items())
96 return "<{{{0}}} -> {1}>".format(', '.join(s), repr(self.parent))
98 def lookup(self, symbol):
99 """Return the value bound to SYMBOL. Errors if SYMBOL is not found."""
100 if symbol in self.bindings:
101 return self.bindings[symbol]
102 if self.parent is not None:
103 return self.parent.lookup(symbol)
104 raise SchemeError("unknown identifier: {0}".format(str(symbol)))
107 def global_frame(self):
108 """The global environment at the root of the parent chain."""
110 while e.parent is not None:
114 def make_call_frame(self, formals, vals):
115 """Return a new local frame whose parent is SELF, in which the symbols
116 in the Scheme formal parameter list FORMALS are bound to the Scheme
117 values in the Scheme value list VALS. Raise an error if too many or too
118 few arguments are given.
120 >>> env = create_global_frame()
121 >>> formals, vals = read_line("(a b c)"), read_line("(1 2 3)")
122 >>> env.make_call_frame(formals, vals)
123 <{a: 1, b: 2, c: 3} -> <Global Frame>>
126 while formals != nil:
127 if not isinstance(formals, Pair):
128 frame.bindings[formals] = vals
131 frame.bindings[formals.first] = vals.first
132 formals = formals.second
136 def define(self, sym, val):
137 """Define Scheme symbol SYM to have value VAL in SELF."""
138 self.bindings[sym] = val
141 def _force_promise(self):
142 if self.value is None:
143 self.value = scheme_eval(self.body, self.env)
146 Promise.force = _force_promise
153 def __init__(self, name, impl):
158 return '#[form ' + self.name + ']'
160 class LogicForm(SpecialForm):
161 def __init__(self, name, impl):
162 super().__init__(name, lambda vals, env: scheme_eval(impl(vals, env), env))
164 def do_lambda_form(vals, env):
165 """Evaluate a lambda form with parameters VALS in environment ENV."""
168 check_formals(formals)
169 return LambdaProcedure(formals, vals.second, env)
171 def do_mu_form(vals, env):
172 """Evaluate a mu form with parameters VALS."""
175 check_formals(formals)
176 "*** YOUR CODE HERE ***"
178 def do_define_form(vals, env):
179 """Evaluate a define form with parameters VALS in environment ENV."""
182 if scheme_symbolp(target):
183 check_form(vals, 2, 2)
184 value = scheme_eval(vals[1], env)
185 elif isinstance(target, Pair) and scheme_symbolp(target.first):
186 (target, args) = (target.first, target.second)
187 value = do_lambda_form(Pair(args, vals.second), env)
189 raise SchemeError("bad argument to define")
190 if target.startswith('#'):
191 raise SchemeError("can't override builtins starting with '#'")
192 env.define(target, value)
195 def do_quote_form(vals, env):
196 """Evaluate a quote form with parameters VALS."""
197 check_form(vals, 1, 1)
201 def do_let_form(vals, env):
202 """Evaluate a let form with parameters VALS in environment ENV."""
206 if not scheme_listp(bindings):
207 raise SchemeError("bad bindings list in let form")
209 # Add a frame containing bindings
210 names, values = nil, nil
211 while bindings is not nil:
212 next_pair = bindings.first
213 check_form(next_pair, 2, 2)
214 if not scheme_symbolp(next_pair[0]):
215 raise SchemeError("bad name in bindings list in let")
216 names = Pair(next_pair[0], names)
217 values = Pair(scheme_eval(next_pair[1], env), values)
218 bindings = bindings.second
219 new_env = env.make_call_frame(names, values)
221 # Evaluate all but the last expression after bindings, and return the last
223 for i in range(0, last):
224 scheme_eval(exprs[i], new_env)
225 return exprs[last], new_env
228 def do_delay_form(vals, env):
229 check_form(vals, 1, 1)
230 return Promise(vals[0], env)
233 def do_cons_stream_form(vals, env):
234 check_form(vals, 2, 2)
235 first = scheme_eval(vals[0], env)
236 rest = do_delay_form(vals.second, env)
237 return Pair(first, rest)
240 #########################
241 # Logical Special Forms #
242 #########################
244 def do_if_form(vals, env):
245 """Evaluate if form with parameters VALS in environment ENV."""
246 check_form(vals, 2, 3)
247 if scheme_true(scheme_eval(vals[0], env)):
254 def do_and_form(vals, env):
255 """Evaluate short-circuited and with parameters VALS in environment ENV."""
259 first = scheme_eval(vals.first, env)
260 if scheme_false(first) or vals.second is nil:
262 return do_and_form(vals.second, env)
265 """Return a Scheme expression quoting the Scheme VALUE.
267 >>> s = quote('hello')
270 >>> scheme_eval(s, Frame(None)) # "hello" is undefined in this frame.
273 return Pair("quote", Pair(value, nil))
275 def do_or_form(vals, env):
276 """Evaluate short-circuited or with parameters VALS in environment ENV."""
280 first = scheme_eval(vals.first, env)
281 if scheme_true(first):
283 return do_or_form(vals.second, env)
285 def do_cond_form(vals, env):
286 """Evaluate cond form with parameters VALS in environment ENV."""
287 num_clauses = len(vals)
288 for i, clause in enumerate(vals):
289 check_form(clause, 1)
290 if clause.first == "else":
291 if i < num_clauses-1:
292 raise SchemeError("else must be last")
294 if clause.second is nil:
295 raise SchemeError("badly formed else clause")
297 test = scheme_eval(clause.first, env)
298 if scheme_true(test):
299 if clause.second is nil:
301 return Pair("begin", clause.second)
304 def do_begin_form(vals, env):
305 """Evaluate begin form with parameters VALS in environment ENV."""
308 for i in range(0, last):
309 scheme_eval(vals[i], env)
316 "cond": do_cond_form,
317 "begin": do_begin_form,
320 # Utility methods for checking the structure of Scheme programs
322 def check_form(expr, min, max = None):
323 """Check EXPR (default SELF.expr) is a proper list whose length is
324 at least MIN and no more than MAX (default: no maximum). Raises
325 a SchemeError if this is not the case."""
326 if not scheme_listp(expr):
327 raise SchemeError("badly formed expression: " + str(expr))
330 raise SchemeError("too few operands in form: " + str(expr))
331 elif max is not None and length > max:
332 raise SchemeError("too many operands in form: " + str(expr))
334 def check_formals(formals):
335 """Check that FORMALS is a valid parameter list, a Scheme list of symbols
336 in which each symbol is distinct. Raise a SchemeError if the list of formals
337 is not a well-formed list of symbols or if any symbol is repeated.
339 >>> check_formals(read_line("(a b c)"))
341 if scheme_symbolp(formals):
342 # "Dotted-tail notation" with no positional params.
346 original_formals = formals
347 while formals is not nil:
348 if not isinstance(formals, Pair) or not scheme_symbolp(formals.first):
349 raise SchemeError("invalid argument list: " + str(original_formals))
350 if formals.first in idents:
351 raise SchemeError("parameter '" + formals.first + "' appears multiple times")
352 idents.add(formals.first)
353 if scheme_symbolp(formals.second):
354 if formals.second in idents:
355 raise SchemeError("parameter '" + formals.second + "' appears multiple times")
357 formals = formals.second
363 def scheme_optimized_eval(expr, env):
364 """Evaluate Scheme expression EXPR in environment ENV."""
367 raise SchemeError("Cannot evaluate an undefined expression.")
370 if scheme_symbolp(expr):
371 return env.lookup(expr)
372 elif scheme_atomp(expr) or scheme_stringp(expr) or expr is okay:
375 # All non-atomic expressions are lists.
376 if not scheme_listp(expr):
377 raise SchemeError("malformed list: {0}".format(str(expr)))
378 first, rest = expr.first, expr.second
380 # Evaluate Combinations
381 if (scheme_symbolp(first) # first might be unhashable
382 and first in LOGIC_FORMS):
383 "*** YOUR CODE HERE ***"
384 elif first == "lambda":
385 return do_lambda_form(rest, env)
387 return do_mu_form(rest)
388 elif first == "define":
389 return do_define_form(rest, env)
390 elif first == "quote":
391 return do_quote_form(rest)
393 "*** YOUR CODE HERE ***"
395 "*** YOUR CODE HERE ***"
397 ################################################################
398 # Uncomment the following line to apply tail call optimization #
399 ################################################################
400 # scheme_eval = scheme_optimized_eval
407 def scheme_format(value):
414 def read_eval_print_loop(next_line, env, quiet=False, startup=False,
415 interactive=False, load_files=()):
416 """Read and evaluate input until an end of file or keyboard interrupt."""
418 for filename in load_files:
419 scheme_load(filename, True, env)
423 while src.more_on_line:
424 expression = scheme_read(src)
425 result = scheme_eval(expression, env)
426 if not quiet and result is not None:
427 print(scheme_format(result))
428 except (SchemeError, SyntaxError, ValueError, RuntimeError) as err:
429 if (isinstance(err, RuntimeError) and
430 'maximum recursion depth exceeded' not in err.args[0]):
433 except KeyboardInterrupt: # <Control>-C
436 print("\nKeyboardInterrupt")
439 except EOFError: # <Control>-D, etc.
443 def scheme_load(*args):
444 """Load a Scheme source file. ARGS should be of the form (SYM, ENV) or (SYM,
445 QUIET, ENV). The file named SYM is loaded in environment ENV, with verbosity
446 determined by QUIET (default true)."""
447 if not (2 <= len(args) <= 3):
449 raise SchemeError("wrong number of arguments to load: {0}".format(vals))
451 quiet = args[1] if len(args) > 2 else True
453 if (scheme_stringp(sym)):
455 check_type(sym, scheme_symbolp, 0, "load")
456 with scheme_open(sym) as infile:
457 lines = infile.readlines()
458 args = (lines, None) if quiet else (lines,)
460 return buffer_lines(*args)
461 read_eval_print_loop(next_line, env.global_frame(), quiet=quiet)
464 def scheme_open(filename):
465 """If either FILENAME or FILENAME.scm is the name of a valid file,
466 return a Python file opened to it. Otherwise, raise an error."""
468 return open(filename)
469 except IOError as exc:
470 if filename.endswith('.scm'):
471 raise SchemeError(str(exc))
473 return open(filename + '.scm')
474 except IOError as exc:
475 raise SchemeError(str(exc))
477 def create_global_frame():
478 """Initialize and return a single-frame environment with built-in names."""
480 env.define("nil", nil)
481 env.define("eval", PrimitiveProcedure(scheme_eval, True))
482 env.define("apply", PrimitiveProcedure(scheme_apply, True))
483 env.define("load", PrimitiveProcedure(scheme_load, True))
487 env.define("define", SpecialForm("define", do_define_form))
488 env.define("lambda", SpecialForm("lambda", do_lambda_form))
489 env.define("quote", SpecialForm("quote", do_quote_form))
490 env.define("let", SpecialForm("let", lambda args, env: scheme_eval(*do_let_form(args, env))))
492 env.define("delay", SpecialForm("delay", do_delay_form))
493 env.define("cons-stream", SpecialForm("cons-stream", do_cons_stream_form))
495 env.define("if", LogicForm("if", do_if_form))
496 env.define("cond", LogicForm("cond", do_cond_form))
497 env.define("or", LogicForm("or", do_or_form))
498 env.define("and", LogicForm("and", do_and_form))
499 env.define("begin", LogicForm("begin", do_begin_form))
505 next_line = buffer_input
511 if filename == '-load':
512 load_files = argv[1:]
514 input_file = open(argv[0])
515 lines = input_file.readlines()
517 return buffer_lines(lines)
519 except IOError as err:
522 read_eval_print_loop(next_line, create_global_frame(), startup=True,
523 interactive=interactive, load_files=load_files)