racket/collects/preprocessor/scribblings/mzpp.scrbl
Eli Barzilay ac26fe7554 A ton of @scheme*' -> @racket*' and related updates.
Also, updates some of the mzlib files to point at `racket/*' libraries
rather than to `scheme/*' ones.
2011-06-25 04:08:47 -04:00

315 lines
11 KiB
Racket

#lang scribble/doc
@(require scribble/manual
(for-label scheme/base
scheme/contract
scheme/port
preprocessor/mzpp))
@title[#:tag "mzpp"]{@exec{mzpp}}
@exec{mzpp} is a simple preprocessor that allows mixing Racket code with text
files in a similar way to PHP or BRL. Processing of input files works
by translating the input file to Racket code that prints the contents,
except for marked portions that contain Racket code. The Racket parts
of a file are marked with @litchar{<<} and @litchar{>>} tokens by default. The
Racket code is then passed through a read-eval-print loop that is similar to a
normal REPL with a few differences in how values are printed.
@section{Invoking mzpp}
Use the @Flag{-h} flag to get the available flags. See above for an
explanation of the @DFlag{run} flag.
@section{mzpp files}
Here is a sample file that @exec{mzpp} can process, using the default beginning
and ending markers:
@verbatim[#:indent 2]|{
<< (define bar "BAR") >>
foo1
foo2 << bar newline* bar >> baz
foo3
}|
First, this file is converted to the following Racket code:
@verbatim[#:indent 2]|{
(thunk (cd "tmp/") (current-file "foo"))
(thunk (push-indentation ""))
(define bar "BAR") (thunk (pop-indentation))
newline*
"foo1"
newline*
"foo2 "
(thunk (push-indentation " "))
bar newline* bar (thunk (pop-indentation))
" baz"
newline*
"foo3"
newline*
(thunk (cd "/home/eli") (current-file #f))
}|
which is then fed to the REPL, resulting in the following output:
@verbatim[#:indent 2]|{
foo1
foo2 BAR
BAR baz
foo3
}|
To see the processed input that the REPL receives, use the @DFlag{debug}
flag. Note that the processed code contains expressions that have no
side-effects, only values---see below for an explanation of the REPL
printing behavior. Some expressions produce values that change the REPL
environment, for example, the indentation commands are used to keep
track of the column where the Racket marker was found, and @exec{cd} is used
to switch to the directory where the file is (here it was in
@filepath["/home/foo/tmp"]) so including a relative file works. Also, note that
the first @racket[newline*] did not generate a newline, and that the one in the
embedded Racket code added the appropriate spaces for indentation.
It is possible to temporarily switch from Racket to text-mode and back
in a way that does not respect a complete Racket expression, but you
should be aware that text is converted to a @italic{sequence} of side-effect
free expressions (not to a single string, and not expression that uses
side effects). For example:
@verbatim[#:indent 2]|{
<< (if (zero? (random 2))
(list >>foo1<<)
(list >>foo2<<))
>>
<< (if (zero? (random 2)) (list >>
foo1
<<) (list >>
foo2
<<)) >>
}|
will print two lines, each containing @litchar{foo1} or @litchar{foo} (the
first approach plays better with the smart space handling). The @racket[show]
function can be used instead of @racket[list] with the same results, since it
will print out the values in the same way the REPL does. The conversion
process does not transform every continuous piece of text into a single Racket
string because doing this:
@itemize[
@item{the Racket process will need to allocating big strings which makes
this unfeasible for big files,}
@item{it will not play well with ``interactive'' input feeding, for example,
piping in the output of some process will show results only on Racket
marker boundaries,}
@item{special treatment for newlines in these strings will become expensive.}
]
(Note that this is different from the BRL approach.)
@section{Raw preprocessing directives}
Some preprocessing directives happen at the "raw level"---the stage
where text is transformed into Racket expressions. These directives
cannot be changed from within transformed text because they change the
way this transformation happens. Some of these transformation
@itemize[
@item{Skipping input:
First, the processing can be modified by specifying a @racket[skip-to] string
that disables any output until a certain line is seen. This is useful
for script files that use themselves for input. For example, the
following script:
@verbatim[#:indent 2]|{
#!/bin/sh
echo shell output
exec mzpp -s "---TEXT-START---" "$0"
exit 1
---TEXT-START---
Some preprocessed text
123*456*789 = << (* 123 456 789) >>
}|
will produce this output:
@verbatim[#:indent 2]|{
shell output
Some preprocessed text
123*456*789 = 44253432
}|}
@item{Quoting the markers:
In case you need to use the actual text of the markers, you can quote
them. A backslash before a beginning or an ending marker will make
the marker treated as text, it can also quote a sequence of
backslashes and a marker. For example, using the default markers,
@litchar{\<<\>>} will output @litchar{<<>>}, @litchar{\\<<\\\>>} will output @litchar{\<<\\>>} and
@litchar{\a\b\<<} will output @litchar{\a\b<<}.}
@item{Modifying the markers:
Finally, if the markers collide with a certain file contents, it is
possible to change them. This is done by a line with a special
structure---if the current Racket markers are @litchar{<beg1>} and
@litchar{<end1>} then a line that contains exactly:
@verbatim[#:indent 2]|{
<beg1><beg2><beg1><end1><end2><end1>
}|
will change the markers to @litchar{<beg2>} and @litchar{<end2>}. It is
possible to change the markers from the Racket side (see below), but this
will not change already-transformed text, which is the reason for this
special format.}
]
@section{The mzpp read-eval-print loop}
The REPL is initialized by requiring @racket[preprocessor/mzpp], so the same
module provides both the preprocessor functionality as well as bindings for
embedded Racket code in processed files. The REPL is then fed the transformed
Racket code that is generated from the source text (the same code that
@DFlag{debug} shows). Each expression is evaluated and its result is printed
using the @racket[show] function (multiple values are all printed), where
@racket[show] works in the following way:
@itemize[
@item{@|void-const| and @racket[#f] values are ignored.}
@item{Structures of pairs are recursively scanned and their parts printed
(no spaces are used, so to produce Racket code as output you must use
format strings---again, this is not intended for preprocessing Racket
code).}
@item{Procedures are applied to zero arguments (so a procedure that doesn't
accept zero arguments will cause an error) and the result is sent back to
@racket[show]. This is useful for using thunks to wrap side-effects as
values (e.g, the @racket[thunk] wraps shown by the debug output above).}
@item{Promises are forced and the result is sent again to @racket[show].}
@item{All other values are printed with @racket[display]. No newlines are
used after printing values.}
]
@section[#:tag "mzpp-lib"]{Provided bindings}
@defmodule[preprocessor/mzpp]
First, bindings that are mainly useful for invoking the preprocessor:
@defproc[(preprocess [in (or/c path-string? input-port?)] ...) void?]{
This is the main entry point to the preprocessor---invoking it on the
given list of files and input ports. This is quite similar to
@racket[include], but it adds some setup of the preprocessed code environment
(like requiring the @exec{mzpp} module).}
@defparam[skip-to str string?]{
A string parameter---when the preprocessor is started, it ignores
everything until a line that contains exactly this string is
encountered. This is primarily useful through a command-line flag for
scripts that extract some text from their own body.}
@defboolparam[debug? on?]{
A boolean parameter. If true, then the REPL is not invoked, instead,
the converted Racket code is printed as is.}
@defboolparam[no-spaces? on?]{
A boolean parameter. If true, then the "smart" preprocessing of
spaces is turned off.}
@deftogether[(
@defparam[beg-mark str string?]
@defparam[end-mark str string?]
)]{
These two parameters are used to specify the Racket beginning and end
markers.}
All of the above are accessible in preprocessed texts, but the only one that
might make any sense to use is @racket[preprocess] and @racket[include] is a
better choice. When @racket[include] is used, it can be wrapped with parameter
settings, which is why they are available. Note in particular that these
parameters change the way that the text transformation works and have no effect
over the current preprocessed document (for example, the Racket marks are used
in a different thread, and @racket[skip-to] cannot be re-set when processing
has already began). The only one that could be used is @racket[no-spaces?] but
even that makes little sense on selected parts.
The following are bindings that are used in preprocessed texts:
@deftogether[(
@defproc[(push-indentation [str string?]) void?]
@defproc[(pop-indentation) void?]
)]{
These two calls are used to save the indentation column where the Racket
beginning mark was found, and will be used by @racket[newline*] (unless smart
space handling mode is disabled).}
@defproc[(show [v any/c]) void?]{
The arguments are displayed as specified above.}
@defproc[(newline*) void?]{
This is similar to @racket[newline] except that it tries to handle spaces in
a ``smart'' way---it will print a newline and then spaces to reach the
left margin of the opening @litchar{<<}. (Actually, it tries a bit more, for
example, it won't print the spaces if nothing is printed before
another newline.) Setting @racket[no-spaces?] to true disable this leaving
it equivalent to @racket[newline].}
@defproc[(include [file path-string?] ...) void?]{
This is the preferred way of including another file in the processing. File
names are searched relatively to the current preprocessed file, and during
processing the current directory is temporarily changed to make this work.
In addition to file names, the arguments can be input ports (the current
directory is not changed in this case). The files that will be incorporated
can use any current Racket bindings etc, and will use the current
markers---but the included files cannot change any of the parameter settings
for the current processing (specifically, the marks and the working directory
will be restored when the included files are processed).}
Note that when a sequence of files are processed (through command-line
arguments or through a single @racket[include] expression), then they are all
taken as one textual unit---so changes to the markers, working directory etc
in one file can modify the way sequential files are processed. This means
that including two files in a single @racket[include] expression can be
different than using two expressions.
@deftogether[(
@defthing[stdin parameter?]
@defthing[stdout parameter?]
@defthing[stderr parameter?]
@defthing[cd parameter?]
)]{
These are shorter names for the corresponding port parameters and
@racket[current-directory].}
@defparam[current-file path path-string?]{
This is a parameter that holds the name of the currently processed
file, or #f if none.}
@defform[(thunk expr ...)]{
Expands to @racket[(lambda () expr ...)].}