Как получить доступ к атрибутам синтаксического класса в `~ необязательно`, который содержит многоточие? - PullRequest
10 голосов
/ 07 марта 2019

TL; DR

Я пытаюсь расширить необязательный многоточие (~optional datum:my-class ...) в форму, которая использует атрибуты my-class, например, так: #'(list datum.attr ...).

Но поскольку форма необязательна, когда ее нет, она разрешается до #f, что не нравится шаблону многоточия:

;; ?: attribute contains non-list value
;;   value: #f

Я попытался использовать аргумент #:defaults для ~optional, например:

(~optional datum:my-class ... #:defaults ([(datum 1) null]))
;; which works for usage:
#'(list datum ...)
;; but not for:
#'(list datum.attr ...)

Я получил его на работу с помощью этого трюка:

(~optional datum:my-class ...)

#'(list #,@(if (attribute datum) #'(datum.attr ...) #'()))

Я пытаюсь выяснить, есть ли лучший способ.

Полный исполняемый пример

Я пытался свести это к минимуму. Проверьте тестовый субмодуль. Чтобы увидеть проблему, раскомментируйте одну из четырех реализаций parse-bag и выполните raco test file.rkt.

#lang racket/base

(provide parse-bag)

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

(struct bag (label objects) #:transparent)
(struct object (name) #:transparent)

(begin-for-syntax
  (define-syntax-class obj-exp
    #:datum-literals (object name)
    (pattern (object (name <name>:str))
             #:with result #'(object <name>))))

;; IMPLEMENTATION ONE
;; DESC: The naive but failing approach
;; UNCOMMENT TO TEST
#;(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 ...))]))
;; ?: attribute contains non-list value
;;   value: #f


;; IMPLEMENTATION TWO
;; DESC: adding defaults will fail too
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)
                     #:defaults ([(<object> 1) null]))) ; (HERE)
       #'(bag <label>
              (list <object>.result ...))]))
;; ?: attribute contains non-list value
;;   value: #f


;; IMPLEMENTATION THREE
;; DESC: it won't fail when not using syntax-class attributes
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)
                     #:defaults ([(<object> 1) null])))
       #'(bag <label>
              (list <object> ...))])) ; (HERE)
;; name: unbound identifier


;; IMPLEMENTATION FOUR
;; DESC: it works, but I find it ugly
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)))
       #`(bag <label>
              (list #,@(if (attribute <object>)
                           #'(<object>.result ...)
                           #'())))]))


(module+ test (require rackunit)

(check-equal?
  (parse-bag
    (label "Biscuits")
    (objects (object (name "Cookie"))
             (object (name "Brownie"))))

  (bag "Biscuits"
        (list (object "Cookie")
              (object "Brownie"))))

(check-equal?
  (parse-bag (label "Sweets"))
  (bag "Sweets" '()))

)

Ответы [ 2 ]

10 голосов
/ 07 марта 2019

Существует два класса стратегий для исправления ошибок «значение атрибута ложно»:

  1. Укажите значения по умолчанию или альтернативы, чтобы атрибут никогда не был ложным.

    (a) Использование ~optional с #:defaults

    (b) Использование ~or, возможно, с ~parse

    (c) Использование синтаксического класса с несколькими шаблонами

  2. Обработка атрибута, являющегося ложным в теле.

    (a) Использование unsyntax и if

    (б) Использование ~?

1 (а)

Реализации 2 и 3 в вопросе, а также код в ответе Жерома, все используют ~optional с #:defaults, поэтому все они пытаются исправить это с помощью 1 (a).

Ответ Жерома показывает правильное использование этого. Суть в том, что если вы используете атрибут типа <object>.result, вам нужно указать значение по умолчанию для <object>.result, а не просто <object>.

Однако есть один недостаток, если в классе obj-exp есть несколько атрибутов, которые вам нужно использовать. Для этого потребуется указать значение по умолчанию для каждого:

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

1 (б)

Если вы используете несколько атрибутов из синтаксического класса, таких как <object>.result, <object>.x, <object>.y, <object>.z и т. Д., То 1 (a) потребует от вас указать значение по умолчанию для каждого из них в отдельности. Чтобы избежать этого, вместо того, чтобы писать это:

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

Вы можете использовать ~or и ~parse следующим образом:

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

1 (с)

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

2 (а) и (б)

Реализация 4 в вопросе использует unsyntax-splicing и if, так что это пример 2 (a):

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

Однако, как вы заметили, это выглядит некрасиво. И это также имеет другую проблему. Если это само было под многоточием, это ломается, потому что эллипсы не несут свои эффекты внутри #, или #,@.

Именно поэтому ~? существует для 2 (b). Вместо использования #,@(if ....) вы можете использовать:

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

Это не совсем работает, но этот вариант работает:

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

Использование этого в варианте реализации 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)))]))
4 голосов
/ 07 марта 2019

При использовании #:defaults необходимо указать атрибут:

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

Полный код:

(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!

Другим способом было бы перенести использование многоточия на синтаксис-класс, как в этом вопросе: Класс синтаксиса сплайсинга, который соответствует необязательному шаблону и связывает атрибуты

...