学习Scribble as Preprocessor。
scribble/text以及scribble/html是预处理器型语言,用于生成text或者HTML。它们使用和scribble相容的@语句,但是处理的是抽象的可以转化为text或者HTML的文档。
1 Text Generation
scribble/text对顶层环境的一些修改:
- 使用read-syntax-inside来读取模块的正文,此举和Scribble的文档阅读器一致。这意味着默认情况下,所有的文本是以字符串的形式读取的;以及
@
构造可以用来转义racket的函数以及表达式。 - 表达式的量值是以自定义的output函数来打印的。此函数和display很相似,但是为文本输出作了一些特殊修改。
当scribble/text被求访而不是作为#lang使用时,不会改变量值的打印方式,并且不会导入racket/base的对照,incude是以include/text提供,begin是以begin/text提供。
1.1 Writing Text Files
感受一下例子:
#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.
例子2与上一个例子的输出雷同,但是写法不一样
#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].
为了方便写定义,定义之后的换行以及换行前面的缩进都会被忽略:
例子2也可以写成:
#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].
其他情况下的换行不会被忽略,如以下所示:
#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
有其他几种方式帮你避免输出中的空行,比如让函数调用本身跨行:
Start...
@count[3]{Mississippi
}... and I'm done
另一种方法是使用@;
来忽略从它之后到换行的所有内容(包括换行):
Start again...
@count[3]{Massachusetts}@;
... and I'm done again.
当然,也可以让函数来控制空行的生成:
#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.
上面的方法很常用,所以scribble/text提供了方便函数add-newlines:
#lang scribble/text
@(define (count n str)
(add-newlines
(for/list ([i (in-range 1 (+ n 1))])
@list{@i @str,})))
也可以通过#:sep关键字设置分隔符:
#lang scribble/text
@(define (count n str)
(add-newlines #:sep ",\n"
(for/list ([i (in-range 1 (+ n 1))])
@list{@i @str})))
1.2 Defining Functions and More
@的转义的用途有点多,看起来有点晕:
#lang scribble/text
@(define @bold[text] @list{*@|text|*})
An @bold{important} note.
上述定义了一个@构造,叫做@bold用于输出bold格式,代码运行结果是:
An *important* note.
也可以让函数直接接受文本参数:
#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}]
@{...}
和@(...)
的区别是前者将内容作为执行诀,后者将内容作为表达式,后者也可以写为@[...]
。
scribble/text还提供了一个split-lines函数,用来按换行切分字符串:
#lang scribble/text
@(require racket/list)
@(define (features . text)
(add-between (split-lines text)
", "))
@features{red
fast
reliable}.
@构造是可以嵌套的:
#lang scribble/text
@(define ((choose . 1st) . 2nd)
@list{Either you're @1st, or @|2nd|.})
@(define who "me")
@@choose{with @who}{against @who}
外层的@是单入参,所以要对choose进行柯里化。
1.3 Using Printouts
打印的内容会直接被整合进输出:
#lang scribble/text
First
@display{Second}
Third
对应的输出是:
First
Second
Third
所以,可以直接选择在直接打印函数的结果,而不用将其返回:
#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.
但是将打印和返回混合在一起可能有意料以外的效果:
#lang scribble/text
@list{1 @display{two} 3}
上述打印的是"two1 3"。
也可以构造一个无限输出:
#lang scribble/text
@(define (count n)
(cons @list{@n Mississippi,@"\n"}
(lambda ()
(count (add1 n)))))
Start...
@count[1]
this line is never printed!
1.4 Indentation in Preprocessed output
可以使用修引构造来确定输出的具体内容,比如:
#lang scribble/text
@(format "~s" '@list{
a
b
c})
的输出是:
(list "a" "\n" " " "b" "\n" "c")
Scribble读取器会自动忽略无关的缩进。这样代码块的位置不会影响输出。但是代码块内部的缩进会被保留。可以用block来指明代码块:
#lang scribble/text
foo @block{1
2
3}
连对的呈现默认也是按照代码块处理的,可以利用这个特点做一个小功能:
#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
splice则不主动处理缩进,以splice方式呈现的list也步处理缩进。
disable-prefix则可以用来消除缩进,其所在行的缩进也会被消除。disable-prefix之后的会按目标栏缩进。
add-prefix则可以按自己的需求来添加所需的前缀。混合使用disable-prefix和add-prefix的时候,可能需要flush缓存。
1.5 Using External Files
可以简单地求访外部文件。此外,可以使用#lang at-expr
来开启对@-构造地支持,下面是一个例子。
独部定义文件:
; 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.})
独部使用文件:
#lang scribble/text
@(require "stuff.rkt")
Todo:
@itemize[@list{Hack some}
@list{Sleep some}
@list{Hack some
more}]
@summary
上述都是在scribble/text中嵌套racket定义和表达式,也可以反过来在racket中嵌套scribble/text,只需(require scribble/text)
:
#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
})
只需使用output切换到scribble/text输出状态。
还有一种办法,就是直接使用@include
来包含其他文件:
@include["template.html"]
在scribble/text中require其他text是无法工作地,因为返回发起求访地访问之前,被求访地文件就会被运算。
1.6 Text Generation Functions
- outputable/c,适用于ouput地契约判定诀,目前可以接收任意值
(output v [port])
,输出文本内容(block v ...)
,以切块方式输出(splice v ...)
,以拼接方式输出(disable-prefix v ...)
(restore-prefix v ...)
(add-prefix pfx v ...)
(set-prefix pfx v ...)
- flush,输出前缩进和当前前缀来
(with-writer writer v ...)
,调整当前使用地writer(add-newlines items [#:sep sep])
,和add-between类似,但是会忽略#f以及#元素(split-lines items)
,将所列条目转化为一个连对的连对,相邻的非"\n"内容会被归置到一个嵌套的连对,"\n"内容会被丢弃。(include/text maybe-char path-spec)
,将目标文件以scribble/text方式读取进来。可以选用@
以外的命令字符((begin/text form ...))
,类似于begin,但是表达式运算结果会以连对方式收集,并按begin/list构造返回
(未完待续)