Mercurial > lbo > hg > ylisp
changeset 102:da80a755723f
built-ins: Implement `for`, add some more built-ins, use macros for errors
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Wed, 28 Aug 2019 10:29:52 +0200 |
parents | 1e5a8a96753d |
children | 354afb93f898 |
files | src/built-ins.c src/types.h |
diffstat | 2 files changed, 112 insertions(+), 38 deletions(-) [+] |
line wrap: on
line diff
--- a/src/built-ins.c Wed Aug 28 10:28:34 2019 +0200 +++ b/src/built-ins.c Wed Aug 28 10:29:52 2019 +0200 @@ -19,20 +19,18 @@ }; static const struct ybuiltin_id_mapping YBUILTIN_ID_MAPPING[] = { - {"__invalid", YBUILTIN_UNDEF}, {"for", YBUILTIN_FOR}, {"let", YBUILTIN_LET}, - {"defn", YBUILTIN_DEFN}, {"+", YBUILTIN_PLUS}, {"-", YBUILTIN_MINUS}, - {"*", YBUILTIN_MULT}, {"/", YBUILTIN_DIV}, {"if", YBUILTIN_IF}, - {"seq", YBUILTIN_SEQ}, {"==", YBUILTIN_EQ}, {"<", YBUILTIN_LT}, + {"undef", YBUILTIN_UNDEF}, {"for", YBUILTIN_FOR}, {"let", YBUILTIN_LET}, + {"defn", YBUILTIN_DEFN}, {"+", YBUILTIN_PLUS}, {"-", YBUILTIN_MINUS}, + {"*", YBUILTIN_MULT}, {"/", YBUILTIN_DIV}, {"if", YBUILTIN_IF}, + {"seq", YBUILTIN_SEQ}, {"==", YBUILTIN_EQ}, {"<", YBUILTIN_LT}, + {"car", YBUILTIN_CAR}, {"cdr", YBUILTIN_CDR}, {"push", YBUILTIN_PUSH}, }; static const char* YBUILTIN_ENUM_STR[] = { - "BUILTIN:UNDEF", "BUILTIN:FOR", "BUILTIN:LET", "BUILTIN:DEFN", - - "BUILTIN:+", "BUILTIN:-", "BUILTIN:*", "BUILTIN:/", - - "BUILTIN:IF", "BUILTIN:SEQ", - - "BUILTIN:EQ", "BUILTIN:LT", + "BUILTIN:UNDEF", "BUILTIN:FOR", "BUILTIN:LET", "BUILTIN:DEFN", + "BUILTIN:+", "BUILTIN:-", "BUILTIN:*", "BUILTIN:/", + "BUILTIN:IF", "BUILTIN:SEQ", "BUILTIN:EQ", "BUILTIN:LT", + "BUILTIN:CAR", "BUILTIN:CDR", "BUILTIN:PUSH", }; /// Ownership of msg is transferred to this function, ownership of offending is @@ -51,7 +49,7 @@ return exception; } - +/// Return the ID corresponding to the builtin name in the string. static YBUILTIN_TYPE ybuiltin_id(ystr_t* sym) { const size_t builtins = sizeof(YBUILTIN_ID_MAPPING) / sizeof(struct ybuiltin_id_mapping); @@ -63,6 +61,7 @@ return YBUILTIN_UNDEF; } +/// Make a symbolic reference into a built-in reference, if possible. bool ybuiltin_translate(yexpr_t* expr) { if (expr->typ != YEXPR_REF) return false; if (yref_type(&expr->value.ref) != YREF_SYM) return false; @@ -75,50 +74,120 @@ static void nothing(void){}; -/// Expects a list expression starting with built-in "let" on the stack. It is not modified. +/// Return from a built-in function with a "bad expression" exception. +#define BADEXPRFAIL(BUILTINTYPE, invalid_expr) \ + return ybuiltin_type_error( \ + YBUILTIN_##BUILTINTYPE, \ + ystr_new("BUG: not a valid `" #BUILTINTYPE "`expression on stack"), \ + &invalid_expr) + +/// Return from a built-in function with a "not enough arguments" exception. +#define NOTENOUGHFAIL(BUILTINTYPE) \ + return ybuiltin_type_error( \ + YBUILTIN_##BUILTINTYPE, \ + ystr_new("BUG: not enough arguments on call stack"), NULL); +/// Return from a built-in function with a "type mismatch" exception. + +#define TYPEFAIL(BUILTINTYPE, invalid_expr, want) \ + { \ + yexpr_t exc = ybuiltin_type_error( \ + YBUILTIN_##BUILTINTYPE, \ + ystr_new("type mismatch for " #BUILTINTYPE "; want " want \ + "received something else"), \ + &invalid_expr); \ + return exc; \ + } +/// Returns a YEXPR_UNDEF expression. TODO: Automatically add symbolic reference +/// "undef". +yexpr_t ybuiltin_fn_undef(yeval_state_t* state) { return yexpr_new(); } + +/// Expects a list expression starting with built-in "for" on the stack. It is +/// not modified. +/// +/// The list should follow this scheme: (BUILTIN:FOR ref iterated-items +/// [expr...] expr) +/// +/// Returns a list of each of the last expressions in the for loop. If the last +/// expression is an undefined value (`(undef)`), the value is not added to the +/// resulting list. +yexpr_t ybuiltin_fn_for(yeval_state_t* state) { +#define _pattern "(for ref (item [...]) [exprs...] expr)" + yexpr_t for_expr; + yvec_t* for_list = &for_expr.value.list; + size_t items = 0, counter = 0; + yvec_t result; + + if (!yvec_pop(&state->call_stack, &for_expr)) NOTENOUGHFAIL(FOR); + if (for_expr.typ != YEXPR_LIST) BADEXPRFAIL(FOR, for_expr); + if (for_expr.value.list.len < 4) TYPEFAIL(FOR, for_expr, _pattern); + + yexpr_t* for_id = YVEC_AT(for_list, counter++, yexpr_t); + if (for_id->typ != YEXPR_BUILTIN || for_id->value.builtin != YBUILTIN_FOR) + TYPEFAIL(FOR, for_expr, _pattern); + yexpr_t* for_ref = YVEC_AT(for_list, counter++, yexpr_t); + if (for_ref->typ != YEXPR_REF) TYPEFAIL(FOR, for_expr, _pattern); + yexpr_t* for_items = YVEC_AT(for_list, counter++, yexpr_t); + if (for_items->typ != YEXPR_LIST) TYPEFAIL(FOR, for_expr, _pattern); + items = for_items->value.list.len; + YVEC_INIT(&result, items, yexpr_t); + + yvec_t* for_items_l = &for_items->value.list; + yeval_state_t emptystate = {.call_stack = YVEC_NEW(NULL, 0, yexpr_t)}; + // counter now points to the first expression. + for (size_t i = 0; i < for_items_l->len; i++) { + yexpr_t* expr = YVEC_AT(for_items_l, i, yexpr_t); + yexpr_t evald = yeval(&emptystate, expr, false); + yvalue_t value = {.typ = YVALUE_EXPR, .value.expr = evald}; + + // For each item, bind reference and execute body. + yvalue_set(for_ref->value.ref, &value); + yexpr_t expr_result = yeval(&emptystate, expr, false); + // Make deep copy of result + yexpr_t expr_result_cp = yexpr_copy(&expr_result); + + if (expr_result.typ != YEXPR_UNDEF) YVEC_PUSH(&result, &expr_result_cp); + + yexpr_destroy(&expr_result); + yvalue_destroy(&value); + } + + yexpr_t result_expr = yexpr_new(); + yexpr_set_list(&result_expr, result); + return result_expr; + +#undef _pattern +} + +/// Expects a list expression starting with built-in "let" on the stack. It is +/// not modified. /// /// Returns an UNDEF expression by default, or an EXCEPTION if something went /// wrong. yexpr_t ybuiltin_fn_let(yeval_state_t* state) { +#define _pattern "(let ref [expr ...] expr)" yexpr_t let, *ref; size_t counter = 0; yvec_t* letlist = &let.value.list; - if (!yvec_pop(&state->call_stack, &let)) goto notenoughfail; - if (let.typ != YEXPR_LIST) goto badexprfail; - if (let.value.list.len < 3) goto notenoughfail; + if (!yvec_pop(&state->call_stack, &let)) NOTENOUGHFAIL(LET); + if (let.typ != YEXPR_LIST) BADEXPRFAIL(LET, let); + if (let.value.list.len < 3) BADEXPRFAIL(LET, let); yexpr_t let_id = *YVEC_AT(letlist, counter++, yexpr_t); if (let_id.typ != YEXPR_BUILTIN || let_id.value.builtin != YBUILTIN_LET) - goto badexprfail; + BADEXPRFAIL(LET, let); ref = YVEC_AT(letlist, counter++, yexpr_t); - if (ref->typ != YEXPR_REF) goto typefail; + if (ref->typ != YEXPR_REF) TYPEFAIL(LET, let, _pattern); + assert(counter == 2); yvalue_t newvalue = { .typ = YVALUE_EXPR, - .value.expr = yeval_list_return_last(state, letlist, 2)}; + .value.expr = yeval_list_return_last(state, letlist, counter)}; yvalue_set(ref->value.ref, &newvalue); return yexpr_new(); -badexprfail: - return ybuiltin_type_error( - YBUILTIN_LET, ystr_new("BUG: not a let expression on stack"), &let); - -notenoughfail: - return ybuiltin_type_error( - YBUILTIN_LET, - ystr_new("BUG: not enough arguments on call stack"), - NULL); - -typefail: - nothing(); - yexpr_t exc = ybuiltin_type_error( - YBUILTIN_LET, - ystr_new( - "type mismatch: want (let reference [exprs...] expr), received something else"), - &let); - return exc; +#undef _pattern } /** @@ -126,7 +195,8 @@ * * TODO: write built-ins. */ -static const ybuiltin_fn YBUILTIN_FNS[] = {NULL, NULL, ybuiltin_fn_let}; +static const ybuiltin_fn YBUILTIN_FNS[] = {ybuiltin_fn_undef, ybuiltin_fn_for, + ybuiltin_fn_let}; const char* ybuiltin_name(YBUILTIN_TYPE t) { assert(t * sizeof(const char*) < sizeof(YBUILTIN_ENUM_STR));