2015-12-31 12 views
6

Podczas pisania makra korzystającego z syntax/parse utworzyłem klasę składni łączenia, która przechwytuje opcje, które mogą być dostarczone do makra. Te opcje są opcjonalne i mogą być dostarczane w dowolnej kolejności. Korzystanie z głowy wzór ~optional wielokropka czyni to dość proste:Jak grupować opcjonalne atrybuty przechwycone przy pomocy składni-parsowania?

(define-splicing-syntax-class opts 
    (pattern (~seq (~or (~optional (~seq #:a a)) 
         (~optional (~seq #:b b)) 
         (~optional (~seq #:x x)) 
         (~optional (~seq #:y y))) 
       ...)) 

Istnieje jednak pewien haczyk: Chcę, aby móc grupy tych opcji na dwie grupy: grupa zawierająca a i b, a grupa zawierające x i y. Jednak użytkownik nadal może określić opcje w dowolnej kolejności, więc na tym przykładzie wejścia:

(foobar #:b 3 #:y 7 #:a 2) 

Chcę być w stanie przedstawić następujące atrybuty:

first-opts: (#:a 2 #:b 3) 
second-opts: (#:y 7) 

tej pory mam udało się to zrobić ręcznie przy użyciu #:with, ale to nie jest całkiem:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    (pattern (~seq (~or (~optional (~seq #:a a)) 
         (~optional (~seq #:b b)) 
         (~optional (~seq #:x x)) 
         (~optional (~seq #:y y))) 
       ...) 
      #:with (first-opts ...) 
      #`(#,@(if (attribute a) #'(#:a a) #'()) 
       #,@(if (attribute b) #'(#:b b) #'())) 
      #:with (second-opts ...) 
      #`(#,@(if (attribute x) #'(#:x x) #'()) 
       #,@(if (attribute y) #'(#:y y) #'())))) 

ten można uprościć trochę korzystając template z syntax/parse/experimental/template:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    (pattern (~seq (~or (~optional (~seq #:a a)) 
         (~optional (~seq #:b b)) 
         (~optional (~seq #:x x)) 
         (~optional (~seq #:y y))) 
       ...) 
      #:with (first-opts ...) 
      (template ((?? ([email protected] #:a a)) 
         (?? ([email protected] #:b b)))) 
      #:with (second-opts ...) 
      (template ((?? ([email protected] #:a x)) 
         (?? ([email protected] #:b y)))))) 

Jednak jest to naprawdę tylko niektóre cukru na powyższe, a nie faktycznie rozwiązać problem konieczności wyliczać każdą opcję w każdym punkcie. Jeśli na przykład dodałem opcję #:c, muszę pamiętać o dodaniu jej do grupy first-opts, w przeciwnym razie zostanie ona całkowicie zignorowana.

Chcę to deklaratywny sposób grupowania tych zestawów wartości opcjonalnych. Na przykład, chciałbym składni takiego:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    (pattern (~seq (~or (~group first-opts 
           (~optional (~seq #:a a)) 
           (~optional (~seq #:b b))) 
         (~group second-opts 
           (~optional (~seq #:x x)) 
           (~optional (~seq #:y y)))) 
       ...))) 

Albo, jeszcze lepiej, byłoby miło, gdybym mógł wykorzystać istniejące prymitywów, coś takiego:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    (pattern (~seq (~or (~and first-opts 
          (~seq (~optional (~seq #:a a)) 
            (~optional (~seq #:b b)))) 
         (~and second-opts 
          (~seq (~optional (~seq #:x x)) 
            (~optional (~seq #:y y))))) 
       ...))) 

Jednak żaden z te prace. Czy jest jakiś sposób, aby to zrobić za pomocą wbudowanych funkcji dostarczonych przez syntax/parse? Jeśli nie, czy istnieje prosty sposób zdefiniowania czegoś takiego, jak ja sam?

Odpowiedz

0

nie jestem (jeszcze) pewien sposób, można to zrobić z czymś takim ~group, ale istnieje sposób można dokonać istniejący (pracy) rozwiązanie, które wykorzystuje #:with wyglądają dużo ładniej. Może to zadziała w twoim przypadku, może nie.

~optional wykonuje w domyślnym argumentem #:defaults, które można ustawić jako pustą listę składni, #'#f, lub jakąś inną wartość wartowniczy, usuwając swoje wymagania, aby mieć oświadczenie w swojej klauzuli #:withif. Wyglądałoby to tak:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    (pattern (~seq (~or (~optional (~seq #:a a) #:defaults ([a #'#f])) 
         (~optional (~seq #:b b) #:defaults ([b #'#f])) 
         (~optional (~seq #:x x) #:defaults ([x #'#f])) 
         (~optional (~seq #:y y) #:defaults ([y #'#f]))) 
       ...) 
      #:with (first-opts ...) #'(#:a a #:b b) 
      #:with (second-opts ...) #'(#:x x #:y y) 

Nadzieję, że pomaga.

+0

Nie działa to dla mnie, ponieważ nie mam domyślnej wartości do użycia - potrzebuję argumentów słów kluczowych, które nie są dostarczane do * nie * pojawiają się w rozszerzeniu. –

2

Jest sposób, aby to zrobić za pomocą ekspandera ~groups-no-order wzór jak ten:

(define-splicing-syntax-class opts 
    #:attributes ([first-opts 1] [second-opts 1]) 
    [pattern (~groups-no-order 
      [first-opts 
      (~optional (~seq #:a a)) 
      (~optional (~seq #:b b))] 
      [second-opts 
      (~optional (~seq #:x x)) 
      (~optional (~seq #:y y))])]) 

(syntax-parse #'(foobar #:b 3 #:y 7 #:a 2) 
    [(foobar opts:opts) 
    (values #'(opts.first-opts ...) 
      #'(opts.second-opts ...))]) 
; #<syntax (#:a 2 #:b 3)> 
; #<syntax (#:y 7)> 

Gdzie ~groups-no-order można zdefiniować tak:

#lang racket 
(provide ~groups-no-order) 

(require syntax/parse 
     seq-no-order 
     (for-syntax racket/syntax 
        syntax/stx)) 

(define-syntax ~groups-no-order 
    (pattern-expander 
    (lambda (stx) 
    (syntax-case stx() 
     [(groups [group-name member-pat ...] ...) 
     (with-syntax ([ooo (quote-syntax ...)]) 
      (define/with-syntax [[member-tmp ...] ...] 
      (stx-map generate-temporaries #'[[member-pat ...] ...])) 
      (define/with-syntax [group-tmp ...] 
      (generate-temporaries #'[group-name ...])) 
      #'(~and (~seq-no-order (~and (~seq (~var member-tmp) ooo) 
             member-pat) 
           ... ...) 
        (~parse [[(~var group-tmp) ooo] ooo] #'[[member-tmp ooo] ...]) 
        ... 
        (~parse [group-name ooo] #'[group-tmp ooo ooo]) 
        ...))])))) 

To działa tak samo jak pierwszego rozwiązania przy użyciu #:with, ale zamienia to w materiał do ekspandera wzorów wielokrotnego użytku.

0

Myślę, że używanie ~and prowadzi do najprostszego makra, ale wersja wzoru główki ~and jest bardziej restrykcyjna i nie działa tak, że oddzieliłbym część wzoru od główki.

Czy poniższy kod spełnia to, czego szukasz?

Bez wzorów główki tracisz ~optional, więc ręcznie sprawdzam, czy nie ma duplikatów.

Również, first-opts i second-opts nie są spłaszczone, ale podejrzewam, że jest OK?