diff --git a/collects/scribble/doc.txt b/collects/scribble/doc.txt index fe46ffd6a7..5547503fd6 100644 --- a/collects/scribble/doc.txt +++ b/collects/scribble/doc.txt @@ -1,32 +1,4 @@ -The _Scribble_ Collection -========================= - -The Scribble collection contains a few libraries that can be used to -create documents from Scheme. It is made of independently usable -parts. For example, the reader can be used in any situation that -requires lots of free-form text, or you can use the rendering portion -directly to generate documents. - - -Running Scribble ----------------- - -To process a Scribble document, use the `scribble' command-line utility -(use `scribble -h' to see usage information). This is implemented by -the "run-scribble.ss" module, which can be used directly: - -> (render-file input output format) - -Renders the given `input' file to the `output' file using the given -format specification. The input and output files are used as is (no -suffixes are added). The `output' argument can be #f, which will render -the input to the current output port. `format' is a symbol that -specifies the kind of output rendering (use the `scribble' commad to -find the list of available formatters). - -A Scribble document is a MzScheme module file, which provides a -`content' binding. - +(The rest of the Scribble documentation is now a separate manual.) The Scribble Reader ------------------- @@ -71,12 +43,16 @@ Informally, the concrete syntax of @-forms is: where all three parts after "@" are optional, but at least one should be present. (Note that spaces are not allowed between the three -parts.) @litchar["@"] is set as a non-terminating reader macro, so it -can be used as usual in Scheme identifiers unless you want to use it -as a first character of an identifier; in this case you need to quote -with a backslash (`\@foo@') or quote the whole identifier with bars -(`|@foo|'). Of course, "@" is not treated specially in Scheme -strings, character constants, etc. +parts.) "@" is set as a non-terminating reader macro, so it can be +used as usual in Scheme identifiers unless you want to use it as a +first character of an identifier; in this case you need to quote with +a backslash (`\@foo@') or quote the whole identifier with bars +(`|@foo|'). + + (define |@foo| '\@bar@baz) + +Of course, "@" is not treated specially in Scheme strings, character +constants, etc. Roughly, a form matching the above grammar is read as @@ -87,322 +63,541 @@ input. Thus, the initial determines the Scheme code that the input is translated into. The common case is when is a Scheme identifier, which generates a plain Scheme form. -A is made of character sequences, newlines, and nested -@-forms. Note that the syntax for @-forms is the same in a - context as in a Scheme context. A that isn't -an @-form is converted to a string expression for its , -and newlines are converted to "\n" expressions: +A is made of text, newlines, and nested @-forms. Note +that the syntax for @-forms is the same in a context as in +a Scheme context. A that isn't an @-form is converted to +a string expression for its , and newlines are converted +to "\n" expressions: @foo{bar baz blah} - --is-read-as--> + --reads-as--> (foo "bar baz" "\n" "blah") @foo{bar @baz[3] blah} - --is-read-as--> - (foo (baz 3) "\n" "blah") + --reads-as--> + (foo "bar " (baz 3) "\n" "blah") @foo{bar @baz{3} blah} - --is-read-as--> + --reads-as--> (foo "bar " (baz "3") "\n" "blah") @foo{bar @baz[2 3]{4 5} blah} - --is-read-as--> + --reads-as--> (foo "bar " (baz 2 3 "4 5") "\n" "blah") +Note that spaces are not allowed before a "[" or a "{", or they will +be part of the following text (or Scheme code). (More on using braces +in body texts below.) + + @foo{bar @baz[2 3] {4 5}} + --reads-as--> + (foo "bar " (baz 2 3) " {4 5}") + When the above @-forms appear in a Scheme expression context, the -context must provide bindings for `foo' (as a procedure or macro). To -just see the read result for an @-form, you can always a Scheme -`quote': +lexical environment must provide bindings for `foo' (as a procedure or +a macro). + + (let* ([formatter (lambda (fmt) + (lambda args (format fmt (apply string-append args))))] + [bf (formatter "*~a*")] + [it (formatter "/~a/")] + [ul (formatter "_~a_")] + [text string-append]) + @text{@it{Note}: @bf{This is @ul{not} a pipe}.}) + --> "/Note/: *This is _not_ a pipe*." + +If you want to see the expression that is actually being read, you can +use Scheme's `quote'. '@foo{bar} ** Concrete Syntax: The Command Part -Besides being a Scheme identifier, the part of an @-form can be -any Scheme expression. The expression can have Scheme punctuation -prefixes, which will end up wrapping the *whole* expression. +Besides being a Scheme identifier, the part of an @-form can +have Scheme punctuation prefixes, which will end up wrapping the +*whole* expression. - @`',@foo{blah} - - --is-read-as--> - - `',@(foo "blah") + @`',@foo{blah} --reads-as--> `',@(foo "blah") When writing Scheme code, this means that @`',@foo{blah} is exactly the same as `@',@foo{blah} and `',@@foo{blah}, but unlike the latter two, the first construct can appear in body texts with the same meaning, whereas the other two would not work (see below). -As mentioned above, the command itself is not limited to a Scheme -identifier; it can be any Scheme expression. - - @(lambda (x) x){blah} - - --is-read-as--> - - ((lambda (x) x) "blah") - - - - +After the optional punctuation prefix, the itself is not limited +to identifiers -- it can be *any* Scheme expression. + @(lambda (x) x){blah} --reads-as--> ((lambda (x) x) "blah") + @`(unquote foo){blah} --reads-as--> `(,foo "blah") In addition, the command can be omitted altogether, which will omit it -from the translation, resulting in an s-expression that usually contains -just strings: +from the translation, resulting in an S-expression that usually +contains, say, just strings: - @{foo bar --is-read-as--> ("foo bar" "\n" "baz") + @{foo bar --reads-as--> ("foo bar" "\n" "baz") baz} - @'{foo bar --is-read-as--> '("foo bar" "\n" "baz") + @'{foo bar --reads-as--> '("foo bar" "\n" "baz") baz} If the command part begins with a ";" (with no newline between the "@" and the ";"), then the construct is a comment. There are two comment -forms, one for arbitrary-text and possibly nested comments, and another -one for a -to-the-end-of-the-line comment: +forms, one for arbitrary-text and possibly nested comments, and +another one for a line comments: - @;{ ...any-text-including-tested-scribble-pieces... } + @;{ ...} - @; + @; -Note that the first form is analogous to a "#;" comment: the commented -body must still parse correctly. Also note that in the second form all -text from the "@;" to the end of the line and all following -(non-newline) whitespaces are part of the comment (as with TeX `%' -comments). For example: +In the first form, the commented body must still parse correctly; see +the description of the body syntax below. In the second form, all +text from the "@;" to the end of the line *and* all following spaces +(or tabs) are part of the comment (similar to "%" comments in TeX). - @foo{bar @; comment --is-read-as--> (foo "bar baz") + @foo{bar @; comment --reads-as--> (foo "bar baz") baz} -Tip: if you're editing in a Scheme-aware editor, it is useful to comment -out blocks like this: - - @; - { - ... - } - -or +Tip: if you're editing in a Scheme-aware editor (like DrScheme or +Emacs), it is useful to comment out blocks like this: @;{ ... ;} -otherwise you will probably confuse the editor into treating the file as -having imbalanced parenthesis. +so the editor does not treat the file as having unbalanced +parenthesis. -If only the command part is specified, then the result is the command -part only, without an extra set of parenthesis. This makes it suitable -for Scheme escapes in body texts. More below, in the description of the -body part. +If only the part of an @-form is specified, then the result is +the command part only, without an extra set of parenthesis. This +makes it suitable for Scheme escapes in body texts. (More on this +below, in the description of the body part.) -Finally, note that there are no special rules for using "@" in the -command itself, which can lead to things like: + @foo{x @y z} --reads-as--> (foo "x " y " z") + @foo{x @(* y 2) z} --reads-as--> (foo "x " (* y 2) " z") + @{@foo bar} --reads-as--> (foo " bar") - @@foo{bar}{baz} --is-read-as--> ((foo "bar") "baz") +Finally, note that there are currently no special rules for using "@" +in the command itself, which can lead to things like: -To use "@" as in plain Scheme code, you need to quote it as you would -quote other characters, for example: + @@foo{bar}{baz} --reads-as--> ((foo "bar") "baz") - (define |@foo| '\@bar) +** Concrete Syntax: The Datum Part -** Concrete Syntax: the body part +The datum part can contains arbitrary Scheme expressions, which are +simply stacked before the body text arguments: -The syntax of the body part is intended to be as convenient as possible -for writing free text. It can contain almost any text -- the only -character with special meaning is "@", in addition, braces, "|", and -backslash can have special meanings but only in a few contexts. As -described above, the text turns to a sequence of string arguments for -the resulting form. Spaces at the beginning of lines are discarded (but -see the information about indentation below), and newlines turn to -individual "\n" strings. (Spaces are preserved on a single-line text.) -As part of trying to do the `right thing', an empty line at the -beginning and at the end are discarded, so + @foo[1 (* 2 3)]{bar} --reads-as--> (foo 1 (* 2 3) "bar") + @foo[@bar{...}]{blah} --reads-as--> (foo (bar "...") "blah") + +The body part can still be omitted, which is essentially an +alternative syntax for plain (non-textual) S-expressions: + + @foo[bar] --reads-as--> (foo bar) + @foo{bar @f[x] baz} --reads-as--> (foo "bar " (f x) " baz") + +The datum part can be empty, which makes no difference, except when +the body is omitted. It is more common, however, to use an empty body +for the same purpose. + + @foo[]{bar} --reads-as--> (foo "bar") + @foo[] --reads-as--> (foo) + @foo --reads-as--> foo + @foo{} --reads-as--> (foo) + +The most common use of the datum part is for Scheme forms that expect +keyword-value arguments that precede the body of text arguments. + + @foo[#:style 'big]{bar} --reads-as--> (foo #:style 'big "bar") + +** Concrete Syntax: The Body Part + +The syntax of the body part is intended to be as convenient as +possible for free text. It can contain almost any text -- the only +characters with special meaning is "@" for sub forms, and "}" for the +end of the text. In addition, a "{" is allowed as part of the text, +and it makes the matching "}" be part of the text too -- so balanced +braces are valid text. + + @foo{f{o}o} --reads-as--> (foo "f{o}o") + @foo{{{}}{}} --reads-as--> (foo "{{}}{}") + +As described above, the text turns to a sequence of string arguments +for the resulting form. Spaces at the beginning and end of lines are +discarded, and newlines turn to individual "\n" strings (i.e., they +are not merged with other body parts). (See also the information +about newlines and indentation below.) Spaces are *not* discarded if +they appear after the open "{" (before the closing "}") when there is +also text that follows (precedes) it; specifically, they are preserved +in a single-line body. + + @foo{bar} --reads-as--> (foo "bar") + @foo{ bar } --reads-as--> (foo " bar ") + @foo[1]{ bar } --reads-as--> (foo 1 " bar ") + +If "@" appears in a body, then it is interpreted as Scheme code, which +means that the @-reader is applied recursively, and the resulting +syntax appears as part of the S-expression, among other string +contents. + + @foo{a @bar{b} c} --reads-as--> (foo "a " (bar "b") " c") + +If the nested "@" construct has only a command -- no body or datum +parts -- it will not appear in a subform. Given that the command part +can be any Scheme expression, this makes "@" a general escape to +arbitrary Scheme code. + + @foo{a @bar c} --reads-as--> (foo "a " bar " c") + @foo{a @(bar 2) c} --reads-as--> (foo "a " (bar 2) " c") + +This is particularly useful with strings, which can be used to include +arbitrary text. + + @foo{This @"}" is a closing brace} + --reads-as--> + (foo "This } is a closing brace") + +Note that the escaped string is (intentionally) merged with the rest +of the text. This works for "@" too: + + @foo{The command prefix is @"@".} + --reads-as--> + (foo "The command prefix is @.") + + @foo{@"@foo{bar}" reads as (foo "bar")} + --reads-as--> + (foo "@foo{bar} reads as (foo \"bar\")") + +* Concrete Syntax: Alternative Body Syntax + +In addition, there is an alternative syntax for the body, one that +specifies a new marker for its end: use "|{" for the opening marker to +have the text terminated by a "}|". + + @foo|{...}| + --reads-as--> + (foo "...") + + @foo|{close with "}", open with "{"}| + --reads-as--> + (foo "close with \"}\", open with \"{\"") + + @foo|{Nesting |{is}| ok}| + --reads-as--> + (foo "Nesting |{is}| ok") + +This applies to sub-@-forms too -- the "@" must be prefixed with a +"|": + + @foo|{Maze + |@bar{is} + Life!}| + --reads-as--> + (foo "Maze" "\n" (bar "is") "Life!") + + @foo|{Works for |@bar|{subforms}| too}| + --reads-as--> + (foo "Works for " (bar "subforms") " too") + +Note that the subform uses its own delimiters, "{...}" or "|{...}|". +This means that you can copy and paste Scribble text with @-forms +freely, just prefix the "@" if the immediate surrounding text has a +prefix. + +For even better control, you can add characters in the opening +delimiter, between the "|" and the "{". Characters that are put there +(non alphanumeric ASCII characters only, excluding "{" and "@") should +also be used for sub-@-forms, and the end-of-body marker should have +these characters in reverse order with paren-like characters ("(", +"[", "<") mirrored. + + @foo|<<<{Some @x{more} |@{text}|.}>>>| + --reads-as--> + (foo "Some @x{more} |@{text}|.") + + @foo|!!{Blah |!!@bold{blah}...}!!| + --reads-as--> + (foo "Blah " (bold "blah") "...") + +Finally, remember that you can use an expression escape with a Scheme +string for confusing situations. This works well when you only need +to quote short pieces, and the above works well when you have larger +multi-line body texts. + +* Concrete Syntax: Scheme Expression Escapes + +In some cases, you may want to use a Scheme identifier (or a number or +a boolean etc.) in a position that touches the following text; in +these situations you should surround the escaped Scheme expression by +a pair of "|" characters. The text inside the bars is parsed as a +Scheme expression. + + @foo{foo@bar.} --reads-as--> (foo "foo" bar.) + @foo{foo@|bar|.} --reads-as--> (foo "foo" bar ".") + @foo{foo@3.} --reads-as--> (foo "foo" 3.0) + @foo{foo@|3|.} --reads-as--> (foo "foo" 3 ".") + +This form is a generic Scheme expression escape, there is no body text +or datum part when you use this form. + + @foo{foo@|(f 1)|{bar}} --reads-as--> (foo "foo" (f 1) "{bar}") + @foo{foo@|bar|[1]{baz}} --reads-as--> (foo "foo" bar "[1]{baz}") + +This works for string expressions too, but not that unlike the above, +the string is (intentionally) not merged with the rest of the text: + + @foo{x@"y"z} --reads-as--> (foo "xyz") + @foo{x@|"y"|z} --reads-as--> (foo "x" "y" "z") + +Expression escapes also work with *any* number of expressions, + + @foo{x@|1 (+ 2 3) 4|y} --reads-as--> (foo "x" 1 (+ 2 3) 4 "y") + + @foo{x@|* --reads-as--> (foo "x" * * "y") + *|y} + +It seems that "@||" has no purpose -- but remember that these escapes +are never merged with the surrounding text, which can be useful when +you want to control the sub expressions in the form. + + @foo{Alice@||Bob@| --reads-as--> (foo "Alice" "Bob" "Carol") + |Carol} + +Note that "@|{...}|" can be parsed as either an escape expression or +as a no-command @-form. The latter is used in this case (since there +is little point in Scheme code that uses braces. + + @|{blah}| --is-read-as--> ("blah") + +* Concrete Syntax: Comments + +As noted above, there are two kinds of Scribble comments: "@;{...}" is +a (nestable) comment for a whole body of text (following the same +rules for @-forms), and "@;..." is a line-comment. + + @foo{First line@;{there is still a + newline at this point;} + Second line} + --reads-as--> + (foo "First line" "\n" "Second line") + +One useful property of line-comments is that they continue to the end +of the line *and* all following spaces (or tabs). Using this, you can +get further control of the subforms. + + @foo{This is @; + a pretty long @; + single string-@; + argument.} + --reads-as--> + (foo "This is a pretty long single string-argument.") + +Note how this is different from using "@||"s in that strings around it +are not merged. + +* Concrete Syntax: Spaces, Newlines, and Indentation + +The Scribble syntax treats spaces and newlines in a special way is +meant to be sensible for dealing with text. As mentioned above, +spaces at the beginning and end of body lines are discarded, except +for spaces between a "{" and text, or between text and a "}". + + @foo{bar} --reads-as--> (foo "bar") + + @foo{ bar } --reads-as--> (foo " bar ") + + @foo{ bar --reads-as--> (foo " bar" "\n" "baz ") + baz } + +A single newline that follows an open brace or precedes a closing brace is +discarded, unless there are only newlines in the body; other newlines +are read as a "\n" string + + @foo{bar --reads-as--> (foo "bar") + } @foo{ - bar --is-read-as--> (foo "bar") <--is-read-as-- @foo{bar} + bar --reads-as--> (foo "bar") } - @foo{ bar } --is-read-as--> (foo " bar ") + @foo{ -If an "@" appears in the input, then it is interpreted as Scheme code, -which means that the at-reader will be applied recursively, and the -resulting syntax will appear as an argument, among other string -contents. For example: + bar --reads-as--> (foo "\n" "bar" "\n") - @foo{a @bar{b} c} --is-read-as--> (foo "a " (bar "b") " c") + } -If the nested "@" construct has only a command -- no body part, then it -does not appear in a subform. Given that the command part can be any -Scheme expression, this makes "@" a general escape to arbitrary Scheme -code: + @foo{ + bar + --reads-as--> (foo "bar" "\n" "\n" "baz") + baz + } - @foo{a @bar c} --is-read-as--> (foo "a " bar " c") + @foo{ --reads-as--> (foo "\n") + } - @foo{a @(bar 2) c} --is-read-as--> (foo "a " (bar 2) " c") + @foo{ + --reads-as--> (foo "\n" "\n") + } -In some cases, you may want to use a Scheme identifier (or a number or a -boolean) in a position that touches other text that can make an -identifier -- in these situations you should surround the Scheme -identifier (/number/boolean) by a pair of bar characters. The text -inside the bars is parsed as a Scheme expression, but if that fails, it -is used as a quoted identifier -- do not rely on this behavior, and -avoid using whitespace inside the bars. Also, if bars are used, then no -body text is used even if they are followed by braces (see the next -paragraph). Examples: + @foo{ bar --reads-as--> (foo " bar" "\n" "baz ") + baz } - @foo{foo @bar foo} --is-read-as--> (foo "foo " bar " foo") +In the parsed S-expression syntax, a single newline string is used for +all newlines; you can use `eq?' to identify this line. This can be +used to identify newlines in the original . - @foo{foo@bar.} --is-read-as--> (foo "foo" bar.) + (let ([nl (car @'{ + })]) + (for-each (lambda (x) (display (if (eq? x nl) "\n... " x))) + @`{foo + @,@(list "bar" "\n" "baz") + blah}) + (newline)) + --prints--> + foo + ... bar + baz + ... blah - @foo{foo@|bar|.} --is-read-as--> (foo "foo" bar ".") +Spaces at the beginning of body lines do not appear in the resulting +S-expressions, but the column of each line is noticed, and all-space +indentation strings are added so the result has the same indentation. +A indentation string is added to each line according to its distance +from the leftmost syntax object (except for empty lines). (Note: if +you try these examples on a mzscheme REPL, you should be aware that +the reader does not know about the "> " prompt.) - @foo{foo@3.} --is-read-as--> (foo "foo" 3.0) + @foo{ --reads-as--> (foo "bar" "\n" + bar "baz" "\n" + baz "blah") + blah + } - @foo{foo@|3|.} --is-read-as--> (foo "foo" 3 ".") + @foo{ --reads-as--> (foo "begin" "\n" + begin " " "x++;" "\n" + x++; "end") + end} - @foo{foo@|(f 1)|{bar}.} --is-read-as--> (foo "foo" (f 1) "{bar}.") + @foo{ --reads-as--> (foo " " "a" "\n" + a " " "b" "\n" + b "c") + c} -Braces are only problematic because a "}" is used to mark the end of the -text. They are therefore allowed, as long as they are balanced. For -example: +If the first string came from the openning "{" line, it is not +prepended with an indentation (but it can affect the leftmost syntax +object used for indentation). This makes sense when formatting +structured code as well as text (see the last example in the following +block). - @foo{f{o}o} --is-read-as--> (foo "f{o}o") + @foo{bar --reads-as--> (foo "bar" "\n" + baz " " "baz" "\n" + bbb} "bbb") -There is also an alternative syntax for the body, one that specifies a -new marker for the end. To do this, use "|{" for the openning marker, -optionally with additional characters between them (excluding "{", -whitespace, and alphanumerics) -- the matching closing marker should be -the mirrored form of the openning marker (reverse the characters and -swap round, square, curly, and angle parentheses). For example: + @foo{ bar --reads-as--> (foo " bar" "\n" + baz " " "baz" "\n" + bbb} " " "bbb") - @foo|{...}| --is-read-as--> (foo "...") + @foo{bar --reads-as--> (foo "bar" "\n" + baz "baz" "\n" + bbb} "bbb") - @foo|{foo{{{bar}| --is-read-as--> (foo "foo{{{bar") + @foo{ bar --reads-as--> (foo " bar" "\n" + baz "baz" "\n" + bbb} "bbb") - @foo|<{{foo{{{bar}}>| --is-read-as--> (foo "{foo{{{bar}") + @foo{ bar --reads-as--> (foo " bar" "\n" + baz "baz" "\n" + bbb} " " "bbb") -* Concrete Syntax: quoting in body texts + @text{Some text@footnote{And a + footnote comment.}. More text.} + --reads-as--> + (text "Some text" + (footnote "And a" "\n" "footnote comment.") + ". More text.") -To quote braces or "@", precede them with a backslash. Note that this -is an irregular use of backslash quoting! To use "\@" in your text, -simply precede it with a backslash. The general rule is that to use N -backslashes-and-a-special-character, you should precede it with one -extra backslash. Any other use of a backslash (one that is not followed -by more back-slashes and a special character) is preserved in the text -as usual. Examples: - - @foo{b\@ar} --is-read-as--> (foo "b@ar") - @foo{b\\@ar} --is-read-as--> (foo "b\\@ar") - @foo{b\\\@ar} --is-read-as--> (foo "b\\\\@ar") - @foo{b\{\@\@ar} --is-read-as--> (foo "b{@@ar") - @foo{b\ar} --is-read-as--> (foo "b\\ar") - @foo{b\\ar} --is-read-as--> (foo "b\\\\ar") - -* Concrete Syntax: newlines and indentation - -When indentation is used, all-space indentation string syntaxes are -perpended to the beginning of each line. The rule for adding these -string is: -- A spaces-string is added to each line according to its distance from - the leftmost syntax object; -- The first string is not prepended with indentation if it appears on - the first line of output. -Examples: - - @foo{ --is-read-as--> (foo "bar" "\n" - bar " " "baz" "\n" - baz "bbb") - bbb} - - @foo{bar --is-read-as--> (foo "bar" "\n" - baz " " "baz" "\n" - bbb} "bbb") - - @foo{ bar --is-read-as--> (foo " bar" "\n" - baz " " "baz" "\n" - bbb} " " "bbb") - - @foo{bar --is-read-as--> (foo "bar" "\n" - baz "baz" "\n" - bbb} "bbb") - - @foo{ bar --is-read-as--> (foo " bar" "\n" - baz "baz" "\n" - bbb} "bbb") - - @foo{ bar --is-read-as--> (foo " bar" "\n" - baz "baz" "\n" - bbb} " " "bbb") - -Additional notes: -- You can identify indentation strings at the syntax level by the fact - that they have the same location information as the following syntax - object; -- This mechanism depends on line and column number information - (`use-at-readtable' turns them on for the current input port); -- To use this mechanism with nested commands that should preserve - indentation, you will need to do some additional work since the nested - use will have only its own indentation; -- When using it on a command-line, you note that the reader is not aware - of the "> " prompt, which might lead to confusing results. - -[The following is likely to change.] - -For situations where spaces at the beginning of lines matter (various -verbatim environments), you should begin a line with a "|". It has no -other special meaning -- so to use a "|" as the first character in the -text, simply use another before it. +Note that each @-form is parsed to an S-expression that has its own +indentation. This means that Scribble source can be indented like +code, but if indentation matters then you may need to apply +indentation of the outer item to all lines of the inner one. For +example, in @code{ - |(define (foo x) --is-read-as--> (code "(define (foo x)" "\n" - | |error|) " |error|)") + begin + i = 1, r = 1 + @bold{while i < n do + r *= i++ + done} + end } + --reads-as--> + (code "begin" "\n" + " " "i = 1, r = 1" "\n" + " " (bold "while i < n do" "\n" + " " "r *= i++" "\n" + "done") "\n" + "end") -In other situations, newlines matter -- you might want to avoid a -newline token in some place. To avoid a newline and still break the -source line, use a line comment. As in TeX, these will consume text -up-to and including the end of the line and all following whitespace. -Example: +a formatter will need to apply the 2-space indentation to the +rendering of the `bold' body. - @foo{bar @; - baz@; --is-read-as--> (foo "bar baz.") - .} +Note that to get a first-line text to be counted as a leftmost line, +line and column accounting should be on for the input port +(`use-at-readtable' turns them on for the current input port). +Without this, -A "|" that follows this is still used for marking the beginning of the -text: + @foo{x1 + x2 + x3} - @foo{bar @; - baz@; --is-read-as--> (foo "bar baz .") - | .} +will not have 2-space indentations in the parsed S-expression if +source accounting is not on, but -** Concrete Syntax: the keyword-value part + @foo{x1 + x2 + x3} -The keyword-value part can contain arbitrary Scheme expressions, which -are simply stacked before the body text arguments: +will (due to the last line). - @foo[1 (* 2 3)]{bar} --is-read-as--> (foo 1 (* 2 3) "bar") - @foo[@bar{...}]{blah} --is-read-as--> (foo (bar "...") "blah") +For rare situations where spaces at the beginning (or end) of lines +matter, you can begin (or end) a line with a "@||". -But there is one change that makes it easy to use for keyword/values: -(a) "=" is a terminating character in the textual scope, (b) if there is -a "=" sequence (spaces optional), then it is converted -to "#:identifier ": + @foo{ + @|| bar @|| --reads-as--> (foo " bar " "\n" " baz") + @|| baz} - @foo[(* 2 3) a=b]{bar} --is-read-as--> (foo (* 2 3) #:a b "bar") +Finally, you might be need a verbatim-like environment, where the +parsed body matches exactly the textual source. To make this +possible, the @-parser uses syntax properties on the resulting syntax +values. All items that are not physically in the Scribble source -- +newlines and indentation-spaces -- have a 'scribble property. An +indentation string will have 'indentation as the value of this +property, and a newline will have a '(newline S) value where S is the +original newline string including spaces that precede and follow it +(which includes the indentation for the following item). To implement +a verbatim environment you need to drop indentation strings, and use +the original newline strings instead of the single-newline string. +Here is an example of this. -*** How should this be used? - -This facility can be used in any way you want. All you need is to use -function names that you bind. You can even use quasi-quotes, skipping -the need for functions, for example: - - > (define (important . text) @`b{@u{@big{@,@text}}}) - > (important @`p{This is an important announcement! - Read it!}) - (b (u (big (p "This is an important announcement!" "\n" "Read it!")))) + (define-syntax (verb stx) + (syntax-case stx () + [(_ cmd item ...) + #`(cmd . + #,(let loop ([items (syntax->list #'(item ...))]) + (if (null? items) + '() + (let* ([fst (car items)] + [prop (syntax-property fst 'scribble)] + [rst (loop (cdr items))]) + (cond [(not prop) (cons fst rst)] + [(eq? prop 'indentation) rst] + [else (cons (datum->syntax-object + fst (cadr prop) fst) + rst)])))))])) + @verb[string-append]{ + foo + bar + } diff --git a/collects/scribble/reader.ss b/collects/scribble/reader.ss index c620071f5e..6118bf0b5a 100644 --- a/collects/scribble/reader.ss +++ b/collects/scribble/reader.ss @@ -129,25 +129,6 @@ (unless (eof-object? ch) (when (whitespace? ch rt) (read-char port) (loop))))))))) - ;; Wrappers for placeholders, to keep source information for them. (MzScheme - ;; provides nothing for them -- there's not even a predicate. Hopefully, if - ;; something is added it will use the same name, so there's a compiler error - ;; here and this code is adapted.) - ;; (Note: used to wrap special comment values too.) - (define-struct placeholder (p loc)) - (define (syntax/placeholder-line sp) - (if (placeholder? sp) (cadr (placeholder-loc sp)) (syntax-line sp))) - (define (syntax/placeholder-column sp) - (if (placeholder? sp) (caddr (placeholder-loc sp)) (syntax-column sp))) - (define (syntax/placeholder-strip sp) - (if (placeholder? sp) (placeholder-p sp) sp)) - (define (datum->syntax-object/placeholder sp d) - (if (placeholder? sp) - ;; using the syntax for lexical context is not possible for placeholders, - ;; but we don't need it since we're a reader - (datum->syntax-object #f d (placeholder-loc sp)) - (datum->syntax-object sp d sp))) - ;; make n spaces, cached for n (define make-spaces (let ([t (make-hash-table)]) @@ -157,6 +138,17 @@ (let ([s (make-string n #\space)]) (hash-table-put! t n s) s)))))) + (define (bytes-width bs start) + (let ([len (bytes-length bs)]) + (if (regexp-match? #rx"^ *$" bs start) + (- (bytes-length bs) start) + (let loop ([i start] [w 0]) + (if (= i len) + w + (loop (add1 i) (+ w (if (eq? 9 (bytes-ref bs i)) + (- 8 (modulo w 8)) + 1)))))))) + ;; a unique eol string (define eol-token "\n") (define (eol-syntax? x) (and (syntax? x) (eq? eol-token (syntax-e x)))) @@ -191,7 +183,6 @@ (define (read-stx/rt rt) (read-syntax/recursive source-name inp #f rt)) ;; use this to avoid placeholders so we have source location information (define (read-stx*) - ;; the following should not return placeholders, but it does ;; (read-syntax/recursive source-name inp #f (current-readtable) #f) (read-syntax source-name inp)) @@ -201,127 +192,71 @@ (define (*skip rx) (*regexp-match1 rx inp)) (define (*peek rx) (*regexp-match-peek-positions rx inp)) - (define (cur-pos) - (let-values ([(line col pos) (port-next-location inp)]) - pos)) - (define (span-from start) - (and start (- (cur-pos) start))) + (and start (let-values ([(line col pos) (port-next-location inp)]) + (- pos start)))) - (define (read-delimited-list begin-re end-re end-ch tweak-locations) - ;; when `tweak-locations' is not #f, it should be (src line col pos) for - ;; the whole thing -- and we need to adjust the first item so it appears - ;; from its beginning, and the last so it appears to go to its end (used - ;; to make escape sequences not have bogus indentation added) + (define (read-delimited-list begin-re end-re end-ch) (and (*skip begin-re) - (let ([reader (if tweak-locations - ;; should always be `read-syntax/recursive', but - ;; then we don't get location information - read-stx* read-stx)]) - (let loop ([r '()]) - (skip-whitespace inp) - (if (*skip end-re) - (cond [(null? r) r] - [(not tweak-locations) (reverse! r)] - [(null? (cdr r)) - ;; make the single syntax span the whole thing - (list (datum->syntax-object (car r) (syntax-e (car r)) - `(,@tweak-locations - ,(span-from (cadddr tweak-locations)))))] - [else - (let* (;; make the last one span to the end - [last (car r)] - [last (datum->syntax-object last (syntax-e last) - (list (syntax-source last) - (syntax-line last) - (syntax-column last) - (syntax-position last) - (span-from - (syntax-position last))))] - [r (reverse! (cons last (cdr r)))] - ;; make the first go from the beginning - [fst (car r)] - [fst (datum->syntax-object fst (syntax-e fst) - `(,@tweak-locations - ,(let ([tw-pos (cadddr tweak-locations)] - [1pos (syntax-position fst)] - [1span (syntax-span fst)]) - (and tw-pos 1pos 1span - (+ (- 1pos tw-pos) 1span)))))]) - (cons fst (cdr r)))]) - (let ([x (reader)]) - (if (eof-object? x) - (read-error 'eof "expected a '~a'" end-ch) - (loop (if (special-comment? x) r (cons x r)))))))))) + (let loop ([r '()]) + (skip-whitespace inp) + (if (*skip end-re) + (reverse! r) + (let ([x (read-stx)]) + (if (eof-object? x) + (read-error 'eof "expected a '~a'" end-ch) + (loop (if (special-comment? x) r (cons x r))))))))) - ;; adds indentation (as new syntaxes, not merged); if the first line was - ;; not empty, then it is treated specially. called with at least two items - ;; (see below). - (define (add-indents stxs 1st-eol?) - #; ; the reader always turns on line counting - (unless (andmap (lambda (x) - (and (or (syntax? x) (placeholder? x)) - (syntax/placeholder-column x) - (syntax/placeholder-line x))) - stxs) - (read-error "internal error [add-indents] ~s" stxs)) - (let* ([mincol - (let loop ([min #f] [stxs (if 1st-eol? stxs (cdr stxs))]) - (if (null? stxs) - (or min (error "internal error [add-indents]")) - (loop (if (eol-syntax? (car stxs)) - min - (let ([c (syntax/placeholder-column (car stxs))]) - (if (or (not min) (< c min)) c min))) - (cdr stxs))))] - [mincol (if 1st-eol? - mincol - (min mincol (syntax/placeholder-column (car stxs))))]) - (let loop (;; no indentation for text on the first '{' line - [newline? 1st-eol?] [curline -1] [stxs stxs] [r '()]) - (if (null? stxs) - (reverse! r) - (let* ([stx (car stxs)] - [line (syntax/placeholder-line stx)]) - (loop (eol-syntax? stx) line (cdr stxs) - (let* ([stxcol (syntax/placeholder-column stx)] - [stx* (syntax/placeholder-strip stx)] - ;; add spaces - [r (if (and newline? - (< curline line) - (< mincol stxcol)) - (cons (syntax-property - (datum->syntax-object/placeholder stx - (make-spaces (- stxcol mincol))) - 'scribble 'indentation) - r) - r)] - ;; remove special-comments - [r (if (special-comment? stx*) r (cons stx* r))]) - r))))))) - - ;; gets an accumulated (reversed) list of syntaxes, sorts things out - ;; (remove prefix and suffix newlines, adds indentation if needed) - (define (done-lines rlines) - (cond - [(andmap eol-syntax? rlines) - ;; nothing to do (includes null, so the code below can assume a pair) - (reverse! rlines)] - [start-inside? - ;; no newlines removed - (add-indents (reverse! rlines) #t)] ; don't ignore the 1st line - [else - ;; strip off leading and trailing newlines (must have at least one - ;; non-newline item) - (let* ([rlines (if (eol-syntax? (car rlines)) (cdr rlines) rlines)] - [lines (reverse! rlines)] - [1st-eol? (eol-syntax? (car lines))] - [lines (if 1st-eol? (cdr lines) lines)]) - (if (null? (cdr lines)) ; common case: one string - (let ([line (syntax/placeholder-strip (car lines))]) - ;; note: we can get comment values - (if (special-comment? line) '() (list line))) - (add-indents lines 1st-eol?)))])) + ;; gets an accumulated (reversed) list of syntaxes and column markers, and + ;; sorts things out (remove prefix and suffix newlines, adds indentation if + ;; needed) + (define (done-items xs) + ;; a column marker is either a non-negative integer N (saying the the + ;; following code came from at column N), or a negative integer -N + ;; (saying that the following code came from column N but no need to add + ;; indentation at this point because it is at the openning of a {...}); + ;; `get-lines*' is careful not to include column markers before a newline + ;; or the end of the text, and a -N marker can only come from the + ;; beginning of the text (and it's never there if the text began with a + ;; newline) + (if (andmap eol-syntax? xs) + ;; nothing to do + (reverse! xs) + (let ([mincol (let loop ([xs xs] [m #f]) + (if (null? xs) + m + (let ([x (car xs)]) + (loop (cdr xs) + (if (integer? x) + (let ([x (abs x)]) (if (and m (< m x)) m x)) + m)))))]) + (let loop ([xs (if (and (not start-inside?) (eol-syntax? (car xs))) + (cdr xs) ; trim last eol + xs)] + [r '()]) + (if (or (null? xs) + (and (not start-inside?) + ;; trim first eol + (null? (cdr xs)) (eol-syntax? (car xs)))) + r + (loop + (cdr xs) + (let ([x (car xs)]) + (cond [(integer? x) + (if (or (< x 0) (= x mincol)) + r ; no indentation marker, or zero indentation + (let ([eol (cadr xs)] + [spaces (make-spaces (- x mincol))]) + ;; markers always follow end-of-lines + (unless (eol-syntax? eol) + (error 'reader "internal error [done-items]")) + (cons (syntax-property + (datum->syntax-object eol spaces eol) + 'scribble 'indentation) + r)))] + ;; can have special comment values from "@||" + [(special-comment? x) r] + [else (cons x r)])))))))) ;; cons stx (new syntax) to the list of stxs, merging it if both are ;; strings, except for newline markers @@ -342,84 +277,87 @@ (cdr stxs)) (cons stx stxs)))) + ;; helper for `get-lines*' drop a column marker if the previous item was + ;; also a newline (or the beginning) + (define (maybe-drop-marker r) + (if (and (pair? r) (integer? (car r)) + (or (null? (cdr r)) (eol-syntax? (cadr r)))) + (cdr r) + r)) + (define (get-lines* re:begin re:end re:cmd-pfx re:item end-token) - ;; re:begin, re:end, end-token can be false if start-inside? is #t - (let loop ([lvl 0] [r '()]) - (let-values ([(line col pos) (port-next-location inp)]) - (define (make-stx sexpr) - (datum->syntax-object #f - (if (bytes? sexpr) (bytes->string/utf-8 sexpr) sexpr) - (list source-name line col pos (span-from pos)))) - (cond - [(and re:begin (*match1 re:begin)) - => (lambda (m) (loop (add1 lvl) (maybe-merge (make-stx m) r)))] - [(and re:end (*match1 re:end)) - => (lambda (m) - (if (and (zero? lvl) (not start-inside?)) - (done-lines r) - (loop (sub1 lvl) (maybe-merge (make-stx m) r))))] - [(*match1 re:end-of-line) - => (lambda (m) - (loop lvl (cons ; no merge needed - (syntax-property (make-stx eol-token) - 'scribble `(newline ,m)) - r)))] - [(if re:cmd-pfx - (and (*skip re:cmd-pfx) (*peek re:command)) - (*peek re:command)) - ;; read the next value, include comment objs, keep source location - ;; manually (see above) - => (lambda (m) - (let ([x (cond - [(cadr m) - ;; the command is a string escape, use - ;; `read-stx*' to not get a placeholder, so we - ;; can merge the string to others, and adjust - ;; source location to avoid bogus indentation - (make-stx (syntax-e (read-stx*)))] - [(caddr m) - ;; it's an expression escape, get multiple - ;; expressions and put them all here - (read-bytes (caaddr m) inp) - (get-escape-expr #f line col pos)] - [else (read-stx)])]) ; otherwise: a plain sub-read - (loop - lvl - (cond - [(eof-object? x) (read-error 'eof "missing command")] - [(syntax? x) (maybe-merge x r)] - ;; escaped expressions (not empty: @||) - [(pair? x) (append! (reverse x) r)] - ;; a comment in the middle of a line disappears so - ;; strings next to it are merged - [(and (special-comment? x) - (not (and (pair? r) (eol-syntax? (car r))))) - r] - ;; otherwise it's a either null (@||) a comment (at the - ;; beginning of a line) or a placeholder: wrap to get - ;; source info for proper indentation; @|| is turned to - ;; a comment, which can be used to separate strings, or - ;; to make spaces meaningful - [else (let ([x (if (null? x) - (make-special-comment #f) - x)]) - (cons (make-placeholder x ; no merge - (list source-name line col pos - (span-from pos))) - r))]))))] - ;; must be last, since it will always succeed with 1 char - [(*peek re:item) ; don't read: regexp grabs the following text - => (lambda (m) - (loop lvl (maybe-merge (make-stx (read-bytes (cdadr m) inp)) - r)))] - [(*peek #rx#"^$") - (if end-token - (read-error 'eof "missing closing `~a'~a" end-token - (if (and line-num col-num) - (format " for command at ~a:~a" line-num col-num) - "")) - (done-lines r))] - [else (read-error "internal error [get-lines*]")])))) + ;; re:begin, re:end, end-token can be false if start-inside? is #t; + ;; re:cmd-pfx is a regexp when we do sub-@-reads only after a prefix + (let loop ([lvl 0] + [r (let-values ([(l c p) (port-next-location inp)]) + ;; marker for the beginning of the text + (if c (list (- c)) '()))]) + ;; this loop collects lines etc for the body, and also puts in column + ;; markers (integers) after newlines -- the result is handed off to + ;; `done-items' to finish the job + (define make-stx + (let-values ([(line col pos) (port-next-location inp)]) + (lambda (sexpr) + (datum->syntax-object #f + (if (bytes? sexpr) (bytes->string/utf-8 sexpr) sexpr) + (list source-name line col pos (span-from pos)))))) + (cond + [(and re:begin (*match1 re:begin)) + => (lambda (m) (loop (add1 lvl) (maybe-merge (make-stx m) r)))] + [(and re:end (*match1 re:end)) + => (lambda (m) + (if (and (zero? lvl) (not start-inside?)) + ;; drop a marker if it's after a last eol item + (done-items (maybe-drop-marker r)) + (loop (sub1 lvl) (maybe-merge (make-stx m) r))))] + [(*match1 re:end-of-line) + => (lambda (m) + (let ([n (car (regexp-match-positions #rx#"\n" m))]) + (loop lvl (list* ; no merge needed + (bytes-width m (cdr n)) + (syntax-property + (make-stx eol-token) + 'scribble `(newline ,(bytes->string/utf-8 m))) + (maybe-drop-marker r)))))] + [(if re:cmd-pfx + (and (*skip re:cmd-pfx) (*peek re:command)) + (*peek re:command)) + ;; read the next value + => (lambda (m) + (let ([x (cond + [(cadr m) + ;; the command is a string escape, use `read-stx*' + ;; to not get a placeholder, so we can merge the + ;; string to others, and adjust source location to + ;; avoid bogus indentation + (read-stx*)] + [(caddr m) + ;; it's an expression escape, get multiple + ;; expressions and put them all here + (read-bytes (caaddr m) inp) + (get-escape-expr #f)] + [else (read-stx)])]) ; otherwise: a plain sub-read + (loop lvl (cond [(eof-object? x) + (read-error 'eof "missing command")] + ;; throw away comments + [(special-comment? x) r] + ;; escaped expressions: no merge + [(pair? x) (append! (reverse x) r)] + [(null? x) (cons (make-special-comment #f) r)] + [else (maybe-merge x r)]))))] + ;; must be last, since it will always succeed with 1 char + [(*peek re:item) ; don't read: regexp grabs the following text + => (lambda (m) + (loop lvl + (maybe-merge (make-stx (read-bytes (cdadr m) inp)) r)))] + [(*peek #rx#"^$") + (if end-token + (read-error 'eof "missing closing `~a'~a" end-token + (if (and line-num col-num) + (format " for command at ~a:~a" line-num col-num) + "")) + (done-items r))] + [else (read-error "internal error [get-lines*]")]))) (define (get-lines) (cond [(*skip re:lines-begin) (get-lines* re:lines-begin re:lines-end #f @@ -438,22 +376,25 @@ [else #f])) (define (get-attrs) - (read-delimited-list re:attrs-begin re:attrs-end ch:attrs-end #f)) + (read-delimited-list re:attrs-begin re:attrs-end ch:attrs-end)) - (define (get-escape-expr single? line col pos) + (define (get-escape-expr single?) ;; single? means expect just one expression (or none, which is returned ;; as a special-comment) - (let ([xs (parameterize ([current-readtable command-readtable]) - ;; tweak source information to avoid bad indentation - (read-delimited-list - re:expr-escape re:expr-escape ch:expr-escape - (list source-name line col pos)))]) - (cond [(not xs) xs] - [(not single?) xs] - [(null? xs) (make-special-comment #f)] - [(null? (cdr xs)) (car xs)] - [else (read-error* line col pos (span-from pos) - "too many escape expressions")]))) + (let ([get (lambda () + (parameterize ([current-readtable command-readtable]) + ;; tweak source information to avoid bad indentation + (read-delimited-list re:expr-escape re:expr-escape + ch:expr-escape)))]) + (if single? + (let*-values ([(line col pos) (port-next-location inp)] + [(xs) (get)]) + (cond [(not xs) xs] + [(null? xs) (make-special-comment #f)] + [(null? (cdr xs)) (car xs)] + [else (read-error* line col pos (span-from pos) + "too many escape expressions")])) + (get)))) ;; called only when we must see a command in the input (define (get-command) @@ -504,8 +445,7 @@ ;; simple expression escape, same for get-attrs [(get-lines) => (lambda (lines) (values #f #f lines))] [(get-attrs) => (lambda (attrs) (values #f attrs (get-lines)))] - [(get-escape-expr #t line-num col-num position) - => (lambda (expr) (values expr #f #f))] + [(get-escape-expr #t) => (lambda (expr) (values expr #f #f))] [else (values (get-command) (get-attrs) (get-lines))])] [(stx) (and (or attrs lines) (append (or attrs '()) (or lines '())))] @@ -559,18 +499,15 @@ (if (eq? src default-src) (object-name port) src)) (define/kw (*read #:optional [inp (current-input-port)]) - (port-count-lines! inp) (parameterize ([current-readtable at-readtable]) (read inp))) (define/kw (*read-syntax #:optional [src default-src] [inp (current-input-port)]) - (port-count-lines! inp) (parameterize ([current-readtable at-readtable]) (read-syntax (src-name src inp) inp))) (define/kw (read-inside #:optional [inp (current-input-port)]) - (port-count-lines! inp) (let-values ([(line col pos) (port-next-location inp)]) (parameterize ([current-readtable at-readtable]) (syntax-object->datum @@ -578,7 +515,6 @@ (define/kw (read-inside-syntax #:optional [src default-src] [inp (current-input-port)]) - (port-count-lines! inp) (let-values ([(line col pos) (port-next-location inp)]) (parameterize ([current-readtable at-readtable]) ((dispatcher #t) #f inp (src-name src inp) line col pos)))) diff --git a/collects/scribble/test-reader.ss b/collects/scribble/test-reader.ss index 0f5834cf77..090b824229 100755 --- a/collects/scribble/test-reader.ss +++ b/collects/scribble/test-reader.ss @@ -21,9 +21,21 @@ exec mzscheme -r "$0" "$@" [@foo[]{} (foo)] + [foo@ + ,(string->symbol "foo@")] + [fo@o ,(string->symbol "fo@o")] + [\@foo + ,(string->symbol "@foo")] + + [|@foo| + ,(string->symbol "@foo")] + + [(define |@foo| '\@bar@baz) + ,(read-from-string "(define @foo '@bar@baz)")] + [@foo{bar} (foo "bar")] @@ -31,6 +43,31 @@ exec mzscheme -r "$0" "$@" blah} (foo "bar baz" "\n" "blah")] + [@foo{bar @baz[3] + blah} + (foo "bar " (baz 3) "\n" "blah")] + + [@foo{bar @baz{3} + blah} + (foo "bar " (baz "3") "\n" "blah")] + + [@foo{bar @baz[2 3]{4 5} + blah} + (foo "bar " (baz 2 3 "4 5") "\n" "blah")] + + [@foo{bar @baz[2 3] {4 5}} + (foo "bar " (baz 2 3) " {4 5}")] + + [,(let* ([formatter (lambda (fmt) + (lambda args + (format fmt (apply string-append args))))] + [bf (formatter "*~a*")] + [it (formatter "/~a/")] + [ul (formatter "_~a_")] + [text string-append]) + @text{@it{Note}: @bf{This is @ul{not} a pipe}.}) + "/Note/: *This is _not_ a pipe*."] + ['@foo{bar} '(foo "bar")] @@ -56,6 +93,9 @@ exec mzscheme -r "$0" "$@" [@(lambda (x) x){blah} ((lambda (x) x) "blah")] + [@`(unquote foo){blah} + `(,foo "blah")] + [@{foo bar baz} ("foo bar" "\n" "baz")] @@ -68,10 +108,6 @@ exec mzscheme -r "$0" "$@" 3 4) (1 2 3 4)] - [(1 2@; comment, 2 touches 3 but there is a comment syntax between them - 3 4) - (1 2 3 4)] - [@foo{bar @; comment, note the extra space baz} (foo "bar baz")] @@ -80,33 +116,64 @@ exec mzscheme -r "$0" "$@" baz} (foo "barbaz")] - #; ;!!! [@foo{bar @; comment, with space and newline baz} (foo "bar " "\n" "baz")] + [@foo{x @y z} + (foo "x " y " z")] + + [@foo{x @(* y 2) z} + (foo "x " (* y 2) " z")] + + [@{@foo bar} + (foo " bar")] + [@@foo{bar}{baz} ((foo "bar") "baz")] - [(define |@foo| '\@bar) - ,(read-from-string "(define @foo '@bar)")] + [@foo[1 (* 2 3)]{bar} + (foo 1 (* 2 3) "bar")] - [@foo{ - bar - } - @foo{bar}] + [@foo[@bar{...}]{blah} + (foo (bar "...") "blah")] + + [@foo[bar] + (foo bar)] + + [@foo{bar @f[x] baz} + (foo "bar " (f x) " baz")] + + [@foo[]{bar} + (foo "bar")] + + [@foo[] + (foo)] + + [@foo + foo] + + [@foo{} + (foo)] + + [@foo[#:style 'big]{bar} + (foo #:style 'big "bar")] + + [@foo{f{o}o} + (foo "f{o}o")] + + [@foo{{{}}{}} + (foo "{{}}{}")] + + [@foo{bar} + (foo "bar")] [@foo{ bar } (foo " bar ")] - [@foo{ bar - } - (foo " bar")] - - [@foo{ - bar } - (foo "bar ")] + [@foo[1]{ bar } + (foo 1 " bar ")] [@foo{a @bar{b} c} (foo "a " (bar "b") " c")] @@ -117,6 +184,238 @@ exec mzscheme -r "$0" "$@" [@foo{a @(bar 2) c} (foo "a " (bar 2) " c")] + [@foo{This @"}" is a closing brace} + (foo "This } is a closing brace")] + + [@foo{The command prefix is @"@".} + (foo "The command prefix is @.")] + + [@foo{@"@foo{bar}" reads as (foo "bar")} + (foo "@foo{bar} reads as (foo \"bar\")")] + + [@foo|{...}| + (foo "...")] + + [@foo|{close with "}", open with "{"}| + (foo "close with \"}\", open with \"{\"")] + + [@foo|{Nesting |{is}| ok}| + (foo "Nesting |{is}| ok")] + + [@foo|{Maze + |@bar{is} + Life!}| + (foo "Maze" "\n" (bar "is") "\n" "Life!")] + + [@foo|{Works for |@bar|{subforms}| too}| + (foo "Works for " (bar "subforms") " too")] + + [@foo|<<<{Some @x{more} |@{text}|.}>>>| + (foo "Some @x{more} |@{text}|.")] + + [@foo|!!{Blah |!!@bold{blah}...}!!| + (foo "Blah " (bold "blah") "...")] + + [@foo{foo@bar.} + (foo "foo" bar.)] + + [@foo{foo@|bar|.} + (foo "foo" bar ".")] + + [@foo{foo@3.} + (foo "foo" 3.0)] + + [@foo{foo@|3|.} + (foo "foo" 3 ".")] + + [@foo{foo@|(f 1)|{bar}} + (foo "foo" (f 1) "{bar}")] + + [@foo{foo@|bar|[1]{baz}} + (foo "foo" bar "[1]{baz}")] + + [@foo{x@"y"z} + (foo "xyz")] + + [@foo{x@|"y"|z} + (foo "x" "y" "z")] + + [@foo{x@|1 (+ 2 3) 4|y} + (foo "x" 1 (+ 2 3) 4 "y")] + + [@foo{x@|1 (+ 2 3) 4|y} + (foo "x" 1 (+ 2 3) 4 "y")] + + [@foo{x@|* + *|y} + (foo "x" * * "y")] + + [@foo{Alice@||Bob@| + |Carol} + (foo "Alice" "Bob" "Carol")] + + [@|{blah}| + ("blah")] + + [@foo{First line@;{there is still a + newline at this point;} + Second line} + (foo "First line" "\n" "Second line")] + + [@foo{This is @; + a pretty long @; + single string-@; + argument.} + (foo "This is a pretty long single string-argument.")] + + [@foo{bar} + (foo "bar")] + + [@foo{ bar } + (foo " bar ")] + + + [@foo{ bar + baz } + (foo " bar" "\n" "baz ")] + + [@foo{bar + } + (foo "bar")] + + [@foo{ + bar + } + (foo "bar")] + + [@foo{ + + bar + + } + (foo "\n" "bar" "\n")] + + [@foo{ + bar + + baz + } + (foo "bar" "\n" "\n" "baz")] + + [@foo{ + } + (foo "\n")] + + [@foo{ + + } + (foo "\n" "\n")] + + [@foo{ + + + } + (foo "\n" "\n" "\n")] + + [,(let ([nl (car @'{ + })] + [o (open-output-string)]) + (for-each (lambda (x) (display (if (eq? x nl) "\n... " x) o)) + @`{foo + @,@(list "bar" "\n" "baz") + blah}) + (newline o) + (get-output-string o)) + "foo\n... bar\nbaz\n... blah\n"] + + [@foo{ + bar + baz + blah + } + (foo "bar" "\n" "baz" "\n" "blah")] + + [@foo{ + begin + x++; + end} + (foo "begin" "\n" " " "x++;" "\n" "end")] + + [@foo{ + a + b + c} + (foo " " "a" "\n" " " "b" "\n" "c")] + + [@foo{bar + baz + bbb} + (foo "bar" "\n" " " "baz" "\n" "bbb")] + + [@foo{ bar + baz + bbb} + (foo " bar" "\n" " " "baz" "\n" " " "bbb")] + + [@foo{bar + baz + bbb} + (foo "bar" "\n" "baz" "\n" "bbb")] + + [@foo{ bar + baz + bbb} + (foo " bar" "\n" "baz" "\n" "bbb")] + + [@foo{ bar + baz + bbb} + (foo " bar" "\n" "baz" "\n" " " "bbb")] + + [@text{Some text@footnote{And a + footnote comment.}. More text.} + (text "Some text" + (footnote "And a" "\n" "footnote comment.") + ". More text.")] + + [@code{ + begin + i = 1, r = 1 + @bold{while i < n do + r *= i++ + done} + end + } + (code "begin" "\n" + " " "i = 1, r = 1" "\n" + " " (bold "while i < n do" "\n" + " " "r *= i++" "\n" + "done") "\n" + "end")] + + [@foo{ + @|| bar @|| + @|| baz} + (foo " bar " "\n" " baz")] + + [@foo{bar + @|baz| bbb + @|x1 x2| x3 x4 + @|| waaaah + } + (foo "bar" "\n" baz " bbb" "\n" x1 x2 " x3 x4" "\n" " waaaah")] + + [@foo{x1 + x2@; + y2 + x3@;{ + ;}y3 + x4@| + |y4 + x5} + (foo "x1" "\n" "x2y2" "\n" "x3y3" "\n" "x4" "y4" "\n" "x5")] + + )) (define failures 0)