Initial commit.
This commit is contained in:
commit
8a59d45098
818
main.rkt
Normal file
818
main.rkt
Normal file
|
@ -0,0 +1,818 @@
|
|||
#lang scribble/manual
|
||||
|
||||
@(require racket/sandbox
|
||||
scribble/eval)
|
||||
@(define evaluator
|
||||
(parameterize ([sandbox-output 'string]
|
||||
[sandbox-error-output 'string])
|
||||
(make-evaluator 'racket)))
|
||||
|
||||
@(define-syntax-rule (i body ...)
|
||||
(interaction #:eval evaluator body ...))
|
||||
|
||||
@title{Fear of Macros}
|
||||
|
||||
@smaller{Copyright (c) 2012 by Greg Hendershott. All rights reserved.}
|
||||
|
||||
@table-of-contents{}
|
||||
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Introduction}
|
||||
|
||||
I learned Racket after 25 years of doing C/C++ imperative programming.
|
||||
|
||||
Some psychic whiplash resulted.
|
||||
|
||||
"All the parentheses" was actually not a big deal. Instead, the first
|
||||
mind warp was functional programming. Before long I wrapped my brain
|
||||
around it, and went on to become comfortable and effective with many
|
||||
other aspects and features of Racket.
|
||||
|
||||
But two final frontiers remained: Macros and continuations.
|
||||
|
||||
I found that simple macros were easy and understandable, plus there
|
||||
were many good tutorials available. But the moment I stepped past
|
||||
routine pattern-matching, I kind of fell off a cliff into a
|
||||
terminology soup. I marinaded myself in material, hoping it would
|
||||
eventually sink in after enough re-readings. I even found myself using
|
||||
trial and error, rather than having a clear mental model what was
|
||||
going on. Gah.
|
||||
|
||||
I'm starting to write this at the point where the shapes are slowly
|
||||
emerging from the fog.
|
||||
|
||||
My primary motive is selfish. Explaining something forces me to learn
|
||||
it more thorougly. Plus I expect that if I write something with
|
||||
mistakes, other people will be eager to point them out and correct
|
||||
me. Is that a social-engineering variation of meta-programming? Next
|
||||
question, please. :)
|
||||
|
||||
Finally I do hope it may help other people who have a similar
|
||||
background and/or learning style as me.
|
||||
|
||||
I want to show how Racket macro features have evolved as solutions to
|
||||
problems or annoyances. I learn more quickly and deeply when I
|
||||
discover the answer to a question I already have, or find the solution
|
||||
to a problem whose pain I already feel. Therefore I'll give you the
|
||||
questions and problems first, so that you can better appreciate and
|
||||
understand the answers and solutions.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{The plan of attack}
|
||||
|
||||
The macro system you will mostly want to use for production-quality
|
||||
macros is called @racket[syntax-parse]. And don't worry, we'll get to
|
||||
that soon.
|
||||
|
||||
But if we start there, you're likely to feel overwhelmed by concepts
|
||||
and terminology, and get very confused. I did.
|
||||
|
||||
1. Instead let's start with the basics: A syntax object and a function
|
||||
to change it (a "transformer"). We'll work at that level for awhile to
|
||||
get comfortable and to de-mythologize this whole macro business.
|
||||
|
||||
2. Next, we'll realize that some pattern-matching would make life
|
||||
easier. We'll learn about @racket[syntax-case], and its shorthand
|
||||
cousin, @racket[define-syntax-rule]. We'll discover we can get
|
||||
confused if we want to munge pattern variables before sticking them
|
||||
back in the template, and learn how to do that.
|
||||
|
||||
3. At this point we'll be able to write many useful macros. But, what
|
||||
if we want to write the ever-popular anaphoric if, with a "magic
|
||||
variable"? It turns out we've been protected from making certain kind
|
||||
of mistakes. When we want to do this kind of thing on purpose, we use
|
||||
a @racket[syntax parameter]. [There are other, older ways to do
|
||||
this. We won't look at them. We also won't spend a lot of time talking
|
||||
about "hygiene".]
|
||||
|
||||
4. Finally, we'll realize that our macros could be smarter when
|
||||
they're used in error. Normal Racket functions can optionally have
|
||||
contracts and types. These can catch mistakes and provide clear,
|
||||
useful error messages. It would be great if there were somthing
|
||||
similar for macros, and there is. One of the more-recent Racket macro
|
||||
enhancements is @racket[syntax-parse].
|
||||
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Transformers}
|
||||
|
||||
@verbatim[#:indent 2]{
|
||||
YOU ARE INSIDE A ROOM.
|
||||
THERE ARE KEYS ON THE GROUND.
|
||||
THERE IS A SHINY BRASS LAMP NEARBY.
|
||||
|
||||
IF YOU GO THE WRONG WAY, YOU WILL BECOME
|
||||
HOPELESSLY LOST AND CONFUSED.
|
||||
|
||||
> pick up the keys
|
||||
|
||||
YOU HAVE A SYNTAX TRANSFORMER
|
||||
}
|
||||
|
||||
|
||||
@subsection{What is a syntax transformer?}
|
||||
|
||||
A syntax transformer is not one of the トランスフォーマ
|
||||
@hyperlink["http://en.wikipedia.org/wiki/Transformers" "transformers"].
|
||||
|
||||
Instead, it is quite simple. It is a function. The function takes
|
||||
syntax and returns syntax. It transforms syntax.
|
||||
|
||||
Here's a transformer function that ignores its input syntax, and
|
||||
always outputs syntax for a string literal:
|
||||
|
||||
@i[
|
||||
(define-syntax foo
|
||||
(lambda (stx)
|
||||
(syntax "I am foo")))
|
||||
(foo)
|
||||
]
|
||||
|
||||
When we use @racket[define-syntax], we're making a transformer
|
||||
@italic{binding}. This tells the Racket compiler, "Whenever you
|
||||
encounter a chunk of syntax starting with @racket[foo], please give it
|
||||
to my transformer function, and replace it with the syntax I give back
|
||||
to you." So Racket will give anything that looks like @racket[(foo
|
||||
...)] to our function, and we can change it. Much like a
|
||||
search-and-replace.
|
||||
|
||||
Maybe you know that the usual way to define a function in Racket:
|
||||
|
||||
@racket[(define (f x) ...)]
|
||||
|
||||
is shorthand for:
|
||||
|
||||
@racket[(define f (lambda (x) ...))]
|
||||
|
||||
That shorthand lets you avoid typing @racket[lambda] and some parentheses.
|
||||
|
||||
Well there is a similar shorthand for @racket[define-syntax]:
|
||||
|
||||
@i[
|
||||
(define-syntax (also-foo stx)
|
||||
(syntax "I am also foo"))
|
||||
(also-foo)
|
||||
]
|
||||
|
||||
What we want to remember is that this is simply shorthand. We are
|
||||
still defining a transformer function, which takes syntax and returns
|
||||
syntax. Everything we do with macros, will be built on top of this
|
||||
basic idea. It's not magic.
|
||||
|
||||
Speaking of shorthand, there is also a shorthand for @racket[syntax],
|
||||
which is @tt{#'}:
|
||||
|
||||
@i[
|
||||
(define-syntax (quoted-foo stx)
|
||||
#'"I am also foo, using #' instead of syntax")
|
||||
(quoted-foo)
|
||||
]
|
||||
|
||||
Of course, we can emit syntax that is more interesting than a
|
||||
string literal. How about returning @racket[(displayln "hi")]?
|
||||
|
||||
@i[
|
||||
(define-syntax (say-hi stx)
|
||||
#'(displayln "hi"))
|
||||
(say-hi)
|
||||
]
|
||||
|
||||
When Racket expands our program, it sees the occurrence of
|
||||
@racket[(say-hi)], and sees it has a transformer function for that. It
|
||||
calls our function with the old syntax, and we return the new syntax,
|
||||
which is used to evaluate and run our program.
|
||||
|
||||
|
||||
@subsection{What is the input?}
|
||||
|
||||
Our examples so far have been ignoring the input syntax, and
|
||||
outputting a fixed syntax. Usually, we want to transform the input to
|
||||
something else.
|
||||
|
||||
But let's start by looking at what the input @italic{is}:
|
||||
|
||||
@i[
|
||||
(define-syntax (show-me stx)
|
||||
(print stx)
|
||||
#'(void))
|
||||
(show-me '(i am a list))
|
||||
]
|
||||
|
||||
The @racket[(print stx)] shows what our transformer is given: a syntax
|
||||
object.
|
||||
|
||||
A syntax object consists of several things. The first part is the
|
||||
s-expression representing the code, such as @racket['(i am a
|
||||
list)]. Racket (and Scheme and Lisp) expressions are s-expressions---
|
||||
code and data have the same structure, and this makes it vastly easier
|
||||
to rewrite syntax, i.e. write macros.
|
||||
|
||||
Racket syntax is also decorated with some interesting information such
|
||||
as the source file, line number, and column. Finally, it has
|
||||
information about lexical scoping (which you don't need to worry about
|
||||
now, but will turn out to be important later.)
|
||||
|
||||
There are a variety of functions available to access a syntax object:
|
||||
|
||||
@i[
|
||||
(define stx #'(if x (list "true") #f))
|
||||
(syntax->datum stx)
|
||||
(syntax-e stx)
|
||||
(syntax->list stx)
|
||||
(syntax-source stx)
|
||||
(syntax-line stx)
|
||||
(syntax-column stx)
|
||||
]
|
||||
|
||||
When we want to transform syntax, we'll generally take the pieces we
|
||||
were given, maybe rearrange their order, perhaps change some of the
|
||||
pieces, and often introduce brand-new pieces.
|
||||
|
||||
@subsection{Actually transforming the input}
|
||||
|
||||
Let's write a transformer function that reverses the syntax it was
|
||||
given:
|
||||
|
||||
@i[
|
||||
(define-syntax (reverse-me stx)
|
||||
(datum->syntax stx (reverse (cdr (syntax->datum stx)))))
|
||||
(reverse-me "backwards" "am" "i" values)
|
||||
]
|
||||
|
||||
What's going on here? First we take the input syntax, and give it to
|
||||
@racket[syntax->datum]. This converts the syntax into a plain old list:
|
||||
|
||||
@i[
|
||||
(syntax->datum #'(reverse-me "backwards" "am" "i" values))
|
||||
]
|
||||
|
||||
Using @racket[cdr] slics off the first item of the list,
|
||||
@racket[reverse-me], leaving the rmainder:
|
||||
@racket[("backwards" "am" "i" values)]. Passing that to
|
||||
@racket[reverse] changes it to @racket[(values "i" "am" "backwards")]:
|
||||
|
||||
@i[
|
||||
(reverse (cdr '("backwards" "am" "i" values)))
|
||||
]
|
||||
|
||||
Finally we use @racket[syntax->datum] to convert this back to
|
||||
@racket[syntax]:
|
||||
|
||||
@i[
|
||||
(datum->syntax #f '(values "i" "am" "backwards"))
|
||||
]
|
||||
|
||||
That's what our transformer function gives back to the Racket
|
||||
compiler, and @italic{that} syntax is evaluated:
|
||||
|
||||
@i[
|
||||
(values "i" "am" "backwards")
|
||||
]
|
||||
|
||||
|
||||
@subsection{Compile time vs. run time}
|
||||
|
||||
Normal Racket code runs at ... run time. Duh.
|
||||
|
||||
@margin-note{Instead of "compile time vs. run time", you may hear it
|
||||
described as "syntax phase vs. runtime phase". Same difference.}
|
||||
|
||||
But a syntax transformer is run by the Racket compiler, as part of the
|
||||
process of parsing, expanding and understanding your code. In other
|
||||
words, your syntax transformer function is evaluated at compile time.
|
||||
|
||||
This aspect of macros lets you do things that simply aren't possible
|
||||
in normal code. One of the classic examples, is something like the
|
||||
Racket @racket[if] form:
|
||||
|
||||
@racket[(if <condition> <true-expression> <false-expression>)]
|
||||
|
||||
If @racket[if] were implemented as a function, all of the arguments
|
||||
would be evaluated before being provided to the function.
|
||||
|
||||
@i[
|
||||
(define (our-if condition true-expr false-expr)
|
||||
(cond [condition true-expr]
|
||||
[else false-expr]))
|
||||
(our-if #t
|
||||
"true"
|
||||
"false")
|
||||
]
|
||||
|
||||
That seems to work. However, how about this:
|
||||
|
||||
@i[
|
||||
(define (display-and-return x)
|
||||
(displayln x)
|
||||
x)
|
||||
(our-if #t
|
||||
(display-and-return "true")
|
||||
(display-and-return "false"))
|
||||
]
|
||||
|
||||
@margin-note{One answer is that functional programming is good, and
|
||||
side-effects are bad. But avoiding side-effects isn't always
|
||||
practical.}
|
||||
|
||||
Oops. Because the expressions have a side-effect, it's obvious that
|
||||
they are both evaluated. And that could be a problem---what if the
|
||||
side-effect includes deleting a file on disk? You wouldn't want
|
||||
@racket[(if user-wants-file-deleted? (delete-file) (void))] to delete
|
||||
a file even when @racket[user-wants-file-deleted?] is @racket[#f].
|
||||
|
||||
So this simply can't work as a plain function. However a syntax
|
||||
transformer can rearrange the syntax -- rewrite the code -- at compile
|
||||
time. The pieces of syntax are moved around, but they aren't actually
|
||||
evaluated until run time.
|
||||
|
||||
Here is one way to do this:
|
||||
|
||||
@i[
|
||||
(define-syntax (our-if-v2 stx)
|
||||
(define xs (syntax->list stx))
|
||||
(datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
|
||||
[else ,(cadddr xs)])))
|
||||
(our-if-v2 #t
|
||||
(display-and-return "true")
|
||||
(display-and-return "false"))
|
||||
(our-if-v2 #f
|
||||
(display-and-return "true")
|
||||
(display-and-return "false"))
|
||||
]
|
||||
|
||||
That gave the right answer. But how? Let's pull out the transformer
|
||||
function itself, and see what it did. We start with an example of some
|
||||
input syntax:
|
||||
|
||||
@i[
|
||||
(define stx (syntax (our-if-v2 #t "true" "false")))
|
||||
(displayln stx)
|
||||
]
|
||||
|
||||
1. We take the original syntax, and use @racket[syntax->datum] to
|
||||
change it into a plain Racket @racket[list]:
|
||||
|
||||
@i[
|
||||
(define xs (syntax->datum stx))
|
||||
(displayln xs)
|
||||
]
|
||||
|
||||
2. To change this into a Racket @racket[cond] form, we need to take
|
||||
the three interesting pieces---the condition, true-expression, and
|
||||
false-expression---from the list using @racket[cadr], @racket[caddr],
|
||||
and @racket[cadddr] and arrange them into a @racket[cond] form:
|
||||
|
||||
@codeblock{
|
||||
`(cond [,(cadr xs) ,(caddr xs)]
|
||||
[else ,(cadddr xs)])
|
||||
}
|
||||
|
||||
3. Finally, we change that into @racket[syntax] using
|
||||
@racket[datum->syntax]:
|
||||
|
||||
@i[
|
||||
(datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
|
||||
[else ,(cadddr xs)]))
|
||||
]
|
||||
|
||||
So that works, but using @racket[cdddr] etc. to destructure a list is
|
||||
painful and error-prone. Maybe you know Racket's @racket[match]?
|
||||
Using that would let us do pattern-matching.
|
||||
|
||||
@margin-note{Notice that we don't care about the first item in the
|
||||
syntax list. We didn't take @racket[(car xs)] in our-if-v2, and we
|
||||
didn't use @racket[name] when we used pattern-matching. In general, a
|
||||
syntax transformer won't care about that, because it is the name of
|
||||
the transformer binding. In other words, a macro usually doesn't care
|
||||
about its own name.}
|
||||
|
||||
Instead of:
|
||||
|
||||
@i[
|
||||
(define-syntax (our-if-v2 stx)
|
||||
(define xs (syntax->list stx))
|
||||
(datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
|
||||
[else ,(cadddr xs)])))
|
||||
]
|
||||
|
||||
We can write:
|
||||
|
||||
@i[
|
||||
(define-syntax (our-if-using-match stx)
|
||||
(match (syntax->list stx)
|
||||
[(list name condition true-expr false-expr)
|
||||
(datum->syntax stx `(cond [,condition ,true-expr]
|
||||
[else ,false-expr]))]))
|
||||
(our-if-using-match #t "true" "false")
|
||||
]
|
||||
|
||||
But wait, we can't. It's complaining that @racket[match] isn't
|
||||
defined. We havne't required the @racket[racket/match] module?
|
||||
|
||||
It turns out we haven't. Remember, this transformer function is
|
||||
working at compile time, not run time. And at compile time, only
|
||||
@racket[racket/base] is required for you automatically. If we want
|
||||
something like @racket[racket/match], we have to require it
|
||||
ourselves---and require it @italic{for compile time}. Instead of using
|
||||
plain @racket[(require racket/match)], the way to say this is to use
|
||||
@racket[(require (for-syntax racket/match))]---the @racket[for-syntax]
|
||||
part meaning, "for compile time".
|
||||
|
||||
So let's try that:
|
||||
|
||||
@i[
|
||||
(require (for-syntax racket/match))
|
||||
(define-syntax (our-if-using-match-v2 stx)
|
||||
(match (syntax->list stx)
|
||||
[(list _ condition true-expr false-expr)
|
||||
(datum->syntax stx `(cond [,condition ,true-expr]
|
||||
[else ,false-expr]))]))
|
||||
(our-if-using-match-v2 #t "true" "false")
|
||||
]
|
||||
|
||||
To review:
|
||||
|
||||
Syntax transformers work at compile time, not run time. The good
|
||||
news is this means we can do things like delay evaluation, and
|
||||
implement forms like @racket[if] which simply couldn't work properly
|
||||
as run time functions.
|
||||
|
||||
Some other good news is that there isn't some special, weird language
|
||||
for writing syntax transformers. We can write these transformer
|
||||
functions using familiar Racket code. The semi-bad news is that the
|
||||
familiarity can make it easy to forget that we're not working at run
|
||||
time. Sometimes that's important to remember. For example only
|
||||
@racket[racket/base] is required for us automtically. If we need other
|
||||
modules, we have to require them, and we have to require them
|
||||
@italic{for compile time} using @racket[(require (for-syntax))].
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Pattern matching: syntax-case and syntax-rules}
|
||||
|
||||
Most useful syntax transformers work by taking some input syntax, and
|
||||
rearranging the pieces into something else. As we saw, this is
|
||||
possible but tedious using list accessors such as @racket[cdddr]. It's
|
||||
more convenient and less error-prone to use pattern-matching.
|
||||
|
||||
@margin-note{Historically, @racket[syntax-case] and
|
||||
@racket[syntax-parse] pattern matching came first. @racket[match] was
|
||||
added to Racket later.}
|
||||
|
||||
It turns out that pattern-matching was one of the first improvements
|
||||
to be added to the Racket macro system. It's called
|
||||
@racket[syntax-case], and has a shorthand for simple situations called
|
||||
@racket[define-syntax-rule].
|
||||
|
||||
Recall our previous example:
|
||||
|
||||
@codeblock{
|
||||
(require (for-syntax racket/match))
|
||||
(define-syntax (our-if-using-match-v2 stx)
|
||||
(match (syntax->list stx)
|
||||
[(list _ condition true-expr false-expr)
|
||||
(datum->syntax stx `(cond [,condition ,true-expr]
|
||||
[else ,false-expr]))]))
|
||||
}
|
||||
|
||||
Here's what it looks like using @racket[syntax-case]:
|
||||
|
||||
@i[
|
||||
(define-syntax (our-if-using-syntax-case stx)
|
||||
(syntax-case stx ()
|
||||
[(_ condition true-expr false-expr)
|
||||
#'(cond [condition true-expr]
|
||||
[else false-expr])]))
|
||||
(our-if-using-syntax-case #t "true" "false")
|
||||
]
|
||||
|
||||
Prety similar, huh? The pattern part looks almost exactly the
|
||||
same. The "template" part---where we specify the new syntax---is
|
||||
simpler. We don't need to do quasiquoting and unquoting. We don't need
|
||||
to use @racket[datum->syntax]. We simply supply a template, which uses
|
||||
variables from the pattern.
|
||||
|
||||
There is a shorthand for simple pattern-matching cases, which expands
|
||||
into @racket[syntax-case]. It's called @racket[define-syntax-rule]:
|
||||
|
||||
@i[
|
||||
(define-syntax-rule (our-if-using-syntax-rule condition true-expr false-expr)
|
||||
(cond [condition true-expr]
|
||||
[else false-expr]))
|
||||
(our-if-using-syntax-rule #t "true" "false")
|
||||
]
|
||||
|
||||
Here's the thing about @racket[define-syntax-rule]. Because it's so
|
||||
simple, @racket[define-syntax-rule] is ofen the first thing people are
|
||||
taught about macros. But it's almost deceptively simple. It looks so
|
||||
much like defining a normal run time function---yet it's not. It's
|
||||
working at compile time, not run time. Worse, the moment you want to
|
||||
do more than @racket[define-syntax-rule] can handle, you can fall off
|
||||
a cliff into what feels like complicated and confusing
|
||||
territory. Hopefully, because we started with a basic syntax
|
||||
transformer, and worked up from that, we won't have that problem. We
|
||||
can appreciate @racket[define-syntax-rule] as a convenient shorthand,
|
||||
but not be scared of, or confused about, that for which it's
|
||||
shorthand.
|
||||
|
||||
@subsection{Patterns and templates}
|
||||
|
||||
Most of the materials I found for learning macros, including the
|
||||
Racket Guide, do a very good job explaining how the patterns work. I'm
|
||||
not going to regurgitate that here.
|
||||
|
||||
Instead, let's look at some ways we're likely to get tripped up.
|
||||
|
||||
@subsubsection{"A pattern variable can't be used outside of a template"}
|
||||
|
||||
Let's say we want to define a function with a hyphenated name, a-b,
|
||||
but we supply the a and b parts separately. The Racket @racket[struct]
|
||||
form does somethin like this---if we define a @racket[struct] named
|
||||
@racket[foo], it defines a number of functions whose names are
|
||||
variations on the name @racket[foo], such as @racket[foo-field1],
|
||||
@racket[foo-field2], @racket[foo?], and so on.
|
||||
|
||||
So let's pretend we're doing something like that. We want to
|
||||
transform the syntax @racket[(hyphen-define a b (args) body)] to
|
||||
the syntax @racket[(define (a-b args) body)].
|
||||
|
||||
A wrong first attempt is:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/wrong stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(let ([name (string->symbol (format "~a-~a" a b))])
|
||||
#'(define (name args ...)
|
||||
body0 body ...))]))
|
||||
]
|
||||
|
||||
Huh. We have no idea what this error message means. Well, let's see.
|
||||
The "template" is the @racket[#'(define (name args ...) body0 body
|
||||
...)] portion. The @racket[let] isn't part of that template. It
|
||||
sounds like we can't use @racket[a] (or @racket[b]) in the
|
||||
@racket[let] part.
|
||||
|
||||
It turns out we can use a pattern variable in @italic{another}
|
||||
pattern---by using @racket[syntax-case] @italic{again}:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/wrong stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(syntax-case (datum->syntax stx (string->symbol (format "~a-~a"
|
||||
(syntax->datum a)
|
||||
(syntax->datum b)))) ()
|
||||
[name #'(define (name args ...)
|
||||
body0 body ...)])]))
|
||||
]
|
||||
|
||||
@margin-note{I don't have a clear explanation for @italic{why} they
|
||||
need to be @tt{#'a} and @tt{#'b}. Can anyone help?}
|
||||
|
||||
Well, not quite. We can't use @racket[a] and @racket[b] directly. We
|
||||
have to wrap each one in @racket[syntax], or use its reader alias,
|
||||
@tt{#'}:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/ok1 stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(syntax-case (datum->syntax stx
|
||||
(string->symbol (format "~a-~a"
|
||||
(syntax->datum #'a)
|
||||
(syntax->datum #'b)))) ()
|
||||
[name #'(define (name args ...)
|
||||
body0 body ...)])]))
|
||||
(hyphen-define/ok1 first second () #t)
|
||||
(first-second)
|
||||
]
|
||||
|
||||
And now it works!
|
||||
|
||||
There is a shorthand for using @racket[syntax-case] this way. It's
|
||||
called @racket[with-syntax]. This makes it a little simpler:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/ok2 stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(with-syntax ([name (datum->syntax stx
|
||||
(string->symbol (format "~a-~a"
|
||||
(syntax->datum #'a)
|
||||
(syntax->datum #'b))))])
|
||||
#`(define (name args ...)
|
||||
body0 body ...))]))
|
||||
(hyphen-define/ok2 foo bar () #t)
|
||||
(foo-bar)
|
||||
]
|
||||
|
||||
Another handy thing is that @racket[with-syntax] will convert the
|
||||
expression to syntax automatically. So we don't need the
|
||||
@racket[datum->syntax] stuff, and now it becomes even simpler:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/ok3 stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(with-syntax ([name (string->symbol (format "~a-~a"
|
||||
(syntax->datum #'a)
|
||||
(syntax->datum #'b)))])
|
||||
#`(define (name args ...)
|
||||
body0 body ...))]))
|
||||
(hyphen-define/ok3 foo bar () #t)
|
||||
(foo-bar)
|
||||
]
|
||||
|
||||
Recap: If you want to munge pattern variables for use in the template,
|
||||
@racket[with-syntax] is your friend. Just remember you have to use
|
||||
@racket[syntax] or @tt{#'} on the pattern variables.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Syntax parameters}
|
||||
|
||||
"Anaphoric if" or "aif" is a popular macro example. Instead of writing:
|
||||
|
||||
@codeblock{
|
||||
(let ([tmp (big-long-calculation)])
|
||||
(if tmp
|
||||
(foo tmp)
|
||||
#f))
|
||||
}
|
||||
|
||||
It would be great to write:
|
||||
|
||||
@codeblock{
|
||||
(aif (big-long-calculation)
|
||||
(foo it)
|
||||
#f)
|
||||
}
|
||||
|
||||
In other words, when the condition is true, an @racket[it] identifier
|
||||
is automatically created and set to the value of the condition. This
|
||||
should be easy:
|
||||
|
||||
|
||||
@i[
|
||||
(define-syntax-rule (aif condition true-expr false-expr)
|
||||
(let ([it condition])
|
||||
(if it
|
||||
true-expr
|
||||
false-expr)))
|
||||
(aif #t (displayln it) (void))
|
||||
]
|
||||
|
||||
Wait, what---@racket[it] is undefined?
|
||||
|
||||
It turns out that all along we have been protected from making a
|
||||
certain kind of mistake in our macros. The mistake is to introduce a
|
||||
variable that accidentally conflicts with one in the code that is
|
||||
using our macro.
|
||||
|
||||
The Racket Reference
|
||||
@hyperlink["http://docs.racket-lang.org/reference/syntax-model.html#(part._transformer-model)" "Section
|
||||
1.2.3.5 Transformer Bindings."] has a good explanation of this, and an
|
||||
example. (You can stop when you reach the part about set!
|
||||
transformers.) Basically, the input syntax has "marks" to preserve
|
||||
lexical scope. This makes your macro behave like a normal function. If
|
||||
a normal function defines a variable named @racket[x], it won't
|
||||
conflict with a variable named @racket[x] in an outer scope.
|
||||
|
||||
This makes it easy to write reliable macros that behave
|
||||
predictably. Unfortunately, once in awhile, we want to introduce a
|
||||
magic variable like @racket[it] for @racket[aif] on purpose.
|
||||
|
||||
The way to do this is with @racket[define-syntax-parameter] and
|
||||
@racket[syntax-parameterize]. You're probably familiar with regular
|
||||
parameters in Racket.
|
||||
|
||||
@i[
|
||||
(define current-foo (make-parameter "some default value"))
|
||||
(current-foo)
|
||||
(parameterize ([current-foo "I have a new value, for now"])
|
||||
(current-foo))
|
||||
(current-foo)
|
||||
]
|
||||
|
||||
@margin-note{Historically, there are other ways to do this. If you
|
||||
know them, you will want to unlearn them. But if you're the target
|
||||
audience I'm writing for, you don't know them yet. You can skip
|
||||
learning them now. (Someday if you want to understand someone else's
|
||||
older macros, you can learn about them then.)}
|
||||
|
||||
The syntax variation of them works similarly. The idea is, we'll
|
||||
define @racket[it] to mean an error by default. Only inside of our
|
||||
@racket[aif] will it have a meaningful value:
|
||||
|
||||
@i[
|
||||
(require racket/stxparam)
|
||||
(define-syntax-parameter it
|
||||
(lambda (stx)
|
||||
(raise-syntax-error (syntax-e stx) "can only be used inside aif")))
|
||||
(define-syntax-rule (aif condition true-expr false-expr)
|
||||
(let ([tmp condition])
|
||||
(if tmp
|
||||
(syntax-parameterize ([it (make-rename-transformer #'tmp)])
|
||||
true-expr)
|
||||
false-expr)))
|
||||
(aif 10 (displayln it) (void))
|
||||
(aif #f (displayln it) (void))
|
||||
]
|
||||
|
||||
We can still use @racket[it] as a normal variable name:
|
||||
|
||||
@i[
|
||||
(define it 10)
|
||||
it
|
||||
]
|
||||
|
||||
If we try to use @racket[it] outside of an @racket[aif] form, and
|
||||
@racket[it] isn't otherwise defined, we get an error like we want:
|
||||
|
||||
@i[
|
||||
(displayln it)
|
||||
]
|
||||
|
||||
Perfect.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Robust macros: syntax-parse}
|
||||
|
||||
TO-DO.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Other questions}
|
||||
|
||||
Hopefully I will answer these in the course of the other sections. But
|
||||
just in case:
|
||||
|
||||
@subsection{What's the point of @racket[with-syntax]?}
|
||||
|
||||
Done.
|
||||
|
||||
@subsection{What's the point of @racket[begin-for-syntax]?}
|
||||
|
||||
TO-DO.
|
||||
|
||||
@subsection{What's the point of @racket[racket/splicing]?}
|
||||
|
||||
TO-DO.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{References/Acknowledgements}
|
||||
|
||||
Eli Barzliay wrote a blog post,
|
||||
@hyperlink["http://blog.racket-lang.org/2011/04/writing-syntax-case-macros.html" "Writing
|
||||
‘syntax-case’ Macros"], which explains many key details. However it's written
|
||||
especially for people already familiar with "un-hygienic" "defmacro"
|
||||
style macros. If you're not familiar with those, it may seem slightly
|
||||
weird to the extent it's trying to convince you to change an opinion
|
||||
you don't have. Even so, many key details are presented in Eli's typically
|
||||
concise, clear fashion.
|
||||
|
||||
Eli Barzilay wrote another blog post,
|
||||
@hyperlink["http://blog.racket-lang.org/2008/02/dirty-looking-hygiene.html" "Dirty
|
||||
Looking Hygiene"], which explains syntax-parameterize. I relied
|
||||
heavily on that, mostly just updating it since his post was written
|
||||
before PLT Scheme was renamed to Racket.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Epilog}
|
||||
|
||||
@centered{
|
||||
"Before I had studied Chan (Zen) for thirty years, I saw mountains as
|
||||
mountains, and rivers as rivers. When I arrived at a more intimate
|
||||
knowledge, I came to the point where I saw that mountains are not
|
||||
mountains, and rivers are not rivers. But now that I have got its very
|
||||
substance I am at rest. For it's just that I see mountains once again
|
||||
as mountains, and rivers once again as rivers"
|
||||
|
||||
@smaller{--Buddhist saying originally formulated by Qingyuan Weixin,
|
||||
later translated by D.T. Suzuki in his @italic{Essays in Zen
|
||||
Buddhism}.}
|
||||
}
|
||||
|
||||
|
||||
Translated into Racket:
|
||||
|
||||
@codeblock{
|
||||
(dynamic-wind (lambda ()
|
||||
(and (eq? 'mountains 'mountains)
|
||||
(eq? 'rivers 'rivers)))
|
||||
(lambda ()
|
||||
(not (and (eq? 'mountains 'mountains)
|
||||
(eq? 'rivers 'rivers))))
|
||||
(lambda ()
|
||||
(and (eq? 'mountains 'mountains)
|
||||
(eq? 'rivers 'rivers))))
|
||||
}
|
||||
|
233
racket.css
Normal file
233
racket.css
Normal file
|
@ -0,0 +1,233 @@
|
|||
|
||||
/* See the beginning of "scribble.css". */
|
||||
|
||||
/* Monospace: */
|
||||
.RktIn, .RktRdr, .RktPn, .RktMeta,
|
||||
.RktMod, .RktKw, .RktVar, .RktSym,
|
||||
.RktRes, .RktOut, .RktCmt, .RktVal {
|
||||
font-family: monospace;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
/* Serif: */
|
||||
.inheritedlbl {
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
/* Sans-serif: */
|
||||
.RBackgroundLabelInner {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Inherited methods, left margin */
|
||||
|
||||
.inherited {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
text-align: left;
|
||||
background-color: #ECF5F5;
|
||||
}
|
||||
|
||||
.inherited td {
|
||||
font-size: 82%;
|
||||
padding-left: 1em;
|
||||
text-indent: -0.8em;
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
|
||||
.inheritedlbl {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Racket text styles */
|
||||
|
||||
.RktIn {
|
||||
color: #cc6633;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.RktInBG {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.RktRdr {
|
||||
}
|
||||
|
||||
.RktPn {
|
||||
color: #843c24;
|
||||
}
|
||||
|
||||
.RktMeta {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.RktMod {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.RktOpt {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.RktKw {
|
||||
color: black;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
|
||||
.RktErr {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.RktVar {
|
||||
color: #262680;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.RktSym {
|
||||
color: #262680;
|
||||
}
|
||||
|
||||
.RktValLink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.RktModLink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.RktStxLink {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
|
||||
.RktRes {
|
||||
color: #0000af;
|
||||
}
|
||||
|
||||
.RktOut {
|
||||
color: #960096;
|
||||
}
|
||||
|
||||
.RktCmt {
|
||||
color: #c2741f;
|
||||
}
|
||||
|
||||
.RktVal {
|
||||
color: #228b22;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Some inline styles */
|
||||
|
||||
.together {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prototype, .argcontract, .RBoxed {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.prototype td {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.longprototype td {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.RktBlk {
|
||||
white-space: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.RktBlk tr {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.RktBlk td {
|
||||
vertical-align: baseline;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.argcontract td {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
background-color: #ddddff;
|
||||
}
|
||||
|
||||
.defmodule {
|
||||
width: 100%;
|
||||
background-color: #F5F5DC;
|
||||
}
|
||||
|
||||
.specgrammar {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.RBibliography td {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.leftindent {
|
||||
margin-left: 1em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
.insetpara {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.Rfilebox {
|
||||
}
|
||||
|
||||
.Rfiletitle {
|
||||
text-align: right;
|
||||
margin: 0em 0em 0em 0em;
|
||||
}
|
||||
|
||||
.Rfilename {
|
||||
border-top: 1px solid #6C8585;
|
||||
border-right: 1px solid #6C8585;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
background-color: #ECF5F5;
|
||||
}
|
||||
|
||||
.Rfilecontent {
|
||||
margin: 0em 0em 0em 0em;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* For background labels */
|
||||
|
||||
.RBackgroundLabel {
|
||||
float: right;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.RBackgroundLabelInner {
|
||||
position: relative;
|
||||
width: 25em;
|
||||
left: -25.5em;
|
||||
top: 0px;
|
||||
text-align: right;
|
||||
color: white;
|
||||
z-index: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.RForeground {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: 1;
|
||||
}
|
153
scribble-common.js
Normal file
153
scribble-common.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Common functionality for PLT documentation pages
|
||||
|
||||
// Page Parameters ------------------------------------------------------------
|
||||
|
||||
var page_query_string =
|
||||
(location.href.search(/\?([^#]+)(?:#|$)/) >= 0) && RegExp.$1;
|
||||
|
||||
var page_args =
|
||||
((function(){
|
||||
if (!page_query_string) return [];
|
||||
var args = page_query_string.split(/[&;]/);
|
||||
for (var i=0; i<args.length; i++) {
|
||||
var a = args[i];
|
||||
var p = a.indexOf('=');
|
||||
if (p >= 0) args[i] = [a.substring(0,p), a.substring(p+1)];
|
||||
else args[i] = [a, false];
|
||||
}
|
||||
return args;
|
||||
})());
|
||||
|
||||
function GetPageArg(key, def) {
|
||||
for (var i=0; i<page_args.length; i++)
|
||||
if (page_args[i][0] == key) return unescape(page_args[i][1]);
|
||||
return def;
|
||||
}
|
||||
|
||||
function MergePageArgsIntoLink(a) {
|
||||
if (page_args.length == 0 ||
|
||||
(!a.attributes["pltdoc"]) || (a.attributes["pltdoc"].value == ""))
|
||||
return;
|
||||
a.href.search(/^([^?#]*)(?:\?([^#]*))?(#.*)?$/);
|
||||
if (RegExp.$2.length == 0) {
|
||||
a.href = RegExp.$1 + "?" + page_query_string + RegExp.$3;
|
||||
} else {
|
||||
// need to merge here, precedence to arguments that exist in `a'
|
||||
var i, j;
|
||||
var prefix = RegExp.$1, str = RegExp.$2, suffix = RegExp.$3;
|
||||
var args = str.split(/[&;]/);
|
||||
for (i=0; i<args.length; i++) {
|
||||
j = args[i].indexOf('=');
|
||||
if (j) args[i] = args[i].substring(0,j);
|
||||
}
|
||||
var additions = "";
|
||||
for (i=0; i<page_args.length; i++) {
|
||||
var exists = false;
|
||||
for (j=0; j<args.length; j++)
|
||||
if (args[j] == page_args[i][0]) { exists = true; break; }
|
||||
if (!exists) str += "&" + page_args[i][0] + "=" + page_args[i][1];
|
||||
}
|
||||
a.href = prefix + "?" + str + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
// Cookies --------------------------------------------------------------------
|
||||
|
||||
function GetCookie(key, def) {
|
||||
var i, cookiestrs;
|
||||
try {
|
||||
if (document.cookie.length <= 0) return def;
|
||||
cookiestrs = document.cookie.split(/; */);
|
||||
} catch (e) { return def; }
|
||||
for (i = 0; i < cookiestrs.length; i++) {
|
||||
var cur = cookiestrs[i];
|
||||
var eql = cur.indexOf('=');
|
||||
if (eql >= 0 && cur.substring(0,eql) == key)
|
||||
return unescape(cur.substring(eql+1));
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
function SetCookie(key, val) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime()+(365*24*60*60*1000));
|
||||
try {
|
||||
document.cookie =
|
||||
key + "=" + escape(val) + "; expires="+ d.toGMTString() + "; path=/";
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// note that this always stores a directory name, ending with a "/"
|
||||
function SetPLTRoot(ver, relative) {
|
||||
var root = location.protocol + "//" + location.host
|
||||
+ NormalizePath(location.pathname.replace(/[^\/]*$/, relative));
|
||||
SetCookie("PLT_Root."+ver, root);
|
||||
}
|
||||
|
||||
// adding index.html works because of the above
|
||||
function GotoPLTRoot(ver, relative) {
|
||||
var u = GetCookie("PLT_Root."+ver, null);
|
||||
if (u == null) return true; // no cookie: use plain up link
|
||||
// the relative path is optional, default goes to the toplevel start page
|
||||
if (!relative) relative = "index.html";
|
||||
location = u + relative;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Utilities ------------------------------------------------------------------
|
||||
|
||||
normalize_rxs = [/\/\/+/g, /\/\.(\/|$)/, /\/[^\/]*\/\.\.(\/|$)/];
|
||||
function NormalizePath(path) {
|
||||
var tmp, i;
|
||||
for (i = 0; i < normalize_rxs.length; i++)
|
||||
while ((tmp = path.replace(normalize_rxs[i], "/")) != path) path = tmp;
|
||||
return path;
|
||||
}
|
||||
|
||||
// `noscript' is problematic in some browsers (always renders as a
|
||||
// block), use this hack instead (does not always work!)
|
||||
// document.write("<style>mynoscript { display:none; }</style>");
|
||||
|
||||
// Interactions ---------------------------------------------------------------
|
||||
|
||||
function DoSearchKey(event, field, ver, top_path) {
|
||||
var val = field.value;
|
||||
if (event && event.keyCode == 13) {
|
||||
var u = GetCookie("PLT_Root."+ver, null);
|
||||
if (u == null) u = top_path; // default: go to the top path
|
||||
u += "search/index.html?q=" + escape(val);
|
||||
if (page_query_string) u += "&" + page_query_string;
|
||||
location = u;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function TocviewToggle(glyph, id) {
|
||||
var s = document.getElementById(id).style;
|
||||
var expand = s.display == "none";
|
||||
s.display = expand ? "block" : "none";
|
||||
glyph.innerHTML = expand ? "▼" : "►";
|
||||
}
|
||||
|
||||
// Page Init ------------------------------------------------------------------
|
||||
|
||||
// Note: could make a function that inspects and uses window.onload to chain to
|
||||
// a previous one, but this file needs to be required first anyway, since it
|
||||
// contains utilities for all other files.
|
||||
var on_load_funcs = [];
|
||||
function AddOnLoad(fun) { on_load_funcs.push(fun); }
|
||||
window.onload = function() {
|
||||
for (var i=0; i<on_load_funcs.length; i++) on_load_funcs[i]();
|
||||
};
|
||||
|
||||
AddOnLoad(function(){
|
||||
var links = document.getElementsByTagName("a");
|
||||
for (var i=0; i<links.length; i++) MergePageArgsIntoLink(links[i]);
|
||||
var label = GetPageArg("ctxtname",false);
|
||||
if (!label) return;
|
||||
var indicator = document.getElementById("contextindicator");
|
||||
if (!indicator) return;
|
||||
indicator.innerHTML = label;
|
||||
indicator.style.display = "block";
|
||||
});
|
0
scribble-style.css
Normal file
0
scribble-style.css
Normal file
471
scribble.css
Normal file
471
scribble.css
Normal file
|
@ -0,0 +1,471 @@
|
|||
|
||||
/* CSS seems backward: List all the classes for which we want a
|
||||
particular font, so that the font can be changed in one place. (It
|
||||
would be nicer to reference a font definition from all the places
|
||||
that we want it.)
|
||||
|
||||
As you read the rest of the file, remember to double-check here to
|
||||
see if any font is set. */
|
||||
|
||||
/* Monospace: */
|
||||
.maincolumn, .refpara, .refelem, .tocset, .stt, .hspace, .refparaleft, .refelemleft {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* Serif: */
|
||||
.main, .refcontent, .tocview, .tocsub, i {
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
/* Sans-serif: */
|
||||
.version, .versionNoNav {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
|
||||
p, .SIntrapara {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h2 { /* per-page main title */
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6, h7, h8 {
|
||||
margin-top: 1.75em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.SSubSubSubSection {
|
||||
font-weight: bold;
|
||||
font-size: 0.83em; /* should match h5; from HTML 4 reference */
|
||||
}
|
||||
|
||||
/* Needed for browsers like Opera, and eventually for HTML 4 conformance.
|
||||
This means that multiple paragraphs in a table element do not have a space
|
||||
between them. */
|
||||
table p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Main */
|
||||
|
||||
body {
|
||||
color: black;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.maincolumn {
|
||||
width: 43em;
|
||||
margin-right: -40em;
|
||||
margin-left: 15em;
|
||||
}
|
||||
|
||||
.main {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Navigation */
|
||||
|
||||
.navsettop, .navsetbottom {
|
||||
background-color: #f0f0e0;
|
||||
padding: 0.25em 0 0.25em 0;
|
||||
}
|
||||
|
||||
.navsettop {
|
||||
margin-bottom: 1.5em;
|
||||
border-bottom: 2px solid #e0e0c0;
|
||||
}
|
||||
|
||||
.navsetbottom {
|
||||
margin-top: 2em;
|
||||
border-top: 2px solid #e0e0c0;
|
||||
}
|
||||
|
||||
.navleft {
|
||||
margin-left: 1ex;
|
||||
position: relative;
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.navright {
|
||||
margin-right: 1ex;
|
||||
position: relative;
|
||||
float: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nonavigation {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.searchform {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.searchbox {
|
||||
width: 16em;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#contextindicator {
|
||||
position: fixed;
|
||||
background-color: #c6f;
|
||||
color: #000;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
padding: 2px 10px;
|
||||
display: none;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Version */
|
||||
|
||||
.versionbox {
|
||||
position: relative;
|
||||
float: right;
|
||||
left: 2em;
|
||||
height: 0em;
|
||||
width: 13em;
|
||||
margin: 0em -13em 0em 0em;
|
||||
}
|
||||
.version {
|
||||
font-size: small;
|
||||
}
|
||||
.versionNoNav {
|
||||
font-size: xx-small; /* avoid overlap with author */
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Margin notes */
|
||||
|
||||
.refpara, .refelem {
|
||||
position: relative;
|
||||
float: right;
|
||||
left: 2em;
|
||||
height: 0em;
|
||||
width: 13em;
|
||||
margin: 0em -13em 0em 0em;
|
||||
}
|
||||
|
||||
.refpara, .refparaleft {
|
||||
top: -1em;
|
||||
}
|
||||
|
||||
.refcolumn {
|
||||
background-color: #F5F5DC;
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 13em;
|
||||
font-size: 85%;
|
||||
border: 0.5em solid #F5F5DC;
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
.refcontent {
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
.refcontent p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.refparaleft {
|
||||
position: relative;
|
||||
float: left;
|
||||
right: 2em;
|
||||
height: 0em;
|
||||
width: 13em;
|
||||
margin: 0em 0em 0em -13em;
|
||||
}
|
||||
|
||||
.refcolumnleft, .refelemleft {
|
||||
background-color: #F5F5DC;
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 13em;
|
||||
font-size: 85%;
|
||||
border: 0.5em solid #F5F5DC;
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Table of contents, inline */
|
||||
|
||||
.toclink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.toptoclink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Table of contents, left margin */
|
||||
|
||||
.tocset {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 12.5em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
.tocset td {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.tocview {
|
||||
text-align: left;
|
||||
background-color: #f0f0e0;
|
||||
}
|
||||
|
||||
.tocsub {
|
||||
text-align: left;
|
||||
margin-top: 0.5em;
|
||||
background-color: #f0f0e0;
|
||||
}
|
||||
|
||||
.tocviewlist, .tocsublist {
|
||||
margin-left: 0.2em;
|
||||
margin-right: 0.2em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
.tocviewlist table {
|
||||
font-size: 82%;
|
||||
}
|
||||
|
||||
.tocviewsublist, .tocviewsublistonly, .tocviewsublisttop, .tocviewsublistbottom {
|
||||
margin-left: 0.4em;
|
||||
border-left: 1px solid #bbf;
|
||||
padding-left: 0.8em;
|
||||
}
|
||||
.tocviewsublist {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.tocviewsublist table,
|
||||
.tocviewsublistonly table,
|
||||
.tocviewsublisttop table,
|
||||
.tocviewsublistbottom table {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.tocviewtitle * {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tocviewlink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.tocviewselflink {
|
||||
text-decoration: underline;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.tocviewtoggle {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
font-size: 75%; /* looks better, and avoids bounce when toggling sub-sections due to font alignments */
|
||||
}
|
||||
|
||||
.tocsublist td {
|
||||
padding-left: 1em;
|
||||
text-indent: -1em;
|
||||
}
|
||||
|
||||
.tocsublinknumber {
|
||||
font-size: 82%;
|
||||
}
|
||||
|
||||
.tocsublink {
|
||||
font-size: 82%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tocsubseclink {
|
||||
font-size: 82%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tocsubnonseclink {
|
||||
font-size: 82%;
|
||||
text-decoration: none;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.tocsubtitle {
|
||||
font-size: 82%;
|
||||
font-style: italic;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.sepspace {
|
||||
font-size: 40%;
|
||||
}
|
||||
|
||||
.septitle {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
/* ---------------------------------------- */
|
||||
/* Some inline styles */
|
||||
|
||||
.indexlink {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nobreak {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stt {
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 200%;
|
||||
font-weight: normal;
|
||||
margin-top: 2.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
pre { margin-left: 2em; }
|
||||
blockquote { margin-left: 2em; }
|
||||
|
||||
ol { list-style-type: decimal; }
|
||||
ol ol { list-style-type: lower-alpha; }
|
||||
ol ol ol { list-style-type: lower-roman; }
|
||||
ol ol ol ol { list-style-type: upper-alpha; }
|
||||
|
||||
i {
|
||||
}
|
||||
|
||||
.SCodeFlow {
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 0em;
|
||||
margin-right: 1em;
|
||||
margin-top: 0em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.SVInsetFlow {
|
||||
display: block;
|
||||
margin-left: 0em;
|
||||
margin-bottom: 0em;
|
||||
margin-right: 0em;
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.SubFlow {
|
||||
display: block;
|
||||
margin: 0em;
|
||||
}
|
||||
|
||||
.boxed {
|
||||
width: 100%;
|
||||
background-color: #E8E8FF;
|
||||
}
|
||||
|
||||
.hspace {
|
||||
}
|
||||
|
||||
.slant {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.badlink {
|
||||
text-decoration: underline;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.plainlink {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.techoutside { text-decoration: underline; color: #b0b0b0; }
|
||||
.techoutside:hover { text-decoration: underline; color: blue; }
|
||||
|
||||
/* .techinside:hover doesn't work with FF, .techinside:hover>
|
||||
.techinside doesn't work with IE, so use both (and IE doesn't
|
||||
work with inherit in the second one, so use blue directly) */
|
||||
.techinside { color: black; }
|
||||
.techinside:hover { color: blue; }
|
||||
.techoutside:hover>.techinside { color: inherit; }
|
||||
|
||||
.SCentered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.imageleft {
|
||||
float: left;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
.Smaller{
|
||||
font-size: 82%;
|
||||
}
|
||||
|
||||
.Larger{
|
||||
font-size: 122%;
|
||||
}
|
||||
|
||||
/* A hack, inserted to break some Scheme ids: */
|
||||
.mywbr {
|
||||
width: 0;
|
||||
font-size: 1px;
|
||||
}
|
||||
|
||||
.compact li p {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
.noborder img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.SAuthorListBox {
|
||||
position: relative;
|
||||
float: right;
|
||||
left: 2em;
|
||||
top: -2.5em;
|
||||
height: 0em;
|
||||
width: 13em;
|
||||
margin: 0em -13em 0em 0em;
|
||||
}
|
||||
.SAuthorList {
|
||||
font-size: 82%;
|
||||
}
|
||||
.SAuthorList:before {
|
||||
content: "by ";
|
||||
}
|
||||
.author {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
}
|
Loading…
Reference in New Issue
Block a user