So, we’ve described our domain-specific language (DSL). We want something Lisp-like, but with a few additional syntaxes. First, we have a new assignment construct with the ‘<- symbol when appearing to the right of X, Y, Z, or W. Also, we have an implicit assignment to X if that new assignment symbol does not appear anywhere in the forms.
This assignment syntax is not lisp-like. The symbol does not appear in a function context, and the standard Lisp reader would not handle it well. This is where the use of macros enters. One important advantage of Lisp macros when designing DSLs is that the body of a macro is not fully parsed. It has to enter through the standard reader, so parentheses must be balanced, single-quotes for literal lists and double-quotes for strings still behave the same way, and so on, but the symbols themselves are not interpreted. A macro can manipulate the body forms in many ways before passing them to the eval stage.
We need to characterize the forms we’ve received before we can manipulate them. To decide whether there is an implicit assignment, we’ll need to walk the expression forms we’ve been given and identify all of the symbols used in the tree (the tree is the list of body forms). We’ll say that if the ‘<- symbol appears, then there’s at least one of our special assignment functions, so no implicit assignment will be used. So, our first requirement is a function that returns a list of all symbols in the tree it is passed. That will be this function, get-symbols-in-list:
(defun get-symbols-in-list (rlist) (let ((rval '())) (dolist (element rlist) (cond ((listp element) (setf rval (append rval (get-symbols-in-list element)))) ((symbolp element) (push element rval)))) (delete-duplicates rval)))
This function parses the passed list and its sublists, and collects a list of distinct symbols. Each appears only once. Here’s the output acting on the factorial form from the previous post:
CL-USER> (get-symbols-in-list '((let ((n X) (rv X)) (assert (and (integerp X) (> X 0))) (dotimes (i (1- n)) (setf rv (* rv (- n i 1)))) X <- rv))) (<- LET ASSERT AND INTEGERP X > DOTIMES 1- SETF RV * I N -)