[pro] Fwd: Re: Lisp and DSLs

raito at raito.com raito at raito.com
Wed Jul 20 16:20:06 UTC 2011


This may not be much of an example, but it's not only what I have, it's 
what got me hooked on Lisp. 

When I was a young lad, and because I lived not too far from Lake 
Geneva, I played this newfangled thing called D&D (back when it first 
came out. Get off my lawn). And one of the things that intrigued me was 
the tables (the kind where you rolled dice and looked up the results). 
And also being as I was learning programming (on a mainframe. There 
were no microcompputers. See my previous comment about lawns.) I 
thought about writing a table language. One where I could specify not 
only how many chances a particular result had (because I'd already 
figured out that that was better than using the dice themselves for a 
bell curve), but also being able to modify the tables themselves at 
run-time by feeding them arguments, being able to have sub-tables, 
detailed results, etc. But at the time all we had was Honeywell 
mainframe BASIC (supposedly FORTRAN was available, but we couldn't get 
a manual), and even the instructor didn't know anything about writing 
parsers, let alone languages. 

Fast forward, and the idea stuck with me. I mean, I'd gone to school 
and doubles in ECE and Comp. Sci., I should be able to do this. But I 
never did, because it's a very big hassle to write your own language. 
Especially in languages like C. 

So when I started using Lisp, I read up all this stuff about DSLs. Hm, 
I wonder if I could use Lisp to do my language. Well, half an hour and 
15 lines of code later, I not only had my language, but more than I 
wanted. And here it is (ta da):

(defmacro table (name args &rest rest)
   `(defun ,name (, at args)
      (declare (special , at args))
      (let* ((base ',(loop for x in rest collect (car x)))
             (results ',(loop for x in rest collect (cadr x)))
             (chances (loop for x in base collect (eval x)))
             (total (loop for x in chances sum x))
             (roll (+ 1 (random total))))
;(format t "Base:~{~a~^, ~}~%Chances:~{~a~^, ~}~%Results:~{~a~^, 
~}~%total:~A roll:~A~%"
        ;  base chances results total roll)
        (do ((n chances (cdr n))
             (cnt 0)
             (item 0 (+ item 1)))
            ((eq n nil) nil)
          (setf cnt (+ cnt (car n)))
          (when (<= roll cnt)
            (return (eval (nth item results))))))))

and it's used like this (a simple example):

(table my-table (added-chances)
   (1 "one")
   (1 "two")
   ((+ 1 added-chances) "three")
)

I've done some stuff with it that's been quite a bit of fun. Like 
another layer of macro on top for those who think in terms of dice. 

The upshot is that it's a project I didn't do for 25 years because it 
was too big a pain. And I did it in Lisp in half an hour, including 
figuring out that I had to use dynamic scoping for args. I have to use 
dynamic scoping because the forms being eval'ed need to be able to 
access args, but args is not in their lexical scope (though it looks as 
if they are in the macro invocation -- which is the point). 

Neil Gilmore
raito at raito.com




More information about the pro mailing list