zoukankan      html  css  js  c++  java
  • 用 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;
    

    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('
    ');
    }
    

    最后,我们使用 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] = '';
    
        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('
    ');
    }
    
    /* 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
    ");
    
        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

    相关阅读:

  • 相关阅读:
    iOS的生命周期
    【iOS开发】在 App 中加入 AdMob 广告
    iOS性能优化:Instruments
    动态的计算行高 加载数据源 有多少显示多少 tableView 包含 colloctionView 显示复杂的界面写法
    Java String.split()注意点
    Java eclipse export jar包 包括第三方引入的jar
    微信网页授权获取用户基本信息
    android连接本地tomcat服务器,报timeout
    iOS内存管理retain,assign,copy,strong,weak
    static与全局与局部变量的区别
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309352.html
Copyright © 2011-2022 走看看