Permalink
Browse files

Initial versions of tiddlylisp.py, sqrt.tl, and eval.tl.

  • Loading branch information...
0 parents commit da7339f22606b31b1cfbc6184f7b7cf4a1cf6936 @mnielsen committed Apr 7, 2012
Showing with 324 additions and 0 deletions.
  1. +96 −0 eval.tl
  2. +10 −0 sqrt.tl
  3. +218 −0 tiddlylisp.py
96 eval.tl
@@ -0,0 +1,96 @@
+(define caar (lambda (x) (car (car x))))
+(define cadr (lambda (x) (car (cdr x))))
+(define cadar (lambda (x) (cadr (car x))))
+(define caddr (lambda (x) (cadr (cdr x))))
+(define caddar (lambda (x) (caddr (car x))))
+
+(define not (lambda (x) (if x False True)))
+
+(define append (lambda (x y)
+ (if (null x) y (cons (car x) (append (cdr x) y)))))
+
+(define pair (lambda (x y) (cons x (cons y (q ()) ))))
+
+(define pairlis
+ (lambda (x y)
+ (if (null x)
+ (q ())
+ (cons (pair (car x) (car y)) (pairlis (cdr x) (cdr y))))))
+
+(define assoc (lambda (x y)
+ (if (eq (caar y) x) (cadar y) (assoc x (cdr y)))))
+
+(define eval
+ (lambda (e a)
+ (cond
+ ((atom e) (assoc e a))
+ ((atom (car e))
+ (cond
+ ((eq (car e) (q car)) (car (eval (cadr e) a)))
+ ((eq (car e) (q cdr)) (cdr (eval (cadr e) a)))
+ ((eq (car e) (q cons)) (cons (eval (cadr e) a) (eval (caddr e) a)))
+ ((eq (car e) (q atom)) (atom (eval (cadr e) a)))
+ ((eq (car e) (q eq)) (eq (eval (cadr e) a) (eval (caddr e) a)))
+ ((eq (car e) (q quote)) (cadr e))
+ ((eq (car e) (q q)) (cadr e))
+ ((eq (car e) (q cond)) (evcon (cdr e) a))
+ (True (eval (cons (assoc (car e) a) (cdr e)) a))))
+ ((eq (caar e) (q lambda))
+ (eval (caddar e) (append (pairlis (cadar e) (evlis (cdr e) a)) a))))))
+
+(define evcon
+ (lambda (c a)
+ (cond ((eval (caar c) a) (eval (cadar c) a))
+ (True (evcon (cdr c) a)))))
+
+(define evlis
+ (lambda (m a)
+ (cond ((null m) (q ()))
+ (True (cons (eval (car m) a) (evlis (cdr m) a))))))
+
+
+(define assert-equal (lambda (x y) (= x y)))
+
+(define assert-not-equal (lambda (x y) (not (assert-equal x y))))
+
+(assert-equal (eval (q x) (q ((x test-value))))
+ (q test-value))
+(assert-equal (eval (q y) (q ((y (1 2 3)))))
+ (q (1 2 3)))
+(assert-not-equal (eval (q z) (q ((z ((1) 2 3)))))
+ (q (1 2 3)))
+(assert-equal (eval (q (quote 7)) (q ()))
+ (q 7))
+(assert-equal (eval (q (atom (q (1 2)))) (q ()))
+ False)
+(assert-equal (eval (q (eq 1 1)) (q ((1 1))))
+ True)
+(assert-equal (eval (q (eq 1 2)) (q ((1 1) (2 2))))
+ False)
+(assert-equal (eval (q (eq 1 1)) (q ((1 1))))
+ True)
+(assert-equal (eval (q (car (q (3 2)))) (q ()))
+ (q 3))
+(assert-equal (eval (q (cdr (q (1 2 3)))) (q ()))
+ (q (2 3)))
+(assert-not-equal (eval (q (cdr (q (1 (2 3) 4)))) (q ()))
+ (q (2 3 4)))
+(assert-equal (eval (q (cons 1 (q (2 3)))) (q ((1 1)(2 2)(3 3))))
+ (q (1 2 3)))
+(assert-equal (eval (q (cond ((atom x) (q x-atomic))
+ ((atom y) (q y-atomic))
+ ((q True) (q nonatomic))))
+ (q ((x 1)(y (3 4)))))
+ (q x-atomic))
+(assert-equal (eval (q (cond ((atom x) (q x-atomic))
+ ((atom y) (q y-atomic))
+ ((q True) (q nonatomic))))
+ (q ((x (1 2))(y 3))))
+ (q y-atomic))
+(assert-equal (eval (q (cond ((atom x) (q x-atomic))
+ ((atom y) (q y-atomic))
+ ((q True) (q nonatomic))))
+ (q ((x (1 2))(y (3 4)))))
+ (q nonatomic))
+(assert-equal (eval (q ((lambda (x) (car (cdr x))) (q (1 2 3 4)))) (q ()))
+ 2)
10 sqrt.tl
@@ -0,0 +1,10 @@
+(define sqrt (lambda (x) (sqrt-iter 1.0 x)))
+(define sqrt-iter
+ (lambda (guess x)
+ (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x))))
+(define good-enough?
+ (lambda (guess x) (< (abs (- x (square guess))) 0.00001)))
+(define abs (lambda (x) (if (< 0 x) x (- 0 x))))
+(define square (lambda (x) (* x x)))
+(define improve (lambda (guess x) (average guess (/ x guess))))
+(define average (lambda (x y) (* 0.5 (+ x y))))
@@ -0,0 +1,218 @@
+#### tiddlylisp.py
+#
+# Based on Peter Norvig's lispy (http://norvig.com/lispy.html),
+# copyright by Peter Norvig, 2010.
+#
+# Adaptations by Michael Nielsen. See
+# http://michaelnielsen.org/ddi/lisp-as-the-maxwells-equations-of-software/
+
+import sys
+import traceback
+
+#### Symbol, Env classes
+
+Symbol = str
+
+class Env(dict):
+ "An environment: a dict of {'var':val} pairs, with an outer Env."
+
+ def __init__(self, params=(), args=(), outer=None):
+ self.update(zip(params, 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 add_globals(env):
+ "Add some built-in functions and variables to the environment."
+ import operator
+ env.update(
+ {'+': lambda *args: sum(args),
+ '-': operator.sub,
+ '*': operator.mul,
+ '/': operator.div,
+ '>': operator.gt,
+ '<': operator.lt,
+ '>=': operator.ge,
+ '<=': operator.le,
+ '=': operator.eq
+ })
+ env.update({'True': True, 'False': False})
+ return env
+
+global_env = add_globals(Env())
+
+isa = isinstance
+
+#### eval
+
+def eval(x, env=global_env):
+ "Evaluate an expression in an environment."
+ if isa(x, Symbol): # variable reference
+ return env.find(x)[x]
+ elif not isa(x, list): # constant literal
+ return x
+ elif x[0] == 'quote' or x[0] == 'q': # (quote exp), or (q exp)
+ (_, exp) = x
+ return exp
+ elif x[0] == 'atom': # (atom exp)
+ (_, exp) = x
+ return not isa(eval(exp, env), list)
+ elif x[0] == 'eq': # (eq exp1 exp2)
+ (_, exp1, exp2) = x
+ v1, v2 = eval(exp1, env), eval(exp2, env)
+ return (not isa(v1, list)) and (v1 == v2)
+ elif x[0] == 'car': # (car exp)
+ (_, exp) = x
+ return eval(exp, env)[0]
+ elif x[0] == 'cdr': # (cdr exp)
+ (_, exp) = x
+ return eval(exp, env)[1:]
+ elif x[0] == 'cons': # (cons exp1 exp2)
+ (_, exp1, exp2) = x
+ return [eval(exp1, env)]+eval(exp2,env)
+ elif x[0] == 'cond': # (cond (p1 e1) ... (pn en))
+ for (p, e) in x[1:]:
+ if eval(p, env):
+ return eval(e, env)
+ elif x[0] == 'null': # (null exp)
+ (_, exp) = x
+ return eval(exp,env) == []
+ elif x[0] == 'if': # (if test conseq alt)
+ (_, test, conseq, alt) = x
+ return eval((conseq if eval(test, env) else alt), env)
+ elif x[0] == 'set!': # (set! var exp)
+ (_, var, exp) = x
+ env.find(var)[var] = eval(exp, env)
+ elif x[0] == 'define': # (define var exp)
+ (_, var, exp) = x
+ env[var] = eval(exp, env)
+ elif x[0] == 'lambda': # (lambda (var*) exp)
+ (_, vars, exp) = x
+ return lambda *args: eval(exp, Env(vars, args, env))
+ elif x[0] == 'begin': # (begin exp*)
+ for exp in x[1:]:
+ val = eval(exp, env)
+ return val
+ else: # (fn exp*)
+ exps = [eval(exp, env) for exp in x]
+ fn = exps.pop(0)
+ return fn(*exps)
+
+#### parsing
+
+def parse(s):
+ "Parse a Lisp expression from a string."
+ return read_from(tokenize(s))
+
+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."
+ if not isa(exp, list):
+ return str(exp)
+ else:
+ return '('+' '.join(map(to_string, exp))+')'
+
+#### Load from a file and run
+
+def load(filename):
+ """
+ Load the tiddlylisp program in filename, execute it, and start the
+ repl. If an error occurs, execution stops, and we are left in the
+ repl. Note that load copes with multi-line tiddlylisp code by
+ merging lines until the number of opening and closing parentheses
+ match.
+ """
+ print "Loading and executing %s" % filename
+ f = open(filename, "r")
+ program = f.readlines()
+ f.close()
+ rps = running_paren_sums(program)
+ full_line = ""
+ for (paren_sum, program_line) in zip(rps, program):
+ program_line = program_line.strip()
+ full_line += program_line+" "
+ if paren_sum == 0 and full_line.strip() != "":
+ try:
+ val = eval(parse(full_line))
+ if val is not None: print to_string(val)
+ except:
+ handle_error()
+ print "\nThe line in which the error occurred:\n%s" % full_line
+ break
+ full_line = ""
+ repl()
+
+def running_paren_sums(program):
+ """
+ Map the lines in the list program to a list whose entries contain
+ a running sum of the per-line difference between the number of '('
+ parentheses and the number of ')' parentheses.
+ """
+ count_open_parens = lambda line: line.count("(")-line.count(")")
+ paren_counts = map(count_open_parens, program)
+ rps = []
+ total = 0
+ for paren_count in paren_counts:
+ total += paren_count
+ rps.append(total)
+ return rps
+
+#### repl
+
+def repl(prompt='tiddlylisp> '):
+ "A prompt-read-eval-print loop."
+ while True:
+ try:
+ val = eval(parse(raw_input(prompt)))
+ if val is not None: print to_string(val)
+ except KeyboardInterrupt:
+ print "\nExiting tiddlylisp\n"
+ exit()
+ except:
+ handle_error()
+
+#### error handling
+
+def handle_error():
+ """
+ Simple error handling for both the repl and load.
+ """
+ print "An error occurred. Here's the Python stack trace:\n"
+ traceback.print_exc()
+
+#### on startup from the command line
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ load(sys.argv[1])
+ else:
+ repl()

0 comments on commit da7339f

Please sign in to comment.