-
Notifications
You must be signed in to change notification settings - Fork 5
/
syntax-parse-example.scrbl
258 lines (220 loc) · 8.47 KB
/
syntax-parse-example.scrbl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#lang scribble/manual
@require[
syntax-parse-example/render
(for-label
racket/base
scribble/manual
syntax/parse
syntax-parse-example/first-class-or/first-class-or)
]
@(define GITHUB-URL "https://github.com/bennn/syntax-parse-example")
@title{Syntax Parse Examples}
Source code: @url[GITHUB-URL]
@defmodulelang[syntax-parse-example]{
This package is a collection of useful and illustrative macros written
using the @racketmodname[syntax/parse] library.
The @secref{sec:reference} section documents the @racketmodname[syntax-parse-example] language.
}
@; =============================================================================
@section{How to browse the examples}
Two options:
@itemlist[
@item{
Scroll through this document, read the macros' source code and look at the
example uses of each macro.
}
@item{@(let ([example-macro-name @tt{big-mac}]) @list{
The source code for each macro is in a top-level folder at @url[GITHUB-URL].
For example, the source for a macro named @|example-macro-name| would be in
the folder @tt{@|GITHUB-URL|/@|example-macro-name|}.
})}
]
@section{How to use the examples in another project}
Three options:
@itemlist[
@item{
Copy/paste the example code into a new file in your project, require that
new file normally.
}
@item{@(let ([example-macro-name @tt{first-class-or}]
[example-macro-modname @filepath{syntax-parse-example/first-class-or/first-class-or}]) @list{
Install the @racketmodname[syntax-parse-example] package, then require
the macro's defining module.
For example, the defining module for the @|example-macro-name| macro is
@|example-macro-modname|.
})}
@item{
Clone the @hyperlink[GITHUB-URL]{source code}, then require the
module path of the file that defines the macro.
}
]
@section{Tutorials}
Where to learn about @racket[syntax-parse]?
@itemlist[
@item{
@hyperlink["https://school.racket-lang.org/2019/plan/index.html"]{How
to Design Languages} track of @emph{Racket School 2019},
especially the Tuesday classes. This is a compact introduction
to fundamental use of macros with lots of examples posed as
exercises.
}
@item{
Official documentation, @emph{Syntax: Meta-Programming Helpers}.
@hyperlink["https://docs.racket-lang.org/syntax/index.html"]{docs.racket-lang.org/syntax/index.html}
The
@hyperlink["https://docs.racket-lang.org/syntax/stxparse-intro.html"]{basic
examples in Section 1.1} and more complex ideas in
@hyperlink["https://docs.racket-lang.org/syntax/stxparse-examples.html"]{Section
1.2} (for example
@hyperlink["https://docs.racket-lang.org/syntax/Optional_Keyword_Arguments.html"]{1.2.2
"Optional Keyword Arguments"}) are a great way to get started.
}
@item{
@emph{Mythical Macros}.
@hyperlink["https://soegaard.github.io/mythical-macros/"]{soegaard.github.io/mythical-macros}
}
@item{
@emph{Macros and Languages in Racket}.
@hyperlink["http://rmculpepper.github.io/malr/index.html"]{rmculpepper.github.io/malr/index.html}
An unfinished manuscript from 2016 with a great "Basic Macrology"
section. Recommended as a first introduction to macros. Demonstrates
how to write "minimal macros" which defer evaluation of expressions
but then call run-time functions to do most of their work.
}
]
The @hyperlink["https://www.greghendershott.com/fear-of-macros/"]{Fear of
Macros} tutorial is a great resource for basic macro engineering (though, not
syntax-parse in particular).
@section{A @racket[syntax-parse] Crash Course}
The @racket[syntax-parse] form is a tool for unpacking data from a
@tech/reference{syntax object}.
It is similar to Racket's @racket[match].
Since the input to a @tech/guide{macro} is always a syntax object,
@racket[syntax-parse] is helpful for writing macros.
A syntax object is a Racket representation of source code.
For example, @racket[#'(+ 1 2)] is a syntax object that represents the
sequence of characters @litchar{(+ 1 2)}, along with the information that
the @racket[+] identifier is bound to a function in the @racketmodname[racket/base]
library.
A macro is a compile-time function on syntax objects.
In other words, a macro:
(1) is a function,
(2) expects a syntax object as input,
(3) returns a new syntax object, and
(4) runs at compile-time [@secref["expansion" #:doc '(lib "scribblings/reference/reference.scrbl")]].
Here is a simple macro that expects two arguments and returns its first argument.
When the expander finds a macro application @racket[(_K 1 2)], it invokes the macro @racket[_K] with a syntax object @racket[#'(_K 1 2)] representing the whole application [@secref["macro-transformers" #:doc '(lib "scribblings/guide/guide.scrbl")]].
@margin-note{The name @racket[_K] is @hyperlink["http://wiki.c2.com/?EssAndKayCombinators"]{historic}.}
@examples[#:eval (make-base-eval)
(require (for-syntax racket/base))
(define-syntax (K args-stx)
(define args (syntax-e args-stx)) (code:comment "syntax->list works too")
(if (= (length args) 3)
(cadr args)
(raise-argument-error
'K
"syntax object containing a list with 3 elements"
args-stx)))
(K 1 2)
(eval:error (K 1))
(eval:error (K 1 2 3))
]
Here is the same macro, defined using @racket[syntax-parse] instead of the low-level
@racket[syntax-e] and @racket[cadr] functions:
@examples[#:eval (make-base-eval)
(require (for-syntax racket/base syntax/parse))
(define-syntax (K args-stx)
(syntax-parse args-stx
[(_ ?arg0 ?arg1)
#'?arg0]))
(K 1 2)
(eval:error (K 1))
(eval:error (K 1 2 3))
]
I don't expect that all this makes sense so far.
Try running and modifying these examples.
Try reading the documentation for @racket[define-syntax] and @racket[syntax-e]
and @racket[syntax-parse] and @racket[syntax] (aka @litchar{#'}).
The last thing to point out is that @racket[(_ _?arg0 _?arg1)] is a
@tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{syntax pattern}.
@itemlist[
@item{
the parentheses say this pattern matches a (special kind of) list,
}
@item{
the underscore (@litchar{_}) means the first element of the list can be anything,
}
@item{
the name @racket[_?arg0] means the second element of the list can be anything
and gets bound to the @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern variable}
@racket[_?arg0],
}
@item{
the name @racket[_?arg1] binds the third element to another pattern variable,
}
@item{
and if the list has more or fewer elements the pattern does not match.
}
]
A pattern variable is a special kind of variable; it can only be referenced inside
a new syntax object.
The name @racket[_?arg0] starts with a @litchar{?} as a style choice --- it helps me remember that it is the name of a pattern variable.
@; =============================================================================
@include-section{index.scrbl}
@; =============================================================================
@section{How to contribute a new example}
@(let ([example-macro-name "EUGENE"]) @list{
To create an example named @tt[example-macro-name]:
@itemlist[
@item{
Clone this repository (@hyperlink[GITHUB-URL]{link}).
}
@item{
Run @exec{raco syntax-parse-example --new @|example-macro-name|} in the
top-level folder of the cloned repository.
This generates three new files:
@itemlist[
@item{
@tt{@|example-macro-name|/@|example-macro-name|.rkt} (source code)
}
@item{
@tt{@|example-macro-name|/@|example-macro-name|-test.rkt} (tests)
}
@item{
@tt{@|example-macro-name|/@|example-macro-name|-doc.scrbl} (Scribble documentation)
}
]
}
@item{
Fill the holes in the newly-generated files with an implementation,
some unit tests,
and documentation.
}
@item{
Run @exec{raco setup syntax-parse-example} to generate the documentation.
}
]
})
@section[#:tag "sec:reference"]{Example-Formatting Tools}
The @racketmodname[syntax-parse-example] language is a small language for
documenting example macros. It:
@itemlist[
@item{
uses the reader from @racketmodname[scribble/base]; and
}
@item{
provides a few utility functions, documented below.
}
]
@defmodule[syntax-parse-example/render]{
Helpers for rendering documentation.
}
@defproc[(tech/guide [pre-content pre-content?] ...) element?]{
Similar to @racket[tech], but links to @other-manual['(lib "scribblings/guide/guide.scrbl")].
}
@defproc[(tech/reference [pre-content pre-content?] ...) element?]{
Similar to @racket[tech], but links to @other-manual['(lib "scribblings/reference/reference.scrbl")].
}
@defproc[(racketfile [filename path-string?]) element?]{
Typesets the contents of the given file as if its contents were wrapped in a @racket[racketblock].
}