robpike/lisp turns page 13 of the 1962 manual into a working interpreter
Toy Lisp 1.5 interpreter
At a glance
- What is it?
- robpike/lisp is a toy implementation of Lisp 1.5, the language defined in the first few pages of the LISP 1.5 Programmer's Manual from MIT in 1962, written in Go and installed with a single go install command. The author calls it a pedagogical experiment, lists its shortcomings plainly, and says he plans to use it as a teaching tool rather than a practical programming environment.
- Who is it for?
- robpike/lisp suits a teacher or a learner who wants to see a minimal Lisp evaluator, or a reader who wants to understand what EVALQUOTE and APPLY actually do, since the whole program is a direct Go rendering of them. It does not suit anyone who wants to write Lisp programs, because it has no SET or SETQ, no PROG, no character handling and no I/O, and the author says so himself.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A toy interpreter with a stated purpose
The description of the repository is a single line, Toy Lisp 1.5 interpreter, and the README explains where the language comes from. It is an implementation of the language defined, with what the README calls sublime concision, in the first few pages of the LISP 1.5 Programmer's Manual by McCarthy, Abrahams, Edwards, Hart, and Levin, from MIT in 1962.
The stated experiment is precise: to see just how well the interpreter, actually EVALQUOTE and APPLY, defined on page 13 of that book really works. The answer given is perfectly, of course.
That framing explains everything about the project. It is not trying to be a modern Lisp, and the README says its purpose was fun and education, and in no way to create a modern or even realistic Lisp implementation. The goal was to turn that one page into a working interpreter using clean, direct Go code. The code is in the lisp1_5/ directory, main.go holds the entry point, lib.lisp is a startup library, and the module is robpike.io/lisp with no dependencies beyond the standard library.
The audience is a teacher, a student, or a reader who wants to understand the smallest possible complete Lisp. That is a narrow audience and a real one, and the project is honest about it rather than dressing the toy up as a product.
The shortfall list is in the README, which is unusual and useful
Most interpreter projects list what they can do. This one lists what it cannot, and it does so prominently.
The program has several profound shortcomings, the README says, even with respect to the Lisp 1.5 book. There is no SET or SETQ, so nothing can assign to a variable after evaluation. There is no PROG, and the explanation is the interesting part: the interpreter, by calling APPLY, can evaluate only a single expression, a possibly recursive function invocation. But this is Lisp, and that is still a lot.
Then no character handling, and no I/O, which means the system is interactive only, although it can start by reading a file specified on the command line.
The line about being slow and very, very far from Common Lisp or Scheme is there too, and so is the note that the author may add PROG and perhaps SET[Q] one day, and that this is about where he would draw the line. The plan is to use it as a teaching tool, not a practical programming environment.
This honesty is what makes the project useful as a document. A reader who wants to know what the smallest complete Lisp looks like gets the list of omissions alongside the list of features, which is the same information a textbook would give in a chapter on evaluation. The missing PROG in particular is not a missing feature so much as the point of the exercise, since the absence of a way to sequence more than one expression is what forces the single expression discipline that the rest of the design follows from.
Lexical scoping is the one deliberate departure from the book
There is one thing the implementation does differently from the 1962 text, and the README calls it out as important: lexical scoping.
In the book, a Lisp 1.5 function is an association list of local bindings threaded through the evaluator, so a function reaches its bindings through the environment it was defined in and called from. This implementation has no association list at all. A function has access only to the locals in its own frame, as well as to globals such as T.
That is a substantive change, not a cosmetic one, and it is the kind of change that has consequences in every direction. It makes the evaluator simpler, because there is no environment list to search and no alist to copy on each call, and it makes closure behaviour different from the Lisp 1.5 original, so a program written from the book may not behave the same here.
For a teaching tool, the simplification is a feature. The whole point of the exercise is to make EVALQUOTE and APPLY legible, and an evaluator with an environment chain threaded through every call is considerably harder to read than one with a frame. The trade is that the interpreter is no longer a faithful implementation of the book, and the README is upfront that it deviates in this respect.
It is also worth noting what lexical scoping does not fix. Without SET and SETQ there is still no way to rebind a name, so the scoping rule is a change in how names are looked up rather than a step towards a complete language. The two features are independent, and the project has both of them out of scope today.
The dialect looks nothing like the Lisp you probably know
The syntax is a short set of deviations, and each of them would confuse anyone arriving with modern Lisp habits, which is why the README lists them.
Comments start with a semicolon and run to the end of the line. The quote shorthand is the familiar one, with 'A meaning (QUOTE A). Case is unusual: T and F are upper case, but all the other words, car and nil and such, are lower case. The addition function is called add and not +, and identifiers must be alphanumeric, which rules out the symbol characters that later Lisps inherited from the 1970s.
Numbers are implemented using Go's big.Int, so there is no floating point, and the numbers can be big. The demo in the README makes that visible with the factorial of 100 printing a hundred digit integer.
Identifiers can be Unicode, and for fun the Greek lambda is a synonym for lambda, with the parenthetical observation that it is really the other way around. The function definition form uses a defn builtin that takes a list of name and lambda pairs, so a single defn can define several functions at once, as the add2 and add4 example shows.
None of this is accidental cruelty. The 1962 language is small on purpose, and the deviations are chosen so that the evaluator stays small, which is the stated goal. But a reader who copies a modern Lisp example into this interpreter will get a confusing error, and the README's list is the thing to read first.
The built-in list is short, and the naming comes from Fortran
The built-in functions are given in one paragraph, and the naming rationale is a small pleasure.
The author never liked to type DIFFERENCE or QUOTIENT, so arithmetic uses add, sub, mul, div and rem. The comparison operators come from Fortran, why not, and they are eq, ne, lt, le, gt and ge, along with and and or.
The rest are the classic ones: apply, atom, car, cdr, cond, cons, list, null and quote.
What the list does not contain is as informative as what it does. There is no evaluation operator, no assignment, no iteration, no printing and no character predicates, and that set of omissions is a fair summary of what a language without PROG and without I/O can be.
The arity conventions are ordinary Lisp. The list function is variadic, and the README demonstrates it against the cons equivalent it replaces, with (list 'a 'b '(c)) giving the same result as nesting three cons calls, which is the kind of comparison a teaching tool should spend space on.
The function definition form is worth pausing on, because it is where the dialect diverges from what a reader will expect. Instead of a single def, a defn takes a parenthesised list of bindings, so the example writes (defn ((add2 (λ (n) (add 2 n))) (add4 (λ (n) (add2 (add2 n))))), and the interpreter replies with the list of names it defined, (add2) and (add4). The tool is telling you what it created, which is a helpful touch in a REPL aimed at people learning.
The transcript shows the one real constraint on the language
The example session in the README is worth reading as a document, because it demonstrates the interpreter's central limitation through a failure rather than through prose.
The session starts with a library in lib.lisp, and passing it as an argument causes lisp to load it before reading standard input, so the transcript opens with % lisp lib.lisp and then a list of the definitions that were loaded, fac, gcd, ack, equal, not, negate, mapcar, length, opN, member, union and intersection.
The interesting part comes later, when the author builds a function that returns a lambda and then tries to call it. A direct call of the computed form produces cannot eval ((addN 5) 3), and the README comments on it as a Lisp 1.5-ism: apply versus eval. The workaround is to go through apply, so (apply (addN 5) 3) returns 8, and the same pattern then drives a mapcar over a list of numbers.
That is the missing PROG showing up in practice. Because the evaluator can only evaluate a single expression through APPLY, a value that is itself a computed function has to be applied explicitly rather than called in place, and the transcript walks through a second function, opN, that takes the operator as a parameter, to show the pattern generalising.
The last demonstration is the Ackermann function, with the comment that apply was called 51535 times, and the answer 125. It is the traditional way of showing that an interpreter is really working, since the function grows fast enough to exercise the evaluator hard without being hard to write.
Install it with one command and treat it as a reading exercise
Installation is the shortest instruction in the repository:
go install robpike.io/lisp@latestThe module is robpike.io/lisp and the go.mod requires Go 1.25.0, with no third party dependencies, so the build is the standard library and the project source.
For a reader's first session, the shape of the work is: build or install it, run lisp with lib.lisp as an argument so the standard library is loaded, and then work through the example transcript. The demo covers the pieces that matter, how to inspect a definition with car, cdr and the list accessors, how to build a list with list instead of nested cons, how to define a function with defn, and how to apply a computed lambda.
What a reader gets from that session is a mental model of a Lisp evaluator, and what they do not get is a Lisp to work in. The README says it directly: no SET or SETQ, no PROG, no character handling, no I/O, and it is slow. So the honest use is to read the transcript and then read the evaluator in lisp1_5/, which is the actual product of the project, an implementation of EVALQUOTE and APPLY in Go that you can step through in a debugger.
On maintenance, the licence is BSD-3-Clause, the repository is not archived, the last push was on 2026-10-11, and there are no published releases, which is consistent with a repository whose artefact is a program you install from the module path rather than a versioned distribution. The changelog of the language is the 1962 manual, and this repository is the transcription of it into something you can run.
Editorial conclusion
robpike/lisp suits a teacher or a learner who wants to see a minimal Lisp evaluator, or a reader who wants to understand what EVALQUOTE and APPLY actually do, since the whole program is a direct Go rendering of them. It does not suit anyone who wants to write Lisp programs, because it has no SET or SETQ, no PROG, no character handling and no I/O, and the author says so himself. Verify first that the language you want to teach or study is actually Lisp 1.5, since the dialect deliberately departs from what most people call Lisp, and read the known limitations list before planning a session around assignment or iteration.
Frequently asked questions
What is robpike/lisp?
It is a toy Lisp 1.5 interpreter written in Go, an implementation of the language defined in the first few pages of the LISP 1.5 Programmer's Manual by McCarthy, Abrahams, Edwards, Hart and Levin from MIT in 1962, and the author describes it as a pedagogical experiment and a teaching tool.
How do I install the robpike/lisp interpreter?
With go install robpike.io/lisp@latest. The module requires Go 1.25.0 and declares no third party dependencies, and the interpreter itself lives in the lisp1_5/ directory with the entry point in main.go.
What does the robpike/lisp interpreter deliberately leave out?
There is no SET or SETQ, no PROG, no character handling and no I/O, so it is interactive only apart from starting by reading a file given on the command line. The README states it is slow and far from Common Lisp or Scheme, and that PROG and possibly SETQ might be added one day.
How does robpike/lisp differ from the original Lisp 1.5 book?
It uses lexical scoping, where the book uses an association list. There is no association list here, so a function has access only to the locals in its own frame and to globals such as T.
Which functions does the robpike/lisp interpreter provide?
Arithmetic is add, sub, mul, div and rem, comparisons come from Fortran as eq, ne, lt, le, gt and ge, there is and and or, and the rest are apply, atom, car, cdr, cond, cons, list, null and quote, with defn used to define functions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/robpike-lisp)Was this useful?