Compiling Common Lisp to GNU Emacs Calc



Tags: #calc #commonlisp #emacs

Created on April 26th, 2026.

GNU Emacs Calc is the stack-based calculator that ships with Emacs. Arbitrary-precision arithmetic, a programming model similar to a 1980s RPN calculator, and the ability to be driven through keyboard macros — sequences of keystrokes operating on a stack, replayed with X. The notation is concise, sometimes obscure.

With 123456789 and 3 already on the stack, returning the first three digits of 123456789 takes:

TAB RET H L F 1 + C-u 3 C-M-i - n f S F

More examples in the GitHub project calc-problem-solving, which solves several Project Euler problems.

cl-lisp2calc takes an other route: write the algorithm in Common Lisp, compile it down to the corresponding Calc keystrokes.

A simple example:

(lisp2calc:convert '(+ 3 4))
;; output = 3 SPC 4 +

A less trivial one — Project Euler 1, the sum of all multiples of 3 or 5 below 1000:

(lisp2calc:convert
 '(let* ((n 1000)
         (sum 0))
   (dotimes (i n)
     (when (or (= 0 (mod i 3)) (= 0 (mod i 5)))
       (incf sum i)))
   sum))

The compiler emits:

1000 SPC 0 SPC 0 Z{ RET C-u 4 C-j 1 - a> Z/ 0 C-j 3 % a= Z[ 1 Z: 0 C-j 5 % a= Z] Z[ C-j C-j + C-u 3 M-DEL TAB Z: Z] RET 1 + M-DEL Z} DEL RET M-DEL M-DEL

To run it inside Emacs: highlight the macro, M-x read-kbd-macro, switch to Calc, press X. The right answer comes out.

The supported subset of Common Lisp is deliberately small — arithmetic, comparisons, let/let*, setq/setf, incf/decf, if/when, dotimes, loop repeat, plus a handful of lisp2calc-internal operators (while, prime-factorization, next-prime, last-element). Enough to express the first ten Project Euler problems, all shipped as worked examples in the README.

Unit tests check that convert produces the expected keystrokes for known inputs — but a correct keystroke string and a correct computation are not quite the same thing. An optional integration layer therefore spawns emacs --batch, feeds the generated macro through read-kbd-macro, runs it inside a real Calc session, and reads back the resulting stack, to verify we actually land on the expected result.

Tested with SBCL on Windows.