作者:pixelcao,腾讯 IEG 后台开发工程师
一、引子
最近的工作需要用表达式做一些参数的配置,然后发现大脑一片空白,在 Google 里试了几个关键词(起初搜了下“符号引擎”,发现根本不是我想要的)之后,明白过来自己应该是需要补一些编译原理的知识了。在掉了两晚上头发之后,决定整理一下自己的知识网络。
要解析的表达式大概长这个样子:
avg(teams[*].players.attributes[skill])*rules[latency].maxLatency
正则表达式是个办法,但不是最优解,除了很难通过一句正则解析整条语句外,以后扩展更多语法时,正则表达式的修改也分外麻烦。
作为非计算机科班出身的工程师,还是知道一点,任何领域发展出的课题都需要产、学、研三者的配合才能完成,“产” 指企业,“学” 指高等院校,“研” 指科研机构。
其实自打 C 语言从贝尔实验室走出来之后,“研” 究这一步就已经完成大半了,于是被下放到 “学” 校,有了《编译原理》这门课程,最后流入 “产” 业中大规模应用。
所以这篇文章主要从两方面给初学者(尤其是跟我一样非科班出身的 coder)一个指南:
在科学原理上,通俗的解释一些专有名词,厘清基本概念——编译原理这块的术语简直太多了,多到糊脸的那种;
在工程实践上,给到一个可行的实现方法,主要是面向 golang 的
goyacc
,如果本文有幸被你搜索到,你肯定最想看这一部分(现网关于 goyacc 的中文资料太少了)。
二、理论原理
以下内容均为个人理解,欢迎探讨,如有不精确之处,以教科书为准~
2.1 计算机语言是怎么回事儿
编译器由词法分析、语法分析、语义检查再到中间表示输出和最后二进制生成的流程,这些已经可以作为前置知识,就不提了。
随手打开一个工程,我们就能发现形形色色的语言文件,比如 yaml 格式的服务配置文件、json 格式的工程配置文件、js 和 go 等源代码文件等。忽略掉他们繁杂的用途,按其表达能力,可以分为两种:
DSL(Domain Specific Language):特定领域语言,比如用来描述数据的 json、用来查询数据的 sql、标记型的 xml 和 html,都属于面向特定领域的专用语言,用在正确的领域上就是利器,用错地方就是自找麻烦(比如用 sql 来一段冒泡排序);
GPL(General Purpose Language):通用用途语言,比如 C、JavaScript、Golang,这类语言是 图灵完备 的,你可以用一门 GPL 语言去设计和实现一种 DSL 语言。
歪个楼,这里有一个吊诡的事实,yaml 竟然是图灵完备的!甚至很多语言都需要特别使用 safe_load 来加载 yaml 文件,比如用 java 直接 load 这段 yaml,会执行一次 HTTP 请求。
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://localhost\"]]]]
不管是为特定领域而发明的各类 DSL,还是图灵完备的 GPL 语言,他们基本都符合 BNF(巴科斯范式)。
BNF 是一种 上下文无关文法,举个例子就是,人类的语言就是一种 上下文有关文法,我随时都可以讲一句 “以上说的都是废话”,戏弄一下读者阅读本文所花的时间(每当回忆起来,我都会坐在轮椅上大呼过瘾)。
关于 BNF 具体定义,这里摘抄一下维基百科,后面做详细解说:
BNF 规定是推导规则(产生式)的集合,写为:
<符号> ::= <使用符号的表达式>
这里的 <符号> 是非终结符,而