表达式求值

求值函数的具体行为和之前的并无太大变化。我们需要将其适配本章定义的 lval* 以及更加灵活的表达式定义。其实可以把求值函数想象成某种转换器--它读取 lval* 作为输入,通过某种方式将其转化为新的 lval* 并输出。在有些时候,求值函数不对输入做任何修改,原封不动的将其返回;有些时候,它会对输入的做一些改动;而在大多数情况下,它会将输入的 lval* 删除,返回完全不同的东西。如果要返回新的东西,一定要记得将原有的作为输入的 lval* 删除。

对于 S-表达式,我们首先遍历它所有的子节点,如果子节点有任何错误,我们就使用稍后定义的函数 lval_take 将遇到的第一个错误返回。

对于没有子节点的 S-表达式直接将其返回就可以了,这是为了处理空表达式 {} 的情况。另外,我们还需要检查只有一个子节点的表达式,例如 {5},这种情况我们应该将其包含的表达式返回。

如果以上情况都不成立,那我们就知道这是一个合法的表达式,有个多于一个的子节点。对于此种情况,我们使用稍后定义的函数 lval_pop 将第一个元素从表达式中分离开来,然后检查确保它是一个 symbol。然后根据它的具体类型,将它和参数一起传入 builtin_op 函数计算求值。如果它不是 symbol,我们就将它以及传进来的其它参数删除,然后返回一个错误。

对于其它的非 S-表达式类型,我们就直接将其返回。

  1. lval* lval_eval_sexpr(lval* v) {
  2. /* Evaluate Children */
  3. for (int i = 0; i < v->count; i++) {
  4. v->cell[i] = lval_eval(v->cell[i]);
  5. }
  6. /* Error Checking */
  7. for (int i = 0; i < v->count; i++) {
  8. if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
  9. }
  10. /* Empty Expression */
  11. if (v->count == 0) { return v; }
  12. /* Single Expression */
  13. if (v->count == 1) { return lval_take(v, 0); }
  14. /* Ensure First Element is Symbol */
  15. lval* f = lval_pop(v, 0);
  16. if (f->type != LVAL_SYM) {
  17. lval_del(f); lval_del(v);
  18. return lval_err("S-expression Does not start with symbol!");
  19. }
  20. /* Call builtin with operator */
  21. lval* result = builtin_op(v, f->sym);
  22. lval_del(f);
  23. return result;
  24. }
  1. lval* lval_eval(lval* v) {
  2. /* Evaluate Sexpressions */
  3. if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(v); }
  4. /* All other lval types remain the same */
  5. return v;
  6. }

上面的代码中用到了两个我们还没有定义的函数:lval_poplval_take。这两个都是用于操作 lval 类型的通用型函数,我们在本章和之后的章节中都会用到。

lval_pop 函数将所操作的 S-表达式的第 i 个元素取出,并将在其后面的元素向前移动填补空缺,使得这个 S-表达式不再包含这个元素。然后将取出的元素返回。需要注意的是,这个函数并不会将这个 S- 表达式删除。它只是从中取出某个元素,剩下的元素都保持原样。这意味着这两部分最终都需要在某个地方使用 lval_del 函数删除。

lval_takelval_pop 函数类似,不过它将取出元素之后剩下的列表删除了。它利用了 lval_pop 函数并做了一点小小的改变,却使得我们的代码可读性更高了一些。所以,不同于 lval_pop,你只需负责使用 lval_del 删除取出的元素即可。

  1. lval* lval_pop(lval* v, int i) {
  2. /* Find the item at "i" */
  3. lval* x = v->cell[i];
  4. /* Shift memory after the item at "i" over the top */
  5. memmove(&v->cell[i], &v->cell[i+1],
  6. sizeof(lval*) * (v->count-i-1));
  7. /* Decrease the count of items in the list */
  8. v->count--;
  9. /* Reallocate the memory used */
  10. v->cell = realloc(v->cell, sizeof(lval*) * v->count);
  11. return x;
  12. }
  1. lval* lval_take(lval* v, int i) {
  2. lval* x = lval_pop(v, i);
  3. lval_del(v);
  4. return x;
  5. }

我们还需要定义求值函数 builtin_op,它和我们在之前章节定义的 eval_op 函数类似,改成了接受一个 lval* 来代表一系列的参数。该函数应该对参数做更加严格的检查,如果有任何参数不是数字类型的 lval*,都应该返回一个错误。

首先,它确保所有的输入参数的类型都为数字。然后将第一个数字弹出开始计算。如果后面没有其它的子表达式,并且操作符为减号时,它会对第一个数字进行取反操作。这确保了类似于 (- 5) 这种表达式能够正确工作。

如果还有更多的参数,它就不断地从列表中取出,将其和之前的计算结果一起进行相应的数学运算。如果做除法时遇到被除数为零的情况,就将临时变量 x 和 y 以及参数列表删除,并返回一个错误。

如果没有错误,参数列表最终会被删除,并返回一个新的表达式。

  1. lval* builtin_op(lval* a, char* op) {
  2. /* Ensure all arguments are numbers */
  3. for (int i = 0; i < a->count; i++) {
  4. if (a->cell[i]->type != LVAL_NUM) {
  5. lval_del(a);
  6. return lval_err("Cannot operate on non-number!");
  7. }
  8. }
  9. /* Pop the first element */
  10. lval* x = lval_pop(a, 0);
  11. /* If no arguments and sub then perform unary negation */
  12. if ((strcmp(op, "-") == 0) && a->count == 0) {
  13. x->num = -x->num;
  14. }
  15. /* While there are still elements remaining */
  16. while (a->count > 0) {
  17. /* Pop the next element */
  18. lval* y = lval_pop(a, 0);
  19. if (strcmp(op, "+") == 0) { x->num += y->num; }
  20. if (strcmp(op, "-") == 0) { x->num -= y->num; }
  21. if (strcmp(op, "*") == 0) { x->num *= y->num; }
  22. if (strcmp(op, "/") == 0) {
  23. if (y->num == 0) {
  24. lval_del(x); lval_del(y);
  25. x = lval_err("Division By Zero!"); break;
  26. }
  27. x->num /= y->num;
  28. }
  29. lval_del(y);
  30. }
  31. lval_del(a); return x;
  32. }

到此我们的求值函数就完成了。我们只需要再次更新一下 main 函数,在其打印表达式之前,先将输入经由求值函数处理即可。

  1. lval* x = lval_eval(lval_read(r.output));
  2. lval_println(x);
  3. lval_del(x);

现在,你应该可以向上一章一样,正确地对表达式进行求值了。稍作休息,尝试一下新的求值过程。确保能够正常运行,运行结果正确。在下一章,我们将充分利用本章做出的改变,实现一些更加酷炫的特性。

  1. lispy> + 1 (* 7 5) 3
  2. 39
  3. lispy> (- 100)
  4. -100
  5. lispy>
  6. ()
  7. lispy> /
  8. /
  9. lispy> (/ ())
  10. Error: Cannot operate on non-number!
  11. lispy>