view src/eval.c @ 132:bad34d9a4443

eval: Implement function calls (with stack-refs)
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 01 Sep 2019 19:24:24 +0200
parents 63650268d006
children 08d010255027
line wrap: on
line source

#include "eval.h"

#include "built-ins.h"
#include "expr.h"
#include "value.h"

yexpr_t yeval_call_func(yeval_state_t* state, yfunc_t* ref, yexpr_t* call) {
    assert(call->typ == YEXPR_LIST);
    if (ref->args.len != call->value.list.len - 1) {
        yexpr_t exc = yexpr_new();
        ystr_t msg = ystr_new(NULL);
        ystr_build(&msg,
                   "Unexpected number of arguments in call to function %s: "
                   "Want %u, got %u",
                   ystr_str(&ref->name), ref->args.len,
                   call->value.list.len - 1);
        yexpr_set_exception(&exc, msg);
        return exc;
    }
    yvec_t* args = &ref->args;
    size_t stack_start = state->call_stack.len - 1;
    for (size_t i = 0; i < args->len; i++) {
        yexpr_t evald =
            yeval(state, YVEC_AT(&call->value.list, i + 1, yexpr_t), false);
        YVEC_PUSH(&state->call_stack, &evald);
    }
    yexpr_t result = yeval_list_return_last(state, &ref->body, 0);
    for (size_t i = 0; i < args->len; i++) {
        yexpr_t old;
        yvec_pop(&state->call_stack, &old);
        yexpr_destroy(&old);
    }
    return result;
}

yexpr_t yeval_list_return_last(yeval_state_t* state, yvec_t* list, size_t off) {
    if (list->len - off == 0) return yexpr_new();
    assert(list->size == sizeof(yexpr_t));
    for (size_t i = off; i < (list->len - (unsigned int)1); i++) {
        yeval(state, YVEC_AT(list, i, yexpr_t), true);
    }
    return yeval(state, YVEC_AT(list, list->len - 1, yexpr_t), false);
}

yexpr_t yeval(yeval_state_t* state, yexpr_t* expr, bool in_place) {
    yexpr_t result;
    if (in_place) result = yexpr_new();
    switch (expr->typ) {
        case YEXPR_UNDEF:
            // UNDEFs are often the result of removing defns from the parsed
            // tree.
        case YEXPR_ATOM:
        case YEXPR_NUMBER:
        case YEXPR_EXCEPTION:
        case YEXPR_BUILTIN:
            if (in_place) break;  // values can't have side effects
            // Flat copy is enough.
            return *expr;
        case YEXPR_STRING:
            if (in_place) break;
            result = *expr;
            result.value.str = ystr_clone(&expr->value.str);
            return result;
        case YEXPR_REF:
            // Reference is resolved if expr-ref. func-refs are not
            // resolved. NOTE: Can this result in infinite recursion?
            if (in_place) break;  // values can't have side effects
            // YREF_STACK references a value pushed on the stack.
            if (yref_type(&expr->value.ref) == YREF_STACK) {
                result = yexpr_copy(YVEC_AT(
                    &state->call_stack,
                    state->call_stack.len - 1 - yref_cix(&expr->value.ref),
                    yexpr_t));
                return result;
            }
            yvalue_t* val = yvalue_get(expr->value.ref);
            if (val->typ == YVALUE_EXPR) {
                result = yexpr_copy(&val->value.expr);
                return result;
            } else if (val->typ == YVALUE_FUNC) {
                return *expr;
            }
            assert(val->typ == YVALUE_FUNC || val->typ == YVALUE_EXPR);
            break;
        case YEXPR_LIST:
            // Is empty list?
            if (expr->value.list.len == 0) {
                if (in_place) break;
                result = *expr;
                result.value.list = yvec_clone(&expr->value.list);
                return result;
            }
            // Is builtin call?
            yexpr_t* first = YVEC_AT(&expr->value.list, 0, yexpr_t);
            if (first->typ == YEXPR_BUILTIN) {
                // both must be yexpr_t vecs.
                assert(expr->value.list.size == state->call_stack.size);
                // Builtins expect their call list on the stack.
                YVEC_PUSH(&state->call_stack, expr);
                return ybuiltin_run(first->value.builtin, state);
            }
            // Is function call?
            if (first->typ == YEXPR_REF) {
                yvalue_t* val = yvalue_get(first->value.ref);
                // Call function.
                if (val->typ == YVALUE_FUNC) {
                    result = yeval_call_func(state, &val->value.func, expr);
                    if (in_place) {
                        yexpr_destroy(&result);
                        return result;
                    }
                    return result;
                } else if (val->typ == YVALUE_EXPR) {
                    // resolve as normal below; this seems to be a "data
                    // list".
                } else {
                    assert(val->typ == YVALUE_FUNC || val->typ == YVALUE_EXPR);
                }
            }

            result = *expr;

            // Don't store results
            if (in_place) {
                for (size_t i = 0; i < result.value.list.len; i++) {
                    yexpr_t* elem = YVEC_AT(&result.value.list, i, yexpr_t);
                    yexpr_t elem_result =
                        yeval(state, elem, /* in_place= */ true);
                    yexpr_destroy(&elem_result);
                }
                return result;
            }

            // Store results; input list is copied shallowly, and assigned
            // with copied values from yeval.
            result.value.list = yvec_clone(&expr->value.list);
            for (size_t i = 0; i < result.value.list.len; i++) {
                yexpr_t* elem = YVEC_AT(&result.value.list, i, yexpr_t);
                yexpr_t elem_result = yeval(state, elem, /* in_place= */ false);
                *elem = elem_result;
            }
            return result;
        default:
            assert(false /* yeval is not yet fully implemented */);
    }
    return result;
}