Syntax classes offer a powerful mechanism for abstracting over classes of conceptually related patterns. Moreover, syntax classes can be parameterized, with expr/c being the most famous example.
In this example, we define a syntax class that roughly captures the grammar of formal parameters in function headers. It parses the name of the formal parameter, the default expressions of optional arguments and the keywords of keyworded arguments.
The syntax class argument is parameterized over two parameters: whether optional arguments are allowed and whether keyworded arguments are allowed.
#lang racket/base
(require (for-syntax racket/base
racket/syntax
syntax/parse))
(begin-for-syntax
;; We use _splicing_ syntax classes to parse a sequence of elements in the third clause.
(define-splicing-syntax-class (argument [allow-optional? #t]
#:keyword? [allow-keyword? #t])
#:attributes (name fresh-var keyword default)
#:commit
(pattern name:id
#:attr keyword #f
#:attr default #f
#:with fresh-var (generate-temporary #'name))
(pattern [name:id default:expr]
#:fail-unless allow-optional? "optional argument is not allowed"
#:attr keyword #f
#:with fresh-var (generate-temporary #'name))
;; The ~seq pattern describes a _sequence_ of elements in the enclosing list:
;; a keyword followed by an identifier
(pattern (~seq keyword:keyword name:id)
#:fail-unless allow-keyword? "keyword argument is not allowed"
#:attr default #f
#:with fresh-var (generate-temporary #'name))
(pattern (~seq keyword:keyword [name:id default:expr])
#:fail-unless allow-optional? "optional argument is not allowed"
#:fail-unless allow-keyword? "keyword argument is not allowed"
#:with fresh-var (generate-temporary #'name))))
Here is an example use of the argument syntax class. It defines a new function definition form that disallows mutation of the arguments.
(require (for-syntax racket/base
syntax/parse
syntax/transformer))
(define-syntax (define/immutable-parameter stx)
(syntax-parse stx
[(_ (name:id arg:argument ...) body:expr ...+)
;; The `~@` form _splices_ the keyword, if exists, into the function header.
;;
;; The `~?` form at (B) chooses the first form if both arg.fresh-var attribute and
;; the arg.default attribute have values. Otherwise, `~?` chooses the second form.
#'(define (name (~@ (~? arg.keyword) ;; (A)
(~? [arg.fresh-var arg.default] ;; (B)
arg.fresh-var))
...)
;; Disable set! for arg
(define-syntax arg.name
(make-variable-like-transformer #'arg.fresh-var #f))
...
body ...)]))
If we want to adjust the parameter of argument and disallow optional arguments or keyword arguments, the colon syntax for specifying the syntax class is not sufficient anymore. As an alternative, we can use either the pattern ~var or the directive #:declare:
Directly invoking argument with parameters in the colon syntax, unfortunately, results in confusing error messages. For example, this piece of program produces an error message about ellipsis:
;; Example A
(define-syntax (define/immutable-parameter stx)
(syntax-parse stx
[(_ (name:id arg:(argument #:keyword? #f) ...) body:expr ...+)
...
;; Error message:
stxclasses.rkt:43:21: syntax: no pattern variables before ellipsis in template
at: (~@ (~? arg.keyword) ......
In another example, the #' part of the output of the macro is highlighted by the syntax error:
;; Example B
(define-syntax (define/immutable-parameter stx)
(syntax-parse stx
[(_ (name:id arg:(argument #:keyword? #'stxobj) ...) body:expr ...+)
#'(define (name (~@ (~? arg.keyword) ;; <- the error points to this #'
...
;; Error message:
stxclasses.rkt:43:5: syntax: pattern variable cannot be used outside of a template
at: syntax
in: (syntax (define (name (~@ (~? arg.keyword) ......
In these two examples, the colon part and the syntax class "invocation" part are in fact being read as two consecutive patterns by the S-expression reader, instead of a variable pattern with syntax class specification:
;; Example A
(_ (name:id arg: (argument #:keyword? #f) ...) body:expr ...+)
;; Example B
(_ (name:id arg: (argument #:keyword? #'stxobj) ...) body:expr ...+)
Therefore, in example A the bound pattern variables are arg: (without ellipsis) and argument (with an ellipsis), hence the error message about missing pattern variables before ellipses.
Example B is conceptually the same but even more involved. The syntax object #'stxobj is a shorthand for (syntax stxobj). Therefore, the bound pattern variables are arg:, syntax and stxobj. Consequently, the #' in the syntax template is shadowed and highlighted in the error.
Here is an example usage of define/immutable-parameter for testing.
I license the code in this issue under the same MIT License that the Racket language uses and the texts under the Creative Commons Attribution 4.0 International License
Syntax classes offer a powerful mechanism for abstracting over classes of conceptually related patterns. Moreover, syntax classes can be parameterized, with
expr/c
being the most famous example.In this example, we define a syntax class that roughly captures the grammar of formal parameters in function headers. It parses the name of the formal parameter, the default expressions of optional arguments and the keywords of keyworded arguments.
The syntax class
argument
is parameterized over two parameters: whether optional arguments are allowed and whether keyworded arguments are allowed.Here is an example use of the
argument
syntax class. It defines a new function definition form that disallows mutation of the arguments.If we want to adjust the parameter of
argument
and disallow optional arguments or keyword arguments, the colon syntax for specifying the syntax class is not sufficient anymore. As an alternative, we can use either the pattern~var
or the directive#:declare
:or:
Directly invoking
argument
with parameters in the colon syntax, unfortunately, results in confusing error messages. For example, this piece of program produces an error message about ellipsis:In another example, the
#'
part of the output of the macro is highlighted by the syntax error:In these two examples, the colon part and the syntax class "invocation" part are in fact being read as two consecutive patterns by the S-expression reader, instead of a variable pattern with syntax class specification:
Therefore, in example A the bound pattern variables are
arg:
(without ellipsis) andargument
(with an ellipsis), hence the error message about missing pattern variables before ellipses.Example B is conceptually the same but even more involved. The syntax object
#'stxobj
is a shorthand for(syntax stxobj)
. Therefore, the bound pattern variables arearg:
,syntax
andstxobj
. Consequently, the#'
in the syntax template is shadowed and highlighted in the error.Here is an example usage of
define/immutable-parameter
for testing.Licence
I license the code in this issue under the same MIT License that the Racket language uses and the texts under the Creative Commons Attribution 4.0 International License