求值

现在知道了 lval 类型的使用方法,我们需要用它来替换掉之前使用的 long 类型。

这不仅仅是简单地将 long 替换为 lval,我们还需要修改函数使其能正确处理数字或是错误作为输入的情况。

eval_op 函数中,如果检测到错误,函数应该立即返回,当且仅当两个操作数都为数字类型时才做计算。另外,对于本章开头的除数为零的错误,也应该返回错误信息。

  1. lval eval_op(lval x, char* op, lval y) {
  2. /* If either value is an error return it */
  3. if (x.type == LVAL_ERR) { return x; }
  4. if (y.type == LVAL_ERR) { return y; }
  5. /* Otherwise do maths on the number values */
  6. if (strcmp(op, "+") == 0) { return lval_num(x.num + y.num); }
  7. if (strcmp(op, "-") == 0) { return lval_num(x.num - y.num); }
  8. if (strcmp(op, "*") == 0) { return lval_num(x.num * y.num); }
  9. if (strcmp(op, "/") == 0) {
  10. /* If second operand is zero return error */
  11. return y.num == 0
  12. ? lval_err(LERR_DIV_ZERO)
  13. : lval_num(x.num / y.num);
  14. }
  15. return lval_err(LERR_BAD_OP);
  16. }

另外,eval 函数也需要小小地修整一下,为数字转换部分增加一点错误处理代码。

新代码中,我们选用 strtol 函数进行字符串到数字的转换,因为可以通过检测 errno 变量确定是否转换成功。这无疑比使用 atoi 函数更为明智。

  1. lval eval(mpc_ast_t* t) {
  2. if (strstr(t->tag, "number")) {
  3. /* Check if there is some error in conversion */
  4. errno = 0;
  5. long x = strtol(t->contents, NULL, 10);
  6. return errno != ERANGE ? lval_num(x) : lval_err(LERR_BAD_NUM);
  7. }
  8. char* op = t->children[1]->contents;
  9. lval x = eval(t->children[2]);
  10. int i = 3;
  11. while (strstr(t->children[i]->tag, "expr")) {
  12. x = eval_op(x, op, eval(t->children[i]));
  13. i++;
  14. }
  15. return x;
  16. }

最后的一小步!使用新定义的打印函数:

  1. lval result = eval(r.output);
  2. lval_println(result);
  3. mpc_ast_delete(r.output);

完成!尝试运行新程序,确保除数为零时不会崩溃了:)

  1. lispy> / 10 0
  2. Error: Division By Zero!
  3. lispy> / 10 2
  4. 5