Sam
Sam

Reputation: 796

Transforming a list of symbols to a list of identifiers to be used in a macro

Consider the scenario where I would like to specify a very simplistic actor language using Racket macros. An actor is defined by a behaviour that defines some local state and message handlers that implement some logic. The body of a message handler can use both the formal parameters of the message, as well as the state variables. An example is implemented in the code below.

There is quite a lot of context in the code which is probably not even necessary. However, I have included it regardless in order to provide a running example, and the fact that I need to use syntax-parametrize may complicate the solution. The special point of interest is the with-syntax clause in the MESSAGE macro, where I require the (local-state-variable ...) pattern to match a list of identifiers, currently #'local-state-variables which is a list of symbols (bound by syntax-parameterize in the ACTOR macro), and thus does not match. So far I have not been able to find the solution, although it does not seem like it should be shockingly difficult. Am I missing something obvious?

#lang racket

(require (for-syntax syntax/parse))
(require racket/stxparam)

(define LOCAL_STATE
  (lambda (stx)
    (raise-syntax-error 'LOCAL_STATE "should only be used inside an actor" stx)))

; Define some syntax classes because abstractions are nice
(begin-for-syntax
  (define-syntax-class actor-local-state
    #:description "actor local state"
    #:literals (LOCAL_STATE)
    (pattern (LOCAL_STATE state-variable:id ...)))

  (define-syntax-class message-pattern
    #:description "actor message pattern"
    (pattern (identifier:id argument:id ...))))


(define-syntax-parameter local-state-variables
  (lambda (stx)
    (raise-syntax-error 'local-state-variables "reserved keyword for actors" stx)))


(define-syntax (MESSAGE stx)
  (syntax-parse stx
    [(_ pattern:message-pattern body:expr ...+)
     ; Currently there is a "binding match failed" error on the following line, but replacing #'local-state-variables with #'(a b) (a list of identifiers) needless to say works. 
     (with-syntax ([(local-state-variable ...) #'local-state-variables])
       ; For simplicity just display the state variables - this is normally where some magic happens
       #'(display '(local-state-variable ...)))]))


(define-syntax (ACTOR stx)
  (syntax-parse stx
    [(_ state:actor-local-state handler:expr ...+)
     #'(syntax-parameterize
           ([local-state-variables '(state.state-variable ...)])
         ; For the sake of simplicity, an actor is currently a list of message handlers
         (list handler ...))]))


; in this proof-of-concept code this should print (a b)
(define behaviour
  (ACTOR (LOCAL_STATE a b)
         (MESSAGE (add x y) (+ a b x y))))

Upvotes: 3

Views: 588

Answers (2)

Ryan Culpepper
Ryan Culpepper

Reputation: 10663

Use syntax-parameter-value. Here's an example of using syntax parameters to manage lists of variables:

;; vars : syntax parameter of (Listof Identifier)
(define-syntax-parameter vars null)

;; with-vars: like let, but set vars
(define-syntax (with-vars stx)
  (syntax-parse stx
    [(_ ([var:id rhs:expr] ...) . body)
     #'(let ([var rhs] ...)
         (syntax-parameterize ([vars (list (quote-syntax var) ...)])
           . body))]))

;; get-vars: get vars (symbolic name) and their values
(define-syntax (get-vars stx)
  (syntax-parse stx
    [(_)
     (with-syntax ([(var ...) (syntax-parameter-value #'vars)])
       #'(list (list (quote var) var) ...))]))

;; Examples:    

(get-vars)
;; => '()

(with-vars ([x 1])
  (get-vars))
;; => '((x 1))

(with-vars ([x 1])
  (with-vars ([y 2] [z 3])
    (set! z 17)
    (get-vars)))
;; => '((y 2) (z 17))

Upvotes: 3

Leif Andersen
Leif Andersen

Reputation: 22342

The easiest way to turn any datum (including a list of symbol) into an identifier with datum->syntax. (You can also use format-id, but that works on only a single identifier.) With these functions, you pass in a syntax object for the scopes you want your new identifier to have, or #f if you want it to inherit the scopes that your current macro is generating.1 Getting your list of identifiers (as one single syntax object, would just be:

(syntax->datum stx '(a b c))

Where '(a b c) is your list of identifiers. Finally, you can then add this in your with-syntax:

(with-syntax ([(local-state-variables ...) (datum->syntax stx ...)])
  ...)

As a side note, the way to answer the title of your question, just iterate over your list with map producing a new list using format-id:

(map (curry format-id stx "~a") '(a b c)

1Unless I'm wrong, if so, please correct this.

Upvotes: 1

Related Questions