<commit_before>#include <math.h>    // fmod
#include <stdarg.h>  // va_list
#include <stdio.h>   // FILE

#include "core.h"
#include "scheme.h"
#include "value.h"
#include "vm.h"
#include "write.h"

// Checks if there are exactly n arguments (if at_least is false)
//                  or at least n arguments (if at_least is true)
bool arity_check(vm_t *vm, const char *fn_name, value_t args, int n,
                 bool at_least) {
    int argc = cons_len(args);
    if (!at_least) {  // exactly
        if (argc != n) {
            error_runtime(vm, "%s: not enough args: %d expected, %d given!",
                          fn_name, n, argc);
            return false;
        }
    } else {  // at least
        if (argc < n) {
            error_runtime(vm, "%s: not enough args: >= %d expected, %d given!",
                          fn_name, n, argc);
            return false;
        }
    }
    return true;
}

/* /== -------------------------- ==\ */
/* *** core environment functions *** */
/* \== -------------------------- ==/ */

/* *** core - numbers *** */

static value_t add(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return NUM_VAL(0.0F);
    }
    value_t eargs = eval_list(vm, env, args);
    double result = 0.0F;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "+: argument is not a number!");
            return NIL_VAL;
        }
        result += AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t multiply(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return NUM_VAL(1.0F);
    }
    value_t eargs = eval_list(vm, env, args);
    double result = 1.0F;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "*: argument is not a number!");
            return NIL_VAL;
        }
        result *= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t subtract(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "-", args, 1, true);
    double result = 0.0F;
    value_t eargs = eval_list(vm, env, args);
    value_t arg = AS_CONS(eargs)->car;
    result = AS_NUM(arg);
    if (IS_NIL(AS_CONS(eargs)->cdr)) {
        return NUM_VAL(-result);
    }
    value_t iter;
    SCM_FOREACH (arg, AS_CONS(AS_CONS(eargs)->cdr), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "-: argument is not a number!");
            return NIL_VAL;
        }
        result -= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t builtin_rem(vm_t *vm, env_t *env, value_t args) {
    // (remainder <n> <m>) => n `rem` m
    value_t eargs = eval_list(vm, env, args);
    arity_check(vm, "remainder", args, 2, false);
    value_t n = AS_CONS(eargs)->car;
    value_t m = AS_CONS(AS_CONS(eargs)->cdr)->car;

    if (!IS_NUM(n) || !IS_NUM(m)) {
        error_runtime(vm, "remainder: argument is not a number!");
        return NIL_VAL;
    }

    return NUM_VAL(fmod(AS_NUM(n), AS_NUM(m)));
}

static value_t builtin_div(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "/", args, 1, true);
    double result = 1.0F;
    value_t eargs = eval_list(vm, env, args);
    value_t arg = AS_CONS(eargs)->car;
    result = AS_NUM(arg);

    if (IS_NIL(AS_CONS(eargs)->cdr)) {
        return NUM_VAL(result);
    }
    value_t iter;
    SCM_FOREACH (arg, AS_CONS(AS_CONS(eargs)->cdr), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "/: argument is not a number!");
            return NIL_VAL;
        }
        result /= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

// TODO: we shouldn't need to remember <prev>,
//       we just need first...
static value_t gt(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, ">: argument is not a number!");
            return NIL_VAL;
        }
        if (!IS_NIL(prev)) {
            // if we find a pair where the '>' relation doesn't hold,
            // return false
            if (AS_NUM(prev) <= AS_NUM(arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

static value_t num_equal(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "=: argument is not a number!");
            return NIL_VAL;
        }
        if (!IS_NIL(prev)) {
            // if we find a pair where the '==' relation doesn't hold,
            // return false
            if (AS_NUM(prev) != AS_NUM(arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

/* *** core - predicates and equalities *** */

// Checks for eq? using the val_eq function from value.h
// BEWARE: It assumes transitivity (tries only a == b && b == c && c == d ...)
static value_t eq(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NIL(prev)) {
            // if we find a pair where the 'eq?' relation doesn't hold,
            // return false
            if (!val_eq(prev, arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

// Checks for equal? using the val_equal function from value.h
// BEWARE: It assumes transitivity (tries only a == b && b == c && c == d ...)
static value_t equal(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NIL(prev)) {
            // if we find a pair where the 'equal?' relation doesn't hold,
            // return false
            if (!val_equal(prev, arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

// type predicate functions
// they ARE NOT added to environment automatically
// therefore any change here must be done also in <scm_env_default> fn

#define TYPE_PREDICATE_FN(type, is_type_fn)                                \
    static value_t builtin_is_##type(vm_t *vm, env_t *env, value_t args) { \
        value_t eargs = eval_list(vm, env, args);                          \
        arity_check(vm, #type "?", eargs, 1, false);                       \
        value_t a = AS_CONS(eargs)->car;                                   \
        return BOOL_VAL(is_type_fn(a));                                    \
    }

TYPE_PREDICATE_FN(cons, IS_CONS)
TYPE_PREDICATE_FN(integer, IS_INT)
TYPE_PREDICATE_FN(number, IS_NUM)
TYPE_PREDICATE_FN(string, IS_STRING)
TYPE_PREDICATE_FN(symbol, IS_SYMBOL)
TYPE_PREDICATE_FN(procedure, IS_PROCEDURE)

// FIXME I have a feeling that this is not the correct functionality...
static value_t quote(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "quote", args, 1, false);
    return AS_CONS(args)->car;
}

static value_t builtin_list(vm_t *vm, env_t *env, value_t args) {
    return eval_list(vm, env, args);
}

static value_t builtin_define(vm_t *vm, env_t *env, value_t args) {
    cons_t *rest = AS_CONS(args);

    if (IS_SYMBOL(rest->car)) {  // (define <name> <body...>)
        symbol_t *sym = AS_SYMBOL(rest->car);
        cons_t *body = AS_CONS(rest->cdr);
        value_t val = eval(vm, env, body->car);
        variable_add(vm, env, sym, val);

        return VOID_VAL;
    } else if (IS_CONS(rest->car)) {  // (define (<name> <params...>) <body...>)
        symbol_t *sym = AS_SYMBOL(AS_CONS(rest->car)->car);
        value_t params = AS_CONS(rest->car)->cdr;
        cons_t *body = AS_CONS(rest->cdr);
        function_t *func = function_new(vm, env, params, PTR_VAL(body));
        variable_add(vm, env, sym, PTR_VAL(func));

        return VOID_VAL;
    } else {
        error_runtime(vm, "define: is wrong - first argument has to be"
                          "either a list or a symbol!");
        return NIL_VAL;
    }
}

static value_t define_macro(vm_t *vm, env_t *env, value_t args) {
    // (define-macro (<name> <params...>) <body...>)
    cons_t *rest = AS_CONS(args);
    if (!IS_CONS(rest->car)) {
        error_runtime(vm, "define-macro: first argument has to be a list!");
        return NIL_VAL;
    }

    symbol_t *sym = AS_SYMBOL(AS_CONS(rest->car)->car);
    value_t params = AS_CONS(rest->car)->cdr;
    cons_t *body = AS_CONS(rest->cdr);

    function_t *macro = macro_new(vm, env, params, PTR_VAL(body));
    variable_add(vm, env, sym, PTR_VAL(macro));

    return VOID_VAL;
}

static value_t lambda(vm_t *vm, env_t *env, value_t args) {
    // (lambda (<params...>) <body...>)

    if (IS_NIL(AS_CONS(args)->car)) {
        // no parameters -> (lambda () <body...>)
        value_t body = AS_CONS(args)->cdr;
        function_t *func = function_new(vm, env, NIL_VAL, body);
        return PTR_VAL(func);
    }

    if (IS_SYMBOL(AS_CONS(args)->car)) {
        // variadic -> (lambda param <body...>)
        value_t param = AS_CONS(args)->car;
        value_t body = AS_CONS(args)->cdr;
        function_t *func = function_new(vm, env, param, body);
        return PTR_VAL(func);
    }

    // TODO: Rewrite using SCM_FOREACH macro
    for (cons_t *cons = AS_CONS(AS_CONS(args)->car);;
         cons = AS_CONS(cons->cdr)) {
        if (!IS_SYMBOL(cons->car)) {
            error_runtime(vm, "lambda: all parameters must be symbols!");
            return NIL_VAL;
        }

        if (IS_NIL(cons->cdr)) {
            break;
        }
    }

    value_t params = AS_CONS(args)->car;
    value_t body = AS_CONS(args)->cdr;
    function_t *func = function_new(vm, env, params, body);
    return PTR_VAL(func);
}

// (if <condition> <then> <otherwise> ...)
static value_t builtin_if(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "if", args, 2, true);

    // First evaluate <condition>
    value_t condition = eval(vm, env, AS_CONS(args)->car);

    // if it's true, return <then>
    if (AS_BOOL(condition)) {
        value_t then = eval(vm, env, AS_CONS(AS_CONS(args)->cdr)->car);
        return eval(vm, env, then);
    }

    // If missing <otherwise> and <condition> is false, return false
    if (IS_NIL(AS_CONS(AS_CONS(args)->cdr)->cdr)) {
        return FALSE_VAL;
    }

    // Else do <otherwise>
    value_t otherwise = AS_CONS(AS_CONS(args)->cdr)->cdr;
    return begin(vm, env, otherwise);
}

static value_t builtin_cons(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "cons", args, 2, false);

    value_t car = eval(vm, env, AS_CONS(args)->car);
    value_t cdr = eval(vm, env, AS_CONS(AS_CONS(args)->cdr)->car);

    value_t cons = cons_fn(vm, car, cdr);
    return cons;
}

static value_t builtin_car(vm_t *vm, env_t *env, value_t args) {
    value_t eargs = eval_list(vm, env, args);
    arity_check(vm, "car", eargs, 1, false);
    value_t a = AS_CONS(eargs)->car;<commit_msg>Fixed <then> branch in 'if' being double-eval'd<commit_after>#include <math.h>    // fmod
#include <stdarg.h>  // va_list
#include <stdio.h>   // FILE

#include "core.h"
#include "scheme.h"
#include "value.h"
#include "vm.h"
#include "write.h"

// Checks if there are exactly n arguments (if at_least is false)
//                  or at least n arguments (if at_least is true)
bool arity_check(vm_t *vm, const char *fn_name, value_t args, int n,
                 bool at_least) {
    int argc = cons_len(args);
    if (!at_least) {  // exactly
        if (argc != n) {
            error_runtime(vm, "%s: not enough args: %d expected, %d given!",
                          fn_name, n, argc);
            return false;
        }
    } else {  // at least
        if (argc < n) {
            error_runtime(vm, "%s: not enough args: >= %d expected, %d given!",
                          fn_name, n, argc);
            return false;
        }
    }
    return true;
}

/* /== -------------------------- ==\ */
/* *** core environment functions *** */
/* \== -------------------------- ==/ */

/* *** core - numbers *** */

static value_t add(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return NUM_VAL(0.0F);
    }
    value_t eargs = eval_list(vm, env, args);
    double result = 0.0F;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "+: argument is not a number!");
            return NIL_VAL;
        }
        result += AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t multiply(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return NUM_VAL(1.0F);
    }
    value_t eargs = eval_list(vm, env, args);
    double result = 1.0F;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "*: argument is not a number!");
            return NIL_VAL;
        }
        result *= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t subtract(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "-", args, 1, true);
    double result = 0.0F;
    value_t eargs = eval_list(vm, env, args);
    value_t arg = AS_CONS(eargs)->car;
    result = AS_NUM(arg);
    if (IS_NIL(AS_CONS(eargs)->cdr)) {
        return NUM_VAL(-result);
    }
    value_t iter;
    SCM_FOREACH (arg, AS_CONS(AS_CONS(eargs)->cdr), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "-: argument is not a number!");
            return NIL_VAL;
        }
        result -= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

static value_t builtin_rem(vm_t *vm, env_t *env, value_t args) {
    // (remainder <n> <m>) => n `rem` m
    value_t eargs = eval_list(vm, env, args);
    arity_check(vm, "remainder", args, 2, false);
    value_t n = AS_CONS(eargs)->car;
    value_t m = AS_CONS(AS_CONS(eargs)->cdr)->car;

    if (!IS_NUM(n) || !IS_NUM(m)) {
        error_runtime(vm, "remainder: argument is not a number!");
        return NIL_VAL;
    }

    return NUM_VAL(fmod(AS_NUM(n), AS_NUM(m)));
}

static value_t builtin_div(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "/", args, 1, true);
    double result = 1.0F;
    value_t eargs = eval_list(vm, env, args);
    value_t arg = AS_CONS(eargs)->car;
    result = AS_NUM(arg);

    if (IS_NIL(AS_CONS(eargs)->cdr)) {
        return NUM_VAL(result);
    }
    value_t iter;
    SCM_FOREACH (arg, AS_CONS(AS_CONS(eargs)->cdr), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "/: argument is not a number!");
            return NIL_VAL;
        }
        result /= AS_NUM(arg);
    }
    return NUM_VAL(result);
}

// TODO: we shouldn't need to remember <prev>,
//       we just need first...
static value_t gt(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, ">: argument is not a number!");
            return NIL_VAL;
        }
        if (!IS_NIL(prev)) {
            // if we find a pair where the '>' relation doesn't hold,
            // return false
            if (AS_NUM(prev) <= AS_NUM(arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

static value_t num_equal(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NUM(arg)) {
            error_runtime(vm, "=: argument is not a number!");
            return NIL_VAL;
        }
        if (!IS_NIL(prev)) {
            // if we find a pair where the '==' relation doesn't hold,
            // return false
            if (AS_NUM(prev) != AS_NUM(arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

/* *** core - predicates and equalities *** */

// Checks for eq? using the val_eq function from value.h
// BEWARE: It assumes transitivity (tries only a == b && b == c && c == d ...)
static value_t eq(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NIL(prev)) {
            // if we find a pair where the 'eq?' relation doesn't hold,
            // return false
            if (!val_eq(prev, arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

// Checks for equal? using the val_equal function from value.h
// BEWARE: It assumes transitivity (tries only a == b && b == c && c == d ...)
static value_t equal(vm_t *vm, env_t *env, value_t args) {
    if (IS_NIL(args)) {
        return TRUE_VAL;
    }
    value_t eargs = eval_list(vm, env, args);
    value_t prev = NIL_VAL;
    value_t arg, iter;
    SCM_FOREACH (arg, AS_CONS(eargs), iter) {
        if (!IS_NIL(prev)) {
            // if we find a pair where the 'equal?' relation doesn't hold,
            // return false
            if (!val_equal(prev, arg)) {
                return FALSE_VAL;
            }
        }
        prev = arg;
    }
    return TRUE_VAL;
}

// type predicate functions
// they ARE NOT added to environment automatically
// therefore any change here must be done also in <scm_env_default> fn

#define TYPE_PREDICATE_FN(type, is_type_fn)                                \
    static value_t builtin_is_##type(vm_t *vm, env_t *env, value_t args) { \
        value_t eargs = eval_list(vm, env, args);                          \
        arity_check(vm, #type "?", eargs, 1, false);                       \
        value_t a = AS_CONS(eargs)->car;                                   \
        return BOOL_VAL(is_type_fn(a));                                    \
    }

TYPE_PREDICATE_FN(cons, IS_CONS)
TYPE_PREDICATE_FN(integer, IS_INT)
TYPE_PREDICATE_FN(number, IS_NUM)
TYPE_PREDICATE_FN(string, IS_STRING)
TYPE_PREDICATE_FN(symbol, IS_SYMBOL)
TYPE_PREDICATE_FN(procedure, IS_PROCEDURE)

// FIXME I have a feeling that this is not the correct functionality...
static value_t quote(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "quote", args, 1, false);
    return AS_CONS(args)->car;
}

static value_t builtin_list(vm_t *vm, env_t *env, value_t args) {
    return eval_list(vm, env, args);
}

static value_t builtin_define(vm_t *vm, env_t *env, value_t args) {
    cons_t *rest = AS_CONS(args);

    if (IS_SYMBOL(rest->car)) {  // (define <name> <body...>)
        symbol_t *sym = AS_SYMBOL(rest->car);
        cons_t *body = AS_CONS(rest->cdr);
        value_t val = eval(vm, env, body->car);
        variable_add(vm, env, sym, val);

        return VOID_VAL;
    } else if (IS_CONS(rest->car)) {  // (define (<name> <params...>) <body...>)
        symbol_t *sym = AS_SYMBOL(AS_CONS(rest->car)->car);
        value_t params = AS_CONS(rest->car)->cdr;
        cons_t *body = AS_CONS(rest->cdr);
        function_t *func = function_new(vm, env, params, PTR_VAL(body));
        variable_add(vm, env, sym, PTR_VAL(func));

        return VOID_VAL;
    } else {
        error_runtime(vm, "define: is wrong - first argument has to be"
                          "either a list or a symbol!");
        return NIL_VAL;
    }
}

static value_t define_macro(vm_t *vm, env_t *env, value_t args) {
    // (define-macro (<name> <params...>) <body...>)
    cons_t *rest = AS_CONS(args);
    if (!IS_CONS(rest->car)) {
        error_runtime(vm, "define-macro: first argument has to be a list!");
        return NIL_VAL;
    }

    symbol_t *sym = AS_SYMBOL(AS_CONS(rest->car)->car);
    value_t params = AS_CONS(rest->car)->cdr;
    cons_t *body = AS_CONS(rest->cdr);

    function_t *macro = macro_new(vm, env, params, PTR_VAL(body));
    variable_add(vm, env, sym, PTR_VAL(macro));

    return VOID_VAL;
}

static value_t lambda(vm_t *vm, env_t *env, value_t args) {
    // (lambda (<params...>) <body...>)

    if (IS_NIL(AS_CONS(args)->car)) {
        // no parameters -> (lambda () <body...>)
        value_t body = AS_CONS(args)->cdr;
        function_t *func = function_new(vm, env, NIL_VAL, body);
        return PTR_VAL(func);
    }

    if (IS_SYMBOL(AS_CONS(args)->car)) {
        // variadic -> (lambda param <body...>)
        value_t param = AS_CONS(args)->car;
        value_t body = AS_CONS(args)->cdr;
        function_t *func = function_new(vm, env, param, body);
        return PTR_VAL(func);
    }

    // TODO: Rewrite using SCM_FOREACH macro
    for (cons_t *cons = AS_CONS(AS_CONS(args)->car);;
         cons = AS_CONS(cons->cdr)) {
        if (!IS_SYMBOL(cons->car)) {
            error_runtime(vm, "lambda: all parameters must be symbols!");
            return NIL_VAL;
        }

        if (IS_NIL(cons->cdr)) {
            break;
        }
    }

    value_t params = AS_CONS(args)->car;
    value_t body = AS_CONS(args)->cdr;
    function_t *func = function_new(vm, env, params, body);
    return PTR_VAL(func);
}

// (if <condition> <then> <otherwise> ...)
static value_t builtin_if(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "if", args, 2, true);

    // First evaluate <condition>
    value_t condition = eval(vm, env, AS_CONS(args)->car);

    // if it's true, return <then>
    if (AS_BOOL(condition)) {
        value_t then = AS_CONS(AS_CONS(args)->cdr)->car;
        return eval(vm, env, then);
    }

    // If missing <otherwise> and <condition> is false, return false
    if (IS_NIL(AS_CONS(AS_CONS(args)->cdr)->cdr)) {
        return FALSE_VAL;
    }

    // Else do <otherwise>
    value_t otherwise = AS_CONS(AS_CONS(args)->cdr)->cdr;
    return begin(vm, env, otherwise);
}

static value_t builtin_cons(vm_t *vm, env_t *env, value_t args) {
    arity_check(vm, "cons", args, 2, false);

    value_t car = eval(vm, env, AS_CONS(args)->car);
    value_t cdr = eval(vm, env, AS_CONS(AS_CONS(args)->cdr)->car);

    value_t cons = cons_fn(vm, car, cdr);
    return cons;
}

static value_t builtin_car(vm_t *vm, env_t *env, value_t args) {
    value_t eargs = eval_list(vm, env, args);
    arity_check(vm, "car", eargs, 1, false);
    value_t a = AS_CONS(eargs)->car;
{'fraction_non_alphanumeric': 0.14625212575764182, 'fraction_numerical': 0.0017442113984214886, 'mean_word_length': 2.212044817927171, 'pattern_counts': {'":': 0, '<': 62, '<?xml version=': 0, '>': 168, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3972483', 'n_tokens_mistral': 8427, 'n_tokens_neox': 8236, 'n_words': 2069}