diff --git a/collects/scribblings/scribble/preprocessor.scrbl b/collects/scribblings/scribble/preprocessor.scrbl index c393f0b1..e502ddc8 100644 --- a/collects/scribblings/scribble/preprocessor.scrbl +++ b/collects/scribblings/scribble/preprocessor.scrbl @@ -1,7 +1,7 @@ #lang scribble/doc -@(require scribble/manual - "utils.ss" +@(require scribble/manual scribble/struct "utils.ss" (for-label scheme/base)) +@initialize-tests @title[#:tag "preprocessor"]{Text Preprocessor} @@ -12,35 +12,106 @@ changes that make it suitable as a preprocessor language: @itemize{ @item{It uses @scheme[read-syntax-inside] to read the body of the - module, similar to @secref["docreader"].} + module, similar to @secref["docreader"]. This means that by + default, all text is read in as Scheme strings; and + @seclink["reader"]|{@-forms}| can be used to use Scheme + functions and expression escapes.} - @item{It has a custom printer (@scheme[current-print]) that displays - all values. The printer is also installed as the - @scheme[port-display-handler] so it can be used through - @scheme[display] as well as @litchar{~a} in format strings. - The printer displays most values (as is usual for - @scheme[display]), except for - @itemize{@item{@scheme[void] and @scheme[#f] are not - displayed,} - @item{pairs are displayed recursively (just their - contents, no parentheses),} - @item{promises are forced, thunks are invoked.}}}} + @item{Values of expressions are printed with a custom + @scheme[output] function. This function displays most values + in a similar way to @scheme[display], except that it is more + convenient for a preprocessor output.}} } -This means that to write a text file that has scheme code, you simply -write it as a module in the @scheme[scribble/text] language, and run -it through @exec{mzscheme}. Here is a sample file: +@;-------------------------------------------------------------------- +@section{Writing Preprocessor Files} -@verbatim[#:indent 2]|{ - #lang scribble/text - @(define (angled . body) (list "<" body ">"))@; - @(define (shout . body) @angled[(map string-upcase body)])@; - blah @angled{blah @shout{blah} blah} blah -}| +The combination of the two features makes text in files in the +@scheme[scribble/text] language be read as strings, which get printed +out when the module is @scheme[require]d, for example, when a file is +given as an argument to @exec{mzscheme}. (In these example the left +part shows the source input, and the right part the printed result.) -(Note how @litchar["@;"] is used to avoid empty lines in the output.) +@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 Scheme +functions. + +@example|-{#lang scribble/text + @(require scheme/list) + @(define Foo "Preprocessing") + @(define (3x . x) + (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 @scheme[output] function simply +scans nested list structures recursively, which makes them convenient +for function results. In addition, @scheme[output] prints most values +similarly to @scheme[display] \- a notable exception 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) + @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.}-| + +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, + I fixed @errors[1]. + ---***--- + You have 3 errors in your code, + I fixed 1 error.}-| @;-------------------------------------------------------------------- @section{Using External Files} diff --git a/collects/scribblings/scribble/utils.ss b/collects/scribblings/scribble/utils.ss index 0c5b8437..d70962b2 100644 --- a/collects/scribblings/scribble/utils.ss +++ b/collects/scribblings/scribble/utils.ss @@ -102,9 +102,9 @@ (require scheme/list (for-syntax scheme/base scheme/list)) -(define max-textsample-width 32) +(define max-textsample-width 35) -(define (textsample-verbatim-boxes 1st 2nd more) +(define (textsample-verbatim-boxes line 1st 2nd more) (define (split str) (regexp-split #rx"\n" str)) (define strs1 (split 1st)) (define strs2 (split 2nd)) @@ -128,7 +128,8 @@ [s (in-list s)]) (max m (string-length s))))]) (if (negative? d) - (error 'textsample-verbatim-boxes "left box too wide") + (error 'textsample-verbatim-boxes + "left box too wide for sample at line ~s" line) (hspace d)))) (values (make-table '([alignment right left] [valignment top top]) @@ -141,8 +142,8 @@ filenames strsm))) box2)) -(define (textsample 1st 2nd . more) - (define-values (box1 box2) (textsample-verbatim-boxes 1st 2nd more)) +(define (textsample line 1st 2nd . more) + (define-values (box1 box2) (textsample-verbatim-boxes line 1st 2nd more)) (make-table '([alignment left left left] [valignment center center center]) (list (map as-flow (list box1 (make-paragraph '(nbsp rarr nbsp)) box2))))) @@ -189,8 +190,8 @@ [((file text ...) ...) files] [add-to-tests (cadr tests-ids)]) (syntax/loc stx - (let ([t (list (string-append i/o ...) ... + (let ([t (list line (string-append i/o ...) ... (cons file (string-append text ...)) ...)]) - (add-to-tests (cons line t)) + (add-to-tests t) (apply textsample t)))))] [_ (raise-syntax-error #f "no separator found in example text")]))])) diff --git a/collects/tests/scribble/main.ss b/collects/tests/scribble/main.ss index b95d0d3d..b1465088 100644 --- a/collects/tests/scribble/main.ss +++ b/collects/tests/scribble/main.ss @@ -1,8 +1,10 @@ #lang scheme/base -(require tests/eli-tester scribble/text/syntax-utils scheme/runtime-path) +(require tests/eli-tester scribble/text/syntax-utils scheme/runtime-path + scheme/sandbox (lib "scribblings/scribble/preprocessor.scrbl")) (define-runtime-path text-dir "text") +(define-runtime-path this-dir ".") (test @@ -78,7 +80,7 @@ (f 3 #:> "]" #:< "[")) => '(1 ("<" 1 ">") ("[" 2 ">") ("[" 3 "]")) - ;; preprocessor functionality + ;; preprocessor tests (parameterize ([current-directory text-dir]) (for ([ifile (map path->string (directory-list))] #:when (and (file-exists? ifile) @@ -90,5 +92,16 @@ (parameterize ([current-output-port o]) (dynamic-require (path->complete-path ifile) #f)) (test (get-output-bytes o) => expected))) + ;; preprocessor tests that are part of the documentation + (parameterize ([current-directory this-dir] + [sandbox-output 'string] + [sandbox-error-output current-output-port]) + (define (text-test line in out . more) + (define e (make-module-evaluator in)) + (test + #:failure-message (format "preprocessor test failure at line ~s" line) + (equal? (get-output e) out))) + (call-with-trusted-sandbox-configuration + (lambda () (for ([t (in-list (tests))]) (apply text-test t))))) )