前言
这一篇来简单的实践一下编译器的词法分析,考虑到词法分析实现的复杂度,这里选择以 markdown 文本语言作为对象进行简单介绍。
正文
1. 什么是 markdown?
定义:
Markdown是一种轻量级标记语言,创始人为约翰·格鲁伯。它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档。
由于Markdown的轻量化、易读易写特性,并且对于图片、图表、数学式、代码块都有支持,目前许多网站都广泛使用Markdown来撰写帮助文档或是用于论坛上发表消息。
标准化:
随着时间的推移,Markdown 已经成为典型的转换为HTML的非正式规范和参考实现,出现了许多Markdown实现。人们开发这些主要是由于在基本语法之上需要额外的功能 - 如表格,脚注,定义列表(技术上的HTML描述列表)和HTML块内的Markdown。与此同时,非正式规范中的一些含糊不清引起了人们的注意。这些问题促使Markdown解析器的一些开发人员努力实现标准化。
2016年3月发布了RFC 7763和RFC 7764。RFC 7763 从原始变体引入了MIME类型 text/markdown。RFC 7764讨论并注册了MultiMarkdown、GitHub Flavored Markdown (GFM)、Pandoc、CommonMark及Markdown等变体。
2. markdowm 与 html 的关系
下面列举了 部分 markdown语法 与 html 语法的对应关系
markdown 语法 | html 语法 |
---|---|
# 标题1 | <h1>标题</h1> |
## 标题2 | <h2> 标题2 </h2> |
空白行分隔 表示段落 | <p></p> |
行末尾 两个空格 表示换行 | <p> <br /> </p> |
_斜体_ | <em>斜体</em> |
粗体 | <strong>粗体</strong> |
`代码` | <code>代码<code> |
--- | <hr /> |
* 1 | <ul> <li> 1 </li> </ul> |
由于篇幅有限 ,这里先列举这么多语法,其他更多语法可以参考《了不起的Markdown》一书。
现在已经知道来 markdown 与 html 的语法对应关系出来,那么应该如何实现一个 compiler 编译器(解析器) 去进行语法识别和转换呢 ?
3. markdowm 词法分析
有了 markdown 的语法标准,就可以进行语法的分析,也就是这里的词法分析,下面以 heading 为例,
首先需要一个 词法解析器,按照 heading 的语法规则 生产出对应的 tokens 序列。
// 词法解析器
Class Lexer {
constructor(options) {
this.tokens = [];
this.tokens.links = Object.create(null);
this.options = options || {};
this.rules = {
// heading 匹配规则
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/
// ...
};
}
// 生成 token 序列
token(src) {
while (src) {
// heading
if (cap = this.rules.heading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'heading',
depth: cap[1].length,
text: cap[2]
});
continue;
}
// other
// ...
}
return this.tokens;
}
}
4. markdown 编译(解析)成 html
上面已经拿到了 markdown 的 tokens 序列,下面还需要把它按照 html 的语法解析成对应的标签, 如下
// 解析器 - Parsing & Compiling
class Parser {
constructor(options) {
this.tokens = [];
this.token = null;
this.options = options || {};
this.renderer = new Renderer();
}
parse(src) {
this.tokens = src.reverse(); // 先进先出
var out = '';
while (this.next()) {
out += this.tok();
}
return out;
}
next() {
this.token = this.tokens.pop();
return this.token;
}
tok() {
switch (this.token.type) {
// ...
case 'heading': {
return this.renderer.heading(
this.token.text,
this.token.depth
)
}
// ...
}
}
}
// 渲染器(代码生产器)
class Renderer {
constructor(options) {
this.options = options || {};
}
heading(text, level, raw) {
if (this.options.headerIds) {
return '<h'
+ level
+ ' id="'
+ this.options.headerPrefix
+ '">'
+ text
+ '</h'
+ level
+ '>\n';
}
return '<h' + level + '>' + text + '</h' + level + '>\n';
};
}
后记
由于 markdown 是一种标记文本语言,因此这里代码解析转换时候 不需要对其进行 抽象语法树的分析(生成/分析 AST 的环节),相当于只进行了词法分析和代码生成的过程。其编译解析过程相对 图灵完备的语言 较简单。
思考题:
根据 javascript 语法 加减乘除 优先级,是否可以构建一个简单的文法解析器呢?
例如:var a = 1 + 3 * 2;
- 相关文章