""" CS 61A Project 4, 2012 TA Name: Person A Name: Login: cs61a- Person B Name: Login: cs61a- """ """This module implements the core Scheme interpreter functions, including the Eval/Apply mutual recurrence, source code parsing, and the read-eval-print interactive loop. """ import sys from ucb import main, trace from scheme_tokens import tokenize_lines, DELIMITERS from scheme_primitives import * from buffer import Buffer ############## # Eval/Apply # ############## def scheme_eval(expr, env): """Evaluate Scheme expression EXPR in enivornment ENV. >>> expr = read_line("(+ 2 2)") >>> expr Pair('+', Pair(2, Pair(2, NULL))) >>> scheme_eval(expr, create_global_frame()) 4 """ if expr is None: raise SchemeError("Cannot evaluate an undefined expression.") # Evaluate Atoms if scheme_symbolp(expr): return env[expr] elif scheme_atomp(expr): return expr # All non-atomic expressions are lists. if not scheme_listp(expr): raise SchemeError("malformed list: {0}".format(str(expr))) first, rest = expr.first, expr.second # Evaluate Combinations if first in LOGIC_FORMS: return scheme_eval(LOGIC_FORMS[first](rest, env), env) elif first == "lambda": return do_lambda_form(rest, env) elif first == "define": do_define_form(rest, env) return None elif first == "quote": return do_quote_form(rest) elif first == "let": expr, env = do_let_form(rest, env) return scheme_eval(expr, env) elif first == "let*": expr, env = do_let_star_form(rest, env) return scheme_eval(expr, env) else: procedure = scheme_eval(first, env) args = rest.map(lambda operand: scheme_eval(operand, env)) return scheme_apply(procedure, args, env) def scheme_apply(procedure, args, env): """Apply scheme PROCEDURE to argument values ARGS in environment ENV.""" if isinstance(procedure, PrimitiveProcedure): arg_list = [arg for arg in args] return apply_primitive(procedure, arg_list, env) elif isinstance(procedure, LambdaProcedure): "*** YOUR CODE HERE ***" else: raise SchemeError("Cannot call {0}".format(repr(procedure))) def apply_primitive(procedure, arg_list, env): """Apply PrimitiveProcedure procedure to Python list arg_list. >>> env = create_global_frame() >>> plus = env["+"] >>> twos = [2, 2] >>> apply_primitive(plus, twos, env) 4 >>> add_twos = [read_line("(+ 2 2)")] >>> add_twos [Pair('+', Pair(2, Pair(2, NULL)))] >>> apply_primitive(env["eval"], add_twos, env) 4 """ "*** YOUR CODE HERE ***" raise NotImplementedError ################ # Environments # ################ class Frame: """An environment frame, representing a mapping from Scheme symbols to Scheme values, possibly enclosed within another frame.""" def __init__(self, parent): """An empty frame that is attached to the frame parent.""" self.inner = {} self.parent = parent def __getitem__(self, sym): return self.find(sym).inner[sym] def __repr__(self): if self.parent is None: return "" else: s = sorted('{0}: {1}'.format(k, v) for k, v in self.inner.items()) return "<{{{0}}} -> {1}>".format(', '.join(s), repr(self.parent)) def find(self, sym): """The environment frame at or parent SELF that defined SYM. It is an error if SYM does not exist.""" "*** YOUR CODE HERE ***" raise SchemeError("unknown identifier: {0}".format(str(sym))) def global_frame(self): """The global environment at the root of the parent list.""" e = self while e.parent is not None: e = e.parent return e def make_call_frame(self, formals, vals): """A new local frame attached to SELF in which the symbols in the Scheme formal parameter list FORMALS are bound to the Scheme values in the Scheme value list VALS. FORMALS has either of the formats allowed by the check_formals method. If FORMALS is an ordinary Scheme list, then the number of formals must be the same as the number of VALS, and each symbol in FORMALS is bound to the corresponding value in VALS. If the last second in FORMALS is a symbol, then the number of values in VALS must be at least as large as the number of preceding ("normal") formal symbols, and the last formal symbol is bound to a Scheme list containing the remaining values in VALS (which may be empty). >>> env = create_global_frame() >>> formals, vals = read_line("(a b c)"), read_line("(1 2 3)") >>> env.make_call_frame(formals, vals) <{a: 1, b: 2, c: 3} -> > >>> env.make_call_frame(read_line("(a . b)"), vals) <{a: 1, b: (2 3)} -> > """ frame = Frame(self) "*** YOUR CODE HERE ***" return frame def define(self, sym, val): """Define Scheme symbol SYM to have value VAL in SELF.""" self.inner[sym] = val class LambdaProcedure: """A function defined by a lambda expression or the complex define form.""" def __init__(self, formals, body, env): """A function whose formal parameter list is FORMALS (a Scheme list), whose body is the single Scheme expression BODY, and whose environment is the Frame ENV. A lambda expression containing multiple expressions, such as (lambda (x) (display x) (+ x 1)) can be handled by using (begin (display x) (+ x 1)) as the body.""" self.formals = formals self.body = body self.env = env def __str__(self): return "(lambda {0} {1})".format(str(self.formals), str(self.body)) def __repr__(self): args = (self.formals, self.body, self.env) return "LambdaProcedure({0}, {1}, {2})".format(*(repr(a) for a in args)) ################# # Special forms # ################# def do_lambda_form(vals, env): """Evaluate a lambda form with parameters VALS in environment ENV.""" check_form(vals, 2) formals = vals[0] check_formals(formals) "*** YOUR CODE HERE ***" def do_define_form(vals, env): """Evaluate a define form with parameters VALS in environment ENV.""" check_form(vals, 2) "*** YOUR CODE HERE ***" def do_quote_form(vals): """Evaluate a quote form with parameters VALS.""" check_form(vals, 1, 1) return vals[0] def do_let_form(vals, env): """Evaluate a let form with parameters VALS in environment ENV.""" check_form(vals, 2) bindings = vals[0] exprs = vals.second if not scheme_listp(bindings): raise SchemeError("bad bindings list in let form") # Add a frame containing bindings names, vals = NULL, NULL "*** YOUR CODE HERE ***" new_env = env.make_call_frame(names, vals) # Evaluate all but the last expression after bindings, and return the last last = len(exprs)-1 for i in range(0, last): scheme_eval(exprs[i], new_env) return exprs[last], new_env def do_let_star_form(vals, env): """Evaluate a let* form with parameters VALS in environment ENV.""" check_form(vals, 2) bindings = vals[0] exprs = vals.second if not scheme_listp(bindings): raise SchemeError("bad bindings list in let form") # Add a frame containing bindings names, vals = NULL, NULL "*** YOUR CODE HERE ***" # Evaluate all but the last expression after bindings, and return the last last = len(exprs)-1 for i in range(0, last): scheme_eval(exprs[i], env) return exprs[last], env ######################### # Logical Special Forms # ######################### def do_if_form(vals, env): """Evaluate if form with parameters VALS in environment ENV.""" check_form(vals, 3, 3) "*** YOUR CODE HERE ***" return NULL def do_and_form(vals, env): """Evaluate short-circuited and with parameters VALS in environment ENV.""" "*** YOUR CODE HERE ***" return True def do_or_form(vals, env): """Evaluate short-circuited or with parameters VALS in environment ENV.""" "*** YOUR CODE HERE ***" return False def do_cond_form(vals, env): """Evaluate cond form with parameters VALS in environment ENV.""" num_clauses = len(vals) for i, clause in enumerate(vals): check_form(clause, 1) if clause.first == "else": if i < num_clauses-1: raise SchemeError("else must be last") test = True if clause.second is NULL: raise SchemeError("badly formed else clause") else: test = scheme_eval(clause.first, env) if test: "*** YOUR CODE HERE ***" def do_begin_form(vals, env): """Evaluate begin form with parameters VALS in environment ENV.""" check_form(vals, 1) "*** YOUR CODE HERE ***" def do_case_form(vals, env): """Evaluate case form with parameters VALS in environment ENV.""" "*** YOUR CODE HERE ***" return False LOGIC_FORMS = { "and": do_and_form, "or": do_or_form, "if": do_if_form, "cond": do_cond_form, "begin": do_begin_form, "case": do_case_form } # Utility methods for checking the structure of Scheme programs def check_form(expr, min, max = None): """Check EXPR (default SELF.expr) is a proper list whose length is at least MIN and no more than MAX (default: no maximum). Raises a SchemeError if this is not the case.""" if not scheme_listp(expr): raise Exception("badly formed expression: %s", expr) length = len(expr) if length < min: raise SchemeError("too few operands in form") elif max is not None and length > max: raise SchemeError("too many operands in form") def check_formals(formals): """Check that FORMALS is a valid parameter list having either the form symrest OR (sym1 sym2 ... symn) OR (sym1 sym2 ... symn . symrest), where each symX is a distinct symbol. >>> check_formals(read_line("(a b c)")) >>> check_formals(read_line("(a b . c)")) >>> check_formals("a") """ "*** YOUR CODE HERE ***" ################ # Input/Output # ################ def scheme_read(input_port): """Read the next expression from INPUT_PORT, a buffer.Buffer. >>> scheme_read(Buffer(tokenize_lines(["(1", "2 .", "'(3 4))", "4"]))) Pair(1, Pair(2, Pair('quote', Pair(Pair(3, Pair(4, NULL)), NULL)))) """ def read_tail(): """Assuming that input is positioned inside a Scheme list or pair, immediately before a final parenthesis or another item in the list, return the remainder of the list from that point. Thus, returns (2 3) when positioned at the carat in "(1 ^ 2 3)", returns nil when positioned at the carat in "(1 2 3 ^ )", and returns the pair (2 . 3) when positioned at the carat in (1 ^ 2 . 3). """ if input_port.current() is None: raise SchemeError("unexpected end of file") "*** YOUR CODE HERE ***" if input_port.current() is None: return EOF val = input_port.pop() if scheme_atomp(val) and val not in DELIMITERS: return val elif val == "'": return Pair("quote", Pair(scheme_read(input_port), NULL)) elif val == "(": return read_tail() else: raise SchemeError("unexpected token: {0}".format(val)) def read_eval_print(input_port, prompt, env, print_input): """Read and evaluate from the current input port until the end of file. If PROMPT is not None, use it to prompt for input and print values of each expression.""" while True: try: if prompt is not None: print(prompt, end = "") sys.stdout.flush() expr = scheme_read(input_port) if expr is EOF: return if print_input: print(expr) val = scheme_eval(expr, env) if prompt is not None and val is not None: scheme_display(val) scheme_newline() except SchemeError as exc: if not exc.args[0]: print("Error", file=sys.stderr) else: print("Error: {0}".format(exc.args[0]), file=sys.stderr) sys.stderr.flush() def scheme_load(sym, env): """Load Scheme source file SYM.""" check_type(sym, scheme_symbolp, 0, "load") with scheme_open(sym) as inp: scheme_repl(inp, "", env.global_frame(), False) def scheme_repl(source, prompt, env, print_input=True): """Start a read-eval-print loop reading from SOURCE.""" read_eval_print(Buffer(tokenize_lines(source)), prompt, env, print_input) def scheme_open(filename): """If either FILENAME or FILENAME.scm is the name of a valid file, return a Python file opened to it. Otherwise, raise an error.""" try: return open(filename) except IOError as exc: if filename.endswith('.scm'): raise SchemeError(str(exc)) try: return open(filename + '.scm') except IOError as exc: raise SchemeError(str(exc)) def read_line(line): """Read a single string LINE as a Scheme expression.""" return scheme_read(Buffer(tokenize_lines([line]))) def create_global_frame(): """Initialize and return a single-frame environment with built-in names.""" env = Frame(None) env.define("eval", PrimitiveProcedure(scheme_eval, True)) env.define("apply", PrimitiveProcedure(scheme_apply, True)) env.define("load", PrimitiveProcedure(scheme_load, True)) add_primitives(env) return env @main def run(*argv): if argv: try: input_file = open(argv[0]) print_input = True except IOError as exc: print("could not open {0}: {1}".format(argv[0], exc.args[0]), file=sys.stderr) sys.exit(1) else: input_file = sys.stdin print_input = False scheme_repl(input_file, "scm> ", create_global_frame(), print_input)