目录

文章目录

  • 目录
  • 前文列表
  • 异常捕获
    • 定义 Lisp Value 函数

前文列表

《用 C 语言开发一门编程语言 — 交互式解析器l》
《用 C 语言开发一门编程语言 — 跨平台的可移植性》
《用 C 语言开发一门编程语言 — 语法解析器》
《用 C 语言开发一门编程语言 — 抽象语法树》

异常捕获

在开发过程中,程序崩溃是很正常的。但我们希望最后发布的产品能够告诉用户错误出在哪里,而不是简单粗暴的退出。目前,我们的程序仅能打印出语法上的错误,但对于表达式求值过程中产生的错误却无能为力。

C 语言有很多种错误处理方式,但针对当前的项目,我们考虑将错误也作为表达式求值的一种结果。也就是说,在 Lispy 中,表达式求值的结果要么是数字,要么便是错误。举例说,表达式 + 1 2 求值会得到数字 3,而表达式 / 10 0 求值则会得到一个错误。

定义 Lisp Value 函数

为了达到这个目的,我们需要能表示这两种结果(成功 or 失败)的数据结构。简单起见,我们使用结构体来表示,并使用 type 字段来说明当前哪个字段是有意义的。结构体名为 lval,取义 Lisp Value,定义如下:

/* Declare New lval Struct */
typedef struct {int type;long num;int err;
} lval;

lval 的 type 和 err 字段的类型都是 int,这意味着它们皆由整数值来表示。之所以选用 int,是因为 “成功或失败” 符合二元对立的情形。但 C 语言中,没有 True or False 这样的 Boolean 数据类型,所以我们使用 0/1 代替:

  • 如果 type 为 0,那么此结构体表示一个数字。
  • 如果 type 为 1,那么此结构体表示一个错误。

并且,我们可以给这些数字起一个有意义的名字,以提高代码的可读性。通过 整型别名 这两个特征,我们很自然的会联想到枚举数据类型:

/* Create Enumeration of Possible lval Types */
enum {LVAL_NUM,  // 默认整型数值为 0LVAL_ERR   // 默认整型数值为 0 + 1
};

另外,Error 也必然是可以枚举的,所以同样使用枚举数据类型:

/* Create Enumeration of Possible Error Types */
enum {LERR_DIV_ZERO,  // 除数为零LERR_BAD_OP,   // 操作符未知LERR_BAD_NUM    // 操作数过大
};

我们再定义两个函数来完成 “lval 类型实例” 的初始化:

/* Create a new number type lval* 因为使用无名创建方式定义的 lval 结构体是自定义数据类型,* 所以我们可以使用 lval 来声明函数返回值类型。*/
lval lval_num(long x) {lval v;v.type = LVAL_NUM;v.num = x;return v;
}/* Create a new error type lval */
lval lval_err(int x) {lval v;v.type = LVAL_ERR;v.err = x;return v;
}/* Print an "lval" */
void lval_print(lval v) {switch (v.type) {/* In the case the type is a number print it *//* Then 'break' out of the switch. */case LVAL_NUM:printf("%li", v.num);break;/* In the case the type is an error */case LVAL_ERR:/* Check what type of error it is and print it */if (v.err == LERR_DIV_ZERO) {printf("Error: Division By Zero!");}if (v.err == LERR_BAD_OP)   {printf("Error: Invalid Operator!");}if (v.err == LERR_BAD_NUM)  {printf("Error: Invalid Number!");}break;}
}/* Print an "lval" followed by a newline */
void lval_println(lval v) {lval_print(v);putchar('\n');
}

最后,我们使用 lval 类型来替换掉之前使用的 long 类型,此外,我们还需要修改函数使其能正确处理数字或是错误作为输入的情况:

#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/* Create Enumeration of Possible lval Types */
enum {LVAL_NUM,LVAL_ERR
};/* Create Enumeration of Possible Error Types */
enum {LERR_DIV_ZERO,LERR_BAD_OP,LERR_BAD_NUM
};/* Declare New lval Struct* 使用 lval 枚举类型来替换掉之前使用的 long 类型。* 单存的 long 类型没办法携带成功或失败、若失败,是什么失败等信息。* 所以我们定义 lval 枚举类型来作为 “算子” 及 “结果”。*/
typedef struct {int type;long num;int err;
} lval;/* Create a new number type lval */
lval lval_num(long x) {lval v;v.type = LVAL_NUM;v.num = x;return v;
}/* Create a new error type lval */
lval lval_err(long x) {lval v;v.type = LVAL_ERR;v.err = x;return v;
}/* Print an "lval"* 通过对 lval 枚举类型变量的解析来完成对计算结果的解析。*/
void lval_print(lval v) {switch (v.type) {/* In the case the type is a number print it */case LVAL_NUM:printf("%li", v.num);break;/* In the case the type is an error */case LVAL_ERR:/* Check what type of error it is and print it */if (v.err == LERR_DIV_ZERO) {printf("Error: Division By Zero!");}else if (v.err == LERR_BAD_OP) {printf("Error: Invalid Operator!");}else if (v.err == LERR_BAD_NUM) {printf("Error: Invalid Number!");}break;}
}/* Print an "lval" followed by a newline */
void lval_println(lval v) {lval_print(v);putchar('\n');
}/* Use operator string to see which operation to perform */
lval eval_op(lval x, char *op, lval y) {/* If either value is an error return it* 如果 “算子” 的类型是错误,则直接返回。*/if (x.type == LVAL_ERR) { return x; }if (y.type == LVAL_ERR) { return y; }/* Otherwise do maths on the number values* 如果 “算子” 是 Number,则取出操作数进行运算。*/if (strcmp(op, "+") == 0) { return lval_num(x.num + y.num); }if (strcmp(op, "-") == 0) { return lval_num(x.num + y.num); }if (strcmp(op, "*") == 0) { return lval_num(x.num + y.num); }if (strcmp(op, "/") == 0) {/* If second operand is zero return error */if (y.type == LVAL_NUM) {return y.num == 0? lval_err(LERR_DIV_ZERO): lval_num(x.num / y.num);}}return lval_err(LERR_BAD_OP);
}lval eval(mpc_ast_t *t) {/* If tagged as number return it directly. */if (strstr(t->tag, "number")) {/* Check if there is some error in conversion */errno = 0;/* 使用 strtol 函数进行字符串到数字的转换,* 这样就可以通过检测 errno 变量确定是否转换成功,* 对数据类型转换的准确性进行了加强。*/long x = strtol(t->contents, NULL, 10);return errno != ERANGE? lval_num(x): lval_err(LERR_BAD_NUM);}/* The operator is always second child. */char *op = t->children[1]->contents;/* We store the third child in `x` */lval x = eval(t->children[2]);/* Iterate the remaining children and combining. */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 */lval result = eval(r.output);lval_println(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 -g -std=c99 -Wall parsing.c mpc.c -lreadline -lm -o parsing

运行:

$ ./parsing
Lispy Version 0.1
Press Ctrl+c to Exitlispy> / 10 0
Error: Division By Zero!
lispy> / 10 2
5
lispy>
<stdin>:1:1: error: expected '+', '-', '*' or '/' at end of input
lispy> / 10 2
5

用 C 语言开发一门编程语言 — 异常处理相关推荐

  1. 用 C 语言开发一门编程语言 — 字符串与文件加载

    目录 文章目录 目录 前文列表 字符串 读取字符串 注释 文件加载函数 命令行参数 打印函数 报错函数 源代码 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器> <用 C ...

  2. 用 C 语言开发一门编程语言 — 条件分支

    目录 文章目录 目录 前文列表 条件分支 排序函数 等于函数 if 函数 递归函数 源代码 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器> <用 C 语言开发一门编程语 ...

  3. 用 C 语言开发一门编程语言 — 基于 Lambda 表达式的函数设计

    目录 文章目录 目录 前文列表 函数 Lambda 表达式 函数设计 函数的存储 实现 Lambda 函数 函数的运行环境 函数调用 可变长的函数参数 源代码 前文列表 <用 C 语言开发一门编 ...

  4. 用 C 语言开发一门编程语言 — 变量元素设计

    目录 文章目录 目录 前文列表 变量 变量语法规则 变量的读取和存储 将变量加入 Lisp Value 体系 变量的计算 变量的定义与赋值 异常处理优化 源代码 前文列表 <用 C 语言开发一门 ...

  5. 用 C 语言开发一门编程语言 — Q-表达式

    目录 文章目录 目录 前文列表 Q-表达式 读取并存储输入 实现 Q-Expression 语法解析器 读取 Q-Expression 实现 Q-Expression 的函数 Head & T ...

  6. 用 C 语言开发一门编程语言 — S-表达式

    目录 文章目录 目录 前文列表 使用 S-表达式进行重构 读取并存储输入 实现 S-Expression 语法解析器 实现 S-Expression 存储器 实现 lval 变量的构造函数 实现 lv ...

  7. 用 C 语言开发一门编程语言 — 抽象语法树

    目录 文章目录 目录 前文列表 抽象语法树的结构 使用递归来遍历树结构 实现求值计算 抽象语法树与行为树 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器l> <用 C 语 ...

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

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

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

    目录 文章目录 目录 前文列表 实现跨平台的可移植性 使用预处理器指令 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器l> 实现跨平台的可移植性 理想情况下,我希望我的代码可以 ...

最新文章

  1. Linux中bashrc河bash_profile
  2. 转:在windows通过Xrdp软件远程桌面连接Fedora
  3. [py]django url 参数/reverse和HttpResponseRedirect
  4. Spring MVC零配置(全注解)(版本5.0.7)
  5. Matlab中pickic_法语「野餐」怎么写?不是picnic哦
  6. ASP.NET Core Identity 实战(3)认证过程
  7. git clone 报错 Clone failed: Authentication failed for
  8. python 描述器 详解_深入解析Python中的descriptor描述器的作用及用法
  9. PostreSQL崩溃试验全记录
  10. hana 查看表字段_hana 查看表数据库
  11. LeetCode 124. Binary Tree Maximum Path Sum
  12. spring学习笔记--IOC接口
  13. 分布式存储系统学习笔记(二)—分布式文件系统(4)—内容分发网络(CDN)
  14. SAP笔记-abap SD 定价公式(例程,即Formula)
  15. 当我跑步时我在想什么读后感
  16. 【附源码】计算机毕业设计SSM社区志愿者管理系统
  17. 【百度编辑器】修改上传图片缩略图大小
  18. python自动排版_你熟悉Python的代码规范吗?如何一键实现代码排版
  19. h5直播|微直播weLiveShow|视频h5|video直播
  20. js实现加buff功能

热门文章

  1. Xamarin Anroid开发教程之验证环境配置是否正确
  2. mysql 添加多条数据类型_向数据库添加多条数据类型
  3. c语言指针和结构体难点,C语言指针和结构体
  4. python布尔测试对象_面试题十九期-测试开发面试题之python系列-这个中~
  5. 基于AI的便携式神经假肢让截肢14年患者操作自如,高精度、低延迟
  6. python读取.set文件
  7. 一句话就能让AI找到3A游戏Bug?准确率达86%,Demo在线可玩
  8. 一个模型搞定图像标注、读图问答两件事,VQA准确率逼近人类水平 | Demo可玩...
  9. Meta AI发布图音文大一统模型Data2vec,4天在GitHub揽1.5万星
  10. 小时候糖吃多了,长大后记性会变差| Nature子刊最新研究