← Back to Main Site

LispBM vs Scheme R5RS: Quasiquotation Comparison

Implementing quasiquotation (qq) is challenging! The implementation of qq in LispBM is based on Bawden's paper Quasiquotation in Lisp. To understand the algorithms from Bawden's appendix and to turn them into continuation passing style C code took a lot of effort! The correctness of the implementation is a big concern of mine.

This page compares LispBM's quasiquote expansion with Guile 3 as a way to explore similarities and differences and to, perhaps, gain some confidence that LispBM quasiquotation is OK and useful.

A comparison to a more standard Scheme is also useful for people with that experience if at some point they would want to write a LispBM program.

Overview of Quasiquotation

Quasiquotation allows you to create templates where most of the structure is quoted (literal), but specific parts can be unquoted for evaluation. The three main constructs are:

Quasiquotation Examples

The examples below compare quasiquotation expansion between LispBM and Guile. In the cases where a quasiquoted expression expands identically between LispBM and Guile, the background will be green. Sometimes the quasiquoted expression expands differently in LispBM and Guile. In these cases we follow up with a comparison of evaluating the two expressions. If two expressions are identical after evaluation the background will be blue and LispBM and Guile will be considered to semantically be identical in their handling of that expression. If the results differ both in expansion and after evaluation, the background is red.

Green: Exact Match - Both systems produce identical results
Blue: Semantic Match - Different syntax but equivalent when evaluated
Red: Difference - Results differ in behavior or evaluation
LispBM Guile 3 (Scheme R5RS)
`(,@(list 1 2 3))
; Result: (1 2 3)
`(,@(list 1 2 3))
; Result: (1 2 3)
`(a ,@(list 1 2) b)
; Result: (a 1 2 b)
`(a ,@(list 1 2) b)
; Result: (a 1 2 b)
`(,@())
; Result: nil
`(,@())
; Result: ERROR or unsupported
`(1 ,@(list 2 3) 4)
; Result: (1 2 3 4)
`(1 ,@(list 2 3) 4)
; Result: (1 2 3 4)
``(a ,,(+ 1 2))
; Result: (append (quote (a)) (list 3))
; Eval: (a 3)
``(a ,,(+ 1 2))
; Result: (quasiquote (a (unquote 3)))
; Eval: (a 3)
`(quote ,@(list 1 2))
; Result: (quote 1 2)
`(quote ,@(list 1 2))
; Result: (quote 1 2)
`(,@(cdr (quote (0 1 2 3))))
; Result: (1 2 3)
`(,@(cdr (quote (0 1 2 3))))
; Result: (1 2 3)
`(list ,@(list 1 2 3))
; Result: (list 1 2 3)
`(list ,@(list 1 2 3))
; Result: (list 1 2 3)
`,'a
; Result: a
`,'a
; Result: a
``,a
; Result: a
; Eval: variable_not_bound
``,a
; Result: (quasiquote (unquote a))
; Eval: Unbound variable: a
`,`a
; Result: a
`,`a
; Result: a
`(1 . 2)
; Result: (1 . 2)
`(1 . 2)
; Result: (1 . 2)
`',(car ())
; Result: (quote nil)
`',(car ())
; Result: ERROR or unsupported
`(,1 ,2 . ,3)
; Result: (1 2 . 3)
`(,1 ,2 . ,3)
; Result: (1 2 . 3)
`(,@nil ,1)
; Result: (1)
`(,@nil ,1)
; Result: ERROR or unsupported
`(1 2 ,@() ,@() 3)
; Result: (1 2 3)
`(1 2 ,@() ,@() 3)
; Result: ERROR or unsupported
``(,,@(list 0 1 2))
; Result: (list 0 1 2)
; Eval: (0 1 2)
``(,,@(list 0 1 2))
; Result: (quasiquote ((unquote 0 1 2)))
; Eval: (0 1 2)
``(,,@(cdr '(0 1 2 3)) ,4)
; Result: (append (list 1 2 3) (list 4))
; Eval: (1 2 3 4)
``(,,@(cdr '(0 1 2 3)) ,4)
; Result: (quasiquote ((unquote 1 2 3) (unquote 4)))
; Eval: (1 2 3 4)
``(1 2 ,,@() ,,@())
; Result: (append (quote (1 2)) (list) (list))
``(1 2 ,,@() ,,@())
; Result: ERROR or unsupported
``(a ,,(+ 1 2) ,(+ 3 4))
; Result: (append (quote (a)) (list 3) (list (+ 3 4)))
; Eval: (a 3 7)
``(a ,,(+ 1 2) ,(+ 3 4))
; Result: (quasiquote (a (unquote 3) (unquote (+ 3 4))))
; Eval: (a 3 7)
`5
; Result: 5
`5
; Result: 5
`,5
; Result: 5
`,5
; Result: 5
(let ((x 5)) `(let ((x ,(+ x 10))) `(list ,,x ,x)))
; Result: (let ((x 15)) (append (quote (list)) (list 5) (list x)))
; Eval: (list 5 15)
(let ((x 5)) `(let ((x ,(+ x 10))) `(list ,,x ,x)))
; Result: (let ((x 15)) (quasiquote (list (unquote 5) (unquote x))))
; Eval: (list 5 15)
`,@0
; Result: read_error
; Eval: read_error
`,@0
; Result: (unquote-splicing 0)
; Eval: ERROR
`(1 2 ,@'(3 . 4))
; Result: (1 2 3 . 4)
`(1 2 ,@'(3 . 4))
; Result: (1 2 3 . 4)
`(1 2 ,@(list 3 4 5) ,@(list 6 7 8) 9 10)
; Result: (1 2 3 4 5 6 7 8 9 10)
`(1 2 ,@(list 3 4 5) ,@(list 6 7 8) 9 10)
; Result: (1 2 3 4 5 6 7 8 9 10)
`(1 2 ,@'() ,@'() 3)
; Result: (1 2 3)
`(1 2 ,@'() ,@'() 3)
; Result: (1 2 3)
``(1 2 ,,@'() ,,@'())
; Result: (append (quote (1 2)) (list) (list))
; Eval:  (1 2)
``(1 2 ,,@'() ,,@'())
; Result: (quasiquote (1 2 (unquote) (unquote)))
; Eval: (1 2)

Important note about nil

Guile does not have a nil so some of the comparisons above are a bit unfair. Look at the example below that in Guile is an error because nil is not defined by default:

`(,@nil ,1)
Unbound variable: nil
    

If we define nil to be the empty list in guile then the following happens:

(define nil '())
scheme@(guile-user) [5]> `(,@nil ,1)
$1 = (1)
    

So in that case it behaves the same as LispBM. So let's try one more thing:

`(,@() ,1) 
While compiling expression:
Syntax error:
unknown location: unexpected syntax in form ()
    

Here we get an error again. Let's try one more thing in guile:

`(,@'() ,1)
 $2 = (1)
    

In this final case Guile and LispBM behave exactly the same again.

Deeply nested quasiquotes

Example: `(list 1 `(,@(list 1 2 3) `(,@(list 4 5 6))))

The example above is an expression with deeply nested quasiquotes. These do not fit the pattern used in the tests above where either the quasiquote expands equally in LispBM and Guile or we check if they are Semantically equivalent after one round of evaluation.

In this case we would need to traverse into the result and evaluate subtrees then test equality of value.

LispBM expands the example as follows: (list 1 (append (list 1 2 3) (list (quote (list 4 5 6))))).

Guile expands the same example as: (list 1 (quasiquote ((unquote-splicing (list 1 2 3)) (quasiquote ((unquote-splicing (list 4 5 6)))))))

After one round of LispBM evaluation: (1 (1 2 3 (list 4 5 6)))

After one round of Guile evaluation: (1 (1 2 3 (quasiquote ((unquote-splicing (list 4 5 6))))))

The two final expressions are semantically equivalent because in LispBM (list 4 5 6) evaluates to (4 5 6) and in Guile (quasiquote ((unquote-splicing (list 4 5 6)))) also evaluates to (4 5 6).

LispBM Arrays vs Guile Vectors in Quasiquotation

Another difference between LispBM and Guile is how they handle array/vector data structures within quasiquotation contexts. Both languages have array-like structures, but they integrate differently with the quasiquote system.

Guile Vectors with Quasiquotation

Guile has vectors with literal syntax #(...) that integrates seamlessly with quasiquotation. As I understand it, the # can be thought of as a ' for vectors.

;; Literal vector (no evaluation)
#(1 2 (+ 1 2))          ; → #(1 2 (+ 1 2))

;; Vector with quasiquote (selective evaluation)  
`#(,(+ 1 2) 4)          ; → #(3 4)

;; Vector constructor (full evaluation)
(vector (+ 1 2) 4)      ; → #(3 4)
    

LispBM Arrays with Quasiquotation

LispBM has arrays with literal syntax [| ... |] that are always literal, regardless of quasiquote context:

;; Literal array (no evaluation)
[| 1 2 (+ 1 2) |]       ; → [| 1 2 (+ 1 2) |]

;; Array in quasiquote (still no evaluation!)
`[| ,(+ 1 2) 4 |]       ; → [|([comma] (+ 1 2)) 4|]

;; Array constructor (full evaluation)
`(array ,(+ 1 2) 4)     ; → (array 3 4), which evaluates to an array
    

The example where `[| ,(+ 1 2) 4 |] becomes [|([comma] (+ 1 2)) 4|] is unfortunate. The [comma] is how LispBM prints the comma symbol, and this internal symbol should not be a part of any fully parsed expression. This is an example of where an internal implementation detail is leaking out and becomes visible to the user. This will be fixed by in a future version of LispBM.

Explanation

LispBM is meant for systems with very limited ram and uses different memory regions for cons-cell heap and for more array like data (array-memory). The literal syntax for arrays [| ... |], is processed entirely in the reader and the array can be allocated and filled with data without any involvement of the evaluator. To allow the comma , and comma-at ,@ operations inside of an [| ... |] array would mean that the actual array in array-memory cannot be created until evaluation time and the data will be intermediately stored on the heap as well as on the evaluation stack.

The chosen approach means that arrays specified using [| ... |] can be larger than the evaluation stack as well as larger than the available cons-cell heap even.

Note that the [ ... ] byte array syntax in LispBM is also entirely literal for the exact same reasons.

Conclusion

There are differences but I am not sure these are very important. LispBM seems to handle these cases of quasiquotation perfectly fine.

If you have examples of interesting quasiquotes please send me a message. I would love to expand on this comparison and to explore the limits of the quasiquoter. If we are lucky we find some bugs (hopefully fixable) along the way!