Lithp.py (~2008)

wglb1 pts0 comments

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

Print

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...

self environment print lithp source function

Related Articles