UP | HOME

Summary: Tolk — arith.lisp

Table of Contents

Key Claims

  • tolk/arith is the first interpreter in tolk: arithmetic with + and *.
  • AST classes (num, plus, mult, arith-binary-op) are defined inside eval-when (:compile-toplevel :load-toplevel :execute) so that defmethod-bind can introspect them at macro-expansion time.
  • arith-binary-op carries an op slot (a function object, #'+ or #'*), allowing a single interpret method to handle both binary operations.
  • The parser uses ematch with fare-quasiquote patterns; the reader read-racket-file handles .rkt pathnames.
  • execute combines 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-nodearith-binary-opplus / 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 / multC constructors with no shared superclass. Both produce the same results but the CLOS design is more extensible.

Related