编译原理之 PEG.js

引言

编译原理之手写一门解释型语言[1]中我们全手动写了一个 Parser 来解析我们的脚本,实际上有现成的工具可以更加方便地完成这个工作,比如 PEG.js。该工具通过解析语法规则来自动生成 Parser,开发人员只需要专注于编写语法规则即可。

语法规则基本写法

安装及基础 API 使用详见官网[2],本文通过几个例子介绍一下语法规则的编写方法:

word = [a-zA-Z]*

上面定义了一个名叫 word 的规则,其中 [a-zA-Z]* 跟正则表达式一样,表示匹配 0 个及以上的英文字母。

解析 hello, 上面的规则会返回如下的结构:

"h""e""l""l""o"]

如果想得到整个单词怎么办呢?由于 PEG.js 支持在语法规则中编写 js 代码,所以我们可以这样写:

word = w:[a-zA-Z]* {
  return w.join("")
}

其中,w 表示所匹配到的所有字符组成的数组,即上面的 [ "h", "e", "l", "l", "o"]。或者,也直接这样写:

word = [a-zA-Z]* {
  return text()
}

当然,规则中也可以包含其他的规则,比如:

statement = words:(word (blank / "."))* {
 return words.map(word => word.join("")).join("")
}

word = w:[a-zA-Z]* {
  return w.join("")
}

blank = [ ]

statement 会匹配若干个 word blank(单词后面加个空格) 或 word "."(单词后面加个 . )。上面的规则在解析 hello world.words 的结果如下所示:

[
  ["hello"" "],
  ["world""."]
]

其中,数据第一维表示匹配 (word (blank / "."))* 返回的元素;第二维第一个元素表示 word 匹配得到的结果,第二个元素表示 (blank / ".") 匹配得到的结果。

了解了这些基本的写法后,接下来让我们分析下官网的在线例子:Simple Arithmetics Grammar[3]

Simple Arithmetics Grammar

Expression
  = head:Term tail:(_ ("+" / "-") _ Term)* {
      return tail.reduce(function(result, element) {
        if (element[1] === "+") { return result + element[3]; }
        if (element[1] === "-") { return result - element[3]; }
      }, head);
    }

Term
  = head:Factor tail:(_ ("*" / "/") _ Factor)* {
      return tail.reduce(function(result, element) {
        if (element[1] === "*") { return result * element[3]; }
        if (element[1] === "/") { return result / element[3]; }
      }, head);
    }

Factor
  = "(" _ expr:Expression _ ")" { return expr; }
  / Integer

Integer "integer"
  = _ [0-9]+ { return parseInt(text(), 10); }

"whitespace"
  = [ tnr]*

其中,规则 _IntegerFactor 比较简单,这里就不啰嗦了,我们来看看 TermExpression

为了方便分析 Term,我们暂时修改一下他的规则:

Term
  = head:Factor tail:(_ ("*" / "/") _ Factor)* {
     return { head, tail }
    }

解析 2*3*4,我们可以得到如下结果:

{
   "head": 2,
   "tail": [
      [
         [],
         "*",
         [],
         3
      ],
      [
         [],
         "*",
         [],
         4
      ]
   ]
}

有了上面的结果,相信你已经知道原来的这一段代码是干什么用的了吧:

return tail.reduce(function(result, element) {
  if (element[1] === "*") { return result * element[3]; }
  if (element[1] === "/") { return result / element[3]; }
}, head);

同样的,可以用类似的方法来分析 Expression,这里就不展开了。

总结

本文介绍了 PEG.js 的基本使用方法,借用该工具,我们可以做很多有意义的事情,比如我们开发的数据库管理平台就需要在前端解析 SQL 语句,用到的第三方库 node-sql-parser 中就使用了该工具。

参考资料

[1]

编译原理之手写一门解释型语言: https://mp.weixin.qq.com/s/-9JaieElYz2HBr8vH8fSRA

[2]

PEG.js: https://nathanpointer.com/

[3]

PEG.js Online: https://pegjs.org/online


原文始发于微信公众号(前端游):编译原理之 PEG.js

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/282329.html

(0)
前端游的头像前端游bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!