解析器将识别的语言定义一个语法。我么这里采用BNF描述:

expression: number
| expression ‘*’ expression
| expression ‘/’ expression
| expression ‘+’ expression
| expression ‘-’ expression
;

number: T_INTLIT
;
我们都知道BNF定义的语法是递归定义的,那么我们也需要一个递归函数去解析输入的表达式。在我们现有的语法元素可以构成的表达式中第一个语法元素始终为数字,否则就是语法错误。其后可能是一个运算符,或者只有一个数字。那么我们可以用如下伪代码表示我们的递归下降解析函数:

function expression() {
Scan and check the first token is a number. Error if it’s not
Get the next token
If we have reached the end of the input, return, i.e. base case

Otherwise, call expression()
}
让我们来模拟一次此函数的运行,输入为2 + 3 - 5 T_EOF其中T_EOF 是反映输入结束的标记。

expression0:
Scan in the 2, it’s a number
Get next token, +, which isn’t T_EOF
Call expression()

expression1:Scan in the 3, it's a numberGet next token, -, which isn't T_EOFCall expression()expression2:Scan in the 5, it's a numberGet next token, T_EOF, so return from expression2return from expression1

return from expression0
为了进行语义分析,我们需要代码来解释识别的输入,或者将其转换为另一种格式,例如汇编代码。在旅程的这一部分,我们将为输入构建一个解释器。但要实现这一目标,我们首先要将输入转换为抽象语法树。

抽象语法树的节点结构定义如下:

// defs.h
// AST node types
enum {
A_ADD, A_SUBTRACT, A_MULTIPLY, A_DIVIDE, A_INTLIT
};

// Abstract Syntax Tree structure
struct ASTnode {
int op; // “Operation” to be performed on this tree
struct ASTnode *left; // Left and right child trees
struct ASTnode *right;
int intvalue; // For A_INTLIT, the integer value
};
节点元素op表示该节点的类型,当op的值为A_ADD、A_SUBTRACT等运算符时,该节点具有左右两颗子树,我们将使用op代表的运算符对左右两棵子树的值做计算;当op的值为A_INTLIT时,代表该节点是整数值,是叶节点,节点元素intvalue存储着该整数的值。

tree.c 中的代码具有构建 AST 的功能。函数mkastnode()生成一个节点并返回指向节点的指针:

// tree.c
// Build and return a generic AST node
struct ASTnode *mkastnode(int op, struct ASTnode *left,
struct ASTnode *right, int intvalue) {
struct ASTnode *n;

// Malloc a new ASTnode
n = (struct ASTnode *) malloc(sizeof(struct ASTnode));
if (n == NULL) {
fprintf(stderr, “Unable to malloc in mkastnode()\n”);
exit(1);
}
// Copy in the field values and return it
n->op = op;
n->left = left;
n->right = right;
n->intvalue = intvalue;
return (n);
}
我们对其进一步封装出两个常用的函数,分别用来创建左子树与叶节点:

// Make an AST leaf node
struct ASTnode *mkastleaf(int op, int intvalue) {
return (mkastnode(op, NULL, NULL, intvalue));
}

// Make a unary AST node: only one child
struct ASTnode *mkastunary(int op, struct ASTnode *left, int intvalue) {
return (mkastnode(op, left, NULL, intvalue));
我们将使用 AST 来存储我们识别的每个表达式,以便稍后我们可以递归遍历它来计算表达式的最终值。 我们确实想处理数学运算符的优先级。 这是一个例子。 考虑表达式 2 * 3 4 * 5。现在,乘法比加法具有更高的优先级。 因此,我们希望将乘法操作数绑定在一起并在进行加法之前执行这些操作。

如果我们生成 AST 树看起来像这样:

      +/ \/   \/     \*       */ \     / \
2   3   4   5

然后,在遍历树时,我们会先执行 2 * 3,然后是 4 * 5。一旦我们有了这些结果,我们就可以将它们传递给树的根来执行加法。

在开始解析语法树之前,我们需要一个将扫描到的token转换为AST节点操作值的函数,如下:

// expr.c
// Convert a token into an AST operation.
int arithop(int tok) {
switch (tok) {
case T_PLUS:
return (A_ADD);
case T_MINUS:
return (A_SUBTRACT);
case T_STAR:
return (A_MULTIPLY);
case T_SLASH:
return (A_DIVIDE);
default:
fprintf(stderr, “unknown token in arithop() on line %d\n”, Line);
exit(1);
}
}
我们需要一个函数来检查下一个标记是否是整数文字,并构建一个 AST 节点来保存文字值。如下:

// Parse a primary factor and return an
// AST node representing it.
static struct ASTnode *primary(void) {
struct ASTnode *n;

// For an INTLIT token, make a leaf AST node for it
// and scan in the next token. Otherwise, a syntax error
// for any other token type.
switch (Token.token) {
case T_INTLIT:
n = mkastleaf(A_INTLIT, Token.intvalue);
scan(&Token);
return (n);
default:
fprintf(stderr, “syntax error on line %d\n”, Line);
exit(1);
}
}
这里的Token是一个全局变量,保存着扫描到的最新的值。

那么我们现在可以写解析输入表达式生成AST的方法:

// Return an AST tree whose root is a binary operator
struct ASTnode *binexpr(void) {
struct ASTnode *n, *left, *right;
int nodetype;

// Get the integer literal on the left.
// Fetch the next token at the same time.
left = primary();

// If no tokens left, return just the left node
if (Token.token == T_EOF)
return (left);

// Convert the token into a node type
nodetype = arithop(Token.token);

// Get the next token in
scan(&Token);

// Recursively get the right-hand tree
right = binexpr();

// Now build a tree with both sub-trees
n = mkastnode(nodetype, left, right, 0);
return (n);
}
这只是一个子简单的解析器,他的解析结果没有实现优先级的调整,解析结果如下:

 *
/ \

2 +
/
3 *
/
4 5
正确的树状结构应该是这样的:

      +/ \/   \/     \*       */ \     / \
2   3   4   5

我们将在下一节实现生成一个正确的AST。

那么接下来我们来试着写代码递归的解释这颗AST。我们以正确的语法树为例,伪代码:

interpretTree:
First, interpret the left-hand sub-tree and get its value
Then, interpret the right-hand sub-tree and get its value
Perform the operation in the node at the root of our tree
on the two sub-tree values, and return this value
调用过程可以用如下过程表示:

interpretTree0(tree with +):
Call interpretTree1(left tree with *):
Call interpretTree2(tree with 2):
No maths operation, just return 2
Call interpretTree3(tree with 3):
No maths operation, just return 3
Perform 2 * 3, return 6

Call interpretTree1(right tree with *):
Call interpretTree2(tree with 4):
No maths operation, just return 4
Call interpretTree3(tree with 5):
No maths operation, just return 5
Perform 4 * 5, return 20

Perform 6 + 20, return 26
这是在interp.c 中并依据上述伪代码写的功能:

// Given an AST, interpret the
// operators in it and return
// a final value.
int interpretAST(struct ASTnode *n) {
int leftval, rightval;

// Get the left and right sub-tree values
if (n->left)
leftval = interpretAST(n->left);
if (n->right)
rightval = interpretAST(n->right);

switch (n->op) {
case A_ADD:
return (leftval + rightval);
case A_SUBTRACT:
return (leftval - rightval);
case A_MULTIPLY:
return (leftval * rightval);
case A_DIVIDE:
return (leftval / rightval);
case A_INTLIT:
return (n->intvalue);
default:
fprintf(stderr, “Unknown AST operator %d\n”, n->op);
exit(1);
}
}
这里还有一些其他代码,比如调用 main() 中的解释器:

scan(&Token); // Get the first token from the input
n = binexpr(); // Parse the expression in the file
printf("%d\n", interpretAST(n)); // Calculate the final result
exit(0);
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

C语言编译器开发之旅(二):解析器相关推荐

  1. 用C语言实现SGF格式围棋棋谱解析器

    这是本人(liigo)独立实现的SGF格式围棋棋谱文件解析器,本文介绍其实现细节.网络上肯定可以找到完善的开源的SGF解析器,这是毋庸置疑的,我不直接使用它们,也不参考它们的实现代码,而是自己独立编码 ...

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

    目录 文章目录 目录 前文列表 编程语言的本质 词法分析 语法分析 使用 MPC 解析器组合库 安装 快速入门 实现波兰表达式的语法解析 波兰表达式 正则表达式 代码实现 前文列表 <用 C 语 ...

  3. 7.SpringMVC 配置式开发-ModelAndView和视图解析器

    ModelAndView 1.Model(模型) 1.model的本质就是HashMap,向模型中添加数据,就是往HashMap中去添加数据 2.HashMap 是一个单向查找数组,单向链表数组 3. ...

  4. 【编译器实现笔记】2. 解析器(parser)

    原文地址:https://lisperator.net/pltut/ 解析器的作用 解析器在分词器之上,直接操作 token 流,不用处理单个字符,把代码解析成一个个对象 lambda 解析器 解析标 ...

  5. go语言web开发系列之二十二:用signintech/gopdf库生成带有图片和表格的pdf

    一,安装需要用到的库: 1,gopdf库的地址: https://github.com/signintech/gopdf 2,gopdf库安装的命令: liuhongdi@ku:~$ go get - ...

  6. go语言web开发系列之二十:用gorm+excelize库生成excel表格并下载

    一,安装所需的库 1,excelize库的地址: https://github.com/360EntSecGroup-Skylar/excelize ,excelize库的文档: https://xu ...

  7. FFmpeg开发之旅(二)---音频解码

    [写在前面] 前面我介绍了视频解码的流程,发现基础讲得有点少. 因此这里附上一些额外的基础内容:理解PCM音频数据格式 本篇主要内容: 1.FFmpeg音频解码基本流程 2.libswresample ...

  8. 学会在Linux环境下用c语言多文件制作lrc歌词解析器

    效果: 需要掌握的知识 1. 链表的熟悉运用. 懂得在链表的插入,排序. 2. 学会Linux下基本命令指令. sudo apt-get install vim //下载vim sudo apt-ge ...

  9. c语言编译器手机版使用说明,C语言编译器怎么用,C语言编译器使用教程

    C语言编译器怎么用的详细解析 一.本站站点下载并安装,解压缩后,运行exe安装文件,单击"下一步" 二.选择已安装的组件后,单击"下一步" 三.单击" ...

最新文章

  1. JVM内存溢出的几种情形
  2. MSB3721 命令““C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\bin\nvcc.exe“ 已退出 返回代码为1
  3. malloc分配内存的原理?
  4. SAP CRM WebClient UI session restart
  5. cvMorphologyEx() 多种图像形态学
  6. 搜狗词库.scel文件转换为.txt文件(python3)
  7. CPU虚拟化是否开启
  8. 搭建一个简单的服务器
  9. 基于umijs+lerna+qiankun的微前端实现
  10. 360浏览器怎么设置主页
  11. 2 年前端面试心路历程(字节跳动、YY、虎牙、BIGO)
  12. 如何用c语言给信息加密,求助:如何用C语言实现LFSR加密
  13. matlab 三维立体图,利用matlab将三维数据画成三维立体图
  14. 13-TDengine使用JDBC-JNI连接报错:JNI connection is NULL
  15. 一图了解交通拥堵治理措施
  16. 基于内容的视频信息检索系统
  17. vue3+howler.js实现音频播放,兼容大多数音频格式
  18. 2022跨境电商是宝还是坑-成都扬帆跨境电商
  19. 当容器遇上Ceph和Gluster……
  20. Java框架springBoot企业级进销存ERP系统

热门文章

  1. python写android的App(kivy框架)的实践(1)
  2. Gitee如何上传整个项目文件夹
  3. 怎么才能做好团队管理|方法论加模型案例(附常用管理模型、人员架构图)
  4. 5. Java数组、排序和查找
  5. QObject::moveToThread:
  6. Excel表格复制到Foxmail不显示边框
  7. copypng emitted errors
  8. 零信任的过去、现在和未来
  9. http和tcp区别
  10. linux系统 服务器 安装,服务器上怎么安装linux系统