目录

前文列表

用 C 语言开发一门编程语言 — 交互式解析器l

用 C 语言开发一门编程语言 — 跨平台的可移植性

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

抽象语法树的结构

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

上篇我们通过 MPC 解析器组合库完成了读取输入,对波兰表达式的语法解析并得到表达式的 AST(抽象语法树),操作数(Number)和操作符(Operator)等需要被处理的有效数据都位于叶子节点上。而非叶子节点上则包含了遍历和求值的信息。

但是现在我们仍不能对它进行计算求值。在实现计算求值之前,我们先好好看看 AST 的结构:

typedef struct mpc_ast_t {
char *tag;
char *contents;
mpc_state_t state;
int children_num;
struct mpc_ast_t **children;
} mpc_ast_t;
  • tag:就是在节点内容之前的信息,它表示了解析这个节点时所用到的所有规则。例如:expr|number|regex。tag 字段非常重要,因为它可以让我们知道创建节点时所匹配到的规则。
  • contents:包含了节点中具体的操作数和操作符内容,例如 *(5。你会发现,对于表示分支的非叶子节点,这个字段为空。而对于叶子节点,则包含了操作数或操作符的字符串形式。
  • state:这里面包含了解析器发现这个节点时所处的状态,例如行数和列数等信息。本书不会用到这个字段。
  • children_numchildren:帮助我们来遍历 AST。前一个字段告诉我们有多少个子节点,后一个字段是包含这些节点的数组。其中,children 的数据类型为 mpc_ast_t ** 二重指针类型,是一个指针数组。
/* Load AST from output。
* 因为 mpc_ast_t* 是指向结构体的指针类型,所以获取其字段的语法有些许不同。我们需要使用 -> 符号,而不是 . 符号。
*/
mpc_ast_t *a = r.output;
printf("Tag: %s\n", a->tag);
printf("Contents: %s\n", a->contents);
printf("Number of children: %i\n", a->children_num); /* Get First Child */
mpc_ast_t *c0 = a->children[0];
printf("First Child Tag: %s\n", c0->tag);
printf("First Child Contents: %s\n", c0->contents);
printf("First Child Number of children: %i\n",
c0->children_num);

使用递归来遍历树结构

树形结构是自身重复的。树的每个子节点都是树,每个子节点的子节点也是树,以此类推。可见,树形结构也是递归和重复的。如果我们想编写函数处理所有可能的情况,就必须要保证函数可以处理任意深度,我们可以使用递归函数的天生优势来轻松地处理这种重复自身的结构。

递归函数就是在执行的过程中调用自身的函数。理论上,递归函数会无穷尽地执行下去。但实际上,递归函数对于不同的输入会产生不同的输出,如果我们每次递归都改变或使用不同的输入,并设置递归终止的条件,我们就可以使用递归实现预期的效果。例如:使用递归来计算树形结构中节点个数。

首先考虑最简单的情况,如果输入的树没有子节点,我们只需简单的返回 1 表示根节点就行了。如果输入的树有一个或多个子节点,这时返回的结果就是根节点再加上所有子节点的值。

使用递归,遍历统计子节点的数量:

int number_of_nodes(mpc_ast_t* t) {
if (t->children_num == 0) { return 1; }
if (t->children_num >= 1) {
int total = 1;
for (int i = 0; i < t->children_num; i++) {
total = total + number_of_nodes(t->children[i]);
}
return total;
}
}

实现求值计算

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

在实现代码之前再好好总结一下 AST 输出的特征:

  • 有 number 标签的节点一定是一个数字,并且没有子节点。我们可以直接将其转换为一个数字。
  • 如果一个节点有 expr 标签,但没有 number 标签,那么第一个子节点永远是 ( 字符,最后一个子节点是 ) 字符。我们需要看他的第二个子节点是什么操作符,然后我们需要使用这个操作符来对后面的子节点进行求值。

在对语法树进行求值的时候,还需要保存计算的结果。在这里,我们使用 C 语言中 long 类型。另外,为了检测节点的类型,或是为了获得节点中保存的数值,我们会用到节点中的 tag 和 contents 字段。这些字段都是字符串类型的。

我们引入一些辅助性的库函数:



我们可以使用 strcmp 来检查应该使用什么操作符,并使用 strstr 来检测 tag 中是否含有某个字段:

#include <stdio.h>
#include <stdlib.h>
#include "mpc.h" #ifdef _WIN32
#include <string.h> static char buffer[2048]; char *readline(char *prompt) {
fputs(prompt, stdout);
fgets(buffer, 2048, stdin); char *cpy = malloc(strlen(buffer) + 1); strcpy(cpy, buffer);
cpy[strlen(cpy) - 1] = '\0'; return cpy;
} void add_history(char *unused) {} #else #ifdef __linux__
#include <readline/readline.h>
#include <readline/history.h>
#endif #ifdef __MACH__
#include <readline/readline.h>
#endif #endif /* Use operator string to see which operation to perform */
long eval_op(long x, char *op, long y) {
if (strcmp(op, "+") == 0) { return x + y; }
if (strcmp(op, "-") == 0) { return x - y; }
if (strcmp(op, "*") == 0) { return x * y; }
if (strcmp(op, "/") == 0) { return x / y; }
return 0;
} long eval(mpc_ast_t *t) { /* If tagged as number return it directly.
* 有 number 标签的节点一定是一个数字,并且没有子节点
* 直接将其转换为一个数字。
*/
if (strstr(t->tag, "number")) {
return atoi(t->contents);
} /* The operator is always second child.
* 如果一个节点有 expr 标签,但没有 number 标签,那么它的第二个子节点肯定是操作符。
* 这个操作符后面的子节点肯定是操作数。
*/
char *op = t->children[1]->contents;
long x = eval(t->children[2]); /* 迭代剩余的子节点,并求值。 */
int i = 3;
while (strstr(t->children[i]->tag, "expr")) {
x = eval_op(x, op, eval(t->children[i]));
i++;
}
return x;
} int main(int argc, char *argv[]) { /* 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 : <number> | '(' <operator> <expr>+ ')' ; \
lispy : /^/ <operator> <expr>+ /$/ ; \
",
Number, Operator, Expr, Lispy); puts("Lispy Version 0.1");
puts("Press Ctrl+c to Exit\n"); while(1) {
char *input = NULL; input = readline("lispy> ");
add_history(input); /* Attempt to parse the user input */
mpc_result_t r; if (mpc_parse("<stdin>", input, Lispy, &r)) {
/* On success print and delete the AST */
long result = eval(r.output);
printf("%li\n", result);
mpc_ast_delete(r.output);
} else {
/* Otherwise print and delete the Error */
mpc_err_print(r.error);
mpc_err_delete(r.error);
} free(input); } /* Undefine and delete our parsers */
mpc_cleanup(4, Number, Operator, Expr, Lispy); return 0;
}

编译:

gcc -std=c99 -Wall parsing.c mpc.c -lreadline -lm -o parsing

运行:

$ ./parsing
Lispy Version 0.1
Press Ctrl+c to Exit lispy> - (* 10 10) (+ 1 1 1)
97
lispy> + 5 6
11

抽象语法树与行为树



行为树和抽象语法树之间有一个细微但非常重要的区别,我们应该区别对待(这促成了解析器的改写)。

简单来说,行为树是带有上下文的 AST。上下文是一个函数返回的类型的信息,或者两个地方使用的变量实际上是相同的变量。 因为它需要弄清楚并记住所有这些上下文,生成行为树的代码需要大量的命名空间查找表和其他的东西。

一旦我们有了行为树,运行代码就很容易了。 每个行为节点都有一个函数 “execute”,它接受一些输入,不管行为应该如何(包括可能调用子行为),返回行为的输出。 这是行为中的解释器。

用 C 语言开发一门编程语言 — 抽象语法树的更多相关文章

  1. 编译器开发系列--Ocelot语言1.抽象语法树

    从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树 ...

  2. JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

  3. AST抽象语法树

    抽象语法树简介 (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并 ...

  4. 理解Babel是如何编译JS代码的及理解抽象语法树(AST)

    Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是?   很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都 ...

  5. 【Static Program Analysis - Chapter 2】 代码的表征之抽象语法树

    抽象语法树:AbstractSyntaxTrees 定义(wiki): 在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是 ...

  6. 抽象语法树(AST)

    AST描述 在计算机科学中,抽象语法树(AST)或语法树是用编程语言编写的源代码的抽象语法结构的树表示.树的每个节点表示在源代码中出现的构造.语法是“抽象的”,因为它不代表真实语法中出现的每个细节,而 ...

  7. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  8. 五分钟了解抽象语法树(AST)babel是如何转换的?

    抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure acco ...

  9. 抽象语法树 Abstract syntax tree

    什么是抽象语法树? 在计算机科学中,抽象语法和抽象语法树其实是源代码的抽象语法结构的树状表现形式 在线编辑器 我们常用的浏览器就是通过将js代码转化为抽象语法树来进行下一步的分析等其他操作.所以将js ...

  10. 从Babel开始认识AST抽象语法树

    前言 AST抽象语法树想必大家都有听过这个概念,但是不是只停留在听过这个层面呢.其实它对于编程来讲是一个非常重要的概念,当然也包括前端,在很多地方都能看见AST抽象语法树的影子,其中不乏有vue.re ...

随机推荐

  1. 深入理解HashMap和LinkedHashMap的区别

    目录 简介 LinkedHashMap详解 插入 访问 removeEldestEntry 总结 深入理解HashMap和LinkedHashMap的区别 简介 我们知道HashMap的变量顺序是不可 ...

  2. OpenHarmony设备环境查询:Environment

      开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等,需要用到Environment设备环境查询. Environment是ArkUI框架在应用程序启动时创 ...

  3. openstack-train-ovs-ceph 部署

    第一章 Openstack简介 https://baike.baidu.com/item/OpenStack/342467?fr=aladdin Openstack框架图![img](file:/// ...

  4. js调用摄像头,实现简单的视频展台软件

    参考文档: web api:https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices codeantenna:https://code ...

  5. Centos 6.4 配置网页服务器

    Centos 6.4 配置网页服务器 (2013-08-08 22:59:09) 转载▼   分类:linux系统 今天值班,在单位找一台电脑安装了Centos 6.4操作系统. 一.安装软件 yum ...

  6. 【转】Java程序员常用工具类库 - 目录

    原文地址:http://rensanning.iteye.com/blog/1553076 有人说当你开始学习Java的时候,你就走上了一条不归路,在Java世界里,包罗万象,从J2SE,J2ME,J ...

  7. 深入解析Rivest Cipher 4:理论与实践

    第一章:引言 密码学简介: 密码学是研究如何保护通信和信息安全的学科.它涉及加密算法.解密算法.密钥管理等内容,旨在确保信息在传输和存储过程中不被未经授权的人所获取或篡改.密码学可以分为对称加密和非对 ...

  8. Vue3.0里为什么要用 Proxy API 替代 defineProperty API

    一.Object.defineProperty 定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 为什么能实现响应式 ...

  9. OS如何保持对计算机的控制权?

    前面我们提到:OS希望在保持控制权的同时,为用户提供高性能的并发. 那么OS究竟是如何保持对计算机的控制权呢?这似乎是一个令人迷惑(但很重要!)的问题:OS也是进程,自然也需要计算资源.那既然我们希望 ...

  10. gitee基于webhooks实现前端简单自动化部署

    1.为什么采用自动化部署 简而言之,程序员优秀传统:懒 =>高级生产力. 基于gitee进行的自动化部署,服务器环境为Ubuntu 基于webhooks进行的自动化部署更加轻快便捷 2.部署步骤 ...