Add block' to scribble/text', to explicitly ask for an indentation block.

* Lists are now either blocks or splices depending on whether they
  appear inside a block or a splice (default to block).

* Adjusted the docs and a single test where this mattered.

* Change the documentation to be "text.html" and to be titled "text
  generation".
This commit is contained in:
Eli Barzilay 2011-06-28 17:56:34 -04:00
parent fa77770eac
commit 0af236dc2f
8 changed files with 170 additions and 120 deletions

View File

@ -1,23 +1,32 @@
#lang scheme/base
#lang racket/base
(require scheme/promise)
(require racket/promise)
(provide output)
;; Outputs some value, for the preprocessor language.
;; Outputs some value for the `scribble/text' language:
;; - several atomic values are printed as in `display',
;; - promises, thunks, and boxes are indirections for the value they contain
;; (useful in various cases),
;; - some "special" values are used for controlling output (eg, flushing,
;; prefix changes, etc),
;; - specifically, `block's delimit indentation levels, `splice's do not,
;; - lists (more generally, pairs) are like either one depending on the context
;; (same as blocks/splices when inside a `block'/`splice'), at the toplevel
;; they default to blocks.
;;
;; Uses global state because `output' is wrapped around each expression in a
;; scribble/text file so this is much more convenient than wrapping the whole
;; module's body in a `list' (which will be difficult with definitions etc).
;; The state is a pair of prefixes -- one that is the prefix for the current
;; value (which gets accumulated to with nested lists), and the other is the
;; prefix for the current "line" (which is reset after a newline). The
;; line-prefix is needed because a line can hold a list, which means that the
;; line-prefix will apply for the contents of the list including newlines in
;; it. This state is associated to a port via a hash table. Another state
;; that is used is the port's column position, which is maintained by the
;; system (when line counts are enabled) -- this is used to tell what part of a
;; prefix is already displayed.
;; value (which gets extended with nested blocks), and the other is the prefix
;; for the current "line" (which is reset after a newline). The line-prefix is
;; needed because a line can hold a block, which means that the line-prefix
;; will apply for the contents of the block including newlines in it. This
;; state is associated with a port via a hash table. Another state that is
;; used is the port's column position, which is maintained by the system (when
;; line counts are enabled) -- this is used to tell what part of a prefix is
;; already displayed.
;;
;; Each prefix is either an integer (for a number of spaces) or a string. The
;; prefix mechanism can be disabled by using #f for the global prefix, and in
@ -28,6 +37,8 @@
(define (output x [p (current-output-port)])
;; these are the global prefix and the one that is local to the current line
(define pfxs (port->state p))
;; the current mode for lists
(define list=block? #t)
;; the low-level string output function (can change with `with-writer')
(define write write-string)
;; to get the output column
@ -98,6 +109,17 @@
(output-pfx col pfx lpfx)
;; the spaces were already added to lpfx
(write x p (if m (cdar m) start)))))])))))
;; blocks and splices
(define (output-block c)
(let* ([pfx (mcar pfxs)] [lpfx (mcdr pfxs)]
[npfx (pfx+col (pfx+ pfx lpfx))])
(set-mcar! pfxs npfx) (set-mcdr! pfxs 0)
(if (list? c)
(for ([c (in-list c)]) (loop c))
(begin (loop (car c)) (loop (cdr c))))
(set-mcar! pfxs pfx) (set-mcdr! pfxs lpfx)))
(define (output-splice c)
(for-each loop c))
;; main loop
(define (loop x)
(cond
@ -107,13 +129,7 @@
;; one, then output the contents recursively (no need to change the
;; state, since we pass the values in the loop, and we'd need to restore
;; it afterwards anyway)
[(pair? x) (if (list? x)
(let* ([pfx (mcar pfxs)] [lpfx (mcdr pfxs)]
[npfx (pfx+col (pfx+ pfx lpfx))])
(set-mcar! pfxs npfx) (set-mcdr! pfxs 0)
(for ([x (in-list x)]) (loop x))
(set-mcar! pfxs pfx) (set-mcdr! pfxs lpfx))
(begin (loop (car x)) (loop (cdr x))))]
[(pair? x) (if list=block? (output-block x) (output-splice x))]
;; delayed values
[(and (procedure? x) (procedure-arity-includes? x 0)) (loop (x))]
[(promise? x) (loop (force x))]
@ -122,7 +138,17 @@
[(special? x)
(let ([c (special-contents x)])
(case (special-flag x)
[(splice) (for-each loop c)]
;; preserve tailness & avoid `set!' for blocks/splices if possible
[(block) (if list=block?
(output-block c)
(begin (set! list=block? #t)
(output-block c)
(set! list=block? #f)))]
[(splice) (if list=block?
(begin (set! list=block? #f)
(output-splice c)
(set! list=block? #t))
(output-splice c))]
[(flush) ; useful before `disable-prefix'
(output-pfx (getcol) (mcar pfxs) (mcdr pfxs))]
[(disable-prefix) ; save the previous pfxs
@ -196,6 +222,10 @@
(define-syntax define/provide-special
(syntax-rules ()
[(_ (name))
(begin (provide name)
(define (name . contents)
(make-special 'name contents)))]
[(_ (name x ...))
(begin (provide name)
(define (name x ... . contents)
@ -204,6 +234,7 @@
(begin (provide name)
(define name (make-special 'name #f)))]))
(define/provide-special (block))
(define/provide-special (splice))
(define/provide-special flush)
(define/provide-special (disable-prefix))
@ -215,11 +246,11 @@
(define/provide-special (with-writer-change writer))
(define make-spaces ; (efficiently)
(let ([t (make-hasheq)] [v (make-vector 80 #f)])
(let ([t (make-hasheq)] [v (make-vector 200 #f)])
(lambda (n)
(or (if (< n 80) (vector-ref v n) (hash-ref t n #f))
(or (if (< n 200) (vector-ref v n) (hash-ref t n #f))
(let ([spaces (make-string n #\space)])
(if (< n 80) (vector-set! v n spaces) (hash-set! t n spaces))
(if (< n 200) (vector-set! v n spaces) (hash-set! t n spaces))
spaces)))))
;; Convenient utilities

View File

@ -526,13 +526,13 @@ converting @litchar{---} to an em dash or for converting @litchar{"}
and @litchar{'} to suitable curly quotes.
The decoding process for document's stream is ultimately determined by
the @hash-lang[] line that starts the document. The @racketmodname[scribble/base],
@racketmodname[scribble/manual], and @racketmodname[scribble/sigplan]
languages all use the same @racket[decode] operation. The
@racketmodname[scribble/text] language, however, acts more like a
plain-text preprocessor and it does not perform any such decoding
rules. (For more on @racketmodname[scribble/text], see
@secref["preprocessor"].)
the @hash-lang[] line that starts the document. The
@racketmodname[scribble/base], @racketmodname[scribble/manual], and
@racketmodname[scribble/sigplan] languages all use the same
@racket[decode] operation. The @racketmodname[scribble/text] language,
however, acts more like a plain-text genrator and preprocessor, and it
does not perform any such decoding rules. (For more on
@racketmodname[scribble/text], see @secref["text"].)
@margin-note{More precisely, languages like
@racketmodname[scribble/base] apply @racket[decode] only after
@ -607,5 +607,5 @@ Racket, continue with @secref["reader"] and then
@secref["generic-prose"]. Move on to @secref["internals"] when you
need more power.
If you are interested in text preprocessing, continue with
@secref["reader"], but then skip to @secref["preprocessor"].
If you are interested in text generation and preprocessing, continue
with @secref["reader"], but then skip to @secref["text"].

View File

@ -26,7 +26,7 @@ starting with the @filepath{scribble.scrbl} file.
@include-section["generic.scrbl"]
@include-section["plt.scrbl"]
@include-section["lp.scrbl"]
@include-section["preprocessor.scrbl"]
@include-section["text.scrbl"]
@include-section["internals.scrbl"]
@include-section["running.scrbl"]

View File

@ -8,27 +8,28 @@
))
@initialize-tests
@title[#:tag "preprocessor"
@title[#:tag "text"
#:style (make-style #f (list (make-tex-addition "shaded.tex")
(make-css-addition "shaded.css")))
]{Text Preprocessing}
]{Text Generation}
@section-index["Preprocessor"]
@defmodulelang[scribble/text]{The @racketmodname[scribble/text]
language provides everything from @racket[racket/base] with a few
changes that make it suitable as a preprocessor language:
@defmodulelang[scribble/text]{The @racketmodname[scribble/text] language
provides everything from @racket[racket/base] with a few changes that
make it suitable as a text generation or a preprocessor language:
@itemize[
@item{It uses @racket[read-syntax-inside] to read the body of the
module, similar to @secref["docreader"]. This means that by
default, all text is read in as Racket strings; and
@item{The language uses @racket[read-syntax-inside] to read the body
of the module, similar to @secref["docreader"]. This means that
by default, all text is read in as Racket strings; and
@seclink["reader"]|{@-forms}| can be used to use Racket
functions and expression escapes.}
@item{Values of expressions are printed with a custom
@racket[output] function. This function displays most values
in a similar way to @racket[display], except that it is more
convenient for a preprocessor output.}]
@item{Values of expressions are printed with a custom @racket[output]
function. This function displays most values in a similar way
to @racket[display], except that it is more convenient for a
textual output.}]
}
@ -39,7 +40,7 @@ changes that make it suitable as a preprocessor language:
@; * maybe a section on additional utilities: begin/text
@;--------------------------------------------------------------------
@section{Writing Preprocessor Files}
@section{Writing Text Files}
The combination of the two features makes text in files in the
@racket[scribble/text] language be read as strings, which get printed
@ -380,9 +381,9 @@ number of body expressions must be fixed.
@;--------------------------------------------------------------------
@section{Using Printouts}
Because the preprocessor language simply displays each toplevel value
as the file is run, it is possible to print text directly as part of
the output.
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.
@example|-{#lang scribble/text
First
@ -463,13 +464,13 @@ promises, so you can create a loop that is delayed in either form.
@;--------------------------------------------------------------------
@section{Indentation in Preprocessed output}
An issue that can be very important in many preprocessor 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.,
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 @seclink["reader"]|{Scribble reader}| section, but also remember
that you can use quoted forms to see how some form is read.
understanding of how the pieces interact, you may want to review how the
@seclink["reader"]|{Scribble reader}| section, but also remember that
you can use quoted forms to see how some form is read.
@example|-{#lang scribble/text
@(format "~s" '@list{
@ -479,37 +480,48 @@ that you can use quoted forms to see how some form is read.
---***---
(list "a" "\n" " " "b" "\n" "c")}-|
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 how
@emph{can} we render some output text with proper indentation. The
@racket[output] function achieves that by assigning a special meaning
to lists: when a newline is part of a list's contents, it causes the
following text to appear with indentation that corresponds to the
column position at the beginning of the list. In most cases, this
makes the output appear ``as intended'' when lists are used for nested
pieces of text --- either from a literal @racket[list] expression, or
an expression that evaluates to a list, or when a list is passed on as
a value; either as a toplevel expression, or as a nested value; either
appearing after spaces, or after other output.
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
@emph{can} render some output text with proper indentation. The
@racket[output] function achieves that by introducing @racket[block]s.
Just like a list, a @racket[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 @racket[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
--- either from a literal @racket[list] expression, or an expression
that evaluates to a list, or when a list is passed on as a value; either
as a toplevel expression, or as a nested value; either appearing after
spaces, or after other output.
@example|-{#lang scribble/text
foo @list{1
foo @block{1
2
3}
foo @list{4
5
6}
---***---
foo 1
2
3}-|
3
foo 4
5
6}-|
@example|-{#lang scribble/text
@(define (block . text)
@(define (code . text)
@list{begin
@text
end})
@block{first
@code{first
second
@block{
@code{
third
fourth}
last}
@ -697,19 +709,24 @@ appearing after spaces, or after other output.
}-|
There are, however, cases when you need more refined control over the
output. The @racket[scribble/text] provides a few functions for such
cases. The @racket[splice] function is used to group together a
number of values but avoid introducing a new indentation context.
output. The @racket[scribble/text] language provides a few functions
for such cases in addition to @racket[block]. The @racket[splice]
function groups together a number of values but avoids introducing a new
indentation context. Furthermore, lists are not always rendered as
@racket[block]s --- instead, they are rendered as @racket[splice]s when
they are used inside one, so you essentially use @racket[splice] to
avoid the ``indentation group'' behavior, and @racket[block] to restore
it.
@example|-{#lang scribble/text
@(define (block . text)
@(define (blah . text)
@splice{{
blah(@text);
blah(@block{@text});
}})
start
@splice{foo();
loop:}
@list{if (something) @block{one,
@list{if (something) @blah{one,
two}}
end
---***---
@ -759,8 +776,8 @@ example, to print out CPP directives.
}-|
If there are values after a @racket[disable-prefix] value on the same
line, they will get indented to the goal column (unless the output is
already beyond it).
line, they @emph{will} get indented to the goal column (unless the
output is already beyond it).
@example|-{#lang scribble/text
@(define (thunk name . body)
@ -922,8 +939,8 @@ property of @racket[disable-prefix] but only for a nested prefix.
@section{Using External Files}
Using additional files that contain code for your preprocessing is
trivial: the preprocessor source is still source code in a module, so
you can @racket[require] additional files with utility functions.
trivial: the source text is still source code in a module, so you can
@racket[require] additional files with utility functions.
@example|-{#lang scribble/text
@(require "itemize.rkt")
@ -984,13 +1001,13 @@ 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 @"@"-forms for convenience. However,
there is no need to use the preprocessor language in this case;
instead, you can @racket[(require scribble/text)], which will get all
of the bindings that are available in the @racket[scribble/text]
language. Using @racket[output], switching from a preprocessed files
to a Racket file is very easy ---- choosing one or the other depends
on whether it is more convenient to write a text file with occasional
Racket expressions or the other way.
there is no need to use the text language in this case; instead, you can
@racket[(require scribble/text)], which will get all of the bindings
that are available in the @racket[scribble/text] language. Using
@racket[output], switching from a preprocessed files to a Racket file is
very easy ---- choosing one or the other depends on whether it is more
convenient to write a text file with occasional Racket expressions or
the other way.
@example|-{#lang at-exp racket/base
(require scribble/text racket/list)
@ -1022,11 +1039,11 @@ Racket expressions or the other way.
}-|
However, you might run into a case where it is desirable to include a
mostly-text file from a preprocessor file. It might be because you
prefer to split the source text to several files, or because you need
to preprocess a file without even a @litchar{#lang} header (for
example, an HTML template file that is the result of an external
editor). For these cases, the @racket[scribble/text] language
mostly-text file from a @racket[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 @litchar{#lang}
header (for example, an HTML template file that is the result of an
external editor). In these cases, the @racket[scribble/text] language
provides an @racket[include] form that includes a file in the
preprocessor syntax (where the default parsing mode is text).
@ -1075,12 +1092,11 @@ preprocessor syntax (where the default parsing mode is text).
}-|
(Using @racket[require] with a text file in the @racket[scribble/text]
language will not work as intended: using the preprocessor language
means that the text is displayed 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.)
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.)
@;FIXME: add more text on `restore-prefix', `set-prefix', `with-writer'

View File

@ -101,7 +101,7 @@
(map as-flow (list spacer @expr reads-as sexpr))))
r))))))))
;; stuff for the preprocessor examples
;; stuff for the scribble/text examples
(require racket/list (for-syntax racket/base racket/list))

View File

@ -1,9 +1,9 @@
#lang racket/base
(require tests/eli-tester
"reader.rkt" "preprocessor.rkt" "collect.rkt" "docs.rkt")
"reader.rkt" "text-lang.rkt" "collect.rkt" "docs.rkt")
(test do (reader-tests)
do (begin/collect-tests)
do (preprocessor-tests)
do (text-lang-tests)
do (docs-tests))

View File

@ -1,11 +1,11 @@
#lang racket/base
(require tests/eli-tester racket/runtime-path racket/port racket/sandbox
(prefix-in doc: (lib "scribblings/scribble/preprocessor.scrbl")))
(prefix-in doc: (lib "scribblings/scribble/text.scrbl")))
(provide preprocessor-tests)
(provide text-lang-tests)
(define (preprocessor-tests)
(define (text-lang-tests)
;; (sample-file-tests)
(in-documentation-tests))
@ -36,7 +36,7 @@
;; test with name indicating the source
(define-syntax-rule (t . stuff)
(test ;; #:failure-message
;; (format "preprocessor test failure at line ~s" line)
;; (format "text-lang test failure at line ~s" line)
. stuff))
(parameterize ([current-directory this-dir]
[sandbox-output o]

View File

@ -8,17 +8,20 @@
@(define xexpr @tech[#:doc '(lib "xml/xml.scrbl")]{X-expression})
@(define at-reader-ref @secref[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{reader})
@(define text-ref @secref[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{preprocessor})
@(define text-ref
@secref[#:doc '(lib "scribblings/scribble/scribble.scrbl") "text"])
@title[#:tag "templates"]{Templates: Separation of View}
@defmodule[web-server/templates]
The @web-server provides a powerful Web template system for separating the presentation logic of a Web application
and enabling non-programmers to contribute to Racket-based Web applications.
The @web-server provides a powerful Web template system for separating
the presentation logic of a Web application and enabling non-programmers
to contribute to Racket-based Web applications.
@margin-note{Although all the examples here generate HTML, the template language and the @text-ref it is based on can
be used to generate any text-based format: C, SQL, form emails, reports, etc.}
@margin-note{Although all the examples here generate HTML, the template
language and the @text-ref it is based on can be used to generate any
text-based format: C, SQL, form emails, reports, etc.}
@section{Static}