From 0af236dc2f39d5b697ca26af51b6f1e659d6bbe9 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Tue, 28 Jun 2011 17:56:34 -0400 Subject: [PATCH] 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". --- collects/scribble/text/output.rkt | 75 +++++--- .../scribblings/scribble/how-to-paper.scrbl | 18 +- collects/scribblings/scribble/scribble.scrbl | 2 +- .../{preprocessor.scrbl => text.scrbl} | 168 ++++++++++-------- collects/scribblings/scribble/utils.rkt | 2 +- collects/tests/scribble/main.rkt | 4 +- .../{preprocessor.rkt => text-lang.rkt} | 8 +- .../web-server/scribblings/templates.scrbl | 13 +- 8 files changed, 170 insertions(+), 120 deletions(-) rename collects/scribblings/scribble/{preprocessor.scrbl => text.scrbl} (87%) rename collects/tests/scribble/{preprocessor.rkt => text-lang.rkt} (92%) diff --git a/collects/scribble/text/output.rkt b/collects/scribble/text/output.rkt index fe15fb1c18..26ee109917 100644 --- a/collects/scribble/text/output.rkt +++ b/collects/scribble/text/output.rkt @@ -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 diff --git a/collects/scribblings/scribble/how-to-paper.scrbl b/collects/scribblings/scribble/how-to-paper.scrbl index e636102b31..c405a521d6 100644 --- a/collects/scribblings/scribble/how-to-paper.scrbl +++ b/collects/scribblings/scribble/how-to-paper.scrbl @@ -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"]. diff --git a/collects/scribblings/scribble/scribble.scrbl b/collects/scribblings/scribble/scribble.scrbl index 0f8ef9a5a1..82ee5d6318 100644 --- a/collects/scribblings/scribble/scribble.scrbl +++ b/collects/scribblings/scribble/scribble.scrbl @@ -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"] diff --git a/collects/scribblings/scribble/preprocessor.scrbl b/collects/scribblings/scribble/text.scrbl similarity index 87% rename from collects/scribblings/scribble/preprocessor.scrbl rename to collects/scribblings/scribble/text.scrbl index 2d23fe0f17..7bb4b224ca 100644 --- a/collects/scribblings/scribble/preprocessor.scrbl +++ b/collects/scribblings/scribble/text.scrbl @@ -1,5 +1,5 @@ #lang scribble/doc -@(require scribble/manual +@(require scribble/manual scribble/core scribble/html-properties scribble/latex-properties "utils.rkt" (for-label racket/base @@ -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,40 +480,51 @@ 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 - 2 - 3} + 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 - second - @block{ - third - fourth} - last} + @code{first + second + @code{ + third + fourth} + last} ---***--- begin first @@ -697,20 +709,25 @@ 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, - two}} + @list{if (something) @blah{one, + two}} end ---***--- start @@ -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' diff --git a/collects/scribblings/scribble/utils.rkt b/collects/scribblings/scribble/utils.rkt index fa4459c54e..59e02ef35d 100644 --- a/collects/scribblings/scribble/utils.rkt +++ b/collects/scribblings/scribble/utils.rkt @@ -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)) diff --git a/collects/tests/scribble/main.rkt b/collects/tests/scribble/main.rkt index 7ff28b445d..6493534362 100644 --- a/collects/tests/scribble/main.rkt +++ b/collects/tests/scribble/main.rkt @@ -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)) diff --git a/collects/tests/scribble/preprocessor.rkt b/collects/tests/scribble/text-lang.rkt similarity index 92% rename from collects/tests/scribble/preprocessor.rkt rename to collects/tests/scribble/text-lang.rkt index aa2f894fcf..2e7a9922e0 100644 --- a/collects/tests/scribble/preprocessor.rkt +++ b/collects/tests/scribble/text-lang.rkt @@ -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] diff --git a/collects/web-server/scribblings/templates.scrbl b/collects/web-server/scribblings/templates.scrbl index 76c5f5ecdf..264b638c61 100644 --- a/collects/web-server/scribblings/templates.scrbl +++ b/collects/web-server/scribblings/templates.scrbl @@ -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}