PonyText是一个非“所见即所得”的文本排版工具(如MS Word),是一个简易的文本排版以及预处理系统。用户将通过编写纯文本文件,从而获得排版后的文本。
关于这个问题,这东西其实是在我写小说时想到的。写过小说的都知道,人物/地点/物体的名称并不是很好起,有时因为想起个好名字,从而导致陷入一个一筹莫展的死循环中——名字想不到,剧情无法进展。
不过最终,你还是会起了一个名字,并且在后续的情节推动中使用了这个名字。
这确实很美好,但是有一天,因为某些原因你想要去更改这个名字(可能是后来发现这个名字不是很妥当,比如,你想到了一个更好的名字,或者原来的名字蕴含了种族歧视,导致你的部分读者不高兴了)。但是你发现,你的情节已经推动到了不知道什么地方去了,通过查找发现这个名字在文档中出现了上百次。
当然了,你可以直接全局替换。这在大部分场合是可以的,但是有些情况可能并不允许。现在我们来看个例子:
假设你有一个角色,叫做 “星光”,你的文本中有这两句话:
他站在满天的星辰之下,似乎在思考着什么。星光照耀着他的面庞,照出了他脸上的沧桑。
以及
星光兴高采烈的说道:……
这个时候,一般是你写小说的某个时候,你发现“星光”这个名字可能有些模棱两可(ambiguous),于是你想到了一个更好地名字:“星光熠熠”。于是你进行全局替换,你会得到:
他站在满天的星辰之下,似乎在思考着什么。星光熠熠照耀着他的面庞,照出了他脸上的沧桑。
以及
星光熠熠兴高采烈的说道:……
当然,第二句是没有问题,我们能立刻反映出来“星光熠熠”是个人物。但是第一句就有点问题了。这给人的反应是:星光熠熠这个角色拿着某种光源,在照射着那个人的脸庞。于是在上下文中,这意思立刻就改变了。
出于这个原因,我就编写了PonyText,通过引入“宏处理”来解决这一问题,顺带做了排版功能。同时,PonyText也算是我对编译原理这门课的一个实践和理解。
在目前的版本中,PonyText可以……
- 宏处理
- 基本的排版(对齐,加粗,字号,字体等……)
- 文本结构化(这样你就可以方便的使用Git来版本控制了)
我承认,这有些“重复造轮子”。你可以看到,我用了“有些”,而不是“确实”。虽然PonyText和LaTeX功能基本一样,甚至在某些方面不如LaTeX(PonyText无法渲染数学文本,而且PonyText不是图灵完备的)。但这也并不碍事,毕竟PonyText的初衷只是实现简单的文本预处理。
当然了,对比LaTex,PonyText的语法是简洁易懂的,学习曲线并没有LaTeX那么陡峭(i.e. 门槛稍低)。并且和LaTeX一样,支持对指令的拓展。
和编程语言不一样,PonyText只是一个文字排版语言。这就意味着PonyText主要以正常的文字、段落为主。和LaTeX一样,PonyText通过调用一系列的命令(在PonyText里叫做预处理器)来执行一些对文本的操作。
和LaTeX一样,PonyText使用了正常的段落标识——换行表示另起一段。为了方便展示,我们用\n
表示一个换行操作(按一下回车键):
文学程序是用自然语言(比如英语)写出来的对程序逻辑的解释,程序中交织点缀着宏和传统源代码段。在文学编程的源文件中,宏很简单,它或与标题类似,或是解决编程问题时用人类语言描述抽象的解释性短语。它把代码段或更低层次的宏隐藏了起来,且与计算机科学教学时经常用到的,用伪代码写的算法相似。\n 这些任意解释的短语成为新的精确的操作符,操作符由程序员在运行过程中创建,组成了在基本编程语言之上的“元语言”。 预处理器用于替换任意层级,说得更准确些是“在'网'和宏之间创建联系”。
一个预处理器的调用看上去是这个样子的:
@define MACRO "这是一个字符串。"
我们可以看出,符号@
后面(就是我们电子邮箱地址中的那个符号!),紧跟着的是你需要使用的预处理的名称。而之后则是这个预处理器将会接受到的参数,每个参数之间以空格作为分隔。
当然了,参数有不同的类型,这点我们会在后面看到。
PonyText使用 \${.....}
来标记一个调用可能会出现的地方,如:
\${@define MACRO "这是一个字符串。"}
这个意思就是说,我们调用了一个名字叫做define
的预处理器,并传进了两个参数。
注意,这里我把 “一个” 特地加粗了一下,这是因为\${.....}
只能放置一个调用。当然,你也可以把多个调用写道一起,利用\$ .... \$
进行标记多个调用。
\$
@define MACRO "字符串1"
@define MACRO2 "字符串2"
@maintitle "文档标题"
\$
有过编程经验的读者可以看出,每个语句(调用)之间没有冒号分隔。这就意味着换行符在PonyText里有特殊的含义。不管是段落也好,还是调用也好,都是通过换行符分割的。
注意: 为了保证可读性,在标记多行调用时,开头的\$
后面,以及结尾的\$
前面,都必须要放置一个换行符。这也就是说:
\$ @define MACRO "字符串1"
@define MACRO2 "字符串2"
@maintitle "文档标题"
\$
和
\$
@define MACRO "字符串1"
@define MACRO2 "字符串2"
@maintitle "文档标题"\$
都是非法的。
我们其实已经可以看到定义宏的命令是
@define 宏名称 宏的值
如果说我们现在有一个叫做MACRO1
的宏变量,我们可以通过这个方法去使用他:
\${MACRO1}
这个其实是\${@echo MACRO1}
的简写。
声明宏的时候,不用担心保留字问题,因为PonyText没有保留字!哪怕是命令的名称也没问题,比如你可以这么干:\${@define define "define"}
。这一点问题都没有!不过为了可读性,建议不要这么做。
那么我们在开头给的例子可以写成:
\$
@define STARLIGHT "星光熠熠"
\$
他站在满天的星辰之下,似乎在思考着什么。星光照耀着他的面庞,照出了他脸上的沧桑。
\${STARLIGHT}兴高采烈的说道:……
一切都十分完美,这样一来,当我们再想改名字时,我们只需要修改对应的宏变量的值就可以了。
不过还有一点需要注意一下,虽然并不是很致命,但是我觉得还是值得说明一下。我们注意到上面的最后一行:
\${STARLIGHT}兴高采烈的说道:……
这和
\${STARLIGHT}
兴高采烈的说道:……
不是一回事。虽然\${STARLIGHT}
在预处理阶段都会被替换成我们定义的“星光熠熠”。但是在第二个示范中,我们的\${STARLIGHT}
后面跟了个回车(换行符),根据我们之前说过的 “PonyText段落的定义”,我们立刻可以发现这样的\${STARLIGHT}
,会被PonyText识别为一个单独的段落,所以最后渲染出来是:
星光熠熠
兴高采烈的说道:……
而不是
星光熠熠兴高采烈的说道:……
在前面,我们只是说明了一个调用长什么样子的,但是没有给出正式的定义。正式地讲,一个调用是这个样子的:
@<name> [<arg><space>+]*
这是什么意思?学过编译原理的可能一下就看出来了。<name>
是一个表示符,也就是一个只有26个英文字母大小写以及下划线的字符串,如Ae2_ss
,__Hyhj_2
是合法的,而形如3Hz_
以及#Hz2
是不合法的,这和几乎绝大多数编程语言的变量名/函数名定义是一样的。
后面[<arg><space>+]*
表示可以跟随零个或多个参数,在这里,我用<space>+
来强调每个参数之间是需要由一个或多个空格来分割的。
说了那么多参数,我们似乎还没有提到PonyText都支持那些类型的参数。在PonyText里面,参数,也就是上面的<arg>
,有这几种类型:
- 字符串
- 数字
- 字典
- 调用
- 文学结构
字符串和数字可能都很熟悉,前者是任何用冒号围住的字符串,特殊字符需使用\
转义,后者就是普通的数字,如-123
,12.3
,都是合法数字,但是+233
,2.3.3
,2e-2
不是合法数字(是的,PonyText不支持科学计数法)。
字典,其实就是键值对,大家肯定也熟悉。在PonyText中,一个合法的字典是这样的:
可以看到,键可以是字符串或者是普通的标识符,值可以使字符串,数字或者普通表示符。对于普通标识符,PonyText会在预处理阶段时查找相应的宏并进行替换。
那么,后面的调用和文学结构又是什么东西?
关于“调用”,其实就是说你可以命令里面嵌套命令。这是通过使用成对的括号:(...)
来实现的,如:
@maintitle (@echo MY_TITLE)
PonyText执行到这种嵌套时,会去执行括号里面的命令,将命令返回的结果(如果有的话)作为参数。(当然,如果没有结果的话,那就是一个空参数。虽然PonyText不会报错(现阶段),但这一点意义都没有。)