DEPENDENCY:
This package depends on Meta by Jochen Schmidt, version 0.1.1 or later.
http://www.cliki.net/Meta
+Now also: closer-mop meta fare-utils fare-matcher
USAGE:
You can enable behaviour for the macro-character #\[ with
(scribble:enable-sub-scribble-syntax)
(scribble:disable-sub-scribble-syntax)
+New! You can instead enable Racket-like Scribble syntax for the macro-character #\@ with
+ (scribble:enable-scribble-at-syntax)
+and disable it with
+ (scribble:disable-scribble-at-syntax)
+For details, see:
+ http://docs.racket-lang.org/scribble/reader.html
+
+
BASIC SYNTAX:
The syntax of text within brackets is Scribe-like:
http://barzilay.org/misc/scribble-reader.pdf
http://docs.racket-lang.org/scribble/reader.html
-See also Daniel Herring's partial implementation:
+For historical information, see also Daniel Herring's partial implementation:
http://lists.libcl.com/pipermail/libcl-devel-libcl.com/2010-January/000094.html
(:fullname
"scribble"
:depends-on
- ("package" "scribble")
+ ("package" "scribble-scribe" "scribble")
:build-depends-on
- ("meta")
+ ("meta" "fare-utils" "fare-matcher")
:supersedes-asdf
("scribble")))
#+xcvb (module (:depends-on nil))
(cl:defpackage #:scribble
- (:use #:common-lisp #:ll-omega #:meta :fare-utils :fare-quasiquote)
+ (:use #:common-lisp #|#:ll-omega|# #:meta :fare-utils :fare-quasiquote)
#+(or clisp sbcl ccl)
(:import-from #+clisp :gray #+sbcl :sb-gray #+ccl :ccl
:stream-line-column)
#+xcvb (module (:depends-on ("package")))
-
-#|" -*- Mode: Lisp ; Base: 10 ; Syntax: ANSI-Common-Lisp -*-
-Scribble: SCRibe-like reader extension for Common Lisp
-Copyright (c) 2002-2006 by Fare Rideau < fare at tunes dot org >
- http://www.cliki.net/Fare%20Rideau
-
-HOME PAGE:
- http://www.cliki.net/Scribble
-
-LICENSE:
- http://www.geocities.com/SoHo/Cafe/5947/bugroff.html
-You may at your leisure use the LLGPL instead:
- http://www.cliki.net/LLGPL
-
-DEPENDENCY:
-This package depends on Meta by Jochen Schmidt, version 0.1.1 or later.
- http://www.cliki.net/Meta
-
-USAGE:
-You can enable behaviour for the macro-character #\[ with
- (scribble:enable-scribble-syntax)
-and disable it with
- (scribble:disable-scribble-syntax)
-Alternatively, you can enable behaviour for the character #\[
-under the dispatching macro-character #\# using
- (scribble:enable-sub-scribble-syntax)
- (scribble:disable-sub-scribble-syntax)
-
-BASIC SYNTAX:
-The syntax of text within brackets is Scribe-like:
-
-* Text between brackets will expand to a string containing said text,
- unless there are escape forms in the text,
- which are identified by a comma
- followed by either an opening parenthesis or an opening bracket.
-
-* If there are escape forms in the text,
- then the text will be split into components,
- which will be non-empty strings and escape forms.
- The result of parsing the text will be a (LIST ...) of these components.
-
-* A comma followed by a parenthesis denotes an escape form,
- whereas a SEXP beginning with said parenthesis
- is read and used as a component of the surrounding text.
- For instance, [foo ,(bar baz) quux] will (on first approximation) be read as
- (LIST "foo " (bar baz) " quux")
- Mind the spaces being preserved around the internal form.
-
-EXTENSION TO SCRIBE SYNTAX:
-Scribble extends the Scribe syntax in a way that I find very convenient.
-
-* As an extension to Scribe syntax,
- if the first character of text within bracket is an unescaped colon #\:
- then an expression after it is read that is used
- as a "head" for the body of the text resulting from parsing as above.
- [:emph this] is read as (EMPH "this")
- [:(font :size -1) that] is read as (FONT :SIZE -1 "that")
-
-* As another extension to Scribe syntax,
- a comma followed by a bracket will also denote an escape form,
- whereas the bracketed-text using Scribble syntax
- is read and used as a component of the surrounding text.
- This extension is only useful in conjunction with the previous extension.
-
-SYNTACTIC CATCHES:
-There are a few possible sources of problems with the Scribe syntax,
-and solutions provided by Scribe and Scribble to avoid these problems.
-
-* A closing bracket closes the current text.
- Standard Scribe syntax doesn't provide a mean
- to include a closing bracket in bracketed text.
-
-* Conversely, so as to prevent difficult to track syntax errors
- resulting from typos, Standard Scribe syntax forbids
- to include an opening bracket in the text.
-
-* As an extension to Scribe syntax,
- you can include any character in the text,
- without triggering any special or error-raising behaviour,
- by preceding it with a backslash character #\\ in the text
- (which preceding backslash character won't be included in the result string).
- This is useful to include a character among #\\ #\: #\, #\[ #\] #\(.
-
-* While #\\ will always be able to escape all non-alphanumeric characters,
- including the special characters listed above,
- future extensions may give a special meaning to #\\ followed by a character
- in the regexp range [_a-zA-Z0-9].
- If you feel the need for such an extension, I will accept patches;
- I suppose that the C or Perl syntax is what is needed here.
-
-* In the bracket-colon syntax extension, after reading the "head",
- all spacing characters (space, tab, newline, linefeed, return, page)
- are skipped until the next non-space character.
- To insert a space character immediately after the head,
- just escape it using #\\ as above.
-
-* As a restriction from Scribe syntax, Scribble syntax
- doesn't recognize the use of semi-colon as denoting discardable comments.
- In Scribe, a semi-colon at the beginning of a line or of bracketed text
- or of a string component of bracketed text will denote a comment,
- whereas Scribe will ignore any text to the next end of line.
- Scribble will include any such text in the result string.
- You can emulate the original Scribe behaviour in this regard
- by using the preprocessing customization feature described below.
-
-CUSTOMIZATION:
-Scribble can be customized in many ways,
-to accomodate the specificities of your markup language backend.
-
-* As an extension to Scribe semantics, all strings resulting from reading
- bracket-delimited text (as opposed to those resulting from "normal"
- double-quote delimited strings that may appear inside escape forms)
- may be preprocessed. There may be compile-time or run-time preprocessing.
- The variable *SCRIBBLE-PREPROCESS* decides what kind of preprocessing is
- done. If it is NIL, then no preprocessing is done (i.e. strings from the
- [...] notation will be read as such). If it is T, then run-time
- preprocessing is done, via the function PP which itself issues a dynamic
- call to the function *SCRIBBLE-PREPROCESSOR* if not NIL (or else behaves
- as identity). If it is a function or non-boolean symbol, then said value
- is funcall'ed at read-time to preprocess the string form by e.g. wrapping
- it into some macro evaluation. Note that when using run-time preprocessing,
- you may either lexically shadow the function PP or dynamically rebind the
- variable *SCRIBBLE-PREPROCESSOR* to locally select a different preprocessor.
- A macro SCRIBBLE:WITH-PREPROCESSOR is defined to do the dynamic rebinding,
- as in (scribble:with-preprocessor #'string-upcase [foo]) which (assuming
- run-time preprocessing is enabled) will evaluate to "FOO".
-
-* Though the default behaviour of Scribble is to return
- a (possibly preprocessed) string if there are no subcomponents,
- and a form (cl:list ...) if there are multiple components,
- you can customize this behaviour by binding the customization variable
- scribble:*scribble-list* to a function that will do the job,
- taking as many arguments as there were components (zero for empty text).
- If you only want to keep the same general behaviour,
- but change the head of the resulting list from cl:list to something else,
- then don't modify scribble:*scribble-list*
- (or bind it back to scribble:default-scribble-list)
- and instead bind scribble:*scribble-default-head* to a symbol
- that at evaluation time will be bound to a function
- that will properly combine the multiple components.
- Note that this scribble:*scribble-list* is processed at read-time,
- whereas the function named by scribble:*scribble-default-head* (if applicable)
- will be processed at evaluation-time.
-
-* You can select a package from which Scribble will read head forms
- of bracket-colon syntax [:head ...] or [:(head args) ...]
- by changing the symbol-value of scribble:*scribble-package*.
- Typical use is
- (setq scribbe:*scribble-package* :keyword)
- which will do wonders with AllegroServe's net.html.generator.
- Note that this feature happens at read-time, and doesn't affect
- the current package used to read escape forms.
- If the *scribble-package* feature prevents reading
- the arguments to structured head form arguments in the right package,
- [:(head form arguments) ...]
- then you can fall back to normal scribe syntax
- ,(head form argument [...])
- or qualify the symbols in your head form by their package
- [:(cl:head my-package:form foo:arguments) ...]
-
-* You can modify the way that scribble combines
- the head and body of bracket-colon syntax
- by changing the value of variable scribble:*scribble-cons*
- from the default value scribble:default-scribble-cons.
- The function takes as parameters the head specified by bracket-colon syntax
- and the list of components of the bracketed text, and has to return
- Typically, you might want to special case the behaviour
- according to the type of the head: cons or symbol.
- Note that this happens at read-time.
-
-* Example functions to customize scribble for use with various backends
- are given at the end of this file. Check functions
- scribble:configure-scribble
- scribble:configure-scribble-for-araneida
- scribble:configure-scribble-for-htmlgen
- scribble:configure-scribble-for-lml2
- scribble:configure-scribble-for-tml
- scribble:configure-scribble-for-who
- scribble:configure-scribble-for-yaclml
- Please send me updates that include support for your favorite backend.
-
-EXAMPLE USE:
-(load "scribble")
-(use-package :scribble)
-(enable-scribble-syntax)
-'[foo ,[:emph bar] ,[:(baz :size 1) quux ,(tata toto [\:titi])] tutu]
-==>
-(LIST (PP "foo ") (EMPH (PP "bar")) (PP " ")
- (BAZ :SIZE 1 (LIST (PP "quux ") (TATA TOTO (PP ":titi")))) (PP " tutu"))
-
-(let ((p "/home/fare/fare/www/liberty/white_black_magic.scr")
- (eof '#:eof))
- (with-open-file (s p :direction :input :if-does-not-exist :error)
- (loop for i = (read s nil eof nil)
- until (eq i eof)
- collect i)))
-
-(configure-scribble-for-araneida-html)
-(html-stream *stdout* '[:html ...])
-
-TODO:
-* Make it work with aserve, who, and other backends.
-
-Share and enjoy!
-" |#
+;;; -*- Mode: Lisp ; Base: 10 ; Syntax: ANSI-Common-Lisp -*-
+;;; Scribble: SCRibe-like reader extension for Common Lisp
+;;; Copyright (c) 2002-2010 by Fare Rideau < fare at tunes dot org >
+;;; See README.
(in-package :scribble)
; -----------------------------------------------------------------------------
;;; Optimization
-(declaim (optimize (speed 3) (safety 1) (debug 0)))
+;(declaim (optimize (speed 3) (safety 1) (debug 0)))
; -----------------------------------------------------------------------------
;;; Customizing string preprocessing
(defvar *scribble-readtable* nil)
(defun enable-scribble-syntax (&optional readtable)
(setf *scribble-readtable* (push-readtable readtable))
- (set-macro-character #\]
- #'(lambda (stream char)
- (declare (ignore char))
- (issue-parse-error "] outside of a [ construct on ~A @ ~A." stream (file-position stream))))
- (set-macro-character #\[
- #'(lambda (stream char)
- (declare (ignore char))
- (parse-bracket stream)))
+ (do-enable-scribble-syntax *scribble-readtable*)
*scribble-readtable*)
+(defun do-enable-scribble-syntax (&optional readtable)
+ (set-macro-character
+ #\] #'(lambda (stream char)
+ (declare (ignore char))
+ (issue-parse-error "] outside of a [ construct on ~A @ ~A." stream (file-position stream)))
+ nil readtable)
+ (set-macro-character
+ #\[ #'(lambda (stream char)
+ (declare (ignore char))
+ (parse-bracket stream))
+ nil readtable)
+ t)
(defun disable-scribble-syntax ()
(pop-readtable))
(defun reenable-scribble-syntax ()
(in-package :keyword)
(asdf:defsystem scribble
- depends-on (closer-mop meta fare-utils fare-matcher)
+ depends-on (#|closer-mop|# meta fare-utils fare-matcher)
serial t
components (;;(file "ll")
(file "package")
+ (file "scribble-scribe")
(file "scribble")))
(defun ascii-char-p (x)
(and (typep x 'base-char)
- (<= 127 (char-code x))))
+ (<= (char-code x) 127)))
(defun expected-char-p (c expectation)
(check-type c (or null character))
(defvar *lf* (string #\newline))
(memo:define-memo-function n-spaces (n)
- (make-string n :initial-element #\space :element-type 'base-character))
+ (make-string n :initial-element #\space :element-type 'base-char))
(defun expect-char (i &optional expectation)
(let ((c (peek-char nil i nil nil t)))
(and (expected-char-p c expectation) (read-char i))))
+(defun expect-string (i s)
+ (loop :for c :across s :for l :from 0 :do
+ (unless (expect-char i c)
+ (return (values nil (subseq s l))))
+ :finally (return (values t l))))
+
+(defun skip-whitespace-return-column (i &optional (col 0))
+ (loop :for c = (expect-char i #.(format nil " ~c" #\tab))
+ :while c :do
+ (ecase c
+ ((#\space) (incf col))
+ ((#\tab) (setf col (to-next-tab col))))
+ :finally (return col)))
+
+(defun trim-ending-spaces (s)
+ (let ((p (position-if #'(lambda (c) (not (member c '(#\space #\tab)))) s :from-end t)))
+ (if p (subseq s 0 (1+ p)) nil)))
+
+(defun read-to-char (c &optional (i *standard-input*))
+ (with-output-to-string (o)
+ (loop :for char = (expect-char i)
+ :until (eql c char)
+ :do (write-char char o))))
+
(defun parse-at-syntax (i)
;; Parse an @ expression.
- ;; returns multiple values: the parsed expression, and
- ;; some flags to be used by recursive @ calls.
(with-nesting ()
(let* (;;(i (make-instance 'ωs :stream stream)) ; buffered input
(o (make-string-output-stream)) ; buffered output of "current stuff"
(cmdonly nil)
+ (col 0)
+ (line ())
+ (lines ())
(mrof '()))) ; current form (reversed)
(labels
- ((?@ () ; expect a @ expression
- (unless (expect-char i #\@)
- (error "Expected #\@"))
- (?@1))
- (?@1 () ; what to do after a @
- (?punctuation))
+ ((?@1 () ; what to do after a @
+ (cond
+ ((expect-char i #\;)
+ (?at-comment))
+ (t
+ (?punctuation))))
+ (?at-comment ()
+ (cond
+ ((expect-char i #\{) (?{text}))
+ (t (read-line i)))
+ (read-preserving-whitespace i t nil nil))
(?punctuation ()
(let ((char (expect-char i "'`,")))
(ecase char
(?comma ()
(call-with-unquote-reader #'?punctuation))
(?cmd ()
- (let ((char (expect-char i "[{|")))
- (if char
- (?datatext char)
- (?cmd1))))
+ (let ((char (expect-char i "|[{")))
+ (case char
+ ((#\|)
+ (maybe-alttext #'at-pipe))
+ ((#\[ #\{)
+ (?datatext char))
+ (t
+ (?cmd1)))))
+ (maybe-alttext (cont)
+ (unread-char #\| i)
+ (let ((k (?newkey)))
+ (cond
+ (k
+ (setf cmdonly nil)
+ (?{alttext} k))
+ (t
+ (funcall cont)))))
+ (at-pipe ()
+ (read-char i)
+ (let ((r (read-to-char #\| i)))
+ (multiple-value-bind (s #|n|#) (read-from-string r)
+ #|(unless (symbolp s)
+ (error "Expected a symbol, got ~S" r))
+ (unless (= n (length r))
+ (error "Unexpected characters in ~S" r))|#
+ (setf cmdonly t)
+ (form! s)
+ (?end))))
(?cmd1 ()
(setf cmdonly t)
- (form! (read i))
+ (form! (read-preserving-whitespace i t nil nil))
+ (?cmd2))
+ (?cmd2 ()
(let ((char (expect-char i "[{|")))
(if char
(?datatext char)
(setf cmdonly nil)
(?{text}))
((expect-char i #\|)
- (unread-char #\| i)
- (let ((k (?newkey)))
- (cond
- (k
- (setf cmdonly nil)
- (?{alttext} k))
- (t
- (?end)))))
+ (maybe-alttext #'?end))
(t (?end))))
(?newkey ()
(loop
:collect c :into l
:finally (cond
((eql c #\{) (return (coerce l 'base-string)))
- (t (file-position i p) nil))))
+ (t (file-position i p) (return nil)))))
(char! (c)
(write-char c o))
(flush! ()
- (let ((s (get-output-stream-string o)))
+ (let* ((s (get-output-stream-string o)))
+ (when (plusp (length s))
+ (push s line))))
+ (eol! (eol)
+ (let* ((s (get-output-stream-string o))
+ (s (if eol (trim-ending-spaces s) s)))
(when (plusp (length s))
- (form! s))))
- (?{text} ()
- (loop :with brace-level = 1
- ;; :with initial-col = (stream-line-column-harder i)
- :for c = (expect-char i) :do
+ (push s line))
+ (push (cons col (reverse line)) lines))
+ (when eol
+ (setf col (skip-whitespace-return-column i 0)
+ line ()))
+ t)
+ (?{text} (&aux (brace-level 1))
+ (setf col (stream-line-column-harder i)
+ line ())
+ (loop :for c = (expect-char i) :do
(case c
+ ((#\return)
+ (expect-char i #\newline)
+ (eol! t))
+ ((#\newline)
+ (eol! t))
((#\{)
(incf brace-level)
(char! c))
((#\@)
- (NIY))
+ (?inside-at))
((#\})
(decf brace-level)
- (when (zerop brace-level)
- (flush!)
- (return (?end))))
+ (cond
+ ((zerop brace-level)
+ (eol! nil)
+ (flush-text!)
+ (return (?end)))
+ (t
+ (char! c))))
+ (otherwise
+ (char! c)))))
+ (?inside-at ()
+ (let ((c (expect-char i ";\"|")))
+ (case c
+ ((#\;)
+ (cond
+ ((expect-char i #\{)
+ (let ((m mrof) (l line) (ls lines) (c col) (co cmdonly) (oo o))
+ (setf o (make-string-output-stream))
+ (?{text})
+ (setf mrof m line l lines ls col c cmdonly co o oo)))
+ (t
+ (read-line i)
+ (skip-whitespace-return-column i))))
+ ((#\")
+ (unread-char #\" i)
+ (write-string (read-preserving-whitespace i t nil nil) o))
+ ((#\|)
+ (flush!)
+ (let ((r (read-to-char #\| i)))
+ (with-input-from-string (s r)
+ (loop :for x = (read-preserving-whitespace s nil s nil)
+ :until (eq x s) :do (push x line)))))
(otherwise
- (NIY)))))
+ (flush!)
+ (push (parse-at-syntax i) line)))))
+ (flush-text! ()
+ (let* ((mincol (loop :for (col . strings) :in lines
+ :when strings
+ :minimize col))
+ (text (loop :for (col . strings) :in (reverse lines)
+ :for first = t :then nil
+ :append
+ `(,@(when (and strings (> col mincol) (not first))
+ (list (n-spaces (- col mincol))))
+ ,@strings ,*lf*))))
+ (when (eq *lf* (first text))
+ (pop text))
+ (let ((e (every (lambda (x) (eq x *lf*)) text))
+ (r (reverse text)))
+ (unless e
+ (loop :repeat 2 :when (eq *lf* (first r)) :do (pop r)))
+ (setf mrof (append r mrof))))
+ t)
(?{alttext} (key)
- (let ((keylen (length key))
+ (let ((brace-level 1)
(rkey (mirror-string key)))
- (NIY keylen rkey)))
+ (setf col (stream-line-column-harder i)
+ line ())
+ (loop :for c = (expect-char i) :do
+ (case c
+ ((#\return)
+ (expect-char i #\newline)
+ (eol! t))
+ ((#\newline)
+ (eol! t))
+ (#\|
+ (let* ((p (file-position i))
+ (c (and (expect-string i key) (expect-char i "@{"))))
+ (case c
+ ((#\{)
+ (incf brace-level)
+ (char! #\|)
+ (map () #'char! key)
+ (char! c))
+ ((#\@)
+ (?inside-at))
+ (otherwise
+ (file-position i p)
+ (char! #\|)))))
+ ((#\})
+ (let* ((p (file-position i)))
+ (cond
+ ((and (expect-string i rkey) (expect-char i #\|))
+ (decf brace-level)
+ (cond
+ ((zerop brace-level)
+ (eol! nil)
+ (flush-text!)
+ (return (?end)))
+ (t
+ (char! #\})
+ (map () #'char! rkey)
+ (char! #\|))))
+ (t
+ (file-position i p)
+ (char! #\})))))
+ (otherwise
+ (char! c))))))
(?end ()
(if (and cmdonly (length=n-p mrof 1))
(car mrof)
(reverse mrof))))
- (?@))))
+ (?@1))))
(defun do-enable-scribble-at-syntax (&optional (readtable *readtable*))
(enable-quasiquote :readtable readtable)
(declare (ignore char))
(parse-at-syntax stream))
nil readtable)
+ ;;(do-enable-scribble-syntax readtable) ; backward compatibility with former scribble?
+ (set-macro-character
+ #\| #'(lambda (stream char)
+ (declare (ignore stream char))
+ (error "| not allowed when at syntax enabled"))
+ nil readtable)
t)
-(defvar *saved-readtable* *readtable*)
-
-(defparameter *scribble-readtable*
- (let ((r (copy-readtable *saved-readtable*)))
- (do-enable-scribble-at-syntax r)
- r))
+(defvar *scribble-at-readtable* nil)
+(defun enable-scribble-at-syntax (&optional (readtable *readtable*))
+ (setf *scribble-at-readtable* (push-readtable readtable))
+ (do-enable-scribble-at-syntax *scribble-at-readtable*)
+ *scribble-at-readtable*)
(defun parse-at-string (x)
(with-input-from-string (i x)
- (let ((*readtable* *scribble-readtable*))
+ (let ((*readtable* *scribble-at-readtable*))
(scribble::parse-at-syntax i))))
(delete-file *u*)
t)
+
+(deftest test-scribble-at ()
+ ;; Tests taken from http://docs.racket-lang.org/scribble/reader.html
+ (macrolet ((a (x y)
+ `(is (equal (p ,x)
+ ',(subst scribble::*lf* '*lf* y))))
+ (a* (&rest r)
+ `(flet ((p (x)
+ (let ((*readtable* scribble::*scribble-readtable*))
+ (read-from-string (strcat " " x)))))
+ ,@(loop :for (x y) :on r :by #'cddr :collect `(a ,x ,y)))))
+ (a*
+ "@foo{blah blah blah}" (foo "blah blah blah")
+ "@foo{blah \"blah\" (`blah'?)}" (foo "blah \"blah\" (`blah'?)")
+ "@foo[1 2]{3 4}" (foo 1 2 "3 4")
+ "@foo[1 2 3 4]" (foo 1 2 3 4)
+ "@foo[:width 2]{blah blah}" (foo :width 2 "blah blah")
+ "@foo{blah blah
+ yada yada}" (foo "blah blah" *lf* "yada yada")
+ "@foo{
+ blah blah
+ yada yada
+ }" (foo "blah blah" *lf* "yada yada")
+ "@foo{bar @baz{3}
+ blah}" (foo "bar " (baz "3") *lf* "blah")
+ "@foo{@b{@u[3] @u{4}}
+ blah}" (foo (b (u 3) " " (u "4")) *lf* "blah")
+ "@C{while (*(p++))
+ *p = '\\n';}" (C "while (*(p++))" *lf* " " "*p = '\\n';")
+ "@{blah blah}" ("blah blah")
+ "@{blah @[3]}" ("blah " (3))
+ "'@{foo
+ bar
+ baz}" '("foo" *lf* "bar" *lf* "baz")
+ "@foo" foo
+ "@{blah @foo blah}" ("blah " foo " blah")
+ "@{blah @:foo blah}" ("blah " :foo " blah")
+ "@{blah @|foo|: blah}" ("blah " foo ": blah")
+ "@foo{(+ 1 2) -> @(+ 1 2)!}" (foo "(+ 1 2) -> " (+ 1 2) "!")
+ "@foo{A @\"string\" escape}" (foo "A string escape")
+ "@foo{eli@\"@\"barzilay.org}" (foo "eli@barzilay.org")
+ "@foo{A @\"{\" begins a block}" (foo "A { begins a block")
+ "@C{while (*(p++)) {
+ *p = '\\n';
+ }}" (C "while (*(p++)) {" *lf* " " "*p = '\\n';" *lf* "}")
+ "@foo|{bar}@{baz}|" (foo "bar}@{baz")
+ "@foo|{bar |@x{X} baz}|" (foo "bar " (x "X") " baz")
+ "@foo|{bar |@x|{@}| baz}|" (foo "bar " (x "@") " baz")
+ "@foo|--{bar}@|{baz}--|" (foo "bar}@|{baz")
+ "@foo|<<{bar}@|{baz}>>|" (foo "bar}@|{baz")
+ "(define \\@email \"foo@bar.com\")" (define \@email "foo@bar.com")
+ ;;"(define |@atchar| #\\@)" (define \@atchar #\@)
+ "@foo{bar @baz[2 3] {4 5}}" (foo "bar " (baz 2 3) " {4 5}")
+ ;;"@`',@foo{blah}" `',@(foo "blah")
+ ;;"@#`#'#,@foo{blah}" #`#'#,@(foo "blah")
+ "@(lambda (x) x){blah}" ((lambda (x) x) "blah")
+ ;;"@`(unquote foo){blah}" `(,foo "blah")
+ "@{foo bar
+ baz}" ("foo bar" *lf* "baz")
+ "@'{foo bar
+ baz}" '("foo bar" *lf* "baz")
+ "@foo{bar @; comment
+ baz@;
+ blah}" (foo "bar bazblah")
+ "@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")
+ "@foo[1 (* 2 3)]{bar}" (foo 1 (* 2 3) "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") ; #:style in racket
+ "@foo{f{o}o}" (foo "f{o}o")
+ "@foo{{{}}{}}" (foo "{{}}{}")
+ "@foo{bar}" (foo "bar")
+ "@foo{ bar }" (foo " bar ")
+ "@foo[1]{ bar }" (foo 1 " bar ")
+ "@foo{a @bar{b} c}" (foo "a " (bar "b") " c")
+ "@foo{a @bar c}" (foo "a " bar " c")
+ "@foo{a @(bar 2) c}" (foo "a " (bar 2) " c")
+ "@foo{A @\"}\" marks the end}" (foo "A } marks the end")
+ "@foo{The prefix: @\"@\".}" (foo "The prefix: @.")
+ "@foo{@\"@x{y}\" --> (x \"y\")}" (foo "@x{y} --> (x \"y\")")
+ "@foo|{...}|" (foo "...")
+ "@foo|{\"}\" follows \"{\"}|" (foo "\"}\" follows \"{\"")
+ "@foo|{Nesting |{is}| ok}|" (foo "Nesting |{is}| ok")
+ "@foo|{Maze
+ |@bar{is}
+ Life!}|" (foo "Maze" *lf*
+ (bar "is") *lf*
+ "Life!")
+ "@t|{In |@i|{sub|@\"@\"s}| too}|" (t "In " (i "sub@s") " too")
+ "@foo|<<<{@x{foo} |@{bar}|.}>>>|" (foo "@x{foo} |@{bar}|.")
+ "@foo|!!{X |!!@b{Y}...}!!|" (foo "X " (b "Y") "...")
+ "@foo{foo@bar.}" (foo "foo" bar.)
+ "@foo{foo@|bar|.}" (foo "foo" bar ".")
+ "@foo{foo@3.0}" (foo "foo" 3.0) ;; orig had 3. 3.0
+ "@foo{foo@|3|.0}" (foo "foo" 3 ".0") ;; orign had no 0
+ "@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@|*
+ *|y}" (foo "x" *
+ * "y")
+ "@foo{Alice@||Bob@|
+ |Carol}" (foo "Alice" "Bob" "Carol")
+ "@|{blah}|" ("blah")
+ "@|{blah |@foo bleh}|" ("blah " foo " bleh")
+ "@foo{First line@;{there is still a
+ newline here;}
+ Second line}" (foo "First line" *lf* "Second line")
+ "@foo{A long @;
+ single-@;
+ string arg.}" (foo "A long single-string arg.")
+ "@foo{bar}" (foo "bar")
+ "@foo{ bar }" (foo " bar ")
+ "@foo{ bar
+ baz }" (foo " bar" *lf* "baz ")
+ "@foo{bar
+ }" (foo "bar")
+ "@foo{
+ bar
+ }" (foo "bar")
+ "@foo{
+
+ bar
+
+ }" (foo *lf* "bar" *lf*)
+ "@foo{
+ bar
+
+ baz
+ }" (foo "bar" *lf* *lf* "baz")
+ "@foo{
+ }" (foo *lf*)
+ "@foo{
+
+ }" (foo *lf* *lf*)
+ "@foo{ bar
+ baz }" (foo " bar" *lf* "baz ")
+ "@foo{
+ bar
+ baz
+ blah
+ }" (foo "bar" *lf* "baz" *lf* "blah")
+ "@foo{
+ begin
+ x++;
+ end}" (foo "begin" *lf* " " "x++;" *lf* "end")
+ "@foo{
+ a
+ b
+ c}" (foo " " "a" *lf* " " "b" *lf* "c")
+ "@foo{bar
+ baz
+ bbb}" (foo "bar" *lf* " ""baz" *lf* "bbb")
+ "@foo{ bar
+ baz
+ bbb}" (foo " bar" *lf* " " "baz" *lf* " " "bbb")
+ "@foo{bar
+ baz
+ bbb}" (foo "bar" *lf* "baz" *lf* "bbb")
+ "@foo{ bar
+ baz
+ bbb}" (foo " bar" *lf* "baz" *lf* "bbb")
+ "@foo{ bar
+ baz
+ bbb}" (foo " bar" *lf* "baz" *lf* " " "bbb")
+ "@text{Some @b{bold
+ text}, and
+ more text.}" (text "Some " (b "bold" *lf* "text")", and" *lf* "more text.")
+#| ;;; properly render this?
+;;; a formatter will need to apply the 2-space indentation to the rendering of the bold body.
+@code{
+ begin
+ i = 1, r = 1
+ @bold{while i < n do
+ r *= i++
+ done}
+ end
+}
+|#
+ "@foo{
+ @|| bar @||
+ @|| baz}" (foo " bar " *lf* " baz")
+)))