Skip to content

HectareaGalbis/expanders

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Expanders

Welcome to Expanders!! :D

What is this?

This project lets you define expanders. An expander is like a namespace for forms that can be expanded by a macro.

It easier to understand with an example. Suppose we have the following form (op (+ 3 4) b). This form will be used in two different macros named plus-macro and minus-macro. Each macro receives a form. That form could start with op. In that case, op is substituted by + or - respectively.

Let's define 2 different expanders:

;; We assume (use-package #:expanders)
(defexpander plus-expander)
(defexpander minus-expander)
;; Returns
MINUS-EXPANDER

Now we have two different expanders. The function exp:expanderp can tell us if a symbol denotes an expander:

(expanderp 'plus-expander)
;; Returns
T
(expanderp 'hey)
;; Returns
NIL
(expanderp 'minus-expander)
;; Returns
T

Now it is time to define the expansion op for each expander using exp:defexpansion:

(defexpansion plus-expander op (a b)
  "OP to + expansion"
  `(+ ,a ,b))

(defexpansion minus-expander op (a b)
  "OP to - expansion"
  `(- ,a ,b))
;; Returns
OP

We can check if a symbol is an expansion for a given expander using exp:expansionp:

(expansionp 'plus-expander 'op)
;; Returns
T
(expansionp 'plus-expander 'hey)
;; Returns
NIL
(expansionp 'minus-expander 'op)
;; Returns
T

Also, we can retrieve or set the docstring using documentation:

(documentation 'op 'plus-expander)
;; Returns
"OP to + expansion"
(let ((old-docstring (documentation 'op 'minus-expander)))
  (setf (documentation 'op 'minus-expander) "Another docstring")
  (let ((new-docstring (documentation 'op 'minus-expander)))
    (format t "Old: ~s~%New: ~s" old-docstring new-docstring)))
;; Output
Old: "OP to - expansion"
New: "Another docstring"
;; Returns
NIL

We can expand an expansion using exp:expand:

(expand 'plus-expander 'op 3 '(+ 5 6))
;; Returns
(+ 3 (+ 5 6))
(expand 'minus-expander 'op 3 '(+ 5 6))
;; Returns
(- 3 (+ 5 6))

But usually is more convenient to use the exp:expand* function:

(expand* 'plus-expander 'op '(3 (+ 5 6)))
;; Returns
(+ 3 (+ 5 6))

or:

(expand* 'plus-expander '(op 3 (+ 5 6)))
;; Returns
(+ 3 (+ 5 6))

Finally, let's define the macros plus-macro and minus-macro:

(defmacro plus-macro (form)
  (if (and (consp form)
           (expansionp 'plus-expander (car form)))
      (expand* 'plus-expander form)
      form))
;; Returns
PLUS-MACRO
(defmacro minus-macro (form)
  (if (and (consp form)
           (expansionp 'minus-expander (car form)))
      (expand* 'minus-expander form)
      form))
;; Returns
MINUS-MACRO

If we use the form (op 5 4) we will see that each macro will expand to (+ 5 4) or (- 5 4) respectively.

(plus-macro (op 5 4))
;; Returns
9
(minus-macro (op 5 4))
;; Returns
1

Why?

  • It is common: I have noticed that having expanders is a relatively common pattern in macros. The best example is setf and its setf-expanders. Another project using expanders is CFFI and its type parsers. In my own projects I ended up using the same techniques (Clith).
  • Duality of syntax: We can increase the duality of syntax using expanders. The best example is setf. Thanks to setf we don't need names for setters because they come for free from getters.

Reference

Macro: exp:defexpander (sym)

Define an expander represented by the symbol SYM.
If used at top level the expander will be defined at compile time.

Macro: exp:defexpansion (expander name (&rest args) &body body)

Define an expansion for the expander EXPANDER. If used at top level the expansion will be defined at
compile time. NAME must be a symbol denoting the new expansion. ARGS is a destructuring lambda list.
The &whole argument can be supplied to bind a list with all the arguments.
DEFEXPANSION must return the desired expansion for NAME and EXPANDER.

Function: exp:expand (expander expansion &rest args)

Expand an EXPANSION from EXPANDER.

Function: exp:expand* (expander &rest args)

Expand an expansion from EXPANDER. The first argument from ARGS must be a valid expansion.
The last argument can be a symbol denoting the expansion (no arguments),
or a list with the last arguments to use in the expansion.
  Examples:
    (expand* 'my-expander 'my-expansion)   ; No arguments
    (expand* 'my-expander (list 'my-expansion arg1 arg2 ...))
    (expand* 'my-expander 'my-expansion arg1 arg2 (list arg3 arg4 ...))

Function: exp:expanderp (sym)

Check if a symbol denotes an expander.

Function: exp:expansionp (expander expansion)

Check if EXPANSION is a valid expansion for EXPANDER.

About

Expanders for Common Lisp

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors