https://github.com/dzangfan/lineva
Linear evaluation macro system for Common Lisp
https://github.com/dzangfan/lineva
lisp macro utility
Last synced: 3 months ago
JSON representation
Linear evaluation macro system for Common Lisp
- Host: GitHub
- URL: https://github.com/dzangfan/lineva
- Owner: dzangfan
- License: gpl-3.0
- Created: 2022-09-20T07:03:00.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-09-22T02:35:11.000Z (over 2 years ago)
- Last Synced: 2025-01-06T07:12:10.983Z (5 months ago)
- Topics: lisp, macro, utility
- Language: Common Lisp
- Homepage:
- Size: 49.8 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- License: LICENSE
Awesome Lists containing this project
README
* Linear Evaluation Macro System
** Introduction
#+begin_src lisp
(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)))))
#+end_srcAlthough 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#+begin_src C
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;
#+end_srcHowever, in Lisp, we have to write following code without defining
macro:#+begin_src lisp
(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))))
#+end_srcAlthough 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.#+begin_src lisp
(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))
#+end_srcAnother 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~:#+begin_src scheme
(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)))))
#+end_srcHowever, 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:#+begin_src lisp
(defun sort (lst)
(la:leva
(:defun insert (elt lst)
...)
(if (null lst) ...)))
#+end_srcAs 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.** Installation
Since ~leva~ does not depend on other libraries, you can simply download
[[https://github.com/dzangfan/lineva.lisp/blob/main/lineva.lisp][lineva.lisp]] and ~load~ it. To enable ~leva~ cooperate with a bigger
system, ~clone~ this repository and load [[https://github.com/dzangfan/lineva.lisp/blob/main/lineva.asd][lineva.asd]] by [[https://asdf.common-lisp.dev/][asdf]]. For
example, the simplest way is that define your own system and make it
depend on ~lineva~:#+begin_src lisp
;; foo.asd(require :asdf)
(in-package :asdf-user)
(push "/path/to/lineva.lisp/" asdf:*central-registry*)
(defsystem :foo
:depends-on (:lineva)
:components ())
#+end_srcCheck [[https://asdf.common-lisp.dev/asdf/][document of asdf]] for more details.
** Use ~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:#+begin_src lisp
(la:leva
(if (null lst)
(:return lst)))
#+end_src** Create a Instruction
A instruction generally works like macro, except
1. only works in context of ~leva~
2. take rest part of evaluation as a parameterThe "rest part of evaluation" means code expanded from parameters of
~leva~ which follows current instruction. Take the following code for
example:#+begin_src lisp
(la:leva
(:let (x 1))
(:let (y 2))
(+ x y))
#+end_srcFor 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:#+begin_src lisp
(definst :let (&rest let-arguments)
"Define local variables by `let'."
`(let ,let-arguments ,$rest-code))
#+end_srcThe 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.** Built-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)~.*** Local Variables
**** :let
*lambda-list*: ~:LET (&REST LET-ARGUMENTS)~
Define local variables by `let'. LET-ARGUMENTS has the same
meaning of `let'.#+begin_src lisp
(la:leva
(:let (x 10) (y 20))
(+ x y))
#+end_src**** :let-assert
*lambda-list*: ~:LET-ASSERT (&REST LET-ARGUMENTS)~
Define local variables by `let' and assert its
value. LET-ARGUMENTS has the same meaning of `let'.#+begin_src lisp
(la:leva
(:let-assert (x 10) (y 20) (z nil))
(+ x y z))
#+end_src**** :flet
*lambda-list*: ~:FLET (&REST FLET-ARGUMENTS)~
Define local function by `flet', FLET-ARGUMENTS has the same
meaning with `flet'.#+begin_src lisp
(la:leva
(:flet (add1 (x) (+ 1 x))
(dot2 (x) (* 2 x)))
(dot2 (add1 10)))
#+end_src**** :labels
*lambda-list*: ~:LABELS (&REST LABELS-ARGUMENTS)~
Define local function by `labels'. LABELS-ARGUMENTS has the same
meaning with `labels'#+begin_src lisp
(la:leva
(:labels (fib (n)
(if (< n 2)
1
(+ (fib (- n 1)) (fib (- n 2))))))
(fib 5))
#+end_src**** :macrolet
*lambda-list*: ~:MACROLET (&REST MACROLET-ARGUMENTS)~
Define local macro by `macrolet'. MACROLET-ARGUMENTS has the same
meaning with `macrolet'.#+begin_src lisp
(la:leva
(:macrolet (record (&rest values) `(list ,@values)))
(record "Joe" 20 nil))
#+end_src**** :symbol-macrolet
*lambda-list*: ~:SYMBOL-MACROLET (&REST SYMBOL-MACROLET-ARGUMENTS)~
Define a local symbol-macro by `symbol-macrolet'.
SYMBOL-MACROLET-ARGUMENTS has the same meaning with
`symbol-macrolet'.#+begin_src lisp
(la:leva (:symbol-macrolet (x (format t "...~%")))
(list x x x))
#+end_src**** :defun
*lambda-list*: ~:DEFUN (NAME LAMBDA-LIST &BODY BODY)~
Define a local function by `labels'.
#+begin_src lisp
(la:leva
(:defun fac (n)
(if (zerop n)
1
(* n (fac (- n 1)))))
(fac 3))
#+end_src**** :defvar
*lambda-list*: ~:DEFVAR (NAME &OPTIONAL VALUE)~
Define a local variable by `let'.
#+begin_src lisp
(la:leva
(:defvar x 10)
x)
#+end_src**** :bind
*lambda-list*: ~:BIND (LAMBDA-LIST EXPRESSION)~
Define local variables by `destructuring-bind'.
#+begin_src lisp
(la:leva
(:bind (a b &rest c) '(1 2 3 4 5))
(list a b c))
#+end_src**** :setf
*lambda-list*: ~:SETF (PLACE VALUE &KEY (IF T))~
Invoke `setf' to set PLACE to VALUE if IF is not `nil'.
#+begin_src lisp
(la:leva
(:defvar name :alexandria)
(:setf name (symbol-name name)
:if (not (stringp name)))
name)
#+end_src*** Debug
**** :break
*lambda-list*: ~:BREAK (&OPTIONAL FORMAT-CONTROL &REST FORMAT-ARGUMENTS)~
Enter debugger by call `break'. Arguments has the same meaning with
`break'.#+begin_src lisp
(la:leva
(:break "Let's ~A!!!" :burn))
#+end_src**** :inspect
*lambda-list*: ~:INSPECT (OBJECT)~
Enter inspector with OBJECT.
#+begin_src lisp
(la:leva
(:defvar x '(:foo :bar))
(:inspect x))
#+end_src**** :assert
*lambda-list*: ~:ASSERT (&REST CONDITIONS)~
Quickly assert that all CONDITIONS is true.
#+begin_src lisp
(la:leva
(:defvar x 10)
(:assert (numberp x) (plusp x) (evenp x))
x)
#+end_src**** :check-type
*lambda-list*: ~:CHECK-TYPE (&REST CHECK-TYPE-PARAMETERS)~
Invoke `check-type' over each element of CHECK-TYPE-PARAMETERS.
#+begin_src lisp
(la:leva
(:let (name "Joe") (age 20))
(:check-type (name (array character *) "a string")
(age (integer 0 150)))
(list name age))
#+end_src*** Contro Flow
**** :return
*lambda-list*: ~:RETURN (VALUE &KEY (IF T))~
Return VALUE if condition IF is true.
#+begin_src lisp
(la:leva
(:defvar x (read))
(:return (- x) :if (minusp x))
x)
#+end_src**** :try
*lambda-list*: ~:TRY (&REST VALUES)~
Return first value in VALUES which is not `nil'. If all VALUES is
`nil', evaluate rest code.#+begin_src lisp
(la:leva
(:defvar table
'(:bing "cn.bing.com"))
(:try (getf table :google)
(getf table :duckduckgo)
(getf table :bing))
"No search engine available.")
#+end_src**** :defer
*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'.#+begin_src lisp
(la:leva
(:defun close-conn () (format t "Bye!~%"))
(format t "Hello!~%")
(:defer (close-conn) (terpri))
(format t "[...]~%"))
#+end_src*** Display
**** :printf
*lambda-list*: ~:PRINTF (FORMAT-STRING &REST ARGUMENTS)~
Print content to standard output. FORMAT-STRING and ARGUMENTS have
the same meaning of `format'.#+begin_src lisp
(la:leva (:printf "Hello ~S!~%" :world))
#+end_src**** :println
*lambda-list*: ~:PRINTLN (THING)~
Print content to standard output and add newline. Use `princ' to
output.#+begin_src lisp
(la:leva (:println "Hello world!"))
#+end_src**** :pn
*lambda-list*: ~:PN (THING)~
Print content to standard output and add newline. Use `prin1' to
output.#+begin_src lisp
(la:leva (:pn "Hello world!"))
#+end_src