This project is written for educational purposes.
MIT
- Integer:
123
,42
,13
. Signed integer value. - Float:
3.14
,9.8
. Double-preicison floating-point value (IEEE 754). - String:
"abc"
,"string"
. Double-quoted sequence of characters. - Symbol:
lambda
,cond
,x
. Sequence of non-whitespace characters (- some other characters). - Function: lambda or primitive.
- Cons cell.
- NIL: Special kind of value. Used as false value and to mark end of list.
- Self-evaluable types: integer, float, string, function and nil.
- Evaluation of symbols:
- Retrieve the value of symbol from lexical environment.
- If there is no binding, then get the global binding of the symbol.
- Evaluation of cons cells (only proper lists are allowed to be evaluated):
- Evaluate every member of the list.
- The first member of the list should be a function.
- Call the first member of the evaluated list with arguments that are the rest of the evaluated list.
- GC.
- No closures.
- Macros.
- Special variables.
&optional
and&rest
arguments. No keyword arguments.- Some handling of internal errors with restarts.
- Special forms:
quote
,if
,execute
,lambda
,macro
,define
,let-one
,macro-expand
,cond
,redefine
. - Primitives:
binary-add
,binary-substract
,binary-multiply
,binary-divide
,binary-greater
,binary-less
,negate
,%
,round
,floor
,ceil
,truncate
,random
,eq
,eql
,apply
,print
,print-char
,print-pretty
,print-new-line
,load
,read-line
,read
,read-all
,first
,rest
,set-car
,set-cdr
,cons
,list
,null-p
,cons-p
,list-p
,callable-p
,lambda-p
,macro-p
,primitive-p
,integer-p
,float-p
,number-p
,string-p
,symbol-p
,eval
,system-quit
,symbol-intern
,internal-get-stack-trace
. - From prelude:
binary-append
,define-macro
,define-function
,id
,second
,third
,last-p
,plus-p
,minus-p
,nth
,binary-greater-equal
,binary-less-equal
,when
,unless
,let
,print-ln
,not
,and
,or
,call-function
,composition
,reduce
,reduce1
,+
,-
,*
,/
,complement-eql
,>
,>=
,<
,<=
,=
,/=
,equal
,map
,filter
,copy-list
,append
,reverse
,abs
,even-p
,odd-p
,list-length
,length
,alist-get
,plist-get
,take
,drop
,format
,quit
,string-append
.
Lisp value has a two level representation:
- Class
Lisp::Value
is a sum type betweeninteger
,float
,primitive
,nil
and pointer toobject
. It is implemented withstd::variant
help. - Class
Lisp::Object
is a root class for all referencable types and types that hold more than one field or hold variable sized information. The function type is split intoLisp::Primitive
andLisp::LambdaFunction
, becasue a primitive is a simple C++ function pointer, but lambda holds two fields ofValue
(parameters and body).
- The interpreter uses mark-and-sweep GC algorithm.
- All managed objects should inherit from
Lisp::Object
and be created viaLisp::MemoryManager::AllocateObject
. - Objects form an intrusive singly-linked linked list.
- All objects contain mark bit and a pointer to the next allocated object.
- The mark stage and deletion are handled by virtual functions.
- The interpreter interns all strings and symbols. But there is a way to make them without interning.
- Namespace
Lisp::Funcs
contains some function that are useful when dealing with Lisp values. These functions don't check types. - Namespace
Lisp::Primitives
contains primitive functions. - The class
Lisp::Context
is the main class of the interpreter. It's a class that stores intermediate information for functions that operate over Lisp values. - You will notice, that there are lots of classes. I decided to split different algorithmic parts of the interpreter into classes that hold reference to
Lisp::Context
, even if they can be implemented as functions that take aLisp::Context&
parameter.
Because the definition of data and code in Lisp is deeply interconnected, it's natural to Lisp to have different implementations of syntax checking stage, macro expansion stage with evaluation.
I don't understand this part fully, because I haven't read many books about interpreters and Lisp. So for now, I decided to perform macro expansion while evaluating Lisp forms and not to check the correctnes of lambda parameters.