def full_evaluate(expression, environment): """ Fully evaluate an expression until its basic representation """ while True: if is_thunk(expression): if not expression.is_evaluated: expression.is_evaluated = True expression.expression = full_evaluate(expression.expression, expression.environment) return expression.expression elif is_symbol(expression): expression = environment[expression] continue elif (is_atom(expression) or is_nil(expression) or is_procedure(expression) or is_macro(expression) or callable(expression)): return expression elif not is_pair(expression): raise ValueError("Cannot evaluate: %s" % expression) elif car(expression) == 'delay': if len(expression) != 2: raise SyntaxError("Unexpected delay form: %s. Should be (delay <expression>)" % expression) return Thunk(cadr(expression), environment) elif car(expression) == 'defined?': if len(expression) != 2: raise SyntaxError("Unexpected defined? form: %s. Should be (defined? <symbol>)" % expression) name = cadr(expression) if not is_symbol(name): raise SyntaxError("Argument of defined? form should be a symbol. Evaluating: %s" % expression) return environment.exists(name) elif car(expression) == 'define': if len(expression) != 3: raise SyntaxError("Unexpected define form: %s. Should be (define <symbol> <expression>)" % expression) name = cadr(expression) if not is_symbol(name): raise SyntaxError("First argument of define form should be a symbol. Evaluating: %s" % expression) environment[name] = Thunk(caddr(expression), environment) return name elif car(expression) == 'quote': if len(expression) != 2: raise SyntaxError("Unexpected quote form: %s. Should be (quote <expression>)" % expression) return cadr(expression) elif car(expression) == 'eval': if len(expression) != 2: raise SyntaxError("Unexpected eval form: %s. Should be (eval <expression>)" % expression) expression = full_evaluate(cadr(expression), environment) continue elif car(expression) == 'if': if len(expression) != 4: raise SyntaxError("Unexpected if form: %s. Should be (if <condition> <consequent> <alternative>)" % expression) condition = full_evaluate(cadr(expression), environment) expression = caddr(expression) if condition else cadddr(expression) continue elif car(expression) == 'lambda': if len(expression) < 3: raise SyntaxError("Unexpected lambda form: %s. Should be (lambda (<param> ...) <expression> ...)" % expression) parameters = cadr(expression) if is_pair(parameters): current = parameters while is_pair(current): if not is_symbol(car(current)): raise SyntaxError("Lambda parameters should be symbols. In %s" % expression) current = cdr(current) if not is_nil(current) and not is_symbol(current): raise SyntaxError("Lambda optional parameter should be a symbol or nil. In %s" % expression) elif not is_symbol(parameters) and not is_nil(parameters): raise SyntaxError("Lambda parameters should be a symbol or a list of zero or more. In %s" % expression) return Procedure(parameters, # parameters cddr(expression), # body (list of expressions) environment) elif car(expression) == 'macro': if len(expression) < 3: raise SyntaxError("Unexpected define macro: %s. Should be (macro (<resword> ...) (<pattern> <transformation> ...) ...)" % expression) res_words = cadr(expression) rules = cddr(expression) if not is_nil(res_words) and not is_pair(res_words): raise SyntaxError("Macro reserved words should be a list of symbols or nil. In %s" % expression) if is_pair(res_words): for word in res_words: if not is_symbol(word): raise SyntaxError("Macro reserved words should all be symbols. In %s" % expression) for rule in rules: if len(rule) < 2: raise SyntaxError("Macro rule should be in the form (<pattern> <expression> ...). In %s" % expression) return Macro( [(car(e), cdr(e)) for e in rules], # rules [] if not res_words else set(iter(res_words)) ) # reserved words else: # evaluate head operator = full_evaluate(car(expression), environment) if is_macro(operator): # evaluate recursively only the inner expressions (not the last) current = operator.transform(expression) while cdr(current) is not None: full_evaluate(car(current), environment) current = cdr(current) expression = car(current) continue else: # The unevaluated operands unev_operands = cdr(expression) if callable(operator): # evaluate each operand recursively operands = [full_evaluate(e, environment) for e in unev_operands] if unev_operands else [] # return the application of the built-in procedure return operator(make_list(operands)) elif is_procedure(operator): # create Thunks (promise to evaluate) for each operand unev_op_list = list(iter(unev_operands)) if unev_operands else [] proc_environment = Environment(parent=operator.environment) # if the lambda parameters is not in the format ( () [. <symbol>] ) # for taking zero or more arguments if len(operator.parameters) != 1 or not is_nil(operator.parameters[0]): for name in operator.parameters: try: # take next argument proc_environment[name] = Thunk(unev_op_list.pop(0), environment) except IndexError: raise ValueError("Insufficient parameters for procedure %s. It should be at least %d" % (operator, len(operator.parameters))) if not is_nil(operator.optional): # the optional argument is something, that when # evaluated, yields the list of rest of the operands # evaluated proc_environment[operator.optional] = Thunk(cons(lambda x: x, make_list(unev_op_list)), environment) elif unev_op_list: raise ValueError("Too much parameters for procedure %s. It should be %d." % (operator, len(operator.parameters))) # evaluate recursively only the inner procedure expressions # (not the last) current = operator.body while cdr(current) is not None: full_evaluate(car(current), proc_environment) current = cdr(current) environment = proc_environment expression = car(current) # continue not-recursively to evaluate the procedure's body # in the extended environment continue else: raise ValueError("Not an operator: %s, in expression: %s" % (operator, expression))
def make_global_environment(): env = NumericEnvironment() # utf-8 stdin and out stdin = codecs.getreader('utf-8')(sys.stdin) stdout = codecs.getreader('utf-8')(sys.stdout) env.update({ # built-in symbols 'nil': None, '#t': True, '#f': False, # symbolic tests 'procedure?': BuiltinProcedure(lambda args: is_procedure(car(args)), 'procedure?', 1, 1), 'macro?': BuiltinProcedure(lambda args: is_macro(car(args)), 'macro?', 1, 1), 'thunk?': BuiltinProcedure(lambda args: is_thunk(car(args)), 'thunk?', 1, 1), 'symbol?': BuiltinProcedure(lambda args: is_symbol(car(args)), 'symbol?', 1, 1), 'atom?': BuiltinProcedure(lambda args: is_atom(car(args)), 'atom?', 1, 1), 'pair?': BuiltinProcedure(lambda args: is_pair(car(args)), 'pair?', 1, 1), 'nil?': BuiltinProcedure(lambda args: is_nil(car(args)), 'nil?', 1, 1), 'eq?': BuiltinProcedure(lambda args: car(args) is cadr(args), 'eq?', 2, 2), '=': BuiltinProcedure(lambda args: car(args) == cadr(args), '=', 2, 2), # symbolic manipulation 'explode': BuiltinProcedure(lambda args: make_list(list(car(args))), 'explode', 1, 1), 'implode': BuiltinProcedure(lambda args: ''.join(iter(args)), 'implode', 1), # basic data manipulation 'car': BuiltinProcedure(lambda args: caar(args), 'car', 1, 1), "cdr": BuiltinProcedure(lambda args: cdar(args), "cdr", 1, 1), "cons": BuiltinProcedure(lambda args: cons(car(args), cadr(args)), "cons", 2, 2), # I/O operations 'write': BuiltinProcedure( lambda args: stdout.write( unicode(car(args)).encode('utf-8').decode('string_escape')), 'write', 1, 1), 'read': BuiltinProcedure(lambda args: stdin.read(1), 'read', 0, 0), 'file-open': BuiltinProcedure( lambda args: codecs.open(car(args), cadr(args), 'utf-8'), 'file-open', 2, 2), 'file-close': BuiltinProcedure(lambda args: car(args).close(), 'file-close', 1, 1), 'file-write': BuiltinProcedure( lambda args: car(args).write( unicode(cadr(args)).encode('utf-8').decode('string_escape'). decode('utf-8')), 'file-write', 2, 2), 'file-read': BuiltinProcedure(lambda args: car(args).read(1), 'file-read', 1, 1), # dependency inclusion 'include': IncludeMacro(), # arithmetic operations '+': BuiltinProcedure(lambda args: reduce(operator.add, args), '+', 2), '-': BuiltinProcedure(lambda args: reduce(operator.sub, args), '-', 2), '*': BuiltinProcedure(lambda args: reduce(operator.mul, args), '*', 2), '/': BuiltinProcedure(lambda args: reduce(operator.div, args), '/', 2), 'mod': BuiltinProcedure(lambda args: reduce(operator.mod, args), 'mod', 2), '<': BuiltinProcedure(lambda args: car(args) < cadr(args), '<', 2), '>': BuiltinProcedure(lambda args: car(args) > cadr(args), '>', 2), '<=': BuiltinProcedure(lambda args: car(args) <= cadr(args), '<=', 2), '>=': BuiltinProcedure(lambda args: car(args) >= cadr(args), '>=', 2), }) return env
def make_global_environment(): env = NumericEnvironment() # utf-8 stdin and out stdin = codecs.getreader("utf-8")(sys.stdin) stdout = codecs.getreader("utf-8")(sys.stdout) env.update( { # built-in symbols "nil": None, "#t": True, "#f": False, # symbolic tests "procedure?": BuiltinProcedure(lambda args: is_procedure(car(args)), "procedure?", 1, 1), "macro?": BuiltinProcedure(lambda args: is_macro(car(args)), "macro?", 1, 1), "thunk?": BuiltinProcedure(lambda args: is_thunk(car(args)), "thunk?", 1, 1), "symbol?": BuiltinProcedure(lambda args: is_symbol(car(args)), "symbol?", 1, 1), "atom?": BuiltinProcedure(lambda args: is_atom(car(args)), "atom?", 1, 1), "pair?": BuiltinProcedure(lambda args: is_pair(car(args)), "pair?", 1, 1), "nil?": BuiltinProcedure(lambda args: is_nil(car(args)), "nil?", 1, 1), "eq?": BuiltinProcedure(lambda args: car(args) is cadr(args), "eq?", 2, 2), "=": BuiltinProcedure(lambda args: car(args) == cadr(args), "=", 2, 2), # symbolic manipulation "explode": BuiltinProcedure(lambda args: make_list(list(car(args))), "explode", 1, 1), "implode": BuiltinProcedure(lambda args: "".join(iter(args)), "implode", 1), # basic data manipulation "car": BuiltinProcedure(lambda args: caar(args), "car", 1, 1), "cdr": BuiltinProcedure(lambda args: cdar(args), "cdr", 1, 1), "cons": BuiltinProcedure(lambda args: cons(car(args), cadr(args)), "cons", 2, 2), # I/O operations "write": BuiltinProcedure( lambda args: stdout.write(unicode(car(args)).encode("utf-8").decode("string_escape")), "write", 1, 1 ), "read": BuiltinProcedure(lambda args: stdin.read(1), "read", 0, 0), "file-open": BuiltinProcedure(lambda args: codecs.open(car(args), cadr(args), "utf-8"), "file-open", 2, 2), "file-close": BuiltinProcedure(lambda args: car(args).close(), "file-close", 1, 1), "file-write": BuiltinProcedure( lambda args: car(args).write( unicode(cadr(args)).encode("utf-8").decode("string_escape").decode("utf-8") ), "file-write", 2, 2, ), "file-read": BuiltinProcedure(lambda args: car(args).read(1), "file-read", 1, 1), # dependency inclusion "include": IncludeMacro(), # arithmetic operations "+": BuiltinProcedure(lambda args: reduce(operator.add, args), "+", 2), "-": BuiltinProcedure(lambda args: reduce(operator.sub, args), "-", 2), "*": BuiltinProcedure(lambda args: reduce(operator.mul, args), "*", 2), "/": BuiltinProcedure(lambda args: reduce(operator.div, args), "/", 2), "mod": BuiltinProcedure(lambda args: reduce(operator.mod, args), "mod", 2), "<": BuiltinProcedure(lambda args: car(args) < cadr(args), "<", 2), ">": BuiltinProcedure(lambda args: car(args) > cadr(args), ">", 2), "<=": BuiltinProcedure(lambda args: car(args) <= cadr(args), "<=", 2), ">=": BuiltinProcedure(lambda args: car(args) >= cadr(args), ">=", 2), } ) return env
def full_evaluate(expression, environment): """ Fully evaluate an expression until its basic representation """ while True: if is_thunk(expression): if not expression.is_evaluated: expression.is_evaluated = True expression.expression = full_evaluate(expression.expression, expression.environment) return expression.expression elif is_symbol(expression): expression = environment[expression] continue elif (is_atom(expression) or is_nil(expression) or is_procedure(expression) or is_macro(expression) or callable(expression)): return expression elif not is_pair(expression): raise ValueError("Cannot evaluate: %s" % expression) elif car(expression) == 'delay': if len(expression) != 2: raise SyntaxError( "Unexpected delay form: %s. Should be (delay <expression>)" % expression) return Thunk(cadr(expression), environment) elif car(expression) == 'defined?': if len(expression) != 2: raise SyntaxError( "Unexpected defined? form: %s. Should be (defined? <symbol>)" % expression) name = cadr(expression) if not is_symbol(name): raise SyntaxError( "Argument of defined? form should be a symbol. Evaluating: %s" % expression) return environment.exists(name) elif car(expression) == 'define': if len(expression) != 3: raise SyntaxError( "Unexpected define form: %s. Should be (define <symbol> <expression>)" % expression) name = cadr(expression) if not is_symbol(name): raise SyntaxError( "First argument of define form should be a symbol. Evaluating: %s" % expression) environment[name] = Thunk(caddr(expression), environment) return name elif car(expression) == 'quote': if len(expression) != 2: raise SyntaxError( "Unexpected quote form: %s. Should be (quote <expression>)" % expression) return cadr(expression) elif car(expression) == 'eval': if len(expression) != 2: raise SyntaxError( "Unexpected eval form: %s. Should be (eval <expression>)" % expression) expression = full_evaluate(cadr(expression), environment) continue elif car(expression) == 'if': if len(expression) != 4: raise SyntaxError( "Unexpected if form: %s. Should be (if <condition> <consequent> <alternative>)" % expression) condition = full_evaluate(cadr(expression), environment) expression = caddr(expression) if condition else cadddr(expression) continue elif car(expression) == 'lambda': if len(expression) < 3: raise SyntaxError( "Unexpected lambda form: %s. Should be (lambda (<param> ...) <expression> ...)" % expression) parameters = cadr(expression) if is_pair(parameters): current = parameters while is_pair(current): if not is_symbol(car(current)): raise SyntaxError( "Lambda parameters should be symbols. In %s" % expression) current = cdr(current) if not is_nil(current) and not is_symbol(current): raise SyntaxError( "Lambda optional parameter should be a symbol or nil. In %s" % expression) elif not is_symbol(parameters) and not is_nil(parameters): raise SyntaxError( "Lambda parameters should be a symbol or a list of zero or more. In %s" % expression) return Procedure( parameters, # parameters cddr(expression), # body (list of expressions) environment) elif car(expression) == 'macro': if len(expression) < 3: raise SyntaxError( "Unexpected define macro: %s. Should be (macro (<resword> ...) (<pattern> <transformation> ...) ...)" % expression) res_words = cadr(expression) rules = cddr(expression) if not is_nil(res_words) and not is_pair(res_words): raise SyntaxError( "Macro reserved words should be a list of symbols or nil. In %s" % expression) if is_pair(res_words): for word in res_words: if not is_symbol(word): raise SyntaxError( "Macro reserved words should all be symbols. In %s" % expression) for rule in rules: if len(rule) < 2: raise SyntaxError( "Macro rule should be in the form (<pattern> <expression> ...). In %s" % expression) return Macro( [(car(e), cdr(e)) for e in rules], # rules [] if not res_words else set(iter(res_words))) # reserved words else: # evaluate head operator = full_evaluate(car(expression), environment) if is_macro(operator): # evaluate recursively only the inner expressions (not the last) current = operator.transform(expression) while cdr(current) is not None: full_evaluate(car(current), environment) current = cdr(current) expression = car(current) continue else: # The unevaluated operands unev_operands = cdr(expression) if callable(operator): # evaluate each operand recursively operands = [ full_evaluate(e, environment) for e in unev_operands ] if unev_operands else [] # return the application of the built-in procedure return operator(make_list(operands)) elif is_procedure(operator): # create Thunks (promise to evaluate) for each operand unev_op_list = list( iter(unev_operands)) if unev_operands else [] proc_environment = Environment(parent=operator.environment) # if the lambda parameters is not in the format ( () [. <symbol>] ) # for taking zero or more arguments if len(operator.parameters) != 1 or not is_nil( operator.parameters[0]): for name in operator.parameters: try: # take next argument proc_environment[name] = Thunk( unev_op_list.pop(0), environment) except IndexError: raise ValueError( "Insufficient parameters for procedure %s. It should be at least %d" % (operator, len(operator.parameters))) if not is_nil(operator.optional): # the optional argument is something, that when # evaluated, yields the list of rest of the operands # evaluated proc_environment[operator.optional] = Thunk( cons(lambda x: x, make_list(unev_op_list)), environment) elif unev_op_list: raise ValueError( "Too much parameters for procedure %s. It should be %d." % (operator, len(operator.parameters))) # evaluate recursively only the inner procedure expressions # (not the last) current = operator.body while cdr(current) is not None: full_evaluate(car(current), proc_environment) current = cdr(current) environment = proc_environment expression = car(current) # continue not-recursively to evaluate the procedure's body # in the extended environment continue else: raise ValueError("Not an operator: %s, in expression: %s" % (operator, expression))