用 C 语言开发一门编程语言 — 异常处理

举报
云物互联 发表于 2021/08/06 01:16:52 2021/08/06
【摘要】 目录 文章目录 目录前文列表异常捕获定义 Lisp Value 函数 前文列表 《用 C 语言开发一门编程语言 — 交互式解析器l》 《用 C 语言开发一门编程语言 — 跨平台的可移植性》 《用 C 语言开发一门编程语言 — 语法解析器》 《用 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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。