ESC
输入关键词搜索文章
目录

语法分析

Build Your Own Lisp · 第 6 章
使用 mpc 库构建你的第一个语法解析器
Chapter 06 · 语法分析
波兰表达式与 mpc

章节信息

原书章节:第 6 章 Parsing

中文翻译KSCO (GitHub)

原书地址buildyourownlisp.com

在本章中,我们将学习如何使用 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]要求 af 中的任意一个
a?要求 a 字符或什么都没有(可选)
a*要求有 0 个或多个字符 a
a+要求有 1 个或多个字符 a
^开始输入
$结束输入

mpc 中,我们需要将正则表达式包裹在一对 / 中。例如,Number 可以用 /-?[0-9]+/ 来表示。

安装 mpc

在编写语法解析器之前,我们需要安装 mpc 库。你可以从 mpc 项目主页 下载 mpc.hmpc.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_langmpc 后面有个 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:解析用户输入的函数