Advertisement
sconetto

Python - Lisp

Dec 1st, 2017
130
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.15 KB | None | 0 0
  1. from __future__ import division
  2. import re, sys, StringIO
  3.  
  4. class Symbol(str): pass
  5.  
  6. def Sym(s, symbol_table={}):
  7.     "Find or create unique Symbol entry for str s in symbol table."
  8.     if s not in symbol_table: symbol_table[s] = Symbol(s)
  9.     return symbol_table[s]
  10.  
  11. _quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym,
  12. "quote   if   set!  define   lambda   begin   define-macro".split())
  13.  
  14. _quasiquote, _unquote, _unquotesplicing = map(Sym,
  15. "quasiquote   unquote   unquote-splicing".split())
  16.  
  17. class Procedure(object):
  18.     "A user-defined Scheme procedure."
  19.     def __init__(self, parms, exp, env):
  20.         self.parms, self.exp, self.env = parms, exp, env
  21.     def __call__(self, *args):
  22.         return eval(self.exp, Env(self.parms, args, self.env))
  23.  
  24. ################ parse, read, and user interaction
  25.  
  26. def parse(inport):
  27.     "Parse a program: read and expand/error-check it."
  28.     # Backwards compatibility: given a str, convert it to an InPort
  29.     if isinstance(inport, str): inport = InPort(StringIO.StringIO(inport))
  30.     return expand(read(inport), toplevel=True)
  31.  
  32. eof_object = Symbol('#<eof-object>') # Note: uninterned; can't be read
  33.  
  34. class InPort(object):
  35.     "An input port. Retains a line of chars."
  36.     tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)"""
  37.     def __init__(self, file):
  38.         self.file = file; self.line = ''
  39.     def next_token(self):
  40.         "Return the next token, reading new text into line buffer if needed."
  41.         while True:
  42.             if self.line == '': self.line = self.file.readline()
  43.             if self.line == '': return eof_object
  44.             token, self.line = re.match(InPort.tokenizer, self.line).groups()
  45.             if token != '' and not token.startswith(';'):
  46.                 return token
  47.  
  48. def readchar(inport):
  49.     "Read the next character from an input port."
  50.     if inport.line != '':
  51.         ch, inport.line = inport.line[0], inport.line[1:]
  52.         return ch
  53.     else:
  54.         return inport.file.read(1) or eof_object
  55.  
  56. def read(inport):
  57.     "Read a Scheme expression from an input port."
  58.     def read_ahead(token):
  59.         if '(' == token:
  60.             L = []
  61.             while True:
  62.                 token = inport.next_token()
  63.                 if token == ')': return L
  64.                 else: L.append(read_ahead(token))
  65.         elif ')' == token: raise SyntaxError('unexpected )')
  66.         elif token in quotes: return [quotes[token], read(inport)]
  67.         elif token is eof_object: raise SyntaxError('unexpected EOF in list')
  68.         else: return atom(token)
  69.     # body of read:
  70.     token1 = inport.next_token()
  71.     return eof_object if token1 is eof_object else read_ahead(token1)
  72.  
  73. quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing}
  74.  
  75. def atom(token):
  76.     'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.'
  77.     if token == '#t': return True
  78.     elif token == '#f': return False
  79.     elif token[0] == '"': return token[1:-1].decode('string_escape')
  80.     try: return int(token)
  81.     except ValueError:
  82.         try: return float(token)
  83.         except ValueError:
  84.             try: return complex(token.replace('i', 'j', 1))
  85.             except ValueError:
  86.                 return Sym(token)
  87.  
  88. def to_string(x):
  89.     "Convert a Python object back into a Lisp-readable string."
  90.     if x is True: return "#t"
  91.     elif x is False: return "#f"
  92.     elif isa(x, Symbol): return x
  93.     elif isa(x, str): return '"%s"' % x.encode('string_escape').replace('"',r'\"')
  94.     elif isa(x, list): return '('+' '.join(map(to_string, x))+')'
  95.     elif isa(x, complex): return str(x).replace('j', 'i')
  96.     else: return str(x)
  97.  
  98. def load(filename):
  99.     "Eval every expression from a file."
  100.     repl(None, InPort(open(filename)), None)
  101.  
  102. def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout):
  103.     "A prompt-read-eval-print loop."
  104.     sys.stderr.write("Lispy version 2.0\n")
  105.     while True:
  106.         try:
  107.             if prompt: sys.stderr.write(prompt)
  108.             x = parse(inport)
  109.             if x is eof_object: return
  110.             val = eval(x)
  111.             if val is not None and out: print >> out, to_string(val)
  112.         except Exception as e:
  113.             print '%s: %s' % (type(e).__name__, e)
  114.  
  115. ################ Environment class
  116.  
  117. class Env(dict):
  118.     "An environment: a dict of {'var':val} pairs, with an outer Env."
  119.     def __init__(self, parms=(), args=(), outer=None):
  120.         # Bind parm list to corresponding args, or single parm to list of args
  121.         self.outer = outer
  122.         if isa(parms, Symbol):
  123.             self.update({parms:list(args)})
  124.         else:
  125.             if len(args) != len(parms):
  126.                 raise TypeError('expected %s, given %s, '
  127.                                 % (to_string(parms), to_string(args)))
  128.             self.update(zip(parms,args))
  129.     def find(self, var):
  130.         "Find the innermost Env where var appears."
  131.         if var in self: return self
  132.         elif self.outer is None: raise LookupError(var)
  133.         else: return self.outer.find(var)
  134.  
  135. def is_pair(x): return x != [] and isa(x, list)
  136. def cons(x, y): return [x]+y
  137.  
  138. def callcc(proc):
  139.     "Call proc with current continuation; escape only"
  140.     ball = RuntimeWarning("Sorry, can't continue this continuation any longer.")
  141.     def throw(retval): ball.retval = retval; raise ball
  142.     try:
  143.         return proc(throw)
  144.     except RuntimeWarning as w:
  145.         if w is ball: return ball.retval
  146.         else: raise w
  147.  
  148. def add_globals(self):
  149.     "Add some Scheme standard procedures."
  150.     import math, cmath, operator as op
  151.     self.update(vars(math))
  152.     self.update(vars(cmath))
  153.     self.update({
  154.      '+':op.add, '-':op.sub, '*':op.mul, '/':op.div, 'not':op.not_,
  155.      '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
  156.      'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons,
  157.      'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add,
  158.      'list':lambda *x:list(x), 'list?': lambda x:isa(x,list),
  159.      'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol),
  160.      'boolean?':lambda x: isa(x, bool), 'pair?':is_pair,
  161.      'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l),
  162.      'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc,
  163.      'open-input-file':open,'close-input-port':lambda p: p.file.close(),
  164.      'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(),
  165.      'eof-object?':lambda x:x is eof_object, 'read-char':readchar,
  166.      'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)),
  167.      'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))})
  168.     return self
  169.  
  170. isa = isinstance
  171.  
  172. global_env = add_globals(Env())
  173.  
  174. ################ eval (tail recursive)
  175.  
  176. def eval(x, env=global_env):
  177.     "Evaluate an expression in an environment."
  178.     while True:
  179.         if isa(x, Symbol):       # variable reference
  180.             return env.find(x)[x]
  181.         elif not isa(x, list):   # constant literal
  182.             return x
  183.         elif x[0] is _quote:     # (quote exp)
  184.             (_, exp) = x
  185.             return exp
  186.         elif x[0] is _if:        # (if test conseq alt)
  187.             (_, test, conseq, alt) = x
  188.             x = (conseq if eval(test, env) else alt)
  189.         elif x[0] is _set:       # (set! var exp)
  190.             (_, var, exp) = x
  191.             env.find(var)[var] = eval(exp, env)
  192.             return None
  193.         elif x[0] is _define:    # (define var exp)
  194.             (_, var, exp) = x
  195.             env[var] = eval(exp, env)
  196.             return None
  197.         elif x[0] is _lambda:    # (lambda (var*) exp)
  198.             (_, vars, exp) = x
  199.             return Procedure(vars, exp, env)
  200.         elif x[0] is _begin:     # (begin exp+)
  201.             for exp in x[1:-1]:
  202.                 eval(exp, env)
  203.             x = x[-1]
  204.         else:                    # (proc exp*)
  205.             exps = [eval(exp, env) for exp in x]
  206.             proc = exps.pop(0)
  207.             if isa(proc, Procedure):
  208.                 x = proc.exp
  209.                 env = Env(proc.parms, exps, proc.env)
  210.             else:
  211.                 return proc(*exps)
  212.  
  213. ################ expand
  214.  
  215. def expand(x, toplevel=False):
  216.     "Walk tree of x, making optimizations/fixes, and signaling SyntaxError."
  217.     require(x, x!=[])                    # () => Error
  218.     if not isa(x, list):                 # constant => unchanged
  219.         return x
  220.     elif x[0] is _quote:                 # (quote exp)
  221.         require(x, len(x)==2)
  222.         return x
  223.     elif x[0] is _if:
  224.         if len(x)==3: x = x + [None]     # (if t c) => (if t c None)
  225.         require(x, len(x)==4)
  226.         return map(expand, x)
  227.     elif x[0] is _set:
  228.         require(x, len(x)==3);
  229.         var = x[1]                       # (set! non-var exp) => Error
  230.         require(x, isa(var, Symbol), "can set! only a symbol")
  231.         return [_set, var, expand(x[2])]
  232.     elif x[0] is _define or x[0] is _definemacro:
  233.         require(x, len(x)>=3)
  234.         _def, v, body = x[0], x[1], x[2:]
  235.         if isa(v, list) and v:           # (define (f args) body)
  236.             f, args = v[0], v[1:]        #  => (define f (lambda (args) body))
  237.             return expand([_def, f, [_lambda, args]+body])
  238.         else:
  239.             require(x, len(x)==3)        # (define non-var/list exp) => Error
  240.             require(x, isa(v, Symbol), "can define only a symbol")
  241.             exp = expand(x[2])
  242.             if _def is _definemacro:
  243.                 require(x, toplevel, "define-macro only allowed at top level")
  244.                 proc = eval(exp)
  245.                 require(x, callable(proc), "macro must be a procedure")
  246.                 macro_table[v] = proc    # (define-macro v proc)
  247.                 return None              #  => None; add v:proc to macro_table
  248.             return [_define, v, exp]
  249.     elif x[0] is _begin:
  250.         if len(x)==1: return None        # (begin) => None
  251.         else: return [expand(xi, toplevel) for xi in x]
  252.     elif x[0] is _lambda:                # (lambda (x) e1 e2)
  253.         require(x, len(x)>=3)            #  => (lambda (x) (begin e1 e2))
  254.         vars, body = x[1], x[2:]
  255.         require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars))
  256.                 or isa(vars, Symbol), "illegal lambda argument list")
  257.         exp = body[0] if len(body) == 1 else [_begin] + body
  258.         return [_lambda, vars, expand(exp)]
  259.     elif x[0] is _quasiquote:            # `x => expand_quasiquote(x)
  260.         require(x, len(x)==2)
  261.         return expand_quasiquote(x[1])
  262.     elif isa(x[0], Symbol) and x[0] in macro_table:
  263.         return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...)
  264.     else:                                #        => macroexpand if m isa macro
  265.         return map(expand, x)            # (f arg...) => expand each
  266.  
  267. def require(x, predicate, msg="wrong length"):
  268.     "Signal a syntax error if predicate is false."
  269.     if not predicate: raise SyntaxError(to_string(x)+': '+msg)
  270.  
  271. _append, _cons, _let = map(Sym, "append cons let".split())
  272.  
  273. def expand_quasiquote(x):
  274.     """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """
  275.     if not is_pair(x):
  276.         return [_quote, x]
  277.     require(x, x[0] is not _unquotesplicing, "can't splice here")
  278.     if x[0] is _unquote:
  279.         require(x, len(x)==2)
  280.         return x[1]
  281.     elif is_pair(x[0]) and x[0][0] is _unquotesplicing:
  282.         require(x[0], len(x[0])==2)
  283.         return [_append, x[0][1], expand_quasiquote(x[1:])]
  284.     else:
  285.         return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])]
  286.  
  287. def let(*args):
  288.     args = list(args)
  289.     x = cons(_let, args)
  290.     require(x, len(args)>1)
  291.     bindings, body = args[0], args[1:]
  292.     require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol)
  293.                    for b in bindings), "illegal binding list")
  294.     vars, vals = zip(*bindings)
  295.     return [[_lambda, list(vars)]+map(expand, body)] + map(expand, vals)
  296.  
  297. macro_table = {_let:let} ## More macros can go here
  298.  
  299. eval(parse("""(begin
  300.  
  301. (define-macro and (lambda args
  302.   (if (null? args) #t
  303.       (if (= (length args) 1) (car args)
  304.           `(if ,(car args) (and ,@(cdr args)) #f)))))
  305.  
  306. ;; More macros can also go here
  307.  
  308. )"""))
  309.  
  310. if __name__ == '__main__':
  311.     repl()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement