Fantastic, thank you for getting me on my way. I followed your advice where I 
do not use string-producing expressions and ended up with a follow up question.

New syntax classes are below for those reading.

(define (unbounded? v)

(equal? "*" v))

(define-syntax-class racket-version-or-*
(pattern (~var bound string)
#:when (let ([v (syntax-e #'bound)])
(or (unbounded? v)
(valid-version? v)))))

(define-syntax-class racket-version-selection
#:attributes (min max)
(pattern (~and (min:racket-version-or-* max:racket-version-or-*)
(~fail #:unless (let ([min-v (syntax-e #'min)] [max-v (syntax-e #'max)])
(or (unbounded? min-v)
(unbounded? max-v)
(version<=? min-v max-v)))
"minimum Racket version cannot exceed maximum Racket version")))
(pattern (~and (~var v racket-version-or-*)
(~bind [min #'v] [max #'v]))))

Note the condition set in racket-version-selection that checks for backwards 
ranges. From what I understand, it only raises a pattern failure, which means I 
only see the ~fail error message for:

(syntax-parse #'(("8.4" "8.3"))
[(v:racket-version-selection ...) #t])

but not

(syntax-parse #'(("8.4" "8.3"))
[(v:racket-version-selection ...) #t]
[_ #f])
In this case, is it better form to use raise-syntax-error in a #:when pattern 
directive for a syntax class, or beneath a clause of syntax-parse? I suspect 
that syntax classes should not have an opinion about flow control.

~slg

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Thursday, December 17, 2020 4:52 AM, Ryan Culpepper <[email protected]> 
wrote:

> Based on what you have written so far, the `versions` macro has no 
> sub-expressions, so you shouldn't use `expr/c` at all. It requires version 
> bounds to be in the form of literal strings. So you could describe the macro 
> using a grammar as follows:
>
> Expression ::= .... | (versions Version ...)
> Version ::= VersionBound | (VersionBound VersionBound)
> VersionBound ::= String
>
> I think what you want to do is refine VersionBound so that it only accepts 
> strings of a certain form. The best way to do that is with a separate syntax 
> class that matches a string and then puts additional side-conditions on it 
> (using `#:when`, etc). That is, you check the `valid-version?` predicate at 
> compile-time.
>
> By the way, you should also avoid treating the literal strings that your 
> macro receives as if they were also expressions. A syntax object containing a 
> literal string is *not necessarily* a string-valued expression. Once your 
> macro views and validates something as a literal string, the proper way to 
> convert it to a run-time expression is to explicitly quote it. Consider the 
> following test case:
>
> (let-syntax ([#%datum (lambda (stx) #'(exit '0))]) (versions ("7.0" 
> "7.7.0.5") "6.5"))
>
> If your macro produces eg (list (make-version-range (quote "7.0") (quote 
> "7.7.0.5")) (quote "6.5")), then it's fine; if it produces (list 
> (make-version-range "7.0" "7.7.0.5") "6.5"), then it would exit. This 
> particular example is unlikely to happen in practice, but I think it is 
> useful to think clearly about how interpret each argument of a macro. Treat 
> it as a literal string or as an expression, but not both.
>
> A different design would be to say that VersionBound is an expression that 
> produces a string. That would cause problems with your current grammar, 
> because you couldn't tell whether `(f "1.2.3")` was a single version (whose 
> value is produced by a function call) or by a range (whose lower bound is the 
> variable f). But you could change the grammar to avoid that problem. Then you 
> could use `expr/c` to wrap the expressions to check that at run time they 
> produced strings of the proper form.
>
> Ryan
>
> On Thu, Dec 17, 2020 at 12:55 AM Sage Gerard <[email protected]> wrote:
>
>> Typos:
>>
>> - "*" remove a bound ==> "*" removes a bound
>> - All examples should read (versions ...), not (version ...)
>>
>> ~slg
>>
>> ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
>> On Wednesday, December 16, 2020 6:27 PM, Sage Gerard <[email protected]> 
>> wrote:
>>
>>> I'm trying to learn how to write syntax classes. My intended macro 
>>> expresses a set of Racket versions, either as inclusive intervals or as 
>>> exact versions. In an interval, "*" remove a bound.
>>>
>>> - (version "6.5") means exactly version "6.5", as does (version ("6.5" 
>>> "6.5"))
>>>
>>> - (versions ("7.0" "7.7.0.5")) means the inclusive interval between version 
>>> 7.0 and 7.7.0.5
>>> - (versions ("7.0" "7.7.0.5") "6.5"): union of the above two items
>>> - (versions ("6.0" "*")): all Racket versions >= 6.0
>>> - (versions "*"), (versions ("*" "*")): all Racket versions
>>>
>>> I was able to define the syntax class without much issue:
>>>
>>> (define-syntax-class racket-version-selection
>>> #:attributes (min max)
>>> (pattern (min:string max:string))
>>> (pattern (~and (~var v string)
>>> (~bind [min #'v]
>>> [max #'v]))))
>>>
>>> Now I want each attribute-bound expression V to satisfy (or (valid-version? 
>>> V) (equal? V "*")). Where I'm stuck is how I can use #:declare with 
>>> (expr/c) here. From what I understand, expr/c does not really mean much 
>>> because it accepts an expression (as in the expr syntax-class), not 
>>> attributes.
>>>
>>> The only way I can think to fix this is to perform an additional 
>>> syntax-parse so that I can use the attributes in an expression for expr/c 
>>> to consume. But is it possible to do everything I'm thinking of in just one 
>>> syntax class?
>>>
>>> ~slg
>>>
>>> --
>>> You received this message because you are subscribed to the Google Groups 
>>> "Racket Users" group.
>>> To unsubscribe from this group and stop receiving emails from it, send an 
>>> email to [email protected].
>>> To view this discussion on the web visit 
>>> [https://groups.google.com/d/msgid/racket-users/syiNcw0zJpSyA16fO8OkClrQmXFOC4qZEwrBm3JwETX-bGJGlALnP6Apn4ttCbIzMZUoobO7AT4MyRDm9ID0oUA648nXXSAZ1nvaCaj2NbI%3D%40sagegerard.com](https://groups.google.com/d/msgid/racket-users/syiNcw0zJpSyA16fO8OkClrQmXFOC4qZEwrBm3JwETX-bGJGlALnP6Apn4ttCbIzMZUoobO7AT4MyRDm9ID0oUA648nXXSAZ1nvaCaj2NbI%3D%40sagegerard.com?utm_medium=email&utm_source=footer).
>>
>> --
>> You received this message because you are subscribed to the Google Groups 
>> "Racket Users" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected].
>> To view this discussion on the web visit 
>> [https://groups.google.com/d/msgid/racket-users/nfmcqQmNc3_H9zeCyS49LELvXomDYQF2sJbmyrJchu0kiWG8CXJiyS932ZfQ_eSW3cnEYTTzOwqakNlKL4FF_KR4F7HnAARLdQLDVEGxSI0%3D%40sagegerard.com](https://groups.google.com/d/msgid/racket-users/nfmcqQmNc3_H9zeCyS49LELvXomDYQF2sJbmyrJchu0kiWG8CXJiyS932ZfQ_eSW3cnEYTTzOwqakNlKL4FF_KR4F7HnAARLdQLDVEGxSI0%3D%40sagegerard.com?utm_medium=email&utm_source=footer).

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/TKXh6HhBr0T2G5HEf2SE1BpmQBGdSEA016aiywEZqe1tbtfz4l_I68rPdW0S7mq0y_9IJuQYaS0Vrjh0RNP3G9t5ZSSgI1_wAJVETTmbpBc%3D%40sagegerard.com.

Reply via email to