手写一个解析器

作者:jolamjiang,腾讯 WXG 前端开发工程师

前言

最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言(DSL)及其解析器的场景,笔者恰好有相关的经验向大家指一下北。

首先请问一下大家有没有想过这个功能怎么做?

点击播放视频

本文将围绕如何实现类似于 Excel 中 =C1+C2+"123" 这样子的表达式的功能这一例子,在不需要编译原理的相关知识的前提下,用写正则表达式作为类比,借助一个工具库,讲述实现一个领域相关语言的解析器的一般步骤,让你能够快速实现一个解析器。同时,文章最后将给出一个将类似 MySQL 里面 Where 表达式转化成 MongoDB 查询的例子丰富这里的应用。

正则及其限制

在日常工作中,经常会遇到模式匹配的问题,例如你能需要从 0755-8771032 这样的电话号码格式中提取出区号和区号和电话号码,然后保存下来;可能需要判断 test@domain.com.cn 这样的邮箱地址是否合法;又可能你需要实现类似于 Excel 里面表达的功能,例如用户输入 =C1+C2+"123",你需要把 C1 的内容和 C2 的内容和字符串 "123" 拼接起来。

我们一般的做法是使用正则表达来做这个事情,以 Python 为例,系统提供的 API 我们可以看做分三步走:

import re
pattern = "^([0-9])-([0-9]+)$" // 1. write regex string of phone number
prog = re.compile(pattern) // 2. compile regex to a matcher
result = prog.match(string) // 3. match string and handle results

目前为止正则表达式都看起来都没问题,以 =C1+C2+"123" 这个需求为例,你可能会觉得我们按照运算符(+-* 等)分割一下然后再计算就行了,但是考虑下面三个 case:

  1. 运算符有优先级,例如 =C1+C2*C3=C1*C2+C3,需要先计算 * 再计算 +

  2. 字符串里面有运算符,例如 =C1+C2+"=C1+C2"

  3. 运算有左右括号匹配来改变运算优先级,例如 =(C1+C2)*C3

这个时候光使用正则表达式就比较棘手了。

通用做法

业界通用的做法是先定义这个领域相关的语法,将这个语法形式化描述(就像写正则表达式),然后根据这语法实现一个 Parser 将代码转成抽象语法树(AST),再解析和运行这颗抽象语法树。

上述整个过程听起来就比较复杂,事实上要从 0 开始实现一个 Parser 还是比较费时的,那么有没有工具能够让我们可以像写正则一样生成我们的 Parser,进而产生一颗抽象语法树方便我们处理呢?答案是有的,例如 C 语言有 Bison 框架,JS 上选择就更多了,你可以选择 JisonparsimmonPEG.jsNearley 等,本文则基于使用人数较多的 Nearley 框架。

如何写一个解析器

与使用写正则类似,使用 Nearley 等 Parser 产生器的过程,也是分三步走。

1. 用 BNF 来表示你的 DSL 语法

BNF 的全称是 Backus–Naur form,是一种表示上下文无关语法的表示方式,Nearley 的语法基于 BNF 的扩展 EBNF(Extended Backus–Naur form),下面是笔者写的关于这个 Excel 中的表达式的 Nearley 语法文件(为了便于理解,这里只实现了运算符的优先级,没有实现左右括号):

grammar.ne

@builtin "number.ne"
@builtin "whitespace.ne"
@builtin "string.ne"

@{%
    function buildAssignmentExpression(d) {
        return {
            type: "AssignmentExpression",
            op: d[2],
            left: d[0],
            right: d[4]
        };
    }
%}

# Assignment
Exp -> Assignment {% id %}
    | Valu
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值