用 C 语言开发一门编程语言 — 异常处理
目录
前文列表
《用 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;
- 1
- 2
- 3
- 4
- 5
- 6
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
};
- 1
- 2
- 3
- 4
- 5
另外,Error 也必然是可以枚举的,所以同样使用枚举数据类型:
/* Create Enumeration of Possible Error Types */
enum {
LERR_DIV_ZERO, // 除数为零
LERR_BAD_OP, // 操作符未知
LERR_BAD_NUM // 操作数过大
};
- 1
- 2
- 3
- 4
- 5
- 6
我们再定义两个函数来完成 “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');
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
最后,我们使用 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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
编译:
gcc -g -std=c99 -Wall parsing.c mpc.c -lreadline -lm -o parsing
- 1
运行:
$ ./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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。
原文链接:is-cloud.blog.csdn.net/article/details/105399192
- 点赞
- 收藏
- 关注作者
评论(0)