In Racket, I am trying to use s-exp to build a new language. So the macro to build an if statement is:
(define-syntax (buildif stx)
(syntax-case stx ()
;split the body (stx) and split into parts
[(_ condition (then-expr ...) (else-expr ...))
;build the if statement
#'(if condition
(begin then-expr ...)
(begin else-expr ...))]))
The problem I have is that I want to convert the syntax into datums using syntax->datum or syntax->list to modify the syntax. However, I cannot then convert the datum->syntax using the unquote splicing to insert the modified values. I want to apply a macro that takes a list of datums and returns a list of datums such that '((+ x y) (- x y)) gives '((- x y) (+ x y)). I want to use this modified list as the values for the if statement
(define-syntax (rev-if stx) ;modified if
(syntax-case stx ()
[(_ condition (then-expr ...) (else-expr ...))
;convert the syntaxes to datums to manipulate
(let ((con (syntax->datum #'condition))
(newthen (opswap (syntax->list #'(then-expr ...))))
(newelse (opswap (syntax->list #'(else-expr ...)))))
;I want to convert the datums back to syntax to execute
(let ((composed `(if ,con (begin ,@newthen) (begin ,@newelse))))
(datum->syntax #f composed)))]))
If I use datum->syntax I get this error: if: unbound identifier; also, no #%app syntax transformer is bound in: if
If I try using:
(syntax/loc stx ;build the syntax for the if
(if ,con
;,@ unquote splicing to place syntax of datums into syntax
(begin ,@newthen)
(begin ,@newelse)))
I get this error: unquote: not in quasiquote in: (unquote con)
What I am trying to do is to execute an if branch in reverse. I have defined += -= methods etc. So opswap takes a list such as '((+= x 1) (-= y 2)) and converts it into '((+= y 2) (-= x 1)). I put both branches in begin so that it will execute if passed just one expression or multiple.
(define-for-syntax (opswap oppslist)
(begin
(for/list ([item (reverse oppslist)]) ;for every item in the reverse of the opperation list
(begin
(cond
[(and (pair? item) (eq? (car item) '+=)) (cons '-= (cdr item))]
[(and (pair? item) (eq? (car item) '-=)) (cons '+= (cdr item))]
[(and (pair? item) (eq? (car item) '*=)) (cons '/= (cdr item))]
[(and (pair? item) (eq? (car item) '/=)) (cons '*= (cdr item))]
[(and (pair? item) (eq? (car item) 'displayln) item)];make an exception for displayln as they are diagnostic or display
[(and (pair? item) (eq? (car item) 'swap) item)] ;call the swap method (it is it's own inverse
[else item]))))) ;if not assume that it is a method call
The first argument to datum->syntax
tells the syntax expander the lexical context that the returned syntax should have, i.e., which identifiers are bound. The way this is communicated is via an existing syntax object; the first argument's lexical context is copied to the return value.
Passing #f
as the first argument means that no identifiers are in scope, not even if
, which is why you get the error "if: unbound identifier".
One way to solve the problem is to use the lexical context at the site of the macro invocation (k
).
(define-syntax (rev-if stx)
(syntax-case stx ()
[(k condition (then-expr ...) (else-expr ...))
(let ((con (syntax->datum #'condition))
(newthen (opswap (syntax->list #'(then-expr ...))))
(newelse (opswap (syntax->list #'(else-expr ...)))))
(let ((composed `(if ,con (begin ,@newthen) (begin ,@newelse))))
(datum->syntax #'k composed)))]))
You will generally make life easier for yourself and eliminate potential scoping errors by avoiding syntax->datum
, when possible. Instead, use syntax-case
and with-syntax
to deconstruct syntax, and to reconstruct syntax use
syntax
(#'
)quasisyntax
(#`
)unsyntax
(#,
) andunsyntax-splicing
(#,@
)syntax->list
is ok because it doesn't act recursively and returns a list of syntax objects.
Here's what that might look like:
#lang racket
(define-syntax-rule (+= var val) (set! var (+ var val)))
(define-syntax-rule (-= var val) (set! var (- var val)))
(define-syntax-rule (*= var val) (set! var (* var val)))
(define-syntax-rule (/= var val) (set! var (/ var val)))
(define-for-syntax (opswap exprs)
(for/list ([expr (reverse (syntax->list exprs))])
(syntax-case expr ()
[(op var val)
(and (identifier? #'var) (eq? (syntax->datum #'op) '+=))
#'(-= var val)]
[(op var val)
(and (identifier? #'var) (eq? (syntax->datum #'op) '-=))
#'(+= var val)]
[(op var val)
(and (identifier? #'var) (eq? (syntax->datum #'op) '*=))
#'(/= var val)]
[(op var val)
(and (identifier? #'var) (eq? (syntax->datum #'op) '/=))
#'(*= var val)]
[else expr])))
(define-syntax (rev-if stx)
(syntax-case stx ()
[(k condition (then-expr ...) (else-expr ...))
(with-syntax ([(newthen ...) (opswap #'(then-expr ...))]
[(newelse ...) (opswap #'(else-expr ...))])
#'(if condition
(begin (void) newthen ...)
(begin (void) newelse ...)))]))
> (define x 1)
> (rev-if #t ((+= x 1) (/= x 5)) ())
> x
4