表达式求值
求值函数的具体行为和之前的并无太大变化。我们需要将其适配本章定义的 lval*
以及更加灵活的表达式定义。其实可以把求值函数想象成某种转换器--它读取 lval*
作为输入,通过某种方式将其转化为新的 lval*
并输出。在有些时候,求值函数不对输入做任何修改,原封不动的将其返回;有些时候,它会对输入的做一些改动;而在大多数情况下,它会将输入的 lval*
删除,返回完全不同的东西。如果要返回新的东西,一定要记得将原有的作为输入的 lval*
删除。
对于 S-表达式,我们首先遍历它所有的子节点,如果子节点有任何错误,我们就使用稍后定义的函数 lval_take
将遇到的第一个错误返回。
对于没有子节点的 S-表达式直接将其返回就可以了,这是为了处理空表达式 {}
的情况。另外,我们还需要检查只有一个子节点的表达式,例如 {5}
,这种情况我们应该将其包含的表达式返回。
如果以上情况都不成立,那我们就知道这是一个合法的表达式,有个多于一个的子节点。对于此种情况,我们使用稍后定义的函数 lval_pop
将第一个元素从表达式中分离开来,然后检查确保它是一个 symbol
。然后根据它的具体类型,将它和参数一起传入 builtin_op
函数计算求值。如果它不是 symbol
,我们就将它以及传进来的其它参数删除,然后返回一个错误。
对于其它的非 S-表达式类型,我们就直接将其返回。
lval* lval_eval_sexpr(lval* v) {
/* Evaluate Children */
for (int i = 0; i < v->count; i++) {
v->cell[i] = lval_eval(v->cell[i]);
}
/* Error Checking */
for (int i = 0; i < v->count; i++) {
if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
}
/* Empty Expression */
if (v->count == 0) { return v; }
/* Single Expression */
if (v->count == 1) { return lval_take(v, 0); }
/* Ensure First Element is Symbol */
lval* f = lval_pop(v, 0);
if (f->type != LVAL_SYM) {
lval_del(f); lval_del(v);
return lval_err("S-expression Does not start with symbol!");
}
/* Call builtin with operator */
lval* result = builtin_op(v, f->sym);
lval_del(f);
return result;
}
lval* lval_eval(lval* v) {
/* Evaluate Sexpressions */
if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(v); }
/* All other lval types remain the same */
return v;
}
上面的代码中用到了两个我们还没有定义的函数:lval_pop
和 lval_take
。这两个都是用于操作 lval
类型的通用型函数,我们在本章和之后的章节中都会用到。
lval_pop
函数将所操作的 S-表达式的第 i
个元素取出,并将在其后面的元素向前移动填补空缺,使得这个 S-表达式不再包含这个元素。然后将取出的元素返回。需要注意的是,这个函数并不会将这个 S- 表达式删除。它只是从中取出某个元素,剩下的元素都保持原样。这意味着这两部分最终都需要在某个地方使用 lval_del
函数删除。
lval_take
和 lval_pop
函数类似,不过它将取出元素之后剩下的列表删除了。它利用了 lval_pop
函数并做了一点小小的改变,却使得我们的代码可读性更高了一些。所以,不同于 lval_pop
,你只需负责使用 lval_del
删除取出的元素即可。
lval* lval_pop(lval* v, int i) {
/* Find the item at "i" */
lval* x = v->cell[i];
/* Shift memory after the item at "i" over the top */
memmove(&v->cell[i], &v->cell[i+1],
sizeof(lval*) * (v->count-i-1));
/* Decrease the count of items in the list */
v->count--;
/* Reallocate the memory used */
v->cell = realloc(v->cell, sizeof(lval*) * v->count);
return x;
}
lval* lval_take(lval* v, int i) {
lval* x = lval_pop(v, i);
lval_del(v);
return x;
}
我们还需要定义求值函数 builtin_op
,它和我们在之前章节定义的 eval_op
函数类似,改成了接受一个 lval*
来代表一系列的参数。该函数应该对参数做更加严格的检查,如果有任何参数不是数字类型的 lval*
,都应该返回一个错误。
首先,它确保所有的输入参数的类型都为数字。然后将第一个数字弹出开始计算。如果后面没有其它的子表达式,并且操作符为减号时,它会对第一个数字进行取反操作。这确保了类似于 (- 5) 这种表达式能够正确工作。
如果还有更多的参数,它就不断地从列表中取出,将其和之前的计算结果一起进行相应的数学运算。如果做除法时遇到被除数为零的情况,就将临时变量 x 和 y 以及参数列表删除,并返回一个错误。
如果没有错误,参数列表最终会被删除,并返回一个新的表达式。
lval* builtin_op(lval* a, char* op) {
/* Ensure all arguments are numbers */
for (int i = 0; i < a->count; i++) {
if (a->cell[i]->type != LVAL_NUM) {
lval_del(a);
return lval_err("Cannot operate on non-number!");
}
}
/* Pop the first element */
lval* x = lval_pop(a, 0);
/* If no arguments and sub then perform unary negation */
if ((strcmp(op, "-") == 0) && a->count == 0) {
x->num = -x->num;
}
/* While there are still elements remaining */
while (a->count > 0) {
/* Pop the next element */
lval* y = lval_pop(a, 0);
if (strcmp(op, "+") == 0) { x->num += y->num; }
if (strcmp(op, "-") == 0) { x->num -= y->num; }
if (strcmp(op, "*") == 0) { x->num *= y->num; }
if (strcmp(op, "/") == 0) {
if (y->num == 0) {
lval_del(x); lval_del(y);
x = lval_err("Division By Zero!"); break;
}
x->num /= y->num;
}
lval_del(y);
}
lval_del(a); return x;
}
到此我们的求值函数就完成了。我们只需要再次更新一下 main 函数,在其打印表达式之前,先将输入经由求值函数处理即可。
lval* x = lval_eval(lval_read(r.output));
lval_println(x);
lval_del(x);
现在,你应该可以向上一章一样,正确地对表达式进行求值了。稍作休息,尝试一下新的求值过程。确保能够正常运行,运行结果正确。在下一章,我们将充分利用本章做出的改变,实现一些更加酷炫的特性。
lispy> + 1 (* 7 5) 3
39
lispy> (- 100)
-100
lispy>
()
lispy> /
/
lispy> (/ ())
Error: Cannot operate on non-number!
lispy>