Summary: Tolk — arith.lisp
Table of Contents
Key Claims
tolk/arithis the first interpreter in tolk: arithmetic with+and*.- AST classes (
num,plus,mult,arith-binary-op) are defined insideeval-when (:compile-toplevel :load-toplevel :execute)so thatdefmethod-bindcan introspect them at macro-expansion time. arith-binary-opcarries anopslot (a function object,#'+or#'*), allowing a singleinterpretmethod to handle both binary operations.- The parser uses
ematchwithfare-quasiquotepatterns; the readerread-racket-filehandles.rktpathnames. executecombines parse and interpret; it dispatches on both pathname and s-expression via separate methods.eqo(object equality in tests, defined in fiveam) compares AST nodes by class and slot values, not by identity.
Summary
tolk/arith implements the arithmetic language from PLAI Ch.3 in Common Lisp.
AST classes
(defclass ast-node () ())
(defclass num (ast-node) ((n :type number :initarg :n)))
(defclass arith-binary-op (ast-node)
((op :type function :initarg :op)
(l :type ast-node :initarg :l)
(r :type ast-node :initarg :r)))
(defclass plus (arith-binary-op) ((op :initform #'+)))
(defclass mult (arith-binary-op) ((op :initform #'*)))
The inheritance hierarchy is: ast-node ← arith-binary-op ← plus / mult.
plus and mult differ only in their op initform.
Parser
Uses trivia ematch with fare-quasiquote patterns,
activated via named-readtables:
(defmethod parse (sexp)
(ematch sexp
((type number) (make-instance 'num :n sexp))
(`(+ ,l ,r) (make-instance 'plus :l (parse l) :r (parse r)))
(`(* ,l ,r) (make-instance 'mult :l (parse l) :r (parse r)))))
(defmethod parse ((p pathname))
(mapcar #'parse (read-racket-file p)))
Interpreter
Uses defmethod-bind for concise slot-binding method definitions:
(defmethod-bind interpret ((num n))
()
n)
(defmethod-bind interpret ((arith-binary-op op l r))
()
(funcall op (interpret l) (interpret r)))
The arith-binary-op method covers both plus and mult by CLOS dispatch on
the common superclass. The stored op function is applied to the interpreted
values of l and r.
Execute and tests
(defmethod execute (sexp) (interpret (parse sexp)))
(defmethod execute ((p pathname)) (mapcar #'interpret (parse p)))
Test results: 4/4 parse, 3/3 interpret, 4/4 execute all pass.
Representation choices
The op slot on arith-binary-op that stores #'+ / #'* is a design choice
absent from PLAI's presentation. It means adding a new binary operation
requires only a new subclass with a different op initform, not a new
interpreter method. This is an example of the open/closed principle applied
via CLOS.
Differences / Notes
- Tolk stores the operator as a function slot; PLAI uses separate
plusC/multCconstructors with no shared superclass. Both produce the same results but the CLOS design is more extensible.