Linear evaluation macro system

Upstream URL


Li Dzangfan <>



1Linear Evaluation Macro System


  (defun read-first-line (file-name &optional required)
      (:check-type (file-name (or (array character *) pathname)))
      (:let (stream (open file-name)))
      (:defer (close stream))
      (:let (first-line (read-line stream nil)))
      (:return first-line :if (or (not required) first-line))
      (error "File ~A is empty" file-name)))

  ;; Expand to

  (defun read-first-line (file-name &optional required)
      (check-type file-name (or (array character *) pathname))
      (let ((stream (open file-name)))
             (let ((first-line (read-line stream nil)))
               (if (or (not required) first-line)
                   (error "File ~A is empty" file-name)))
          (close stream)))))

Although S-exp is simple, consistent and unambiguous, we do need non-S-exp feature in our code sometime. For example, in most programming languages like C, we can easily write

  char* file_name = get_file_name(stdin);
  assert(file_name != NULL);
  FILE* handle = open_file(file_name);
  assert(handle != NULL);
  Error* error = process(handle);
  if (error != NULL) return FLAG_ERROR;
  return FLAG_OK;

However, in Lisp, we have to write following code without defining macro:

  (let ((file-name (get-file-name stdin)))
    (assert file-name)
    (let ((handle (open-file file-name)))
      (assert handle)
      (let ((err (process handle)))
        (if err

Although the logic is simple, as the indentation increase and the forms nest with others, the code become somewhat complex. This package provides a C-like sequential calculation.

    (:let (file-name (get-file-name stdin)))
    (assert file-name)
    (:let (handle (open-file file-name)))
    (assert handle)
    (:let (err (process handle)))
    (if err :error :ok))

Another example is local functions in Lisp. In Scheme, we use define to define a local function when the body of function is overlong, rather than let:

  (define (sort lst)
    (define (insert elt lst)
      (cond [(null? lst) (list elt)]
            [(< elt (car lst)) (cons elt lst)]
            [else (cons (car lst)
                        (insert elt (cdr lst)))]))
    (if (null? lst)
        (insert (car lst)
                (sort (cdr lst)))))

However, in Common Lisp, since defun always defines a global function instead of a local function, labels and flet are widely used. But the code can become cumbersome quickly as the local functions get longer. Therefore, a Scheme-like feature for defining local functions is available in this package:

  (defun sort (lst)
      (:defun insert (elt lst)
      (if (null lst) ...)))

As we saw, leva (stand for Linearly EVAluate) is the main operator of this package. It is generally a superset of progn: evaluate forms sequentially and treat forms led by keyword specially. Therefore, the title "Linear Evaluation Macro System" does not mean another evaluation rule or a replacement of current Lisp macro system, but a complement of it.


Since leva does not depend on other libraries, you can simply download lineva.lisp and load it. To enable leva cooperate with a bigger system, clone this repository and load lineva.asd by asdf. For example, the simplest way is that define your own system and make it depend on lineva:

  ;; foo.asd

  (require :asdf)

  (in-package :asdf-user)

  (push "/path/to/lineva.lisp/" asdf:*central-registry*)

  (defsystem :foo
    :depends-on (:lineva)
    :components ())

Check document of asdf for more details.

1.3Use leva

leva is the main macro of this package, it is generally a progn. However, forms in top level of body of leva will be treat as a instruction. Formally, leva take any number of forms and each form have one of following shape:

  1. (:name . LAMBDA-LIST)
  2. :name, which is a equivalent of (:name)
  3. Any Lisp form except 1. and 2.

following forms are legal parameter of leva:

  • (:break "Bang!")
  • :break
  • (format t "See you space cb")

Incidentally, :break is a built-in instruction corresponding to standard function break in Common Lisp. Note that a instruction is always a keyword, which allows leva distinguish normal lisp form and instructions. Instructions is valid only in top level of leva. So the following code is invalid:

    (if (null lst)
        (:return lst)))

1.4Create a Instruction

A instruction generally works like macro, except

  1. only works in context of leva
  2. take rest part of evaluation as a parameter

The "rest part of evaluation" means code expanded from parameters of leva which follows current instruction. Take the following code for example:

    (:let (x 1))
    (:let (y 2))
    (+ x y))

For instruction invocation (:let (y 2)), (+ x y) is its "rest code"; for invocation (:let (x 1)), code expanded from (:let (y 2)) is its "rest code". Normally, instruction should not ignore its "rest code".

Instructions are defined by definst, which is basically a equivalent of defmacro except a built-in variable $rest-code is visible in body of definition. For example, to define instruction let, we can write:

  (definst :let (&rest let-arguments)
    "Define local variables by `let'."
    `(let ,let-arguments ,$rest-code))

The first parameter is always a keyword. The second parameter is a lambda-list, which correspond to cdr part of instruction's invocation. Rest parameter is the macro body, which generates code like macro by implicit parameter $rest-code. By convention, if the first component of body is a literal string, it will be interpreted as a docstring of this instruction.

1.5Built-in Instructions

A number of instructions have been defined. Available instructions can be found by (la:available-instructions); detail usage of the instruction can be found by (la:describe-instruction :instruction).

1.5.1Local Variables

lambda-list: :LET (&REST LET-ARGUMENTS)

Define local variables by `let'. LET-ARGUMENTS has the same meaning of `let'.

  (:let (x 10) (y 20))
  (+ x y))


Define local variables by `let' and assert its value. LET-ARGUMENTS has the same meaning of `let'.

(:let-assert (x 10) (y 20) (z nil))
(+ x y z))


Define local function by `flet', FLET-ARGUMENTS has the same meaning with `flet'.

  (:flet (add1 (x) (+ 1 x))
         (dot2 (x) (* 2 x)))
  (dot2 (add1 10)))


Define local function by `labels'. LABELS-ARGUMENTS has the same meaning with `labels'

  (:labels (fib (n)
                (if (< n 2)
                    (+ (fib (- n 1)) (fib (- n 2))))))
  (fib 5))


Define local macro by `macrolet'. MACROLET-ARGUMENTS has the same meaning with `macrolet'.

  (:macrolet (record (&rest values) `(list ,@values)))
  (record "Joe" 20 nil))


Define a local symbol-macro by `symbol-macrolet'.

SYMBOL-MACROLET-ARGUMENTS has the same meaning with`symbol-macrolet'.
(la:leva (:symbol-macrolet (x (format t "...~%")))
  (list x x x))


Define a local function by `labels'.

  (:defun fac (n)
    (if (zerop n)
        (* n (fac (- n 1)))))
  (fac 3))


Define a local variable by `let'.

  (:defvar x 10)


Define local variables by `destructuring-bind'.

  (:bind (a b &rest c) '(1 2 3 4 5))
  (list a b c))

lambda-list: :SETF (PLACE VALUE &KEY (IF T))

Invoke `setf' to set PLACE to VALUE if IF is not `nil'.

  (:defvar name :alexandria)
  (:setf name (symbol-name name)
         :if (not (stringp name)))



Enter debugger by call `break'. Arguments has the same meaning with `break'.

  (:break "Let's ~A!!!" :burn))

lambda-list: :INSPECT (OBJECT)

Enter inspector with OBJECT.

  (:defvar x '(:foo :bar))
  (:inspect x))


Quickly assert that all CONDITIONS is true.

  (:defvar x 10)
  (:assert (numberp x) (plusp x) (evenp x))


Invoke `check-type' over each element of CHECK-TYPE-PARAMETERS.

  (:let (name "Joe") (age 20))
  (:check-type (name (array character *) "a string")
               (age (integer 0 150)))
  (list name age))

1.5.3Contro Flow

lambda-list: :RETURN (VALUE &KEY (IF T))

Return VALUE if condition IF is true.

  (:defvar x (read))
  (:return (- x) :if (minusp x))

lambda-list: :TRY (&REST VALUES)

Return first value in VALUES which is not `nil'. If all VALUES is `nil', evaluate rest code.

  (:defvar table
    '(:bing ""))
  (:try (getf table :google)
        (getf table :duckduckgo)
        (getf table :bing))
  "No search engine available.")

lambda-list: :DEFER (&REST FORMS)

Evaluate rest codes, then evaluate FORMS sequentially. Result of rest code will be returned. Evaluation of rest code will be protected by `unwind-protect'.

  (:defun close-conn () (format t "Bye!~%"))
  (format t "Hello!~%")
  (:defer (close-conn) (terpri))
  (format t "[...]~%"))



Print content to standard output. FORMAT-STRING and ARGUMENTS have the same meaning of `format'.

(la:leva (:printf "Hello ~S!~%" :world))

lambda-list: :PRINTLN (THING)

Print content to standard output and add newline. Use `princ' to output.

(la:leva (:println "Hello world!"))

lambda-list: :PN (THING)

Print content to standard output and add newline. Use `prin1' to output.

(la:leva (:pn "Hello world!"))

Dependencies (1)

  • fiveam

Dependents (0)

    • GitHub
    • Quicklisp