diff --git a/src/py/atom.py b/src/py/atom.py index 874dbe9..e268212 100644 --- a/src/py/atom.py +++ b/src/py/atom.py @@ -23,10 +23,11 @@ def __init__(self, d): def __eq__(self, rhs): if isinstance(rhs, Atom): - return (self.data == rhs.data) + return self.data == rhs.data else: return False + #### Symbols # The symbol was the basic atom in Lisp 1 and served as the basic unit of data. In his early @@ -44,6 +45,7 @@ def __hash__(self): def eval(self, env, args=None): return env.get(self.data) + #### Truth # The first symbol created is `t`, corresponding to logical true. It's a little unclear to me @@ -82,7 +84,9 @@ def eval(self, env, args=None): # I originally added the ability to `combine` strings and symbols, but I might pull that back. def cons(self, e): if e.__class__ != self.__class__ and e.__class__ != Symbol.__class__: - raise UnimplementedFunctionError("Cannot cons a string and a ", e.__class__.__name__) + raise UnimplementedFunctionError( + "Cannot cons a string and a ", e.__class__.__name__ + ) return String(e.data + self.data) diff --git a/src/py/env.py b/src/py/env.py index fb906bc..914aceb 100644 --- a/src/py/env.py +++ b/src/py/env.py @@ -1,23 +1,23 @@ -# The `Environment` class represents the dynamic environment of McCarthy's original Lisp. The creation of -# this class is actually an interesting story. As many of you probably know, [Paul Graham wrote a paper and -# code for McCarthy's original Lisp](http://www.paulgraham.com/rootsoflisp.html) and it was my first exposure to +# The `Environment` class represents the dynamic environment of McCarthy's original Lisp. The creation of +# this class is actually an interesting story. As many of you probably know, [Paul Graham wrote a paper and +# code for McCarthy's original Lisp](http://www.paulgraham.com/rootsoflisp.html) and it was my first exposure to # the stark simplicity of the language. The simplicity is breath-taking! # -# However, while playing around with the code I found that in using the core functions (i.e. `null.`, `not.`, etc.) -# I was not experiencing the full effect of the original. That is, the original Lisp was dynamically scoped, but -# the Common Lisp used to implement and run (CLisp in the latter case) Graham's code was lexically scoped. Therefore, +# However, while playing around with the code I found that in using the core functions (i.e. `null.`, `not.`, etc.) +# I was not experiencing the full effect of the original. That is, the original Lisp was dynamically scoped, but +# the Common Lisp used to implement and run (CLisp in the latter case) Graham's code was lexically scoped. Therefore, # by attempting to write high-level functions using only the magnificent 7 and Graham's core functions in the Common Lisp # I was taking advantage of lexical scope; something not available to McCarthy and company. Of course, the whole reason # that Graham wrote `eval.` was to enforce dynamic scoping (he used a list of symbol-value pairs where the dynamic variables # were added to its front when introduced). However, that was extremely cumbersome to use: -# +# # (eval. 'a '((a 1) (a 2))) # ;=> 1 # # So I then implemented a simple REPL in Common Lisp that fed input into `eval.` and maintained the current environment list. # That was fun, but I wasn't sure that I was learning anything at all. Therefore, years later I came across the simple # REPL and decided to try to implement my own core environment for the magnificent 7 to truly get a feel for what it took -# to build a simple language up from scratch. I suppose if I were a real manly guy then I would have found an IBM 704, but +# to build a simple language up from scratch. I suppose if I were a real manly guy then I would have found an IBM 704, but # that would be totally insane. (email me if you have one that you'd like to sell for cheap) # # Anyway, the point of this is that I needed to start with creating an `Environment` that provided dynamic scoping, and the @@ -51,7 +51,7 @@ def set(self, key, value): if key in self.binds: self.binds[key] = value elif self.parent: - self.parent.set(key,value) + self.parent.set(key, value) else: self.binds[key] = value @@ -60,7 +60,7 @@ def definedp(self, key): return True return False - + # Push a new binding by creating a new Env # # Dynamic scope works like a stack. Whenever a variable is created it's binding is pushed onto a @@ -69,7 +69,7 @@ def definedp(self, key): # # (label a nil) # (label frobnicate (lambda () (cons a nil))) - # + # # ((lambda (a) # (frobnicate)) # (quote x)) @@ -82,7 +82,7 @@ def definedp(self, key): # | ------- | # | a = nil | # +---------+ - # + # # Meaning that when accessing `a`, `frobnicate` will get the binding at the top of the stack, producing the result `(x)`. This push/pop # can become difficult, so people have to do all kinds of tricks to avoid confusion (i.e. pseudo-namespace via variable naming schemes). # @@ -92,7 +92,7 @@ def push(self, bnd=None): def pop(self): return self.parent - def __repr__( self): + def __repr__(self): ret = "\nEnvironment %s:\n" % self.level keys = [i for i in self.binds.keys() if not i[:2] == "__"] diff --git a/src/py/error.py b/src/py/error.py index be179de..a23e004 100644 --- a/src/py/error.py +++ b/src/py/error.py @@ -1,10 +1,13 @@ # I one day plan to create a whole battery of errors so that the REPL provides a detailed report whenever # something goes wrong. That day is not now. + class Error(Exception): """Base class for exceptions in this module.""" + pass + class UnimplementedFunctionError(Error): def __init__(self, message, thing): self.thing = thing @@ -13,6 +16,7 @@ def __init__(self, message, thing): def __str__(self): return self.message + repr(self.thing) + class EvaluationError(Error): def __init__(self, env, args, message): self.env = env @@ -20,4 +24,6 @@ def __init__(self, env, args, message): self.message = message def __str__(self): - return self.message + ", " + repr(self.args) + " in environment " + self.env.level + return ( + self.message + ", " + repr(self.args) + " in environment " + self.env.level + ) diff --git a/src/py/fun.py b/src/py/fun.py index 7137fc4..69421b2 100644 --- a/src/py/fun.py +++ b/src/py/fun.py @@ -11,13 +11,14 @@ def __init__(self, fn): self.fn = fn self.hint = "fun" - def __repr__( self): + def __repr__(self): return "" % id(self.fn) # Evaluation just delegates out to the builtin. def eval(self, env, args): return self.fn(env, args) + # λ λ λ # The real power of McCarthy's Lisp srpings from Alonzo Chruch's λ-calculus. @@ -26,7 +27,7 @@ def __init__(self, n, b): # The names that occur in the arg list of the lambda are bound (or dummy) variables self.names = n # Unlike the builtin functions, lambdas have arbitrary bodies - self.body = b + self.body = b def __repr__(self): return "" % id(self) @@ -47,7 +48,9 @@ def push_bindings(self, containing_env, values): # The bindings are set one by one corresponding to the input values. def set_bindings(self, containing_env, values): for i in range(len(values)): - containing_env.environment.binds[self.names[i].data] = values[i].eval(containing_env.environment) + containing_env.environment.binds[self.names[i].data] = values[i].eval( + containing_env.environment + ) # The evaluation of a lambda is not much more complicated than a builtin function, except that it will # establish bindings in the root context. Additionally, the root context will hold all bindings, so free @@ -56,7 +59,11 @@ def eval(self, env, args): values = [a for a in args] if len(values) != len(self.names): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(len(self.names), len(args))) + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format( + len(self.names), len(args) + ) + ) # Dynamic scope requires that names be bound on the global environment stack ... LITHP = env.get("__lithp__") @@ -73,6 +80,7 @@ def eval(self, env, args): LITHP.pop() return ret + # Closures # McCarthy's Lisp does not define closures and in fact the precense of closures in the context of a pervasive dynamic diff --git a/src/py/interface.py b/src/py/interface.py index 9557aab..3ab0c71 100644 --- a/src/py/interface.py +++ b/src/py/interface.py @@ -7,6 +7,7 @@ class Eval: def eval(self, environment, args=None): raise EvaluationError(environment, args, "Evaluation error") + # I read Henry Baker's paper *Equal Rights for Functional Objects or, The More Things Change, The More They Are the Same* # and got a wild hair about `egal`. However, it turns out that in McCarthy's Lisp the idea is trivial to the extreme. Oh well... # it's still a great paper. [Clojure](http://clojure.org)'s creator Rich Hickey summarizes `egal` much more succinctly than I ever could: @@ -18,4 +19,3 @@ def eval(self, environment, args=None): class Egal: def __eq__(self, rhs): raise UnimplementedFunctionError("Function not yet implemented", rhs) - diff --git a/src/py/lisp.py b/src/py/lisp.py index a7bc844..8194e29 100644 --- a/src/py/lisp.py +++ b/src/py/lisp.py @@ -29,7 +29,7 @@ # class Lisp: SPECIAL = "()" - + # The magnificent seven are tainted by a pair of useful, but ugly functions, `dummy` and `println` # purely for practical matters. def dummy(self, env, args): @@ -39,9 +39,9 @@ def dummy(self, env, args): def println(self, env, args): for a in args: result = a.eval(env) - self.stdout.write( "%s " % str( result)) + self.stdout.write("%s " % str(result)) - self.stdout.write( "\n") + self.stdout.write("\n") return TRUE #### `cond` @@ -50,7 +50,7 @@ def println(self, env, args): # Stephen Kleene defined the notion of a *primitive recursive function* and McCarthy built on # that by defining the conditional as a way to simplify the definition of recursive functions. # How would you define a recursive function without the use of a conditional in the terminating condition? - # It turns out that you *can* define recursive functions this way (see fixed point combinators), but the + # It turns out that you *can* define recursive functions this way (see fixed point combinators), but the # use of the conditional vastly simplifies the matter. # # We take conditionals for granted these days so it's difficult to imagine writing programs that @@ -58,10 +58,10 @@ def println(self, env, args): # # The `cond` form is used as follows: # - # (cond ((atom (quote (a b))) (quote foo)) - # ((atom (quote a)) (quote bar)) + # (cond ((atom (quote (a b))) (quote foo)) + # ((atom (quote a)) (quote bar)) # (t (quote baz))) - # + # # ;=> bar # def cond(self, env, args): @@ -75,7 +75,7 @@ def cond(self, env, args): #### `eq` - # Equality is delegated out to the objects being tested, so I will not discuss the mechanics here. + # Equality is delegated out to the objects being tested, so I will not discuss the mechanics here. # However, examples of usage are as follows: # # (eq nil (quote ())) @@ -89,7 +89,9 @@ def cond(self, env, args): # def eq(self, env, args): if len(args) > 2: - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(2, len(args))) + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(2, len(args)) + ) if args[0].eval(env) == args[1].eval(env): return TRUE @@ -102,7 +104,7 @@ def eq(self, env, args): # # (quote a) # ;=> a - # + # # (quote (car (quote (a b c)))) # ;=> (car (quote (a b c))) # @@ -112,47 +114,49 @@ def eq(self, env, args): # ;=> a # def quote(self, env, args): - if(len(args) > 1): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(1, len(args))) + if len(args) > 1: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(1, len(args)) + ) return args[0] #### `car` # The original Lisp implementation was written for the IBM 704 by Steve Russell (a genius of the highest - # order -- also the creator/discoverer of [Spacewar!](http://pdp-1.computerhistory.org/pdp-1/?f=theme&s=4&ss=3) + # order -- also the creator/discoverer of [Spacewar!](http://pdp-1.computerhistory.org/pdp-1/?f=theme&s=4&ss=3) # and continuations). The somewhat obtuse name for a function that returns the first element of an s-expression # derives from the idiosyncracies of the IBM 704 on which Lisp was first implemented. The `car` function was # thus a shortening of the term "Contents of the Address part of Register number" that in itself has a very interesting # explanation. That is, `car` was used to refer to the first half of the wordsize addressed by the IBM 704. In this # particular machine (and many others at that time and since) the wordsize could address more than twice of the - # actual physical memory. Taking this particular nuance of the IBM 704 into account, programmers were able to - # efficiently create stacks by using the address of the stack's top in one half-word and the negative of the + # actual physical memory. Taking this particular nuance of the IBM 704 into account, programmers were able to + # efficiently create stacks by using the address of the stack's top in one half-word and the negative of the # allocated size in the other (the "Contents of Decrement part of Register number"), like so: # - # +----------+----------+ - # | top | -size | - # +----------+----------+ - # | | size goes toward zero - # | | | - # | | | - # | | v - # | | | | - # 4 | | | | - # | V | | - # 3 | elem3 | | - # | | | ^ - # 2 | elem2 | | | - # | | | | - # 1 | elem1 |<-----+ stack grows up + # +----------+----------+ + # | top | -size | + # +----------+----------+ + # | | size goes toward zero + # | | | + # | | | + # | | v + # | | | | + # 4 | | | | + # | V | | + # 3 | elem3 | | + # | | | ^ + # 2 | elem2 | | | + # | | | | + # 1 | elem1 |<-----+ stack grows up # | | # 0 | elem0 | # +--------+ - # + # # Whenever something was pushed onto the stack the number `1` was added to both half-words. If the decrement # part of the word became zero then that signalled a stack-overflow, that was checked on each push or pop # instruction. However, the use of the car/cdr half-words was used quite differently (McCarthy 1962). That is, - # The contents part contained a pointer to the memory location of the actual cons cell (see the documentation for + # The contents part contained a pointer to the memory location of the actual cons cell (see the documentation for # the next function `cdr` for more information) element, and the decrement part contained a pointer to the # next cell: # @@ -167,34 +171,38 @@ def quote(self, env, args): # # (car (quote (a b c))) # ;=> a - # + # # The car of an empty list is an error (TODO: check if this is the case in McCarthy's Lisp) # def car(self, env, args): - if(len(args) > 1): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(1, len(args))) - + if len(args) > 1: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(1, len(args)) + ) + # Of course, I do not use pointer arithmetic to implement cons cells... cell = args[0].eval(env) - + # ... instead I define it in terms of a sequence abstraction. This is a side-effect of originally # hoping to go further with this implementation (e.g. into linear Lisp), but as of now it's a bit heavy-weight # for what is actually needed. But I wouldn't be a programmer if I didn't needlessly abstract. if not isinstance(cell, Seq): - raise ValueError("Function car not valid on non-sequence type: %s" % cell.data) + raise ValueError( + "Function car not valid on non-sequence type: %s" % cell.data + ) return cell.car() #### `cdr` - # In the previous function definition (`car`) I used the term cons-cell to describe the primitive structure underlying a - # Lisp list. If you allow me, let me spend a few moments describing this elegant structure, and why it's such an important + # In the previous function definition (`car`) I used the term cons-cell to describe the primitive structure underlying a + # Lisp list. If you allow me, let me spend a few moments describing this elegant structure, and why it's such an important # abstract data type (ADT). # - # Lisp from the beginning was built with the philosophy that lists should be a first-class citizen of the language; not only in + # Lisp from the beginning was built with the philosophy that lists should be a first-class citizen of the language; not only in # the realm of execution, but also generation and manipulation. If you look at my implementation of `List` in [seq.py](seq.html) # you'll notice that it's pretty standard fare. That is, it, like most lisp implementations is backed by a boring sequential store - # where one element conceptually points to the next and blah blah blah. **Boring**. Where the cons-cell shines is that it is a + # where one element conceptually points to the next and blah blah blah. **Boring**. Where the cons-cell shines is that it is a # very general purpose ADT that can be used in a number of ways, but primary among them is the ability to represent the list. # # Lists in the early Lisp was precisely a chain of cons cells and the operators `car` and `cdr` pointed to very @@ -208,14 +216,14 @@ def car(self, env, args): # * Heterogeneous # # It would be interesting to learn the precise genesis of the idea behind the cons cell, but I imagine that it must have provoked - # a eureka moment. + # a eureka moment. # # I've already discussed how the IBM 704 hardware was especially ammenable to solving this problem efficiently, but the other points # bear further consideration. Lisp popularly stands for "LISt Processing language" but as I explained, the basic unit of data was # instead the cons cell structure. The fact of the matter is that the cons cell serves as both the implementation detail for lists # **and** the abstraction of a pair, all named oddly as if the implementation mattered. If Lisp had originally gone whole hog into the # abstraction game, then `car` and `cdr` would have been `first` and `rest` and would have spared the world decades of whining. - # + # # Modern Lisps like Common Lisp rarely implement lists as chains of cons cells. Instead, it's preferred to create proper lists # with the `list` or `list*` functions and access them via `first` or `rest` (`cons` still persists thanks to its more general # meaning of "construct") and to only use `car` and `cdr` when dealing with cons cells. You can probably tell a lot about the @@ -227,17 +235,21 @@ def car(self, env, args): # # (cdr (quote (a b c))) # ;=> (b c) - # + # # The cdr of an empty list is an empty list (TODO: check if this is the case in McCarthy's Lisp) # def cdr(self, env, args): - if(len(args) > 1): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(1, len(args))) + if len(args) > 1: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(1, len(args)) + ) cell = args[0].eval(env) if not isinstance(cell, Seq): - raise ValueError("Function cdr not valid on non-sequence type: %s" % cell.data) + raise ValueError( + "Function cdr not valid on non-sequence type: %s" % cell.data + ) return cell.cdr() @@ -246,7 +258,7 @@ def cdr(self, env, args): # So if Common Lisp has a more general sequence abstraction, then why would we still want to keep the cons cell? The reason is # that the cons cell is more flexible than a sequence and allows for a more intuitive way to build things like trees, pairs, and # to represent code structure. - # + # # This function simply delegates the matter of consing to the target object. # # The `cons` function works as follows: @@ -259,16 +271,18 @@ def cdr(self, env, args): # # (cons (quote a) (quote b)) # ;=> Error - # + # # I've agonized long and hard over wheter or not to implement McCarthy Lisp as the language described in *Recursive functions...* # as the anecdotal version only partially described in the *LISP 1.5 Programmer's Manual* and in most cases the former was my # choice. The creation of "dotted pairs" (I believe) was not an aspect of the original description and therefore is not represented - # in Lithp. Sadly, I think that in some cases these version are mixed because I originally went down the path of creating a version of + # in Lithp. Sadly, I think that in some cases these version are mixed because I originally went down the path of creating a version of # Litho compatible with linear Lisp and Lisp 1.5, so this is a product of some pollution in the varying ideas. # def cons(self, env, args): - if(len(args) > 2): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(2, len(args))) + if len(args) > 2: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(2, len(args)) + ) first = args[0].eval(env) second = args[1].eval(env) @@ -284,18 +298,20 @@ def cons(self, env, args): # # (atom (quote a)) # ;=> t - # + # # (atom nil) # ;=> t - # + # # (atom (quote (a b c))) # ;=> () # # Recall that the empty list is falsity. # def atom(self, env, args): - if(len(args) > 1): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(1, len(args))) + if len(args) > 1: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(1, len(args)) + ) first = args[0].eval(env) @@ -310,13 +326,15 @@ def atom(self, env, args): # Defines a named binding in the dynamic environment. def label(self, env, args): - if(len(args) != 2): - raise ValueError("Wrong number of arguments, expected {0}, got {1}".format(2, len(args))) - + if len(args) != 2: + raise ValueError( + "Wrong number of arguments, expected {0}, got {1}".format(2, len(args)) + ) + # Notice that the first argument to `label` (a symbol) is **not** evaluated. This is the key difference between # a Lisp function and a special form (and macro, but I will not talk about those here). That is, in *all* # cases the arguments to a function are evaluated from left to right before being passed into the function. - # Conversely, special forms have special semantics for evaluation that cannot be directly emulated or implemented + # Conversely, special forms have special semantics for evaluation that cannot be directly emulated or implemented # using functions. env.set(args[0].data, args[1].eval(env)) return env.get(args[0].data) diff --git a/src/py/lithp.py b/src/py/lithp.py index a6aa77c..0dde542 100644 --- a/src/py/lithp.py +++ b/src/py/lithp.py @@ -5,7 +5,7 @@ # It wasn't enough to write the Lisp interpreter -- I also wanted to share what I learned with *you*. Reading # this source code provides a snapshot into the mind of John McCarthy, Steve Russell, Timothy P. Hart, and Mike Levin and # as an added bonus, myself. The following source files are available for your reading: -# +# # - [atom.py](atom.html) # - [env.py](env.html) # - [error.py](error.html) @@ -17,12 +17,12 @@ # - [reader.py](reader.html) # - [seq.py](seq.html) # - [core.lisp](core.html) -# +# # The Lithp interpreter requires Python 2.6.1+ to function. # please add comments, report errors, annecdotes, etc. to the [Lithp Github project page](http://github.com/fogus/lithp) -# +# import pdb -import getopt, sys, io +import getopt, sys, io, os from env import Environment from fun import Function from atom import TRUE @@ -38,19 +38,22 @@ WWW = "http://fogus.me/fun/lithp/" PROMPT = "lithp" DEPTH_MARK = "." +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) + class Lithp(Lisp): - """ The Lithper class is the interpreter driver. It does the following: - 1. Initialize the global environment - 2. Parse the cl arguments and act on them as appropriate - 3. Initialize the base Lisp functions - 4. Read input - 5. Evaluate - 6. Print - 7. Loop back to #4 + """The Lithper class is the interpreter driver. It does the following: + 1. Initialize the global environment + 2. Parse the cl arguments and act on them as appropriate + 3. Initialize the base Lisp functions + 4. Read input + 5. Evaluate + 6. Print + 7. Loop back to #4 """ - def __init__( self): - iostreams=(sys.stdin, sys.stdout, sys.stderr) + + def __init__(self): + iostreams = (sys.stdin, sys.stdout, sys.stderr) (self.stdin, self.stdout, self.stderr) = iostreams self.debug = False @@ -65,42 +68,42 @@ def __init__( self): def init(self): # Define core functions - self.environment.set("eq", Function(self.eq)) - self.environment.set("quote", Function(self.quote)) - self.environment.set("car", Function(self.car)) - self.environment.set("cdr", Function(self.cdr)) - self.environment.set("cons", Function(self.cons)) - self.environment.set("atom", Function(self.atom)) - self.environment.set("cond", Function(self.cond)) - + self.environment.set("eq", Function(self.eq)) + self.environment.set("quote", Function(self.quote)) + self.environment.set("car", Function(self.car)) + self.environment.set("cdr", Function(self.cdr)) + self.environment.set("cons", Function(self.cons)) + self.environment.set("atom", Function(self.atom)) + self.environment.set("cond", Function(self.cond)) + # Define utility function - self.environment.set("print", Function(self.println)) + self.environment.set("print", Function(self.println)) # Special forms self.environment.set("lambda", Function(self.lambda_)) - self.environment.set("label", Function(self.label)) + self.environment.set("label", Function(self.label)) # Define meta-elements - self.environment.set("__lithp__", self) + self.environment.set("__lithp__", self) self.environment.set("__global__", self.environment) def usage(self): self.print_banner() - print - print NAME.lower(), " [lithp files]\n" + print() + print(NAME.lower(), " [lithp files]\n") def print_banner(self): - print "The", NAME, "programming shell", VERSION - print " by Fogus,", WWW - print " Type :help for more information" - print + print("The", NAME, "programming shell", VERSION) + print(" by Fogus,", WWW) + print(" Type :help for more information") + print() def print_help(self): - print "Help for Lithp v", VERSION - print " Type :help for more information" - print " Type :env to see the bindings in the current environment" - print " Type :load followed by one or more filenames to load source files" - print " Type :quit to exit the interpreter" + print("Help for Lithp v", VERSION) + print(" Type :help for more information") + print(" Type :env to see the bindings in the current environment") + print(" Type :load followed by one or more filenames to load source files") + print(" Type :quit to exit the interpreter") def push(self, env=None): if env: @@ -114,7 +117,7 @@ def pop(self): def repl(self): while True: # Stealing the s-expression parsing approach from [CLIPS](http://clipsrules.sourceforge.net/) - source = self.get_complete_command() + source = self.get_complete_command() # Check for any REPL directives try: @@ -126,13 +129,12 @@ def repl(self): files = source.split(" ")[1:] self.process_files(files) elif source in [":env"]: - print(self.environment) + print((self.environment)) else: self.process(source) except AttributeError: - print "Could not process command: ", source + print("Could not process command: ", source) return - # Source is processed one s-expression at a time. def process(self, source): @@ -144,7 +146,7 @@ def process(self, source): try: result = self.eval(sexpr) except Error as err: - print(err) + print((err)) if self.verbose: self.stdout.write(" %s\n" % result) @@ -154,9 +156,9 @@ def process(self, source): # In the process of living my life I had always heard that closures and dynamic scope # cannot co-exist. As a thought-experiment I can visualize why this is the case. That is, # while a closure captures the contextual binding of a variable, lookups in dynamic scoping - # occur on the dynamic stack. This means that you may be able to close over a variable as - # long as it's unique, but the moment someone else defines a variable of the same name - # and attempt to look up the closed variable will resolve to the top-most binding on the + # occur on the dynamic stack. This means that you may be able to close over a variable as + # long as it's unique, but the moment someone else defines a variable of the same name + # and attempt to look up the closed variable will resolve to the top-most binding on the # dynamic stack. This assumes the the lookup occurs before the variable of the same name # is popped. While this is conceptually easy to grasp, I still wanted to see what would # happen in practice -- and it wasn't pretty. @@ -171,7 +173,7 @@ def eval(self, sexpr): try: return sexpr.eval(self.environment) except ValueError as err: - print(err) + print((err)) return FALSE # A complete command is defined as a complete s-expression. Simply put, this would be any @@ -181,15 +183,18 @@ def get_complete_command(self, line="", depth=0): line = line + " " if self.environment.level != 0: - prompt = PROMPT + " %i%s " % (self.environment.level, DEPTH_MARK * (depth+1)) + prompt = PROMPT + " %i%s " % ( + self.environment.level, + DEPTH_MARK * (depth + 1), + ) else: if depth == 0: prompt = PROMPT + "> " else: - prompt = PROMPT + "%s " % (DEPTH_MARK * (depth+1)) + prompt = PROMPT + "%s " % (DEPTH_MARK * (depth + 1)) line = line + self.read_line(prompt) - + # Used to balance the parens balance = 0 for ch in line: @@ -201,20 +206,20 @@ def get_complete_command(self, line="", depth=0): balance = balance - 1 if balance > 0: # Balanced parens gives zero - return self.get_complete_command(line, depth+1) + return self.get_complete_command(line, depth + 1) elif balance < 0: raise ValueError("Invalid paren pattern") else: return line - def read_line(self, prompt) : + def read_line(self, prompt): if prompt and self.verbose: self.stdout.write("%s" % prompt) self.stdout.flush() line = self.stdin.readline() - if(len(line) == 0): + if len(line) == 0: return "EOF" if line[-1] == "\n": @@ -227,11 +232,11 @@ def process_files(self, files): self.verbose = False for filename in files: - infile = open( filename, 'r') + infile = open(filename, "r") self.stdin = infile source = self.get_complete_command() - while(source not in ["EOF"]): + while source not in ["EOF"]: self.process(source) source = self.get_complete_command() @@ -241,18 +246,21 @@ def process_files(self, files): self.verbose = True -if __name__ == '__main__': + +if __name__ == "__main__": lithp = Lithp() try: - opts, files = getopt.getopt(sys.argv[1:], "hd", ["help", "debug", "no-core", "no-closures"]) + opts, files = getopt.getopt( + sys.argv[1:], "hd", ["help", "debug", "no-core", "no-closures"] + ) except getopt.GetoptError as err: # Print help information and exit: - print(str( err)) # will print something like "option -a not recognized" + print((str(err))) # will print something like "option -a not recognized" lithp.usage() sys.exit(1) - for opt,arg in opts: + for opt, arg in opts: if opt in ("--help", "-h"): lithp.usage() sys.exit(0) @@ -263,11 +271,11 @@ def process_files(self, files): elif opt in ("--no-closures"): lithp.closures = False else: - print("unknown option " + opt) + print(("unknown option " + opt)) # Process the core lisp functions, if applicable if lithp.core: - lithp.process_files(["../core.lisp"]) + lithp.process_files([os.path.join(CURR_DIR, "..", "core.lisp")]) if len(files) > 0: lithp.process_files(files) diff --git a/src/py/number.py b/src/py/number.py index 152e58d..29b8192 100644 --- a/src/py/number.py +++ b/src/py/number.py @@ -4,36 +4,38 @@ class Number(Eval): - def __init__( self, v): + def __init__(self, v): self.data = v - def __repr__( self): + def __repr__(self): return repr(self.data) - def eval( self, env, args=None): + def eval(self, env, args=None): return self def __eq__(self, rhs): if isinstance(rhs, Number): - return (self.data == rhs.data) + return self.data == rhs.data else: return False + class Integral(Number): - REGEX = re.compile(r'^[+-]?\d+$') + REGEX = re.compile(r"^[+-]?\d+$") - def __init__( self, v): + def __init__(self, v): Number.__init__(self, v) + class LongInt(Number): - REGEX = re.compile(r'^[+-]?\d+[lL]$') + REGEX = re.compile(r"^[+-]?\d+[lL]$") - def __init__( self, v): + def __init__(self, v): Number.__init__(self, v) + class Float(Number): - REGEX = re.compile(r'^[+-]?(\d+\.\d*$|\d*\.\d+$)') + REGEX = re.compile(r"^[+-]?(\d+\.\d*$|\d*\.\d+$)") - def __init__( self, v): + def __init__(self, v): Number.__init__(self, v) - diff --git a/src/py/reader.py b/src/py/reader.py index e1f0fa3..05afd8a 100644 --- a/src/py/reader.py +++ b/src/py/reader.py @@ -8,15 +8,16 @@ DELIM = string.whitespace + Lisp.SPECIAL + class Reader: def __init__(self, str=None): - self.raw_source = str - self.index = 0 - self.length = 0 - self.sexpr = [] + self.raw_source = str + self.index = 0 + self.length = 0 + self.sexpr = [] - if str: - self.sexpr = self.get_sexpr() + if str: + self.sexpr = self.get_sexpr() def get_sexpr(self, source=None): if source: @@ -27,14 +28,14 @@ def get_sexpr(self, source=None): token = self.get_token() expr = None - if token == ')': + if token == ")": raise ValueError("Unexpected right paren") - elif token == '(': + elif token == "(": expr = [] token = self.get_token() - while token != ')': - if token == '(': + while token != ")": + if token == "(": # Start parsing again. self.prev() expr.append(self.get_sexpr()) @@ -49,7 +50,6 @@ def get_sexpr(self, source=None): else: return token - def get_token(self): if self.index >= self.length: return None diff --git a/src/py/seq.py b/src/py/seq.py index 44ea91e..9d9933e 100644 --- a/src/py/seq.py +++ b/src/py/seq.py @@ -1,18 +1,23 @@ from interface import Eval, Egal from error import UnimplementedFunctionError + class Seq(Eval, Egal): - def __init__( self): + def __init__(self): self.data = None def car(self): return self.data[0] def cdr(self): - raise UnimplementedFunctionError("Function not yet implemented for ", self.__class__.__name__) + raise UnimplementedFunctionError( + "Function not yet implemented for ", self.__class__.__name__ + ) def cons(self, e): - raise UnimplementedFunctionError("Function not yet implemented for ", self.__class__.__name__) + raise UnimplementedFunctionError( + "Function not yet implemented for ", self.__class__.__name__ + ) # The following four functions needed for iterability def __iter__(self): @@ -57,7 +62,7 @@ def cdr(self): return List([]) def cons(self, e): - ret = List(self.data[:]) # bugfix 1234977437 + ret = List(self.data[:]) # bugfix 1234977437 ret.data.insert(0, e) return ret diff --git a/src/py/tests/test_atoms.py b/src/py/tests/test_atoms.py index 7986bb6..1641cf3 100644 --- a/src/py/tests/test_atoms.py +++ b/src/py/tests/test_atoms.py @@ -5,19 +5,20 @@ from seq import List from env import Environment + class AtomTests(TestCase): def test_truthiness(self): - self.assertEquals(TRUE, Symbol("t")) + self.assertEqual(TRUE, Symbol("t")) def test_falsiness(self): - self.assertEquals(FALSE, List()) + self.assertEqual(FALSE, List()) def test_atomness(self): foo = Atom("foo") another_foo = Atom("foo") bar = Atom("bar") baz = Atom("baz") - + self.assertTrue(foo == foo) self.assertTrue(foo == another_foo) self.assertTrue(foo != bar) @@ -28,8 +29,8 @@ def test_symbolness(self): foo = Symbol("foo") another_foo = Symbol("foo") bar = Symbol("bar") - e = Environment(None, {"foo":foo}) - + e = Environment(None, {"foo": foo}) + self.assertTrue(foo != bar) self.assertTrue(foo == another_foo) self.assertTrue(another_foo == foo)