Linear evaluation macro system
1Linear Evaluation Macro System
(defun read-first-line (file-name &optional required) (la:leva (: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) (progn (check-type file-name (or (array character *) pathname)) (let ((stream (open file-name))) (unwind-protect (let ((first-line (read-line stream nil))) (if (or (not required) first-line) 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 :error :ok))))
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.
(la:leva (: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
to define a local function when the body of function is overlong,
(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) lst (insert (car lst) (sort (cdr lst)))))
However, in Common Lisp, since
defun always defines a global function
instead of a local function,
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) (la:leva (: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.
leva does not depend on other libraries, you can simply download
load it. To enable
leva cooperate with a bigger
clone this repository and load lineva.asd by asdf. For
example, the simplest way is that define your own system and make it
;; 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.
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:
(:name . LAMBDA-LIST)
:name, which is a equivalent of
- Any Lisp form except
following forms are legal parameter of
(format t "See you space cb")
:break is a built-in instruction corresponding to
break in Common Lisp. Note that a instruction is
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:
(la:leva (if (null lst) (:return lst)))
1.4Create a Instruction
A instruction generally works like macro, except
- only works in context of
- 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
(la:leva (: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
defmacro except a built-in variable
$rest-code is visible in
body of definition. For example, to define instruction
let, we can
(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.
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
:LET (&REST LET-ARGUMENTS)
Define local variables by `let'. LET-ARGUMENTS has the same meaning of `let'.
(la:leva (:let (x 10) (y 20)) (+ x y))
:LET-ASSERT (&REST LET-ARGUMENTS)
Define local variables by `let' and assert its value. LET-ARGUMENTS has the same meaning of `let'.
(la:leva (:let-assert (x 10) (y 20) (z nil)) (+ x y z))
:FLET (&REST FLET-ARGUMENTS)
Define local function by `flet', FLET-ARGUMENTS has the same meaning with `flet'.
(la:leva (:flet (add1 (x) (+ 1 x)) (dot2 (x) (* 2 x))) (dot2 (add1 10)))
:LABELS (&REST LABELS-ARGUMENTS)
Define local function by `labels'. LABELS-ARGUMENTS has the same meaning with `labels'
(la:leva (:labels (fib (n) (if (< n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))) (fib 5))
:MACROLET (&REST MACROLET-ARGUMENTS)
Define local macro by `macrolet'. MACROLET-ARGUMENTS has the same meaning with `macrolet'.
(la:leva (:macrolet (record (&rest values) `(list ,@values))) (record "Joe" 20 nil))
:SYMBOL-MACROLET (&REST SYMBOL-MACROLET-ARGUMENTS)
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))
:DEFUN (NAME LAMBDA-LIST &BODY BODY)
Define a local function by `labels'.
(la:leva (:defun fac (n) (if (zerop n) 1 (* n (fac (- n 1))))) (fac 3))
:DEFVAR (NAME &OPTIONAL VALUE)
Define a local variable by `let'.
(la:leva (:defvar x 10) x)
:BIND (LAMBDA-LIST EXPRESSION)
Define local variables by `destructuring-bind'.
(la:leva (:bind (a b &rest c) '(1 2 3 4 5)) (list a b c))
:SETF (PLACE VALUE &KEY (IF T))
Invoke `setf' to set PLACE to VALUE if IF is not `nil'.
(la:leva (:defvar name :alexandria) (:setf name (symbol-name name) :if (not (stringp name))) name)
:BREAK (&OPTIONAL FORMAT-CONTROL &REST FORMAT-ARGUMENTS)
Enter debugger by call `break'. Arguments has the same meaning with `break'.
(la:leva (:break "Let's ~A!!!" :burn))
Enter inspector with OBJECT.
(la:leva (:defvar x '(:foo :bar)) (:inspect x))
:ASSERT (&REST CONDITIONS)
Quickly assert that all CONDITIONS is true.
(la:leva (:defvar x 10) (:assert (numberp x) (plusp x) (evenp x)) x)
:CHECK-TYPE (&REST CHECK-TYPE-PARAMETERS)
Invoke `check-type' over each element of CHECK-TYPE-PARAMETERS.
(la:leva (:let (name "Joe") (age 20)) (:check-type (name (array character *) "a string") (age (integer 0 150))) (list name age))
:RETURN (VALUE &KEY (IF T))
Return VALUE if condition IF is true.
(la:leva (:defvar x (read)) (:return (- x) :if (minusp x)) x)
:TRY (&REST VALUES)
Return first value in VALUES which is not `nil'. If all VALUES is `nil', evaluate rest code.
(la:leva (:defvar table '(:bing "cn.bing.com")) (:try (getf table :google) (getf table :duckduckgo) (getf table :bing)) "No search engine available.")
: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'.
(la:leva (:defun close-conn () (format t "Bye!~%")) (format t "Hello!~%") (:defer (close-conn) (terpri)) (format t "[...]~%"))
:PRINTF (FORMAT-STRING &REST ARGUMENTS)
Print content to standard output. FORMAT-STRING and ARGUMENTS have the same meaning of `format'.
(la:leva (:printf "Hello ~S!~%" :world))
Print content to standard output and add newline. Use `princ' to output.
(la:leva (:println "Hello world!"))
Print content to standard output and add newline. Use `prin1' to output.
(la:leva (:pn "Hello world!"))