缘起
IT人写技术文档,例如我自己写博客,用的最多的就是 markdown. 但是在浏览器中看到的这些博客都是以 html 的格式展示在人们的面前的. 所以一个自然的问题就是markdown怎么变成html的?
分析
背景
众所周知,markdown和html都是全球通用的标记语言,那么从一种语言要转换为另一种语言不就是编译吗? 这学期刚好学了编译原理. 正好用上.
ps:这里墙裂向大家安利一下 coding 迪士尼陈屹老师的免费课程《自己动手用java写编译器》,一节课讲原理,一节课写代码,知行合一,落到实处.
这里并不想一次性写一个非常完善的markdown转html的语法解析器. 只是想将仅仅包含标题和正文的markdown文档严格遵从编译原理的流程步骤转换为html.
也就是我们写这个编译器的步骤如下
- 提出语法
- 修改语法使之满足 LL(1) 文法. 因为本文打算写一个 自顶向下语法解析器哈~
- 完成词法解析
- 完成语法解析
- 代码生成, 也就是生成 html
为什么要严格遵从上述编译原理的框架? 因为只有这样,这个编译器的扩展性才更好,才能为后续写更复杂的markdown语法转html编译器打下基础框架. 而不是靠灵光一闪的技巧性处理, 那种是很难维护和扩展的.
什么叫做仅仅包含标题和正文? 举个例子,咱们想要处理的markdown文档长下面的样子
### 人类補完计划 Nerv 绝密 \n
不能逃避、不能逃避、不能逃避,知道何谓痛苦的人比较能温柔待人。这和软弱是不同的。 \n
##这是最优先事项 \n
结果人类的敌人还是敌人, 并非使徒. \n
#### 人类终将補完 \n
肉体归于LCL之海, 灵魂回到莉莉丝之卵, 和亚当握手吧!\n
审判日到来之时, 人类还是使徒? 贤者必将做出抉择!
# 死海古卷的仪式被碇源堂改了! SEELE 对 Nerv 绝不会善罢甘休
编译后的html文档如下
人类補完计划 Nerv 绝密
人类補完计划 Nerv 绝密
不能逃避、不能逃避、不能逃避,知道何谓痛苦的人比较能温柔待人。这和软弱是不同的。
这是最优先事项
这是最优先事项
结果人类的敌人还是敌人, 并非使徒.
人类终将補完
人类终将補完
肉体归于LCL之海, 灵魂回到莉莉丝之卵, 和亚当握手吧!
审判日到来之时, 人类还是使徒? 贤者必将做出抉择!
死海古卷的仪式被碇源堂改了! SEELE 对 Nerv 绝不会善罢甘休
死海古卷的仪式被碇源堂改了! SEELE 对 Nerv 绝不会善罢甘休
语法
首先,我们要制定语法. 我们不难 YY 出如下巴克斯范式
article -> title article | line article | ε
title -> POUND line
line -> TEXT LF
其中遵从国际惯例, 小写英文单词表示非终结符, 大写英文单词表示终结符. 关于语法中的token和symbol的说明如下
- article 是文档, 它是语法的全局非终结符
- title 表示文档的标题,
- line 表示一行文本.
- POUND是 若干
#
字符. - TEXT 是遇到换行符之前的文本.
- LF(line feed) 是换行符, 也就是markdown文档中的 '\n'
- ε 表示空,因为我们的编译器允许空的markdown文档.
显然,上述语法其实还可以再用一下边角替换再精简一些,变成标准的 LL(1) 语法
article -> title_or_line article | ε
title_or_line -> title | line
title -> POUND line
line -> TEXT LF
但是注意上述巴克斯范式的第二条,其实是不利于我们写 LL(1) 的,而只利于写递归下降. 所以要进行单子替换,于是得到我们最终的语法推导规则
article -> title_or_line article | ε
title_or_line -> POUND line | line
line -> TEXT LF