bkyk8rc3zvpnsf5inmcqq4n3k98cv6hj-my-site-hyper-literate-git.test.suzanne.soy-0.0.1

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 2b3b4f15e0b67b76f2aadfe6f0aef5a84d9aa5f4
parent 2917d887d68c24d570cd9751c6400a952a089e95
Author: Eli Barzilay <eli@barzilay.org>
Date:   Tue, 28 Jun 2011 17:56:34 -0400

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".

original commit: 0af236dc2f39d5b697ca26af51b6f1e659d6bbe9

Diffstat:
Mcollects/scribble/text/output.rkt | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mcollects/scribblings/scribble/how-to-paper.scrbl | 18+++++++++---------
Dcollects/scribblings/scribble/preprocessor.scrbl | 1183------------------------------------------------------------------------------
Mcollects/scribblings/scribble/scribble.scrbl | 2+-
Acollects/scribblings/scribble/text.scrbl | 1199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcollects/scribblings/scribble/utils.rkt | 2+-
Mcollects/tests/scribble/main.rkt | 4++--
Dcollects/tests/scribble/preprocessor.rkt | 66------------------------------------------------------------------
Acollects/tests/scribble/text-lang.rkt | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 1331 insertions(+), 1284 deletions(-)

diff --git 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 @@ -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/preprocessor.scrbl b/collects/scribblings/scribble/preprocessor.scrbl @@ -1,1183 +0,0 @@ -#lang scribble/doc -@(require scribble/manual - scribble/core scribble/html-properties scribble/latex-properties - "utils.rkt" - (for-label racket/base - ;; FIXME: need to get this in - ;; scribble/text - )) -@initialize-tests - -@title[#:tag "preprocessor" - #:style (make-style #f (list (make-tex-addition "shaded.tex") - (make-css-addition "shaded.css"))) - ]{Text Preprocessing} - -@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: - -@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 - @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.}] - -} - -@; TODO: -@; * make all example sections be subsections, -@; * add a reference section, -@; * a section on "scribble/text.rkt" -@; * maybe a section on additional utilities: begin/text - -@;-------------------------------------------------------------------- -@section{Writing Preprocessor Files} - -The combination of the two features makes text in files in the -@racket[scribble/text] language be read as strings, which get printed -out when the module is @racket[require]d, for example, when a file is -given as an argument to @exec{racket}. (In these example the left -part shows the source input, and the right part the printed result.) - -@example|-{#lang scribble/text - Programming languages should - be designed not by piling - feature on top of feature, but - blah blah blah. - ---***--- - Programming languages should - be designed not by piling - feature on top of feature, but - blah blah blah.}-| - -Using @seclink["reader"]|{@-forms}|, we can define and use Racket -functions. - -@example|-{#lang scribble/text - @(require racket/list) - @(define Foo "Preprocessing") - @(define (3x . x) - ;; racket syntax here - (add-between (list x x x) " ")) - @Foo languages should - be designed not by piling - feature on top of feature, but - @3x{blah}. - ---***--- - Preprocessing languages should - be designed not by piling - feature on top of feature, but - blah blah blah.}-| - -As demonstrated in this case, the @racket[output] function simply -scans nested list structures recursively, which makes them convenient -for function results. In addition, @racket[output] prints most values -similarly to @racket[display] --- notable exceptions are void and -false values which cause no output to appear. This can be used for -convenient conditional output. - -@example|-{#lang scribble/text - @(define (errors n) - (list n - " error" - (and (not (= n 1)) "s"))) - You have @errors[3] in your code, - I fixed @errors[1]. - ---***--- - You have 3 errors in your code, - I fixed 1 error.}-| - -Using the scribble @seclink["reader"]|{@-forms}| syntax, you can write -functions more conveniently too. - -@example|-{#lang scribble/text - @(define (errors n) - ;; note the use of `unless' - @list{@n error@unless[(= n 1)]{s}}) - You have @errors[3] in your code, - I fixed @errors[1]. - ---***--- - You have 3 errors in your code, - I fixed 1 error.}-| - -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. - -@example|-{#lang scribble/text - - @(define (plural n) - (unless (= n 1) "s")) - - @(define (errors n) - @list{@n error@plural[n]}) - - You have @errors[3] in your code, - @(define fixed 1) - I fixed @errors[fixed]. - ---***--- - You have 3 errors in your code, - I fixed 1 error.}-| - -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. - -@example|-{#lang scribble/text - @(define (count n str) - (for/list ([i (in-range 1 (add1 n))]) - @list{@i @str,@"\n"})) - Start... - @count[3]{Mississippi} - ... and I'm done. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - - ... and I'm done.}-| - -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 @litchar|{@;}| 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.) - -@example|-{#lang scribble/text - @(define (count n str) - (for/list ([i (in-range 1 (+ n 1))]) - @list{@i @str,@"\n"})) - Start... - @count[3]{Mississippi - }... done once. - - Start again... - @count[3]{Massachusetts}@; - ... and I'm done again. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - ... done once. - - Start again... - 1 Massachusetts, - 2 Massachusetts, - 3 Massachusetts, - ... and I'm done again.}-| - -A better approach is to generate newlines only when needed. - -@example|-{#lang scribble/text - @(require racket/list) - @(define (counts n str) - (add-between - (for/list ([i (in-range 1 (+ n 1))]) - @list{@i @str,}) - "\n")) - Start... - @counts[3]{Mississippi} - ... and I'm done. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - ... and I'm done.}-| - -In fact, this is common enough that the @racket[scribble/text] -language provides a convenient facility: @racket[add-newlines] is a -function that is similar to @racket[add-between] using a newline -string as the default separator, except that false and void values are -filtered out before doing so. - -@example|-{#lang scribble/text - @(define (count n str) - (add-newlines - (for/list ([i (in-range 1 (+ n 1))]) - @list{@i @str,}))) - Start... - @count[3]{Mississippi} - ... and I'm done. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - ... and I'm done.}-| - -@example|-{#lang scribble/text - @(define (count n str) - (add-newlines - (for/list ([i (in-range 1 (+ n 1))]) - @(and (even? i) @list{@i @str,})))) - Start... - @count[6]{Mississippi} - ... and I'm done. - ---***--- - Start... - 2 Mississippi, - 4 Mississippi, - 6 Mississippi, - ... and I'm done.}-| - -The separator can be set to any value. - -@example|-{#lang scribble/text - @(define (count n str) - (add-newlines #:sep ",\n" - (for/list ([i (in-range 1 (+ n 1))]) - @list{@i @str}))) - Start... - @count[3]{Mississippi}. - ... and I'm done. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi. - ... and I'm done.}-| - - -@;-------------------------------------------------------------------- -@section{Defining Functions and More} - -(Note: most of the tips in this section are applicable to any code -that uses the Scribble @"@"-form 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 @"@"-form for expressions that render as text, which, in the -@racket[scribble/text] language, is any value-producing expression.) -For example, you can use an @"@"-form for a function that you define. - -@example|-{#lang scribble/text - @(define @bold[text] @list{*@|text|*}) - An @bold{important} note. - ---***--- - An *important* note. - }-| - -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 @"@"-form. - -@example|-{#lang scribble/text - @(define (choose 1st 2nd) - @list{Either @1st, or @|2nd|@"."}) - @(define who "us") - @choose[@list{you're with @who} - @list{against @who}] - ---***--- - Either you're with us, or against us. - }-| - -You can even use @"@"-forms 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. - -@example|-{#lang scribble/text - @(define (choose 1st 2nd) - @list{Either @1st, or @|2nd|@"."}) - @(define who "us") - @choose[@list{you're with @who} - @list{against @who}] - @(define-syntax-rule (compare (x ...) ...) - (add-newlines - (list (list "* " x ...) ...))) - Shopping list: - @compare[@{apples} - @{oranges} - @{@(* 2 3) bananas}] - ---***--- - Either you're with us, or against us. - Shopping list: - * apples - * oranges - * 6 bananas - }-| - -Yet another solution is to look at the text values and split the input -arguments based on a specific token. Using @racket[match] can make it -convenient --- you can even specify the patterns with @"@"-forms. - -@example|-{#lang scribble/text - @(require racket/match) - @(define (features . text) - (match text - [@list{@|1st|@... - --- - @|2nd|@...} - @list{>> Pros << - @1st; - >> Cons << - @|2nd|.}])) - @features{fast, - reliable - --- - expensive, - ugly} - ---***--- - >> Pros << - fast, - reliable; - >> Cons << - expensive, - ugly. - }-| - -In particular, it is often convenient to split the input by lines, -identified by delimiting @racket["\n"] strings. Since this can be -useful, a @racket[split-lines] function is provided. - -@example|-{#lang scribble/text - @(require racket/list) - @(define (features . text) - (add-between (split-lines text) - ", ")) - @features{red - fast - reliable}. - ---***--- - red, fast, reliable. - }-| - -Finally, the Scribble reader accepts @emph{any} expression as the head -part of an @"@"-form --- even an @"@" form. This makes it possible to -get a number of text bodies by defining a curried function, where each -step accepts any number of arguments. This, however, means that the -number of body expressions must be fixed. - -@example|-{#lang scribble/text - @(define ((choose . 1st) . 2nd) - @list{Either you're @1st, or @|2nd|.}) - @(define who "me") - @@choose{with @who}{against @who} - ---***--- - Either you're with me, or against me. - }-| - - -@;-------------------------------------------------------------------- -@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. - -@example|-{#lang scribble/text - First - @display{Second} - Third - ---***--- - First - Second - Third}-| - -Taking this further, it is possible to write functions that output -some text @emph{instead} of returning values that represent the text. - -@example|-{#lang scribble/text - @(define (count n) - (for ([i (in-range 1 (+ n 1))]) - (printf "~a Mississippi,\n" i))) - Start... - @count[3]@; avoid an empty line - ... and I'm done. - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - ... and I'm done.}-| - -This can be used to produce a lot of output text, even infinite. - -@example|-{#lang scribble/text - @(define (count n) - (printf "~a Mississippi,\n" n) - (count (add1 n))) - Start... - @count[1] - this line is never printed! - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - 4 Mississippi, - 5 Mississippi, - ...}-| - -However, you should be careful not to mix returning values with -printouts, as the results are rarely desirable. - -@example|-{#lang scribble/text - @list{1 @display{two} 3} - ---***--- - two1 3}-| - -Note that you don't need side-effects if you want infinite output. -The @racket[output] function iterates thunks and (composable) -promises, so you can create a loop that is delayed in either form. -@; Note: there is some sfs-related problem in racket that makes it not -@; run in bounded space, so don't show it for nowx. - -@example|-{#lang scribble/text - @(define (count n) - (cons @list{@n Mississippi,@"\n"} - (lambda () - (count (add1 n))))) - Start... - @count[1] - this line is never printed! - ---***--- - Start... - 1 Mississippi, - 2 Mississippi, - 3 Mississippi, - 4 Mississippi, - 5 Mississippi, - ...}-| - - -@;-------------------------------------------------------------------- -@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., -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. - -@example|-{#lang scribble/text - @(format "~s" '@list{ - a - b - c}) - ---***--- - (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. - -@example|-{#lang scribble/text - foo @list{1 - 2 - 3} - ---***--- - foo 1 - 2 - 3}-| - -@example|-{#lang scribble/text - @(define (block . text) - @list{begin - @text - end}) - @block{first - second - @block{ - third - fourth} - last} - ---***--- - begin - first - second - begin - third - fourth - end - last - end}-| - -@example|-{#lang scribble/text - @(define (enumerate . items) - (add-newlines #:sep ";\n" - (for/list ([i (in-naturals 1)] - [item (in-list items)]) - @list{@|i|. @item}))) - Todo: @enumerate[@list{Install Racket} - @list{Hack, hack, hack} - @list{Profit}]. - ---***--- - Todo: 1. Install Racket; - 2. Hack, hack, hack; - 3. Profit.}-| - -@example[#:hidden]|-{ - #lang scribble/text - @; demonstrates how indentation is preserved inside lists - begin - a - b - @list{c - d - @list{e - f - g} - h - i - @list{j - k - l} - m - n - o} - p - q - end - ---***--- - begin - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - end - }-| - -@example[#:hidden]|-{ - #lang scribble/text - - @list{ - a - - b - } - - c - ---***--- - a - - b - - c - }-| - -@example[#:hidden]|-{ - #lang scribble/text - @; indentation works even when coming from a function - @(define (((if . c) . t) . e) - @list{ - if (@c) - @t - else - @e - fi}) - function foo() { - @list{if (1 < 2) - something1 - else - @@@if{2<3}{something2}{something3} - repeat 3 { - @@@if{2<3}{something2}{something3} - @@@if{2<3}{ - @list{something2.1 - something2.2} - }{ - something3 - } - } - fi} - return - } - ---***--- - function foo() { - if (1 < 2) - something1 - else - if (2<3) - something2 - else - something3 - fi - repeat 3 { - if (2<3) - something2 - else - something3 - fi - if (2<3) - something2.1 - something2.2 - else - something3 - fi - } - fi - return - } - }-| - -@example[#:hidden]|-{ - #lang scribble/text - @; indentation works with a list, even a single string with a newline - @; in a list, but not in a string by itself - function foo() { - prefix - @list{if (1 < 2) - something1 - else - @list{something2 - something3} - @'("something4\nsomething5") - @"something6\nsomething7" - fi} - return - } - @; can be used with a `display', but makes sense only at the top level - @; or in thunks (not demonstrated here) - @(display 123) foo @list{bar1 - bar2 - bar2} - ---***--- - function foo() { - prefix - if (1 < 2) - something1 - else - something2 - something3 - something4 - something5 - something6 - something7 - fi - return - } - 123 foo bar1 - bar2 - bar2 - }-| - -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. - -@example|-{#lang scribble/text - @(define (block . text) - @splice{{ - blah(@text); - }}) - start - @splice{foo(); - loop:} - @list{if (something) @block{one, - two}} - end - ---***--- - start - foo(); - loop: - if (something) { - blah(one, - two); - } - end - }-| - -The @racket[disable-prefix] function disables all indentation -printouts in its contents, including the indentation before the body -of the @racket[disable-prefix] value itself. It is useful, for -example, to print out CPP directives. - -@example|-{#lang scribble/text - @(define (((IFFOO . var) . expr1) . expr2) - (define (array e1 e2) - @list{[@e1, - @e2]}) - @list{var @var; - @disable-prefix{#ifdef FOO} - @var = @array[expr1 expr2]; - @disable-prefix{#else} - @var = @array[expr2 expr1]; - @disable-prefix{#endif}}) - - function blah(something, something_else) { - @disable-prefix{#include "stuff.inc"} - @@@IFFOO{i}{something}{something_else} - } - ---***--- - function blah(something, something_else) { - #include "stuff.inc" - var i; - #ifdef FOO - i = [something, - something_else]; - #else - i = [something_else, - something]; - #endif - } - }-| - -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). - -@example|-{#lang scribble/text - @(define (thunk name . body) - @list{function @name() { - @body - }}) - @(define (ifdef cond then else) - @list{@disable-prefix{#}ifdef @cond - @then - @disable-prefix{#}else - @else - @disable-prefix{#}endif}) - - @thunk['do_stuff]{ - init(); - @ifdef["HAS_BLAH" - @list{var x = blah();} - @thunk['blah]{ - @ifdef["BLEHOS" - @list{@disable-prefix{#}@; - include <bleh.h> - bleh();} - @list{error("no bleh");}] - }] - more_stuff(); - } - ---***--- - function do_stuff() { - init(); - # ifdef HAS_BLAH - var x = blah(); - # else - function blah() { - # ifdef BLEHOS - # include <bleh.h> - bleh(); - # else - error("no bleh"); - # endif - } - # endif - more_stuff(); - } - }-| - -There are cases where each line should be prefixed with some string -other than a plain indentation. The @racket[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. - -@example|-{#lang scribble/text - @(define (comment . body) - @add-prefix["// "]{@body}) - @comment{add : int int -> string} - char *foo(int x, int y) { - @comment{ - skeleton: - allocate a string - print the expression into it - @comment{...more work...} - } - char *buf = malloc(@comment{FIXME! - This is bad} - 100); - } - ---***--- - // add : int int -> string - char *foo(int x, int y) { - // skeleton: - // allocate a string - // print the expression into it - // // ...more work... - char *buf = malloc(// FIXME! - // This is bad - 100); - } - }-| - -When combining @racket[add-prefix] and @racket[disable-prefix] there -is an additional value that can be useful: @racket[flush]. This is a -value that causes @racket[output] to print the current indentation and -prefix. This makes it possible to get the ``ignored as a prefix'' -property of @racket[disable-prefix] but only for a nested prefix. - -@example|-{#lang scribble/text - @(define (comment . text) - (list flush - @add-prefix[" *"]{ - @disable-prefix{/*} @text */})) - function foo(x) { - @comment{blah - more blah - yet more blah} - if (x < 0) { - @comment{even more - blah here - @comment{even - nested}} - do_stuff(); - } - } - ---***--- - function foo(x) { - /* blah - * more blah - * yet more blah */ - if (x < 0) { - /* even more - * blah here - * /* even - * * nested */ */ - do_stuff(); - } - } - }-| - -@example[#:hidden]|-{ - #lang scribble/text - - @(begin - ;; This is a somewhat contrived example, showing how to use lists - ;; and disable-prefix to control the added prefix - (define (item . text) - ;; notes: the `flush' makes the prefix to that point print so the - ;; disable-prefix "* " is printed after it, which overwrites the - ;; "| " prefix - (list flush (add-prefix "| " (disable-prefix "* ") text))) - ;; note that a simple item with spaces is much easier: - (define (simple . text) @list{* @text})) - - start - @item{blah blah blah - blah blah blah - @item{more stuff - more stuff - more stuff} - blah blah blah - blah blah blah} - @simple{more blah - blah blah} - end - ---***--- - start - * blah blah blah - | blah blah blah - | * more stuff - | | more stuff - | | more stuff - | blah blah blah - | blah blah blah - * more blah - blah blah - end - }-| - - -@;-------------------------------------------------------------------- -@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. - -@example|-{#lang scribble/text - @(require "itemize.rkt") - Todo: - @itemize[@list{Hack some} - @list{Sleep some} - @list{Hack some - more}] - ---***--- itemize.rkt - #lang racket - (provide itemize) - (define (itemize . items) - (add-between (map (lambda (item) - (list "* " item)) - items) - "\n")) - ---***--- - Todo: - * Hack some - * Sleep some - * Hack some - more - }-| - -Note that the @seclink["at-exp-lang"]{@racket[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. - -@example|-{#lang scribble/text - @(require "stuff.rkt") - Todo: - @itemize[@list{Hack some} - @list{Sleep some} - @list{Hack some - more}] - @summary - ---***--- stuff.rkt - #lang at-exp racket/base - (require racket/list) - (provide (all-defined-out)) - (define (itemize . items) - (add-between (map (lambda (item) - @list{* @item}) - items) - "\n")) - (define summary - @list{If that's not enough, - I don't know what is.}) - ---***--- - Todo: - * Hack some - * Sleep some - * Hack some - more - If that's not enough, - I don't know what is. - }-| - -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. - -@example|-{#lang at-exp racket/base - (require scribble/text racket/list) - (define (itemize . items) - (add-between (map (lambda (item) - @list{* @item}) - items) - "\n")) - (define summary - @list{If that's not enough, - I don't know what is.}) - (output - @list{ - Todo: - @itemize[@list{Hack some} - @list{Sleep some} - @list{Hack some - more}] - @summary - }) - ---***--- - Todo: - * Hack some - * Sleep some - * Hack some - more - If that's not enough, - I don't know what is. - }-| - -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 -provides an @racket[include] form that includes a file in the -preprocessor syntax (where the default parsing mode is text). - -@example|-{#lang scribble/text - @(require racket/list) - @(define (itemize . items) - (list - "<ul>" - (add-between - (map (lambda (item) - @list{<li>@|item|</li>}) - items) - "\n") - "</ul>")) - @(define title "Todo") - @(define summary - @list{If that's not enough, - I don't know what is.}) - - @include["template.html"] - ---***--- template.html - <html> - <head><title>@|title|</title></head> - <body> - <h1>@|title|</h1> - @itemize[@list{Hack some} - @list{Sleep some} - @list{Hack some - more}] - <p><i>@|summary|</i></p> - </body> - </html> - ---***--- - <html> - <head><title>Todo</title></head> - <body> - <h1>Todo</h1> - <ul><li>Hack some</li> - <li>Sleep some</li> - <li>Hack some - more</li></ul> - <p><i>If that's not enough, - I don't know what is.</i></p> - </body> - </html> - }-| - -(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.) - -@;FIXME: add more text on `restore-prefix', `set-prefix', `with-writer' - -@;FIXME: add this to the reference section -@;@defform[(include filename)]{ -@; -@;Preprocess the @racket[filename] using the same syntax as -@;@racket[scribble/text]. This is similar to using @racket[load] in a -@;namespace that can access names bound in the current file so included -@;code can refer to bindings from the including module. Note, however, -@;that the including module cannot refer to names that are bound the -@;included file because it is still a plain racket module---for such -@;uses you should still use @racket[require] as usual.} - - -@; Two random tests -@example[#:hidden]|-{ - #lang scribble/text - - @define[name]{Racket} - - Suggested price list for "@name" - - @; test mutual recursion, throwing away inter-definition spaces - @; <-- this is needed to get only one line of space above - @(define (items-num) - (length items)) - - @(define average - (delay (/ (apply + (map car items)) (length items)))) - - @(define items - (list @list[99]{Home} - @list[149]{Professional} - @list[349]{Enterprize})) - - @(for/list ([i items] [n (in-naturals)]) - @list{@|n|. @name @cadr[i] edition: $@car[i].99 - @||})@; <-- also needed - - Total: @items-num items - Average price: $@|average|.99 - ---***--- - Suggested price list for "Racket" - - 0. Racket Home edition: $99.99 - 1. Racket Professional edition: $149.99 - 2. Racket Enterprize edition: $349.99 - - Total: 3 items - Average price: $199.99 - }-| -@example[#:hidden]|-{ - #lang scribble/text - - --*-- - @(define (angled . body) (list "<" body ">")) - @(define (shout . body) @angled[(map string-upcase body)]) - @define[z]{blah} - - blah @angled{blah @shout{@z} blah} blah - - @(define-syntax-rule @twice[x] - (list x ", " x)) - - @twice{@twice{blah}} - - @include{inp1} - - @(let ([name "Eli"]) (let ([foo (include "inp2")]) (list foo "\n" foo))) - Repeating yourself much? - ---***--- inp1 - Warning: blah overdose might be fatal - ---***--- inp2 - @(define (foo . xs) (bar xs)) - @(begin (define (isname) @list{is @foo{@name}}) - (define-syntax-rule (DEF x y) (define x y))) - @(DEF (bar x) (list z " " x)) - @(define-syntax-rule (BEG x ...) (begin x ...)) - @(BEG (define z "zee")) - - My name @isname - @DEF[x]{Foo!} - - ... and to that I say "@x", I think. - - ---***--- - --*-- - blah <blah <BLAH> blah> blah - - blah, blah, blah, blah - - Warning: blah overdose might be fatal - - My name is zee Eli - ... and to that I say "Foo!", I think. - My name is zee Eli - ... and to that I say "Foo!", I think. - Repeating yourself much? - }-| diff --git 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/text.scrbl b/collects/scribblings/scribble/text.scrbl @@ -0,0 +1,1199 @@ +#lang scribble/doc +@(require scribble/manual + scribble/core scribble/html-properties scribble/latex-properties + "utils.rkt" + (for-label racket/base + ;; FIXME: need to get this in + ;; scribble/text + )) +@initialize-tests + +@title[#:tag "text" + #:style (make-style #f (list (make-tex-addition "shaded.tex") + (make-css-addition "shaded.css"))) + ]{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 text generation or a preprocessor language: + +@itemize[ + + @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 + textual output.}] + +} + +@; TODO: +@; * make all example sections be subsections, +@; * add a reference section, +@; * a section on "scribble/text.rkt" +@; * maybe a section on additional utilities: begin/text + +@;-------------------------------------------------------------------- +@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 +out when the module is @racket[require]d, for example, when a file is +given as an argument to @exec{racket}. (In these example the left +part shows the source input, and the right part the printed result.) + +@example|-{#lang scribble/text + Programming languages should + be designed not by piling + feature on top of feature, but + blah blah blah. + ---***--- + Programming languages should + be designed not by piling + feature on top of feature, but + blah blah blah.}-| + +Using @seclink["reader"]|{@-forms}|, we can define and use Racket +functions. + +@example|-{#lang scribble/text + @(require racket/list) + @(define Foo "Preprocessing") + @(define (3x . x) + ;; racket syntax here + (add-between (list x x x) " ")) + @Foo languages should + be designed not by piling + feature on top of feature, but + @3x{blah}. + ---***--- + Preprocessing languages should + be designed not by piling + feature on top of feature, but + blah blah blah.}-| + +As demonstrated in this case, the @racket[output] function simply +scans nested list structures recursively, which makes them convenient +for function results. In addition, @racket[output] prints most values +similarly to @racket[display] --- notable exceptions are void and +false values which cause no output to appear. This can be used for +convenient conditional output. + +@example|-{#lang scribble/text + @(define (errors n) + (list n + " error" + (and (not (= n 1)) "s"))) + You have @errors[3] in your code, + I fixed @errors[1]. + ---***--- + You have 3 errors in your code, + I fixed 1 error.}-| + +Using the scribble @seclink["reader"]|{@-forms}| syntax, you can write +functions more conveniently too. + +@example|-{#lang scribble/text + @(define (errors n) + ;; note the use of `unless' + @list{@n error@unless[(= n 1)]{s}}) + You have @errors[3] in your code, + I fixed @errors[1]. + ---***--- + You have 3 errors in your code, + I fixed 1 error.}-| + +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. + +@example|-{#lang scribble/text + + @(define (plural n) + (unless (= n 1) "s")) + + @(define (errors n) + @list{@n error@plural[n]}) + + You have @errors[3] in your code, + @(define fixed 1) + I fixed @errors[fixed]. + ---***--- + You have 3 errors in your code, + I fixed 1 error.}-| + +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. + +@example|-{#lang scribble/text + @(define (count n str) + (for/list ([i (in-range 1 (add1 n))]) + @list{@i @str,@"\n"})) + Start... + @count[3]{Mississippi} + ... and I'm done. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + + ... and I'm done.}-| + +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 @litchar|{@;}| 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.) + +@example|-{#lang scribble/text + @(define (count n str) + (for/list ([i (in-range 1 (+ n 1))]) + @list{@i @str,@"\n"})) + Start... + @count[3]{Mississippi + }... done once. + + Start again... + @count[3]{Massachusetts}@; + ... and I'm done again. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + ... done once. + + Start again... + 1 Massachusetts, + 2 Massachusetts, + 3 Massachusetts, + ... and I'm done again.}-| + +A better approach is to generate newlines only when needed. + +@example|-{#lang scribble/text + @(require racket/list) + @(define (counts n str) + (add-between + (for/list ([i (in-range 1 (+ n 1))]) + @list{@i @str,}) + "\n")) + Start... + @counts[3]{Mississippi} + ... and I'm done. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + ... and I'm done.}-| + +In fact, this is common enough that the @racket[scribble/text] +language provides a convenient facility: @racket[add-newlines] is a +function that is similar to @racket[add-between] using a newline +string as the default separator, except that false and void values are +filtered out before doing so. + +@example|-{#lang scribble/text + @(define (count n str) + (add-newlines + (for/list ([i (in-range 1 (+ n 1))]) + @list{@i @str,}))) + Start... + @count[3]{Mississippi} + ... and I'm done. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + ... and I'm done.}-| + +@example|-{#lang scribble/text + @(define (count n str) + (add-newlines + (for/list ([i (in-range 1 (+ n 1))]) + @(and (even? i) @list{@i @str,})))) + Start... + @count[6]{Mississippi} + ... and I'm done. + ---***--- + Start... + 2 Mississippi, + 4 Mississippi, + 6 Mississippi, + ... and I'm done.}-| + +The separator can be set to any value. + +@example|-{#lang scribble/text + @(define (count n str) + (add-newlines #:sep ",\n" + (for/list ([i (in-range 1 (+ n 1))]) + @list{@i @str}))) + Start... + @count[3]{Mississippi}. + ... and I'm done. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi. + ... and I'm done.}-| + + +@;-------------------------------------------------------------------- +@section{Defining Functions and More} + +(Note: most of the tips in this section are applicable to any code +that uses the Scribble @"@"-form 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 @"@"-form for expressions that render as text, which, in the +@racket[scribble/text] language, is any value-producing expression.) +For example, you can use an @"@"-form for a function that you define. + +@example|-{#lang scribble/text + @(define @bold[text] @list{*@|text|*}) + An @bold{important} note. + ---***--- + An *important* note. + }-| + +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 @"@"-form. + +@example|-{#lang scribble/text + @(define (choose 1st 2nd) + @list{Either @1st, or @|2nd|@"."}) + @(define who "us") + @choose[@list{you're with @who} + @list{against @who}] + ---***--- + Either you're with us, or against us. + }-| + +You can even use @"@"-forms 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. + +@example|-{#lang scribble/text + @(define (choose 1st 2nd) + @list{Either @1st, or @|2nd|@"."}) + @(define who "us") + @choose[@list{you're with @who} + @list{against @who}] + @(define-syntax-rule (compare (x ...) ...) + (add-newlines + (list (list "* " x ...) ...))) + Shopping list: + @compare[@{apples} + @{oranges} + @{@(* 2 3) bananas}] + ---***--- + Either you're with us, or against us. + Shopping list: + * apples + * oranges + * 6 bananas + }-| + +Yet another solution is to look at the text values and split the input +arguments based on a specific token. Using @racket[match] can make it +convenient --- you can even specify the patterns with @"@"-forms. + +@example|-{#lang scribble/text + @(require racket/match) + @(define (features . text) + (match text + [@list{@|1st|@... + --- + @|2nd|@...} + @list{>> Pros << + @1st; + >> Cons << + @|2nd|.}])) + @features{fast, + reliable + --- + expensive, + ugly} + ---***--- + >> Pros << + fast, + reliable; + >> Cons << + expensive, + ugly. + }-| + +In particular, it is often convenient to split the input by lines, +identified by delimiting @racket["\n"] strings. Since this can be +useful, a @racket[split-lines] function is provided. + +@example|-{#lang scribble/text + @(require racket/list) + @(define (features . text) + (add-between (split-lines text) + ", ")) + @features{red + fast + reliable}. + ---***--- + red, fast, reliable. + }-| + +Finally, the Scribble reader accepts @emph{any} expression as the head +part of an @"@"-form --- even an @"@" form. This makes it possible to +get a number of text bodies by defining a curried function, where each +step accepts any number of arguments. This, however, means that the +number of body expressions must be fixed. + +@example|-{#lang scribble/text + @(define ((choose . 1st) . 2nd) + @list{Either you're @1st, or @|2nd|.}) + @(define who "me") + @@choose{with @who}{against @who} + ---***--- + Either you're with me, or against me. + }-| + + +@;-------------------------------------------------------------------- +@section{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. + +@example|-{#lang scribble/text + First + @display{Second} + Third + ---***--- + First + Second + Third}-| + +Taking this further, it is possible to write functions that output +some text @emph{instead} of returning values that represent the text. + +@example|-{#lang scribble/text + @(define (count n) + (for ([i (in-range 1 (+ n 1))]) + (printf "~a Mississippi,\n" i))) + Start... + @count[3]@; avoid an empty line + ... and I'm done. + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + ... and I'm done.}-| + +This can be used to produce a lot of output text, even infinite. + +@example|-{#lang scribble/text + @(define (count n) + (printf "~a Mississippi,\n" n) + (count (add1 n))) + Start... + @count[1] + this line is never printed! + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + 4 Mississippi, + 5 Mississippi, + ...}-| + +However, you should be careful not to mix returning values with +printouts, as the results are rarely desirable. + +@example|-{#lang scribble/text + @list{1 @display{two} 3} + ---***--- + two1 3}-| + +Note that you don't need side-effects if you want infinite output. +The @racket[output] function iterates thunks and (composable) +promises, so you can create a loop that is delayed in either form. +@; Note: there is some sfs-related problem in racket that makes it not +@; run in bounded space, so don't show it for nowx. + +@example|-{#lang scribble/text + @(define (count n) + (cons @list{@n Mississippi,@"\n"} + (lambda () + (count (add1 n))))) + Start... + @count[1] + this line is never printed! + ---***--- + Start... + 1 Mississippi, + 2 Mississippi, + 3 Mississippi, + 4 Mississippi, + 5 Mississippi, + ...}-| + + +@;-------------------------------------------------------------------- +@section{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 +@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{ + a + b + c}) + ---***--- + (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 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 @block{1 + 2 + 3} + foo @list{4 + 5 + 6} + ---***--- + foo 1 + 2 + 3 + foo 4 + 5 + 6}-| + +@example|-{#lang scribble/text + @(define (code . text) + @list{begin + @text + end}) + @code{first + second + @code{ + third + fourth} + last} + ---***--- + begin + first + second + begin + third + fourth + end + last + end}-| + +@example|-{#lang scribble/text + @(define (enumerate . items) + (add-newlines #:sep ";\n" + (for/list ([i (in-naturals 1)] + [item (in-list items)]) + @list{@|i|. @item}))) + Todo: @enumerate[@list{Install Racket} + @list{Hack, hack, hack} + @list{Profit}]. + ---***--- + Todo: 1. Install Racket; + 2. Hack, hack, hack; + 3. Profit.}-| + +@example[#:hidden]|-{ + #lang scribble/text + @; demonstrates how indentation is preserved inside lists + begin + a + b + @list{c + d + @list{e + f + g} + h + i + @list{j + k + l} + m + n + o} + p + q + end + ---***--- + begin + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + end + }-| + +@example[#:hidden]|-{ + #lang scribble/text + + @list{ + a + + b + } + + c + ---***--- + a + + b + + c + }-| + +@example[#:hidden]|-{ + #lang scribble/text + @; indentation works even when coming from a function + @(define (((if . c) . t) . e) + @list{ + if (@c) + @t + else + @e + fi}) + function foo() { + @list{if (1 < 2) + something1 + else + @@@if{2<3}{something2}{something3} + repeat 3 { + @@@if{2<3}{something2}{something3} + @@@if{2<3}{ + @list{something2.1 + something2.2} + }{ + something3 + } + } + fi} + return + } + ---***--- + function foo() { + if (1 < 2) + something1 + else + if (2<3) + something2 + else + something3 + fi + repeat 3 { + if (2<3) + something2 + else + something3 + fi + if (2<3) + something2.1 + something2.2 + else + something3 + fi + } + fi + return + } + }-| + +@example[#:hidden]|-{ + #lang scribble/text + @; indentation works with a list, even a single string with a newline + @; in a list, but not in a string by itself + function foo() { + prefix + @list{if (1 < 2) + something1 + else + @list{something2 + something3} + @'("something4\nsomething5") + @"something6\nsomething7" + fi} + return + } + @; can be used with a `display', but makes sense only at the top level + @; or in thunks (not demonstrated here) + @(display 123) foo @list{bar1 + bar2 + bar2} + ---***--- + function foo() { + prefix + if (1 < 2) + something1 + else + something2 + something3 + something4 + something5 + something6 + something7 + fi + return + } + 123 foo bar1 + bar2 + bar2 + }-| + +There are, however, cases when you need more refined control over the +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 (blah . text) + @splice{{ + blah(@block{@text}); + }}) + start + @splice{foo(); + loop:} + @list{if (something) @blah{one, + two}} + end + ---***--- + start + foo(); + loop: + if (something) { + blah(one, + two); + } + end + }-| + +The @racket[disable-prefix] function disables all indentation +printouts in its contents, including the indentation before the body +of the @racket[disable-prefix] value itself. It is useful, for +example, to print out CPP directives. + +@example|-{#lang scribble/text + @(define (((IFFOO . var) . expr1) . expr2) + (define (array e1 e2) + @list{[@e1, + @e2]}) + @list{var @var; + @disable-prefix{#ifdef FOO} + @var = @array[expr1 expr2]; + @disable-prefix{#else} + @var = @array[expr2 expr1]; + @disable-prefix{#endif}}) + + function blah(something, something_else) { + @disable-prefix{#include "stuff.inc"} + @@@IFFOO{i}{something}{something_else} + } + ---***--- + function blah(something, something_else) { + #include "stuff.inc" + var i; + #ifdef FOO + i = [something, + something_else]; + #else + i = [something_else, + something]; + #endif + } + }-| + +If there are values after a @racket[disable-prefix] value on the same +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) + @list{function @name() { + @body + }}) + @(define (ifdef cond then else) + @list{@disable-prefix{#}ifdef @cond + @then + @disable-prefix{#}else + @else + @disable-prefix{#}endif}) + + @thunk['do_stuff]{ + init(); + @ifdef["HAS_BLAH" + @list{var x = blah();} + @thunk['blah]{ + @ifdef["BLEHOS" + @list{@disable-prefix{#}@; + include <bleh.h> + bleh();} + @list{error("no bleh");}] + }] + more_stuff(); + } + ---***--- + function do_stuff() { + init(); + # ifdef HAS_BLAH + var x = blah(); + # else + function blah() { + # ifdef BLEHOS + # include <bleh.h> + bleh(); + # else + error("no bleh"); + # endif + } + # endif + more_stuff(); + } + }-| + +There are cases where each line should be prefixed with some string +other than a plain indentation. The @racket[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. + +@example|-{#lang scribble/text + @(define (comment . body) + @add-prefix["// "]{@body}) + @comment{add : int int -> string} + char *foo(int x, int y) { + @comment{ + skeleton: + allocate a string + print the expression into it + @comment{...more work...} + } + char *buf = malloc(@comment{FIXME! + This is bad} + 100); + } + ---***--- + // add : int int -> string + char *foo(int x, int y) { + // skeleton: + // allocate a string + // print the expression into it + // // ...more work... + char *buf = malloc(// FIXME! + // This is bad + 100); + } + }-| + +When combining @racket[add-prefix] and @racket[disable-prefix] there +is an additional value that can be useful: @racket[flush]. This is a +value that causes @racket[output] to print the current indentation and +prefix. This makes it possible to get the ``ignored as a prefix'' +property of @racket[disable-prefix] but only for a nested prefix. + +@example|-{#lang scribble/text + @(define (comment . text) + (list flush + @add-prefix[" *"]{ + @disable-prefix{/*} @text */})) + function foo(x) { + @comment{blah + more blah + yet more blah} + if (x < 0) { + @comment{even more + blah here + @comment{even + nested}} + do_stuff(); + } + } + ---***--- + function foo(x) { + /* blah + * more blah + * yet more blah */ + if (x < 0) { + /* even more + * blah here + * /* even + * * nested */ */ + do_stuff(); + } + } + }-| + +@example[#:hidden]|-{ + #lang scribble/text + + @(begin + ;; This is a somewhat contrived example, showing how to use lists + ;; and disable-prefix to control the added prefix + (define (item . text) + ;; notes: the `flush' makes the prefix to that point print so the + ;; disable-prefix "* " is printed after it, which overwrites the + ;; "| " prefix + (list flush (add-prefix "| " (disable-prefix "* ") text))) + ;; note that a simple item with spaces is much easier: + (define (simple . text) @list{* @text})) + + start + @item{blah blah blah + blah blah blah + @item{more stuff + more stuff + more stuff} + blah blah blah + blah blah blah} + @simple{more blah + blah blah} + end + ---***--- + start + * blah blah blah + | blah blah blah + | * more stuff + | | more stuff + | | more stuff + | blah blah blah + | blah blah blah + * more blah + blah blah + end + }-| + + +@;-------------------------------------------------------------------- +@section{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 +@racket[require] additional files with utility functions. + +@example|-{#lang scribble/text + @(require "itemize.rkt") + Todo: + @itemize[@list{Hack some} + @list{Sleep some} + @list{Hack some + more}] + ---***--- itemize.rkt + #lang racket + (provide itemize) + (define (itemize . items) + (add-between (map (lambda (item) + (list "* " item)) + items) + "\n")) + ---***--- + Todo: + * Hack some + * Sleep some + * Hack some + more + }-| + +Note that the @seclink["at-exp-lang"]{@racket[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. + +@example|-{#lang scribble/text + @(require "stuff.rkt") + Todo: + @itemize[@list{Hack some} + @list{Sleep some} + @list{Hack some + more}] + @summary + ---***--- stuff.rkt + #lang at-exp racket/base + (require racket/list) + (provide (all-defined-out)) + (define (itemize . items) + (add-between (map (lambda (item) + @list{* @item}) + items) + "\n")) + (define summary + @list{If that's not enough, + I don't know what is.}) + ---***--- + Todo: + * Hack some + * Sleep some + * Hack some + more + If that's not enough, + I don't know what is. + }-| + +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 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) + (define (itemize . items) + (add-between (map (lambda (item) + @list{* @item}) + items) + "\n")) + (define summary + @list{If that's not enough, + I don't know what is.}) + (output + @list{ + Todo: + @itemize[@list{Hack some} + @list{Sleep some} + @list{Hack some + more}] + @summary + }) + ---***--- + Todo: + * Hack some + * Sleep some + * Hack some + more + If that's not enough, + I don't know what is. + }-| + +However, you might run into a case where it is desirable to include a +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). + +@example|-{#lang scribble/text + @(require racket/list) + @(define (itemize . items) + (list + "<ul>" + (add-between + (map (lambda (item) + @list{<li>@|item|</li>}) + items) + "\n") + "</ul>")) + @(define title "Todo") + @(define summary + @list{If that's not enough, + I don't know what is.}) + + @include["template.html"] + ---***--- template.html + <html> + <head><title>@|title|</title></head> + <body> + <h1>@|title|</h1> + @itemize[@list{Hack some} + @list{Sleep some} + @list{Hack some + more}] + <p><i>@|summary|</i></p> + </body> + </html> + ---***--- + <html> + <head><title>Todo</title></head> + <body> + <h1>Todo</h1> + <ul><li>Hack some</li> + <li>Sleep some</li> + <li>Hack some + more</li></ul> + <p><i>If that's not enough, + I don't know what is.</i></p> + </body> + </html> + }-| + +(Using @racket[require] with a text file in the @racket[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.) + +@;FIXME: add more text on `restore-prefix', `set-prefix', `with-writer' + +@;FIXME: add this to the reference section +@;@defform[(include filename)]{ +@; +@;Preprocess the @racket[filename] using the same syntax as +@;@racket[scribble/text]. This is similar to using @racket[load] in a +@;namespace that can access names bound in the current file so included +@;code can refer to bindings from the including module. Note, however, +@;that the including module cannot refer to names that are bound the +@;included file because it is still a plain racket module---for such +@;uses you should still use @racket[require] as usual.} + + +@; Two random tests +@example[#:hidden]|-{ + #lang scribble/text + + @define[name]{Racket} + + Suggested price list for "@name" + + @; test mutual recursion, throwing away inter-definition spaces + @; <-- this is needed to get only one line of space above + @(define (items-num) + (length items)) + + @(define average + (delay (/ (apply + (map car items)) (length items)))) + + @(define items + (list @list[99]{Home} + @list[149]{Professional} + @list[349]{Enterprize})) + + @(for/list ([i items] [n (in-naturals)]) + @list{@|n|. @name @cadr[i] edition: $@car[i].99 + @||})@; <-- also needed + + Total: @items-num items + Average price: $@|average|.99 + ---***--- + Suggested price list for "Racket" + + 0. Racket Home edition: $99.99 + 1. Racket Professional edition: $149.99 + 2. Racket Enterprize edition: $349.99 + + Total: 3 items + Average price: $199.99 + }-| +@example[#:hidden]|-{ + #lang scribble/text + + --*-- + @(define (angled . body) (list "<" body ">")) + @(define (shout . body) @angled[(map string-upcase body)]) + @define[z]{blah} + + blah @angled{blah @shout{@z} blah} blah + + @(define-syntax-rule @twice[x] + (list x ", " x)) + + @twice{@twice{blah}} + + @include{inp1} + + @(let ([name "Eli"]) (let ([foo (include "inp2")]) (list foo "\n" foo))) + Repeating yourself much? + ---***--- inp1 + Warning: blah overdose might be fatal + ---***--- inp2 + @(define (foo . xs) (bar xs)) + @(begin (define (isname) @list{is @foo{@name}}) + (define-syntax-rule (DEF x y) (define x y))) + @(DEF (bar x) (list z " " x)) + @(define-syntax-rule (BEG x ...) (begin x ...)) + @(BEG (define z "zee")) + + My name @isname + @DEF[x]{Foo!} + + ... and to that I say "@x", I think. + + ---***--- + --*-- + blah <blah <BLAH> blah> blah + + blah, blah, blah, blah + + Warning: blah overdose might be fatal + + My name is zee Eli + ... and to that I say "Foo!", I think. + My name is zee Eli + ... and to that I say "Foo!", I think. + Repeating yourself much? + }-| diff --git 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 @@ -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/preprocessor.rkt @@ -1,66 +0,0 @@ -#lang racket/base - -(require tests/eli-tester racket/runtime-path racket/port racket/sandbox - (prefix-in doc: (lib "scribblings/scribble/preprocessor.scrbl"))) - -(provide preprocessor-tests) - -(define (preprocessor-tests) - ;; (sample-file-tests) - (in-documentation-tests)) - -;; unused now -(define-runtime-path text-dir "text") -(define (sample-file-tests) - (parameterize ([current-directory text-dir]) - (for ([ifile (map path->string (directory-list))] - #:when (and (file-exists? ifile) - (regexp-match? #rx"^i[0-9]+\\.ss$" ifile))) - (define ofile (regexp-replace #rx"^i([0-9]+)\\..*$" ifile "o\\1.txt")) - (define expected (call-with-input-file ofile - (lambda (i) (read-bytes (file-size ofile) i)))) - (define o (open-output-bytes)) - (parameterize ([current-output-port o]) - (dynamic-require (path->complete-path ifile) #f)) - (test (get-output-bytes o) => expected)))) - -(define-runtime-path this-dir ".") -(define (in-documentation-tests) - (define (text-test line in-text out-text more) - (define-values (i o) (make-pipe 512)) - (define-values (expected len-to-read) - (let ([m (regexp-match-positions #rx"\n\\.\\.\\.$" out-text)]) - (if m - (values (substring out-text 0 (caar m)) (caar m)) - (values out-text #f)))) - ;; test with name indicating the source - (define-syntax-rule (t . stuff) - (test ;; #:failure-message - ;; (format "preprocessor test failure at line ~s" line) - . stuff)) - (parameterize ([current-directory this-dir] - [sandbox-output o] - [sandbox-error-output current-output-port]) - (define exn #f) - (define thd #f) - (define (run) - ;; only need to evaluate the module, so we have its output; but do that - ;; in a thread, since we might want to look at just a prefix of an - ;; infinite output - (with-handlers ([void (lambda (e) (set! exn e))]) - (make-module-evaluator in-text) - (close-output-port o))) - (for ([m more]) - (call-with-output-file (car m) #:exists 'truncate - (lambda (o) (display (cdr m) o)))) - (set! thd (thread run)) - (t (with-limits 2 #f - (if len-to-read (read-string len-to-read i) (port->string i))) - => expected) - (t (begin (kill-thread thd) (cond [exn => raise] [else #t]))) - (for ([m more]) - (when (file-exists? (car m)) (delete-file (car m)))))) - (call-with-trusted-sandbox-configuration - (lambda () - (for ([t (in-list (doc:tests))]) - (begin (apply text-test t)))))) diff --git a/collects/tests/scribble/text-lang.rkt b/collects/tests/scribble/text-lang.rkt @@ -0,0 +1,66 @@ +#lang racket/base + +(require tests/eli-tester racket/runtime-path racket/port racket/sandbox + (prefix-in doc: (lib "scribblings/scribble/text.scrbl"))) + +(provide text-lang-tests) + +(define (text-lang-tests) + ;; (sample-file-tests) + (in-documentation-tests)) + +;; unused now +(define-runtime-path text-dir "text") +(define (sample-file-tests) + (parameterize ([current-directory text-dir]) + (for ([ifile (map path->string (directory-list))] + #:when (and (file-exists? ifile) + (regexp-match? #rx"^i[0-9]+\\.ss$" ifile))) + (define ofile (regexp-replace #rx"^i([0-9]+)\\..*$" ifile "o\\1.txt")) + (define expected (call-with-input-file ofile + (lambda (i) (read-bytes (file-size ofile) i)))) + (define o (open-output-bytes)) + (parameterize ([current-output-port o]) + (dynamic-require (path->complete-path ifile) #f)) + (test (get-output-bytes o) => expected)))) + +(define-runtime-path this-dir ".") +(define (in-documentation-tests) + (define (text-test line in-text out-text more) + (define-values (i o) (make-pipe 512)) + (define-values (expected len-to-read) + (let ([m (regexp-match-positions #rx"\n\\.\\.\\.$" out-text)]) + (if m + (values (substring out-text 0 (caar m)) (caar m)) + (values out-text #f)))) + ;; test with name indicating the source + (define-syntax-rule (t . stuff) + (test ;; #:failure-message + ;; (format "text-lang test failure at line ~s" line) + . stuff)) + (parameterize ([current-directory this-dir] + [sandbox-output o] + [sandbox-error-output current-output-port]) + (define exn #f) + (define thd #f) + (define (run) + ;; only need to evaluate the module, so we have its output; but do that + ;; in a thread, since we might want to look at just a prefix of an + ;; infinite output + (with-handlers ([void (lambda (e) (set! exn e))]) + (make-module-evaluator in-text) + (close-output-port o))) + (for ([m more]) + (call-with-output-file (car m) #:exists 'truncate + (lambda (o) (display (cdr m) o)))) + (set! thd (thread run)) + (t (with-limits 2 #f + (if len-to-read (read-string len-to-read i) (port->string i))) + => expected) + (t (begin (kill-thread thd) (cond [exn => raise] [else #t]))) + (for ([m more]) + (when (file-exists? (car m)) (delete-file (car m)))))) + (call-with-trusted-sandbox-configuration + (lambda () + (for ([t (in-list (doc:tests))]) + (begin (apply text-test t))))))