关于编译器 parsing 的理论知识我没有完整学, 就是补过一些片断
因此这篇文章里可能有用理论知识很容易解释的一些问题, 我并无看到
并且 Cirru 的语法坚持要用缩进, 现有的方案是难以让我知足的
这些天用 Go 重写了 Cirru 的 parser, 后面会对思路作一些解释git
以前一个版本的 Cirru parser 解析缩进的方案比较原始, 就是解析文本块
好比说下面这样一个文本块, 我想要按照缩进解析到到一个嵌套的关系:github
a b c d e f
(a (b ((c)) (d))) (e (f))
我从前的作法是对文本进行分割, 先获得 a
和 e
开头的两块文本
而后取出 a
的文本块, 按照缩进进行处理, 获得 a
, 以及缩进之后的文本:数组
a b c d
b c d
而后按照缩进一步步解析, 到缩进消失也就解析完了网络
而文本部分, 我记录了一个状态机, 每次读取一个字符时都有一个状态用于判断
好比说, 对于包含字符串的这样一段代码, 每一个字符解析都有状态:函数
a "b \c d"
token 'a' token ' ' token '"' -> string string 'b' string ' ' string '\' -> escape escape 'c' -> string string ' ' string 'd' string '"' -> token
我还在上面标记了状态转移的步骤, 特定的符号会触发状态的转移
对于括号的缩进, 我原先采起的是 Lispy 教程里的方案分开处理的:指针
= a (str 1)
具体的作法用的是高阶函数, 这里不展开了code
后来渐渐想到这个方法有很差的地方, 当初也是为了技能不够而折衷的
就是, 我解析一段文本, 混用了好几种方式来处理, 更难定位 bug
另外一个, 就是我必定要获取整个文件, 先扫描好缩进, 而后才能开始解析
而一般的解析器能够接收流的数据进行解析, 好比接收网络传送文件的同时进行解析教程
Cirru 的语法规则就几条, 引号和转义, 括号, 缩进, 另外两个特殊规则(, $
)
特殊规则是在语法树解析完成后作的, 构建树的过程先不考虑
引号和转义已经能用状态机解决, 那么我想到的方案是把缩进也用状态机表示
至于括号, 括号对应的是一个嵌套关系的改变, 实际上没有状态改变token
那么怎么把缩进用状态表示呢, 前面的几个字段可不足以表达缩进啊
所以我引入了一个字段 level
, 配合每一行的缩进数量 buffer
来判断状态
好比上边的缩进语法, 我作一些简化, 而后用状态表示出来ci
a b c d e f
其中 level
对应前一个缩进的长度, len(buffer)
对应当前的缩进:
level len(buffer) -> 0 0 -> 0 2 -> push 2 6 -> push push 6 4 -> pop 4 0 -> pop pop 0 2 -> pop
这里的 pop push
对应跳出当前数组和建立数组的操做, 对应表达式的嵌套
而括号的行为也和这里的 pop push
对应:
= a (str 1)
token '=' token ' ' token 'a' token ' ' token '(' -> push token 's' token 't' token 'r' token ' ' token '1' token ')' -> pop
当有一个嵌套的数组的结构, 有个指针用于跳到上级和建立新数组
就能够读取文件, 生成一棵 Cirru 的语法树, 包含嵌套的表达式
因为 Cirru 的语法简单, 这样一个简单的状态机就完成了:
https://github.com/Cirru/parser
考虑假如之后会增长其余的语法的话, 我猜测是直接添加状态来扩展了
好比转义的内容, 要支持 "\u02aa"
之类稍微复杂的语法 固然也可能 Cirru 太不实用, 我彻底没有添加语法的需求 Cirru 如今出于很是原始的状态, 我并不清楚能作些什么