Skip to content
David Jeske edited this page May 22, 2017 · 22 revisions

Irken includes a simplified version of Scheme's syntax-rules macro facility. The main purpose of macros in Irken is for syntax extension, not for inlining of functions (the compiler already inlines aggressively).

The macro facility is of the 'macro by example' variety. The syntax superficially resembles normal pattern matching in Irken, but it's important to remember that you are manipulating source code, before it gets to the compiler.

In additional to normal pattern matching syntax, macros support a few additional forms:

  • ... attaches to the name before it in the source pattern, acting like a regex *. When placed in the output next to the same symbol, reproduces the set of matched values.
  • <literal> in a source pattern requires matching that literal symbol, instead of creating a variable.
  • $symbol in a pattern expression will alpha rename that occurrence of the symbol to make it hygienic, so it does not collide with occurrences of the same symbol in the surrounding scope.

Macro: and

Here's the definition of the and short-circuited special form (see "lib/derived.scm"):

(defmacro and
  (and)                 -> #t
  (and test)            -> test
  (and test1 test2 ...) -> (if test1 (and test2 ...) #f)
  )

This macro defines three cases of translation.

  1. An empty and is converted into the boolean #t.
  2. An (and expr) simplifies to that expression.
  3. And with more than one expression is converted into an if conditional, to create short-circuiting. Notice that this pattern uses itself to make the macro recursive.

Macro: alist/make

Here's another useful macro. Given a datatype for a lisp 'association list'... (see "lib/alist.scm")

(datatype alist
  (:nil)
  (:entry 'a 'b (alist 'a 'b))
 )

...you might want to create a table. But the constructor syntax is pretty annoying:

(define numbers
  (literal
   (alist:entry
    0 'zero
    (alist:entry
     1 'one
     (alist:entry
      2 'two
      (alist:entry
       3 'three
       (alist:entry
	4 'four
	(alist:entry
	 5 'five
	 (alist:entry
	  6 'six
	  (alist:entry
	   7 'seven
	   (alist:entry
	    8 'eight
	    (alist:entry
	     9 'nine
	     (alist:nil)))))))))))))

Here's a macro to the rescue:

(defmacro alist/make
  (alist/make)                     -> (alist:nil)
  (alist/make (k0 v0) (k1 v1) ...) -> (alist:entry k0 v0 (alist/make (k1 v1) ...))
 )

And the much cleaner table definition

(define numbers2
  (literal
   (alist/make 
    (0 'zero)
    (1 'one)
    (2 'two)
    (3 'three)
    (4 'four)
    (5 'five)
    (6 'six)
    (7 'seven)
    (8 'eight)
    (9 'nine)
    )))

Matching literals and variable arguments

If you wish for a macro to match against a literal symbol in the input, then you place it in the match surrounded with <>s, as in <then> and <else> in the following macro. Also note the use of both body ... and body2 ... to catch whatever is inside the provided lists.

(defmacro iff
   (iff expr <then> (body ...) <else> (body2 ...) ) ->
     (match expr with
        #t -> (begin body  ...)
        #f -> (begin body2 ...)
     )
   )

(iff #t then (1) else (2))

Symbol Alpha Renaming with $

;; local x shadows x outside the macro
(defmacro ret-local-x
  (ret-local-x)   -> (let ((x 1)) x))
(let ((x 10))  (ret-local-x))    ;; --> 1

;; local x renamed, returns x from containing closure
(defmacro ret-global-x
  (ret-global-x)   -> (let (($x 1)) x))
(let ((x 10))  (ret-global-x))   ;; --> 10

;; x fully renamed, both local x and global x are visible
(defmacro ret-hygenic
  (ret-hygenic)   -> (let (($x 1)) (+ $x x)))
(let ((x 10))  (ret-hygenic))   ;; --> 11

Example: simple infix macro

Here is a simple macro which allows infix expressions. (though it evaluates in-order, not following typical operator precedence rules)

(defmacro infix
  (infix exp)             -> exp
  (infix exp op rest ...) -> (op exp (infix rest ...))
  )

;; prints 18
(printn (infix 1 + 2 + 3 * 5))

Next: Recursive Types and Null