用 C 语言开发一门编程语言 — 语法解析器

目录

前文列表

用 C 语言开发一门编程语言 — 交互式 Shell
用 C 语言开发一门编程语言 — 跨平台

编程语言的本质

在 19 世纪 50 年代,语言学家 Noam Chomsky 定义了一系列关于语言的重要理论。这些理论支撑了我们今天对于语言结构的基本理解。其中重要的一条结论就是:自然语言都是建立在递归和重复的子结构之上的。Chomsky 提出的理论是非常重要的。它意味着,虽然一门语言可以表达无限的内容,我们仍然可以使用有限的规则去解析所有用该门语言写就的东西。这些有限的规则就叫语法(grammar)。

当我们学习一门自然语言的时候,我们往往从语法开始。当我们学习一门编程语言的时候也一样,当我们尝试开发一门编程语言的时候亦如此,首先要考虑的就是语言的语法、及其语义。

实现语法解析器

为了定义一门编程语言的语法,首先需要能够正确解析用户按照语法规则编写的程序。为此,需要编程语言程序就需要一个语法解析器,用来判断用户的输入是否合法,并产生解析后的内部表示。内部表示是一种计算机更容易理解的表示形式,有了它,我们后面的解析、求值等工作会变得更加的简单可行。

使用 MPC 解析器组合库

MPC(Micro Parser Combinators)是一个用于 C 的轻量且强大的解析器组合库。你可以使用这个库为任何语言编写语法解析器。编写语法解析器的方法有很多,使用解析器组合库的好处就在于,它极大地简化了原本枯燥无聊的工作,你只需要关注编写高层的抽象语法规则就可以了。

注:MPC 的开发者就是《Build Your Own Lisp》的原作者。

MPC 可用于:

  • 解析现有的,或开发新的编程语言
  • 解析现有的,或开发新的数据格式

MPC 的特性:

  • 正则表达式分析器生成器
  • 语法分析器生成器
  • 易于集成到 C 语言项目(以一个源文件的形式存在)
  • 自动生成错误消息
  • Type-Generic(泛式类型)
  • Predictive, Recursive Descent

安装

在我们正式编写这个语法解析器之前,首先需要安装 MPC 库。MPC 库的安装非常简单,只需要将源码下载,把源文件 Copy 到我们的 C 语言项目中,然后在项目中包含 mpc 的头文件并链接 MPC 库即可。

下载

$ git clone https://github.com/orangeduck/mpc.git

Copy

$  ll
总用量 140
-rw-r--r-- 1 root root 111731 4月   7 18:12 mpc.c
-rw-r--r-- 1 root root  11194 4月   7 18:12 mpc.h
-rwxr-xr-x 1 root root   8632 4月   7 18:08 parsing
-rw-r--r-- 1 root root   1203 4月   7 18:11 parsing.c

引入到 parsing.c

#include "mpc.h"

编译

gcc -std=c99 -Wall parsing.c mpc.c -lreadline -lm -o parsing
  • -lm:链接数学库。

快速入门

下面我们来编写一个 Doge(the language of Shiba Inu,柴犬语)语言的语法解析器以便熟悉 MPC 的用法。
在这里插入图片描述

先来看一下 Doge 语言的语法描述:

  • Adjective(形容词):wow、many、so、such。
  • Noun(名词):lisp、language、c、book、build。
  • Phrase(短语):由 Adjective + Noun 组成。
  • Doge(柴犬语):由若干个 Phrase 组成。

下面我们尝试使用 MPC 来定义 Doge 语言。

  • Step 1. 使用 MPC 定义 Adjective 和 Noun,为此我们创建两个解析器:
/* Build a parser 'Adjective' to recognize descriptions */
mpc_parser_t *Adjective = mpc_or(4, 
  mpc_sym("wow"), mpc_sym("many"),
  mpc_sym("so"),  mpc_sym("such")
);

/* Build a parser 'Noun' to recognize things */
mpc_parser_t *Noun = mpc_or(5,
  mpc_sym("lisp"), mpc_sym("language"),
  mpc_sym("book"),mpc_sym("build"), 
  mpc_sym("c")
);

其中,mpc_or 函数会返回一个解析器,该解析器表示 “取其一”,因为我们需要从 Adjective 和 Noun 中 “各取其一” 来组成 Phrase,所以分别定义了两个解析器。

  • Step 2. 使用已经定义好的 Adjective 、 Noun 解析器来定义 Phrase 解析器:
mpc_parser_t *Phrase = mpc_and(2, mpcf_strfold, Adjective, Noun, free);

mpc_and 函数会返回一个解析器,该解析器只接受各 “子句” 按照顺序出现的语句。所以我们将先前定义的 Adjective 和 Noun 传递给它,表示:形容词后面紧跟着名词组成的短语。mpcf_strfold 和 free 指定了各个语句的组织(Fold)及删除(Free)方式。

  • Step 3. 使用 Phrase 解析器来定义 Doge 语言,Doge 是由若干个 Phrase 组成的,mpc_many 函数表达的正是这种逻辑关系:
mpc_parser_t *Doge = mpc_many(mpcf_strfold, Phrase);

上述语句表明 Doge 可以接受任意多条语句。这也意味着 Doge 语言是无穷的。下面列出了一些符合 Doge 语法的例子:

/* 一条 Doge 语句由若干个 Phrase 组成,一个 Phrase 由一个 Adjective + 一个 Noun 构成。 */
"wow book such language many lisp"  
"so c such build such language"
"many build wow c"
""
"wow lisp wow c many language"
"so c"

如上,我们简单定义了一门 Doge 语言。还可以继续使用 mpc 提供的其他函数,一步一步地编写能解析更加复杂的语法的解析器。但很显然,这种代码实现方式并不友好,随着语法的复杂度的增加,代码的可读性也会越来越差。

所以 mpc 还提供了一系列的函数来帮助用户更加简单地完成常见的任务,使用这些函数能够更好更快地构建复杂语言的解析器,并能够提供更加精细地控制。具体的文档说明可以参见项目主页(https://github.com/orangeduck/mpc)。

下面,我们使用 MPC 提供的另一种更加贴近自然的代码实现方式来编写语法规则:将整个语言的语法规则写在一个长字符串中,而不是使用啰嗦难懂的 C 语句。我们也不再需要关心如何使用 mpcf_strfold 或是 free 参数组织或删除各个语句。所有的这些工作都是都是自动完成的。

mpc_parser_t* Adjective = mpc_new("adjective");
mpc_parser_t* Noun      = mpc_new("noun");
mpc_parser_t* Phrase    = mpc_new("phrase");
mpc_parser_t* Doge      = mpc_new("doge");

mpca_lang(MPCA_LANG_DEFAULT,
  "                                           \
    adjective : \"wow\" | \"many\"            \
              |  \"so\" | \"such\";           \
    noun      : \"lisp\" | \"language\"       \
              | \"book\" | \"build\" | \"c\"; \
    phrase    : <adjective> <noun>;           \
    doge      : <phrase>*;                    \
  ",
  Adjective, Noun, Phrase, Doge);

/* Do some parsing here... */

mpc_cleanup(4, Adjective, Noun, Phrase, Doge);
  1. 使用 mpc_new 函数定义语法规则的名字。
  2. 使用 mpca_lang 函数具体定义这些语法规则。

mpca_lang 函数的第一个参数是操作标记,在这里我们使用默认选项 MPCA_LANG_DEFAULT。第二个参数是 C 语言的一个长字符串。这个字符串中定义了具体的语法规则。每个规则分为两部分,用冒号 : 隔开,使用 ; 表示规则结束:

  • 冒号左边是语法规则的名字,e.g. adjective、noun、phrase、doge。
  • 右边是语法规则的定义,e.g. 形容词:wow、many、so、such。

mpca_lang 函数就是对 mpc_many、mpc_and 、 mpc_or 这些函数的封装,自动地完成这些函数的工作,让解析器定义的代码变得干净利落,不拖泥带水。

定义语法规则的一些特殊符号的作用如下:
在这里插入图片描述

发布了508 篇原创文章 · 获赞 1358 · 访问量 188万+

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/105353385