82764d271fc45392f0a27412d8b1326508978584
[sicpelago] / scheme.py
1 """This module implements the core Scheme interpreter functions, including the
2 eval/apply mutual recurrence, environment model, and read-eval-print loop.
3 """
4
5 if __name__ == '__main__':
6     __path__ = ["."]
7
8 from .scheme_primitives import *
9 from .scheme_reader import *
10 from .ucb import main, trace
11
12 import itertools
13
14 ##############
15 # Eval/Apply #
16 ##############
17
18 def scheme_eval(expr, env):
19     """Evaluate Scheme expression EXPR in environment ENV.
20
21     >>> expr = read_line("(+ 2 2)")
22     >>> expr
23     Pair('+', Pair(2, Pair(2, nil)))
24     >>> scheme_eval(expr, create_global_frame())
25     4
26     """
27     if expr is None:
28         raise SchemeError("Cannot evaluate an undefined expression.")
29
30     # Evaluate Atoms
31     if scheme_symbolp(expr):
32         return env.lookup(expr)
33     elif scheme_atomp(expr) or scheme_stringp(expr) or expr is okay:
34         return expr
35
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
40
41     procedure = scheme_eval(first, env)
42
43     # Evaluate Combinations
44     if isinstance(procedure, SpecialForm):
45         return procedure.impl(rest, env)
46     else:
47         args = rest.map(lambda operand: scheme_eval(operand, env))
48         return scheme_apply(procedure, args, env)
49
50
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")
60     else:
61         raise SchemeError("Cannot call {0}".format(str(procedure)))
62
63 def apply_primitive(procedure, args, env):
64     """Apply PrimitiveProcedure PROCEDURE to a Scheme list of ARGS in ENV.
65
66     >>> env = create_global_frame()
67     >>> plus = env.bindings["+"]
68     >>> twos = Pair(2, Pair(2, nil))
69     >>> apply_primitive(plus, twos, env)
70     4
71     """
72     if procedure.use_env:
73         args = itertools.chain(args, (env,))
74     try:
75         return procedure.fn(*args)
76     except TypeError:
77         raise SchemeError("invalid call to " + (procedure.name or str(procedure)))
78
79 ################
80 # Environments #
81 ################
82
83 class Frame:
84     """An environment frame binds Scheme symbols to Scheme values."""
85
86     def __init__(self, parent):
87         """An empty frame with a PARENT frame (that may be None)."""
88         self.bindings = {}
89         self.parent = parent
90
91     def __repr__(self):
92         if self.parent is None:
93             return "<Global Frame>"
94         else:
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))
97
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)))
105
106
107     def global_frame(self):
108         """The global environment at the root of the parent chain."""
109         e = self
110         while e.parent is not None:
111             e = e.parent
112         return e
113
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.
119
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>>
124         """
125         frame = Frame(self)
126         while formals != nil:
127             if not isinstance(formals, Pair):
128                 frame.bindings[formals] = vals
129                 vals = None
130                 break
131             frame.bindings[formals.first] = vals.first
132             formals = formals.second
133             vals = vals.second
134         return frame
135
136     def define(self, sym, val):
137         """Define Scheme symbol SYM to have value VAL in SELF."""
138         self.bindings[sym] = val
139
140
141 def _force_promise(self):
142     if self.value is None:
143         self.value = scheme_eval(self.body, self.env)
144     return self.value
145
146 Promise.force = _force_promise
147
148 #################
149 # Special forms #
150 #################
151
152 class SpecialForm:
153     def __init__(self, name, impl):
154         self.impl = impl
155         self.name = name
156
157     def __str__(self):
158         return '#[form ' + self.name + ']'
159
160 class LogicForm(SpecialForm):
161     def __init__(self, name, impl):
162         super().__init__(name, lambda vals, env: scheme_eval(impl(vals, env), env))
163
164 def do_lambda_form(vals, env):
165     """Evaluate a lambda form with parameters VALS in environment ENV."""
166     check_form(vals, 2)
167     formals = vals.first
168     check_formals(formals)
169     return LambdaProcedure(formals, vals.second, env)
170
171 def do_mu_form(vals, env):
172     """Evaluate a mu form with parameters VALS."""
173     check_form(vals, 2)
174     formals = vals[0]
175     check_formals(formals)
176     "*** YOUR CODE HERE ***"
177
178 def do_define_form(vals, env):
179     """Evaluate a define form with parameters VALS in environment ENV."""
180     check_form(vals, 2)
181     target = vals[0]
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)
188     else:
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)
193     return target
194
195 def do_quote_form(vals, env):
196     """Evaluate a quote form with parameters VALS."""
197     check_form(vals, 1, 1)
198     return vals[0]
199
200
201 def do_let_form(vals, env):
202     """Evaluate a let form with parameters VALS in environment ENV."""
203     check_form(vals, 2)
204     bindings = vals[0]
205     exprs = vals.second
206     if not scheme_listp(bindings):
207         raise SchemeError("bad bindings list in let form")
208
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)
220
221     # Evaluate all but the last expression after bindings, and return the last
222     last = len(exprs)-1
223     for i in range(0, last):
224         scheme_eval(exprs[i], new_env)
225     return exprs[last], new_env
226
227
228 def do_delay_form(vals, env):
229     check_form(vals, 1, 1)
230     return Promise(vals[0], env)
231
232
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)
238
239
240 #########################
241 # Logical Special Forms #
242 #########################
243
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)):
248         return vals[1]
249     elif len(vals) == 3:
250         return vals[2]
251     else:
252         return False
253
254 def do_and_form(vals, env):
255     """Evaluate short-circuited and with parameters VALS in environment ENV."""
256     check_form(vals, 0)
257     if vals is nil:
258         return True
259     first = scheme_eval(vals.first, env)
260     if scheme_false(first) or vals.second is nil:
261         return quote(first)
262     return do_and_form(vals.second, env)
263
264 def quote(value):
265     """Return a Scheme expression quoting the Scheme VALUE.
266
267     >>> s = quote('hello')
268     >>> print(s)
269     (quote hello)
270     >>> scheme_eval(s, Frame(None))  # "hello" is undefined in this frame.
271     'hello'
272     """
273     return Pair("quote", Pair(value, nil))
274
275 def do_or_form(vals, env):
276     """Evaluate short-circuited or with parameters VALS in environment ENV."""
277     check_form(vals, 0)
278     if vals is nil:
279         return False
280     first = scheme_eval(vals.first, env)
281     if scheme_true(first):
282         return quote(first)
283     return do_or_form(vals.second, env)
284
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")
293             test = True
294             if clause.second is nil:
295                 raise SchemeError("badly formed else clause")
296         else:
297             test = scheme_eval(clause.first, env)
298         if scheme_true(test):
299             if clause.second is nil:
300                 return quote(test)
301             return Pair("begin", clause.second)
302     return okay
303
304 def do_begin_form(vals, env):
305     """Evaluate begin form with parameters VALS in environment ENV."""
306     check_form(vals, 1)
307     last = len(vals)-1
308     for i in range(0, last):
309         scheme_eval(vals[i], env)
310     return vals[last]
311
312 LOGIC_FORMS = {
313         "and": do_and_form,
314         "or": do_or_form,
315         "if": do_if_form,
316         "cond": do_cond_form,
317         "begin": do_begin_form,
318         }
319
320 # Utility methods for checking the structure of Scheme programs
321
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))
328     length = len(expr)
329     if length < min:
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))
333
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.
338
339     >>> check_formals(read_line("(a b c)"))
340     """
341     if scheme_symbolp(formals):
342         # "Dotted-tail notation" with no positional params.
343         return
344
345     idents = set()
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")
356             return
357         formals = formals.second
358
359 ##################
360 # Tail Recursion #
361 ##################
362
363 def scheme_optimized_eval(expr, env):
364     """Evaluate Scheme expression EXPR in environment ENV."""
365     while True:
366         if expr is None:
367             raise SchemeError("Cannot evaluate an undefined expression.")
368
369         # Evaluate Atoms
370         if scheme_symbolp(expr):
371             return env.lookup(expr)
372         elif scheme_atomp(expr) or scheme_stringp(expr) or expr is okay:
373             return expr
374
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
379
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)
386         elif first == "mu":
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)
392         elif first == "let":
393             "*** YOUR CODE HERE ***"
394         else:
395             "*** YOUR CODE HERE ***"
396
397 ################################################################
398 # Uncomment the following line to apply tail call optimization #
399 ################################################################
400 # scheme_eval = scheme_optimized_eval
401
402
403 ################
404 # Input/Output #
405 ################
406
407 def scheme_format(value):
408     if value is True:
409         return "#t"
410     if value is False:
411         return "#f"
412     return str(value)
413
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."""
417     if startup:
418         for filename in load_files:
419             scheme_load(filename, True, env)
420     while True:
421         try:
422             src = next_line()
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]):
431                 raise
432             print("Error:", err)
433         except KeyboardInterrupt:  # <Control>-C
434             if not startup:
435                 raise
436             print("\nKeyboardInterrupt")
437             if not interactive:
438                 return
439         except EOFError:  # <Control>-D, etc.
440             return
441
442
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):
448         vals = args[:-1]
449         raise SchemeError("wrong number of arguments to load: {0}".format(vals))
450     sym = args[0]
451     quiet = args[1] if len(args) > 2 else True
452     env = args[-1]
453     if (scheme_stringp(sym)):
454         sym = eval(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,)
459     def next_line():
460         return buffer_lines(*args)
461     read_eval_print_loop(next_line, env.global_frame(), quiet=quiet)
462     return okay
463
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."""
467     try:
468         return open(filename)
469     except IOError as exc:
470         if filename.endswith('.scm'):
471             raise SchemeError(str(exc))
472     try:
473         return open(filename + '.scm')
474     except IOError as exc:
475         raise SchemeError(str(exc))
476
477 def create_global_frame():
478     """Initialize and return a single-frame environment with built-in names."""
479     env = Frame(None)
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))
484
485     add_primitives(env)
486
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))))
491
492     env.define("delay", SpecialForm("delay", do_delay_form))
493     env.define("cons-stream", SpecialForm("cons-stream", do_cons_stream_form))
494
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))
500
501     return env
502
503 @main
504 def run(*argv):
505     next_line = buffer_input
506     interactive = True
507     load_files = ()
508     if argv:
509         try:
510             filename = argv[0]
511             if filename == '-load':
512                 load_files = argv[1:]
513             else:
514                 input_file = open(argv[0])
515                 lines = input_file.readlines()
516                 def next_line():
517                     return buffer_lines(lines)
518                 interactive = False
519         except IOError as err:
520             print(err)
521             sys.exit(1)
522     read_eval_print_loop(next_line, create_global_frame(), startup=True,
523                          interactive=interactive, load_files=load_files)