#!/usr/bin/python ################ Lispy: Scheme Interpreter in Python ## (c) Peter Norvig, 2010; See http://norvig.com/lispy.html ################ Symbol, Env classes from __future__ import division Symbol = str class Env(dict): "An environment: a dict of {'var':val} pairs, with an outer Env." def __init__(self, parms=(), args=(), outer=None): self.update(zip(parms,args)) self.outer = outer def find(self, var): "Find the innermost Env where var appears." return self if var in self else self.outer.find(var) def depth(self): n = 0 x = self.outer while x: n += 1 x = x.outer return n def add_globals(env): "Add some Scheme standard procedures to an environment." import math, operator as op env.update(vars(math)) # sin, sqrt, ... env.update( {'say': lambda x: say(x), 'quit' : goodbye, '+':op.add, '-':op.sub, '*':op.mul, '/':op.div, 'not':op.not_, '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':lambda x,y:[x]+y, 'car':lambda x:x[0],'cdr':lambda x:x[1:], 'append':op.add, 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol)}) return env def say(x): print x def goodbye(): print ";; Bye."; quit() global_env = add_globals(Env()) isa = isinstance ################ eval def eval(x, env=global_env,lvl=0): "Evaluate an expression in an environment." this = x[0] if isa(x,list) else x print "|.. " * lvl + str(this) if isa(x, Symbol): # variable reference return env.find(x)[x] elif not isa(x, list): # constant literal return x elif x[0] == 'load': tmp=eval(x[1],env,lvl+1) return eload(tmp) elif x[0] == 'quote' or x[0] == "'": (_, exp) = x return exp elif x[0] == 'if': # (if test conseq alt) (_, test, conseq, alt) = x return eval((conseq if eval(test, env) else alt), env,lvl+1) elif x[0] == 'set!': # (set! var exp) (_, var, exp) = x env.find(var)[var] = eval(exp, env,lvl+1) elif x[0] == 'define': # (define var exp) (_, var, exp) = x env[var] = eval(exp, env,lvl+1) elif x[0] == 'lambda': # (lambda (var*) exp) (_, vars, exp) = x return lambda *args: eval(exp, Env(vars, args, env),lvl+1) elif x[0] == 'begin': # (begin exp*) for exp in x[1:]: val = eval(exp, env,lvl+1) return val else: # (proc exp*) exps = [eval(exp, env,lvl+1) for exp in x] proc = exps.pop(0) #print ">calling", proc return proc(*exps) ################ parse, read, and user interaction def read(s): "Read a Scheme expression from a string." return read_from(tokenize(s)) parse = read def tokenize(s): "Convert a string into a list of tokens." return s.replace('(',' ( ').replace(')',' ) ').split() def read_from(tokens): "Read an expression from a sequence of tokens." if len(tokens) == 0: raise SyntaxError('unexpected EOF while reading') token = tokens.pop(0) if '(' == token: L = [] while tokens[0] != ')': L.append(read_from(tokens)) tokens.pop(0) # pop off ')' return L elif ')' == token: raise SyntaxError('unexpected )') else: return atom(token) def atom(token): "Numbers become numbers; every other token is a symbol." try: return int(token) except ValueError: try: return float(token) except ValueError: return Symbol(token) def to_string(exp): "Convert a Python object back into a Lisp-readable string." return '('+' '.join(map(to_string, exp))+')' if isa(exp, list) else str(exp) def repl(prompt='lis.py> '): "A prompt-read-eval-print loop." print ";; LITHP ITH LITHTENING ...(v0.1)" while True: val = eval(parse(raw_input(prompt))) if val is not None: print to_string(val) import string def sexp(s) : level,keep = 0,"" while s: if s[0] == ";": while s and s[0] != "\n": s=s[1:] if not s: break if s[0] == "(": level += 1 if level > 0 : keep += s[0] if s[0] == ")": level -= 1 if level==0: yield keep keep="" s = s[1:] if keep: yield keep def eload(f) : with open(f) as contents: code = contents.read() for part in sexp(code): eval(parse(part)) import sys if len(sys.argv) > 1: eload(sys.argv[1]) else: repl() quit()