目录

前文列表

用 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, // 默认整型数值为 0
LVAL_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 Exit lispy> / 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. atitit.面向过程的编程语言异常处理 c语言 asp vbs 的try catch 实现

    atitit.面向过程的编程语言异常处理 c语言 asp vbs 的try catch 实现 1. 返回值法.and全局ERROR 变量法 1 2. 抛出异常Err.Raise 1 3. 实现try  ...

  2. Mac OSX下Go语言开发环境的搭建与配置--使用InteliJ IDEA 13

    折腾了一上午终于把go语言的ide配置好了. 其实GO语言的语法和特性早在去年的时候就学习了一遍.结果后来一直没机会进行开发,结果还是个GO小白.感叹一下,要学好一门编程语言唯一的途径就是多写代码.. ...

  3. go语言开发入门

    go语言开发入门 每个Go程序包含一个名为main的包以及其main函数,在初始化后,程序从main开始执行,避免引入不使用的包(编译不通过) 基础语法 基本数据类型 bool, byte int,i ...

  4. Go语言开发

    Go语言圣经(中文版)     Go编程语言规范 搭建Go开发及调试环境(LiteIDE + GoClipse) -- Windows篇           Go开发工具 Go命令行操作命令详细介绍 ...

  5. Go语言开发第一个Hello,World

    在网上看到go语言的各种评价,也是闻名已久,但是没有自己实践过,也不知道它的好,它的坏,今天就来试试第一个小程序 第一步.如何下载 1)下载go安装程序 下载地址:https://golang.org ...

  6. Go语言开发环境配置

    一.我为什么要学习go语言 当今已经是移动和云计算时代,Go出现在了工业向云计算转型的时刻,简单.高效.内 置并发原语和现代的标准库让Go语言尤其适合云端软件开发(毕竟它就是为此而设计的).到2014 ...

  7. (转载)Go语言开发环境配置

    一.我为什么要学习go语言 当今已经是移动和云计算时代,Go出现在了工业向云计算转型的时刻,简单.高效.内 置并发原语和现代的标准库让Go语言尤其适合云端软件开发(毕竟它就是为此而设计的).到2014 ...

  8. Go语言开发环境安装

    Go是Google开发的一种编译型,並發型,并具有垃圾回收功能的编程语言. 去http://golang.org/doc/install#download 下载相应的版本. 1.安装go语言:2.将g ...

  9. 第一行代码:以太坊(2)-使用Solidity语言开发和测试智能合约

    智能合约是以太坊的核心之一,用户可以利用智能合约实现更灵活的代币以及其他DApp.不过在深入讲解如何开发智能合约之前,需要先介绍一下以太坊中用于开发智能合约的Solidity语言,以及相关的开发和测试 ...

  10. unity3D用什么语言开发好?

    unity3D用什么语言开发好? 一.总结 一句话总结:选c# 同时U3D团队也会把支持的重心转移到C#,也就是说文档和示例以及社区支持的重心都在C#,C#的文档会是最完善的,C#的代码实例会是最详细 ...

随机推荐

  1. #线段树#LOJ 6029「雅礼集训 2017 Day1」市场

    题目 在长度为\(n(n\leq 10^5)\)的数列中, 需要满足区间加,区间下取整的操作 以及能够查询区间和以及区间最小值 除数\(d\)满足\(2\leq d\leq 10^9\) 加数\(c\ ...

  2. #博弈论#HDU 1847 Good Luck in CET-4 Everybody!

    题目 有\(n\)个石子,每次只能取2的自然数幂个, 取完石子的人获胜,问先手是否必胜 分析 如果不是3的倍数,那么取完一次一定能剩下3的倍数个, 反之亦然,那么3的倍数为必败状态 代码 #inclu ...

  3. 【直播回顾】OpenHarmony知识赋能第五期第一课——精益开源

    4月26日晚上19点,知识赋能第五期第一节课<精益开源--理解设计思维.精益创业.敏捷开发是如何应用到开源项目中>,在OpenHarmony开发者成长计划社群内成功举行. 本期课程,由开源 ...

  4. 安装HTMLTestRunner库

    安装 HTMLTestRunner 库的方法非常简单,直接 pip 就可以了 pip install html-testRunner 在 https://pypi.org/  中可以直接搜索到,并且官 ...

  5. HarmonyOS音频开发指导:使用AudioRenderer开发音频播放功能

      AudioRenderer是音频渲染器,用于播放PCM(Pulse Code Modulation)音频数据,相比AVPlayer而言,可以在输入前添加数据预处理,更适合有音频开发经验的开发者,以 ...

  6. 动态规划(二)——背包dp

    01背包问题(每个物品最多选一次) AcWing 2. 0/1背包问题 朴素の版本: #include <bits/stdc++.h> using namespace std; const ...

  7. vue使用 elementUI中el-upload的遇到的问题总结

    使用场景,使用el-upload上传文件,选择文件后不立即上传到服务器上,点击提交按钮时与其他form表单数据一起提交,类似的需求,相信有很多小伙伴遇到,可能也会遇到跟我一起的问题,在这里记录一下 & ...

  8. gRPC入门学习之旅(七)

    gRPC入门学习之旅(一) gRPC入门学习之旅(二) gRPC入门学习之旅(三) gRPC入门学习之旅(四) gRPC入门学习之旅(五) gRPC入门学习之旅(六) 3.6.创建gRPC的桌面应用客 ...

  9. 力扣33(java&python)-搜索旋转排序数组(中等)

    题目: 整数数组 nums 按升序排列,数组中的值 互不相同 . 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 ...

  10. CF1535F String Distance

    \(CF1535F\ \ String\ Distance\) 题意 给 \(n\) 个长度均为 \(len\) 的字符串 \(T_1,T_2,\dots T_n\),定义 \(f(a,b)\) 为将 ...