lithp.py
lithp.py
Lithp - A interpreter for John McCarthy's original Lisp.
The heavily documented code for Lithp can be found on Github.
It wasn't enough to write the Lisp interpreter -- I also wanted to share what I learned with you. Reading<br>this source code provides a snapshot into the mind of John McCarthy, Steve Russell, Timothy P. Hart, and Mike Levin and<br>as an added bonus, myself. The following source files are available for your reading:
atom.py
env.py
error.py
fun.py
interface.py
lisp.py
lithp.py (this file)
number.py
reader.py
seq.py
core.lisp
The Lithp interpreter requires Python 2.6.1+ to function.<br>please add comments, report errors, annecdotes, etc. to the Lithp Github project page
import pdb<br>import getopt, sys, io<br>from env import Environment<br>from fun import Function<br>from atom import TRUE<br>from atom import FALSE<br>from lisp import Lisp<br>from reader import Reader<br>from error import Error<br>from fun import Lambda<br>from fun import Closure
NAME = "Lithp"<br>VERSION = "v1.1"<br>WWW = "http://fogus.me/fun/lithp/"<br>PROMPT = "lithp"<br>DEPTH_MARK = "."
The Lithper class is the interpreter driver. It does the following:
class Lithp(Lisp):
Initialize the global environment
Parse the cl arguments and act on them as appropriate
Initialize the base Lisp functions
Read input
Evaluate
Loop back to #4
def __init__( self):<br>iostreams=(sys.stdin, sys.stdout, sys.stderr)<br>(self.stdin, self.stdout, self.stderr) = iostreams
self.debug = False<br>self.verbose = True<br>self.core = True<br>self.closures = True
self.rdr = Reader()<br>self.environment = Environment()
self.init()
def init(self):
Define core functions
self.environment.set("eq", Function(self.eq))<br>self.environment.set("quote", Function(self.quote))<br>self.environment.set("car", Function(self.car))<br>self.environment.set("cdr", Function(self.cdr))<br>self.environment.set("cons", Function(self.cons))<br>self.environment.set("atom", Function(self.atom))<br>self.environment.set("cond", Function(self.cond))
Define utility function
self.environment.set("print", Function( self.println))
Special forms
self.environment.set("lambda", Function(self.lambda_))<br>self.environment.set("label", Function(self.label))
Define core symbols
self.environment.set("t", TRUE)
There is one empty list, and it's named nil
self.environment.set("nil", FALSE)
Define meta-elements
self.environment.set("__lithp__", self)<br>self.environment.set("__global__", self.environment)
def usage(self):<br>self.print_banner()<br>print<br>print NAME.lower(), " [lithp files]\n"
def print_banner(self):<br>print "The", NAME, "programming shell", VERSION<br>print " by Fogus,", WWW<br>print " Type :help for more information"<br>print
def print_help(self):<br>print "Help for Lithp v", VERSION<br>print " Type :help for more information"<br>print " Type :env to see the bindings in the current environment"<br>print " Type :load followed by one or more filenames to load source files"<br>print " Type :quit to exit the interpreter"
def push(self, env=None):<br>if env:<br>self.environment = self.environment.push(env)<br>else:<br>self.environment = self.environment.push()
def pop(self):<br>self.environment = self.environment.pop()
def repl(self):<br>while True:
Stealing the s-expression parsing approach from CLIPS
source = self.get_complete_command()
Check for any REPL directives
if source in [":quit"]:<br>break<br>elif source in [":help"]:<br>self.print_help()<br>elif source.startswith(":load"):<br>files = source.split(" ")[1:]<br>self.process_files(files)<br>elif source in [":env"]:<br>print(self.environment)<br>else:<br>self.process(source)
Source is processed one s-expression at a time.
def process(self, source):<br>sexpr = self.rdr.get_sexpr(source)
while sexpr:<br>result = None
try:<br>result = self.eval(sexpr)<br>except Error as err:<br>print(err)
if self.verbose:<br>self.stdout.write(" %s\n" % result)
sexpr = self.rdr.get_sexpr()
In the process of living my life I had always heard that closures and dynamic scope<br>cannot co-exist. As a thought-experiment I can visualize why this is the case. That is,<br>while a closure captures the contextual binding of a variable, lookups in dynamic scoping<br>occur on the dynamic stack. This means that you may be able to close over a variable as<br>long as it's unique, but the moment someone else defines a variable of the same name<br>and attempt to look up the closed variable will resolve to the top-most binding on the<br>dynamic stack. This assumes the the lookup occurs before the variable of the same name<br>is popped. While this is conceptually easy to grasp, I still wanted to see what would<br>happen in practice -- and it wasn't pretty.
def lambda_(self, env, args):<br>if self.environment != env.get("__global__") and self.closures:<br>return Closure(env, args[0], args[1:])<br>else:<br>return Lambda(args[0], args[1:])
Delegate evaluation to the form.
def eval(self, sexpr):<br>try:<br>return sexpr.eval(self.environment)<br>except ValueError as err:<br>print(err)<br>return FALSE
A complete command is defined as a complete s-expression. Simply put, this would be any<br>atom or any list with a...