1 Text Generation
#lang scribble/text | package: scribble-text-lib |
The language uses read-syntax-inside to read the body of the module, similar to Document Reader. This means that by default, all text is read in as Racket strings; and @-forms can be used to use Racket functions and expression escapes.
Values of expressions are printed with a custom output function. This function displays most values in a similar way to display, except that it is more convenient for a textual output.
When scribble/text is used via require instead of #lang, then it does not change the printing of values, it does not include the bindings of racket/base, include is provided as include/text, and begin is provided as begin/text.
1.1 Writing Text Files
The combination of the two features makes text in files in the scribble/text language be read as strings, which get printed out when the module is required, for example, when a file is given as an argument to racket. (In these example the left part shows the source input, and the right part the printed result.)
| → |
|
Using @-forms, we can define and use Racket functions.
| → |
|
As demonstrated in this case, the output function simply
scans nested list structures recursively, which makes them convenient
for function results. In addition, output prints most values
similarly to display —
| → |
|
Using the scribble @-forms syntax, you can write functions more conveniently too.
| → |
|
Following the details of the scribble reader, you may notice that in these examples there are newline strings after each definition, yet they do not show in the output. To make it easier to write definitions, newlines after definitions and indentation spaces before them are ignored.
| → |
|
These end-of-line newline strings are not ignored when they follow other kinds of expressions, which may lead to redundant empty lines in the output.
| → |
|
There are several ways to avoid having such empty lines in your output. The simplest way is to arrange for the function call’s form to end right before the next line begins, but this is often not too convenient. An alternative is to use a @; comment, which makes the scribble reader ignore everything that follows it up to and including the newline. (These methods can be applied to the line that precedes the function call too, but the results are likely to have what looks like erroneous indentation. More about this below.)
| → |
|
A better approach is to generate newlines only when needed.
| → |
|
In fact, this is common enough that the scribble/text language provides a convenient facility: add-newlines is a function that is similar to add-between using a newline string as the default separator, except that false and void values are filtered out before doing so.
| → |
|
| → |
|
The separator can be set to any value.
| → |
|
1.2 Defining Functions and More
(Note: most of the tips in this section are applicable to any code that uses the Scribble @-forms syntax.)
Because the Scribble reader is uniform, you can use it in place of any expression where it is more convenient. (By convention, we use a plain S-expression syntax when we want a Racket expression escape, and an @-forms for expressions that render as text, which, in the scribble/text language, is any value-producing expression.) For example, you can use an @-forms for a function that you define.
| → |
|
This is not commonly done, since most functions that operate with text will need to accept a variable number of arguments. In fact, this leads to a common problem: what if we want to write a function that consumes a number of “text arguments” rathen than a single “rest-like” body? The common solution for this is to provide the separate text arguments in the S-expression part of an @-forms.
| → |
|
You can even use @-formss with a Racket quote or quasiquote as the “head” part to make it shorter, or use a macro to get grouping of sub-parts without dealing with quotes.
| → |
|
Yet another solution is to look at the text values and split the input
arguments based on a specific token. Using match can make it
convenient —
| → |
|
In particular, it is often convenient to split the input by lines, identified by delimiting "\n" strings. Since this can be useful, a split-lines function is provided.
| → |
|
Finally, the Scribble reader accepts any expression as the head
part of an @-form —
| → |
|
1.3 Using Printouts
Because the text language simply displays each toplevel value as the file is run, it is possible to print text directly as part of the output.
| → |
|
Taking this further, it is possible to write functions that output some text instead of returning values that represent the text.
| → |
|
This can be used to produce a lot of output text, even infinite.
| → |
|
However, you should be careful not to mix returning values with printouts, as the results are rarely desirable.
| → |
|
Note that you don’t need side-effects if you want infinite output. The output function iterates thunks and (composable) promises, so you can create a loop that is delayed in either form.
| → |
|
1.4 Indentation in Preprocessed output
An issue that can be very important in many text generation applications is the indentation of the output. This can be crucial in some cases, if you’re generating code for an indentation-sensitive language (e.g., Haskell, Python, or C preprocessor directives). To get a better understanding of how the pieces interact, you may want to review how the Scribble reader section, but also remember that you can use quoted forms to see how some form is read.
| → |
|
The Scribble reader ignores indentation spaces in its body. This is an intentional feature, since you usually do not want an expression to depend on its position in the source. But the question is whether we can render some output text with proper indentation. The output function achieves that by introducing blocks. Just like a list, a block contains a list of elements, and when one is rendered, it is done in its own indentation level. When a newline is part of a block’s contents, it causes the following text to appear with indentation that corresponds to the column position at the beginning of the block.
In addition, lists are also rendered as blocks by default, so they can
be used for the same purpose. In most cases, this makes the output
appear “as intended” where lists are used for nested pieces of text
—
| → |
|
| → |
|
| → |
|
There are, however, cases when you need more refined control over the
output. The scribble/text language provides a few functions
for such cases in addition to block. The splice
function groups together a number of values but avoids introducing a new
indentation context. Furthermore, lists are not always rendered as
blocks —
| → |
|
The disable-prefix function disables all indentation printouts in its contents, including the indentation before the body of the disable-prefix value itself. It is useful, for example, to print out CPP directives.
| → |
|
If there are values after a disable-prefix value on the same line, they will get indented to the goal column (unless the output is already beyond it).
| → |
|
There are cases where each line should be prefixed with some string other than a plain indentation. The add-prefix function causes its contents to be printed using some given string prefix for every line. The prefix gets accumulated to an existing indentation, and indentation in the contents gets added to the prefix.
| → |
|
When combining add-prefix and disable-prefix there is an additional value that can be useful: flush. This is a value that causes output to print the current indentation and prefix. This makes it possible to get the “ignored as a prefix” property of disable-prefix but only for a nested prefix.
| → |
|
1.5 Using External Files
Using additional files that contain code for your preprocessing is trivial: the source text is still source code in a module, so you can require additional files with utility functions.
| → |
|
Note that the at-exp language can often be useful here, since such files need to deal with texts. Using it, it is easy to include a lot of textual content.
| → |
|
Of course, the extreme side of this will be to put all of your content
in a plain Racket module, using @-formss for convenience. However,
there is no need to use the text language in this case; instead, you can
(require scribble/text), which will get all of the bindings
that are available in the scribble/text language. Using
output, switching from a preprocessed files to a Racket file is
very easy —
| → |
|
However, you might run into a case where it is desirable to include a mostly-text file from a scribble/text source file. It might be because you prefer to split the source text to several files, or because you need to use a template file that cannot have a #lang header (for example, an HTML template file that is the result of an external editor). In these cases, the scribble/text language provides an include form that includes a file in the preprocessor syntax (where the default parsing mode is text).
| → |
|
(Using require with a text file in the scribble/text language will not work as intended: the language will display the text is when the module is invoked, so the required file’s contents will be printed before any of the requiring module’s text does. If you find yourself in such a situation, it is better to switch to a Racket-with-@-expressions file as shown above.)
1.6 Text Generation Functions
value
Added in version 1.1 of package scribble-text-lib.
procedure
v : outputable/c port : output-port? = (current-output-port)
strings, byte strings, symbols, paths, keywords, numbers, and characters: converts the value to a string along the same lines as display, and then passes the string to the current writer, which is initially write-string
list: output depends on the current mode, which is initially splice mode:
(block v2 ...): outputs each v2 in block mode.
(splice v2 ...): outputs each v2 in splice mode.
(set-prefix pfx v2 ...): sets the current prefix, which is initially empty, to pfx while outputting each v2.
(add-prefix pfx v2 ...): sets the current prefix to by adding pfx while outputting each v2.
(disable-prefix v2 ...): sets the current prefix to empty while outputting each v2.
(restore-prefix v2 ...): rewinds the current prefix by one enclosing adjustments while outputting each v2.
flush: outputs the current indentation and current prefix.
(with-writer writer v2 ...): sets the current writer to writer with outputting each v2.
procedure of 0 arguments: outputs the result of (v)
Any other kind of v triggers an exception.
procedure
(block v ...) → outputable/c
v : outputable/c
procedure
(splice v ...) → outputable/c
v : outputable/c
procedure
(disable-prefix v ...) → outputable/c
v : outputable/c
procedure
(restore-prefix v ...) → outputable/c
v : outputable/c
procedure
(add-prefix pfx v ...) → outputable/c
pfx : (or/c string? exact-nonnegative-integer?) v : outputable/c
procedure
(set-prefix pfx v ...) → outputable/c
pfx : (or/c string? exact-nonnegative-integer?) v : outputable/c
procedure
(with-writer writer v ...) → outputable/c
writer :
(or/c (->* (string? output-port?) (exact-nonnegative-integer?) any/c) #f) v : outputable/c
procedure
(add-newlines items [#:sep sep]) → list?
items : list? sep : an/y = "\n"
procedure
(split-lines items) → (listof list?)
items : list?
syntax
(include/text maybe-char path-spec)
maybe-char =
| #:command-char command-char
The scribble/text language via #lang provides include/text as include.
syntax
(begin/text form ...)
The scribble/text language via #lang provides begin/text as begin.