How to access syntax-class attributes in `~optional` that contains ellipsis?

There are two classes of strategies to fix "attribute value is false" errors:

  1. Put defaults or alternatives so that the attribute is never false.

    (a) Using ~optional with #:defaults

    (b) Using ~or, possibly with ~parse

    (c) Using a syntax-class with multiple patterns

  2. Deal with the attribute being false in the body.

    (a) Using unsyntax and if

    (b) Using ~?

1 (a)

Implementations 2 and 3 in the question, as well as the code in Zoé's answer, all use ~optional with #:defaults, so they all are attempts to fix it with 1 (a).

Zoé's answer shows a correct use of this. The main point is that if you use an attribute like <object>.result, you need to specify a default for <object>.result, not just <object>.

However, one downside to this comes if there are multiple attributes in the obj-exp class that you need to use. That would require you to specify a default for each one:

(~optional (objects <object>:obj-exp ...)
           #:defaults ([(<object>.result 1) null]
                       [(<object>.x 1) null]))

1 (b)

If you use multiple attributes from a syntax class, such as <object>.result, <object>.x, <object>.y, <object>.z, etc, then 1 (a) would require you to specify a default for each one separately. To avoid that, instead of writing this:

(~optional (objects <object>:obj-exp ...)
           #:defaults ([(<object>.result 1) null]
                       [(<object>.x 1) null]
                       [(<object>.y 1) null]
                       [(<object>.z 1) null]))

You can use ~or and ~parse like this:

(~or (objects <object>:obj-exp ...)
     (~and (~seq) (~parse (<object>:obj-exp ...) null)))

1 (c)

(define-splicing-syntax-class maybe-objects
  #:datum-literals (objects)
  [pattern (objects <object>:obj-exp ...)]
  [pattern (~seq) #:with (<object>:obj-exp ...) null])

2 (a) and (b)

Implementation 4 in the question uses unsyntax-splicing and if, so it's an example of 2 (a):

#,@(if (attribute <object>)
       #'(<object>.result ...)
       #'())

However, as you noted, this looks kind of ugly. And it also has another problem. If this itself were under an ellipsis, this breaks down because ellipses don't carry their effects inside a #, or #,@.

That's why ~? exists for 2 (b). Instead of using #,@(if ....), you can use:

(~? (<object>.result ...) ())

That doesn't quite work, but this variant of it does:

(~? (list <object>.result ...) (list))

Using that in a variation on implementation 4:

(define-syntax (parse-bag stx)
  (syntax-parse stx
    #:datum-literals (label objects)
    [(_ (label <label>:str)
        (~optional (objects <object>:obj-exp ...)))
     #`(bag <label>
            (~? (list <object>.result ...) (list)))]))

When using #:defaults, you need to specify the attribute:

(~optional <object>:obj-exp ... #:defaults ([(<object>.result 1) null]))

Complete code:

(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)
                     #:defaults ([(<object>.result 1) null]))) ; + attribute here
       #'(bag <label>
              (list <object>.result ...))])) ; now it works!

Another way would be to move the ellipsis usage to the syntax-class, as in this question: A splicing syntax class that matches an optional pattern and binds attributes

Tags:

Racket