语法分析
Build Your Own Lisp · 第 6 章
使用 mpc 库构建你的第一个语法解析器
Chapter 06 · 语法分析
波兰表达式与 mpc
在本章中,我们将学习如何使用 mpc 库来构建一个语法解析器。这是构建 Lisp 解释器的关键一步——我们需要将用户输入的文本转化为计算机可以理解的结构化数据。
波兰表达式
为了验证 mpc 的威力,本章我们尝试实现一个简单的语法解析器——波兰表达式(Polish Notation),它是我们将要实现的 Lisp 的数学运算部分。
波兰表达式
波兰表达式是一种数学标记语言,它的运算符在操作数的前面。例如:
| 普通表达式 | 波兰表达式 |
|---|---|
1 + 2 + 6 | + 1 2 6 |
6 + (2 * 9) | + 6 (* 2 9) |
(10 * 2) / (4 + 2) | / (* 10 2) (+ 4 2) |
波兰表达式的语法规则可以用白话文描述:
- 程序:由一个操作符加上一个或多个表达式组成
- 表达式:可以是一个数字,或者是包裹在圆括号中的一个操作符加上一个或多个表达式
- 操作符:
+、-、*、/ - 数字:可选的负号
-后面跟着一个或多个数字字符
正则表达式
在表示数字和程序规则时,我们需要用到正则表达式。正则表达式适合定义一些小型的语法规则,例如单词或是数字等。
正则表达式基本规则
| 语法表示 | 作用 |
|---|---|
. | 要求任意字符 |
a | 要求字符 a |
[abcdef] | 要求 abcdef 中的任意一个 |
[a-f] | 要求 a 到 f 中的任意一个 |
a? | 要求 a 字符或什么都没有(可选) |
a* | 要求有 0 个或多个字符 a |
a+ | 要求有 1 个或多个字符 a |
^ | 开始输入 |
$ | 结束输入 |
在 mpc 中,我们需要将正则表达式包裹在一对 / 中。例如,Number 可以用 /-?[0-9]+/ 来表示。
安装 mpc
在编写语法解析器之前,我们需要安装 mpc 库。你可以从 mpc 项目主页 下载 mpc.h 和 mpc.c,放到你的源文件同目录下。
编译命令
Mac 和 Linux:
cc -std=c99 -Wall parsing.c mpc.c -ledit -lm -o parsing
Windows:
cc -std=c99 -Wall parsing.c mpc.c -o parsing
注意:在 Linux 上需要加
-lm 参数来链接数学库。包含头文件的区别
在 C 语言中有两种包含头文件的方式:
#include <stdio.h>:用尖括号包含系统头文件#include "mpc.h":用双引号包含其他头文件
波兰表达式语法解析
下面是波兰表达式最终的语法规则:
/* Create Some Parsers */
mpc_parser_t* Number = mpc_new("number");
mpc_parser_t* Operator = mpc_new("operator");
mpc_parser_t* Expr = mpc_new("expr");
mpc_parser_t* Lispy = mpc_new("lispy");
/* Define them with the following Language */
mpca_lang(MPCA_LANG_DEFAULT,
" \
number : /-?[0-9]+/ ; \
operator : '+' | '-' | '*' | '/' ; \
expr : | '(' + ')' ; \
lispy : /^/ + /$/ ; \
",
Number, Operator, Expr, Lispy);
在 main 函数的最后,还需要将使用完毕的解析器删除:
/* Undefine and Delete our Parsers */
mpc_cleanup(4, Number, Operator, Expr, Lispy);
常见错误:编译时如果遇到
undefined reference to 'mpc_lang',注意函数的名字为 mpca_lang,mpc 后面有个 a 字母。解析用户输入
现在我们可以使用解析器来解析用户的每一条输入。将之前的 printf 语句替换成下面的代码:
/* Attempt to Parse the user Input */
mpc_result_t r;
if (mpc_parse("", input, Lispy, &r)) {
/* On Success Print the AST */
mpc_ast_print(r.output);
mpc_ast_delete(r.output);
} else {
/* Otherwise Print the Error */
mpc_err_print(r.error);
mpc_err_delete(r.error);
}
运行示例
Lispy Version 0.0.0.0.2
Press Ctrl+c to Exit
lispy> + 5 (* 2 2)
>
regex
operator|char:1:1 '+'
expr|number|regex:1:3 '5'
expr|>
char:1:5 '('
operator|char:1:6 '*'
expr|number|regex:1:8 '2'
expr|number|regex:1:10 '2'
char:1:11 ')'
regex
lispy> hello
:1:1: error: expected whitespace, '+', '-', '*' or '/' at 'h'
复习速查
- 波兰表达式:运算符在操作数前面的数学标记语言
- 正则表达式:定义小型语法规则的模式匹配语言
- mpc 库:用于构建语法解析器的 C 库
- mpca_lang:定义语法规则的函数(注意有
a) - mpc_parse:解析用户输入的函数