From 1634b9439b9767fde22cf79d9ddacd9b33d3e0ca Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Tue, 7 Mar 2023 21:37:37 +0100 Subject: [PATCH] Implement for / loop / [k]each loops --- src/CMakeLists.txt | 4 ++ src/apfl.h | 3 ++ src/builtins.c | 61 ++++++++++++++++-------------- src/context.c | 30 +++++++++++++++ src/functional-tests/each.at | 45 ++++++++++++++++++++++ src/functional-tests/for.at | 46 +++++++++++++++++++++++ src/functional-tests/keach.at | 55 +++++++++++++++++++++++++++ src/functional-tests/loop.at | 25 +++++++++++++ src/globals.apfl | 70 ++++++++++++++++++++++++++++++++++- 9 files changed, 310 insertions(+), 29 deletions(-) create mode 100644 src/functional-tests/each.at create mode 100644 src/functional-tests/for.at create mode 100644 src/functional-tests/keach.at create mode 100644 src/functional-tests/loop.at diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7575ea..130b2c8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,7 +98,11 @@ functionaltest("len") functionaltest("not") functionaltest("type") functionaltest("if") +functionaltest("loop") functionaltest("while") +functionaltest("for") +functionaltest("keach") +functionaltest("each") functionaltest("eq") functionaltest("chained-assignments") functionaltest("dictionary-assignments") diff --git a/src/apfl.h b/src/apfl.h index 9bad5cd..83e0b53 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -715,6 +715,9 @@ void apfl_get_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in void apfl_set_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in_list, apfl_stackidx value); // Get the length of a string/list/dict. The value stays on the stack, size_t apfl_len(apfl_ctx, apfl_stackidx); +// Iterate over a dict value, which is popped off the stack first. Calls the callback `it` repeatedly for every +// key-value pair, which will be pushed on to the stack. Iteration stops, if `it` returns false. +void apfl_iterate_dict(apfl_ctx, apfl_stackidx dict, void *opaque, bool (*it)(apfl_ctx, void *)); // Get a string view into a APFL_VALUE_STRING value. The value stays on the stack. // The returned string view is only guaranteed to be valid, as long as the value is on the stack. struct apfl_string_view apfl_get_string(apfl_ctx, apfl_stackidx); diff --git a/src/builtins.c b/src/builtins.c index d1607ac..77787cd 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -308,38 +308,18 @@ type(apfl_ctx ctx) } static void -impl_while(apfl_ctx ctx) +impl_loop(apfl_ctx ctx) { - size_t argc = apfl_len(ctx, 0); - if (argc != 2) { - apfl_raise_const_error(ctx, "while needs 2 functions as arguments"); - } - - apfl_get_list_member_by_index(ctx, 0, 0); - if (apfl_get_type(ctx, -1) != APFL_VALUE_FUNC) { - apfl_raise_const_error(ctx, "while needs 2 functions as arguments"); - } - apfl_get_list_member_by_index(ctx, 0, 1); - if (apfl_get_type(ctx, -1) != APFL_VALUE_FUNC) { - apfl_raise_const_error(ctx, "while needs 2 functions as arguments"); - } - - apfl_drop(ctx, 0); - - apfl_push_nil(ctx); // Return value in case of no iteration - + ONE_ARG(ctx, "loop"); + apfl_list_create(ctx, 0); for (;;) { - apfl_copy(ctx, 0); - apfl_list_create(ctx, 0); + apfl_copy(ctx, -2); + apfl_copy(ctx, -2); apfl_call(ctx, -2, -1); if (!apfl_is_truthy(ctx, -1)) { - break; + apfl_push_nil(ctx); + return; } - - apfl_drop(ctx, -1); - apfl_copy(ctx, 1); - apfl_list_create(ctx, 0); - apfl_call(ctx, -2, -1); } } @@ -596,6 +576,30 @@ set_func_name(apfl_ctx ctx) apfl_set_func_name(ctx, -2, -1); } +static bool +iterate_dict_callback(apfl_ctx ctx, void *opaque) +{ + (void)opaque; + + apfl_list_create(ctx, 2); + apfl_list_append(ctx, -1, -3); + apfl_list_append(ctx, -1, -2); + apfl_copy(ctx, -2); + apfl_call(ctx, -1, -2); + + return apfl_is_truthy(ctx, -1); +} + +static void +iterate_dict(apfl_ctx ctx) +{ + apfl_get_list_member_by_index(ctx, 0, 0); + apfl_get_list_member_by_index(ctx, 0, 1); + apfl_drop(ctx, 0); + apfl_iterate_dict(ctx, -2, NULL, iterate_dict_callback); + apfl_push_nil(ctx); +} + static void add_builtin(apfl_ctx ctx, const char *name, apfl_cfunc func) { @@ -630,7 +634,7 @@ apfl_builtins(apfl_ctx ctx) add_builtin(ctx, "not", not); add_builtin(ctx, "len", len); add_builtin(ctx, "type", type); - add_builtin(ctx, "while", impl_while); + add_builtin(ctx, "loop", impl_loop); add_builtin(ctx, "gc", impl_gc); add_builtin(ctx, "backtrace", impl_backtrace); add_builtin(ctx, "fopen", impl_fopen); @@ -642,4 +646,5 @@ apfl_builtins(apfl_ctx ctx) add_builtin(ctx, "-serialize-bytecode", serialize_bytecode); add_builtin(ctx, "-unserialize-bytecode", unserialize_bytecode); add_builtin(ctx, "set-func-name", set_func_name); + add_builtin(ctx, "iterate-dict", iterate_dict); } diff --git a/src/context.c b/src/context.c index 3083c81..0535a2c 100644 --- a/src/context.c +++ b/src/context.c @@ -1357,6 +1357,36 @@ apfl_len(apfl_ctx ctx, apfl_stackidx index) return 0; } +static void +iterate_dict_inner(apfl_ctx ctx, apfl_stackidx dict_index, void *opaque, bool (*callback)(apfl_ctx, void *)) +{ + struct apfl_value dict_value = apfl_value_set_cow_flag(apfl_stack_must_pop(ctx, dict_index)); + if (!apfl_value_add_as_tmproot(&ctx->gc, dict_value)) { + apfl_raise_alloc_error(ctx); + } + + if (dict_value.type != VALUE_DICT) { + apfl_raise_errorfmt(ctx, "Expected dict value, got {value:type}", dict_value); + } + + HASHMAP_EACH(&(dict_value.dict->map), it) { + struct apfl_value *k = apfl_hashmap_cursor_peek_key(it); + apfl_stack_must_push(ctx, *k); + struct apfl_value *v = apfl_hashmap_cursor_peek_value(it); + apfl_stack_must_push(ctx, *v); + if (!callback(ctx, opaque)) { + break; + } + } +} + +void +apfl_iterate_dict(apfl_ctx ctx, apfl_stackidx dict, void *opaque, bool (*it)(apfl_ctx, void *)) +{ + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + iterate_dict_inner(ctx, dict, opaque, it); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); +} static bool get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value) { diff --git a/src/functional-tests/each.at b/src/functional-tests/each.at new file mode 100644 index 0000000..0d1a523 --- /dev/null +++ b/src/functional-tests/each.at @@ -0,0 +1,45 @@ +===== script ===== +l = ['foo 'bar 'baz] +each l { v -> + print v +} +each [] { v -> + print "???" +} + +d := [ + 'a -> 'foo + 'b -> 'bar + 'c -> 'baz +] +seen-foo := seen-bar := seen-baz := 0 +seen-someting-else := false + +each d { +'foo -> + seen-foo = + 1 seen-foo +'bar -> + seen-bar = + 1 seen-bar +'baz -> + seen-baz = + 1 seen-baz +_ -> + seen-someting-else = true +} + +keach [->] { k v -> + print "???" +} + +print 'seen-foo seen-foo +print 'seen-bar seen-bar +print 'seen-baz seen-baz +print 'seen-someting-else seen-someting-else + +===== output ===== +foo +bar +baz +seen-foo 1 +seen-bar 1 +seen-baz 1 +seen-someting-else false diff --git a/src/functional-tests/for.at b/src/functional-tests/for.at new file mode 100644 index 0000000..b265483 --- /dev/null +++ b/src/functional-tests/for.at @@ -0,0 +1,46 @@ +===== script ===== +for 10 print +print "---" +for 2 10 print +print "---" +for 10 0 print +print "---" +for 20 5 44 print + +===== output ===== +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +--- +2 +3 +4 +5 +6 +7 +8 +9 +--- +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +--- +20 +25 +30 +35 +40 diff --git a/src/functional-tests/keach.at b/src/functional-tests/keach.at new file mode 100644 index 0000000..41105ab --- /dev/null +++ b/src/functional-tests/keach.at @@ -0,0 +1,55 @@ +===== script ===== +l = ['foo 'bar 'baz] +keach l { k v -> + print k v +} +keach [] { k v -> + print "???" +} + +d := [ + 'a -> 'foo + 'b -> 'bar + 'c -> 'baz +] +seen-a := seen-b := seen-c := 0 +seen-someting-else := false +a-ok := b-ok := c-ok := false + +keach d { +'a v -> + seen-a = + seen-a 1 + a-ok = == v 'foo +'b v -> + seen-b = + seen-b 1 + b-ok = == v 'bar +'c v -> + seen-c = + seen-c 1 + c-ok = == v 'baz +_ _ -> + seen-someting-else = true +} + +keach [->] { k v -> + print "???" +} + +print 'seen-a seen-a +print 'a-ok a-ok +print 'seen-b seen-b +print 'b-ok b-ok +print 'seen-c seen-c +print 'c-ok c-ok +print 'seen-someting-else seen-someting-else + +===== output ===== +0 foo +1 bar +2 baz +seen-a 1 +a-ok true +seen-b 1 +b-ok true +seen-c 1 +c-ok true +seen-someting-else false diff --git a/src/functional-tests/loop.at b/src/functional-tests/loop.at new file mode 100644 index 0000000..3b65bad --- /dev/null +++ b/src/functional-tests/loop.at @@ -0,0 +1,25 @@ +===== script ===== +loop { + print "foo" + false +} + +i := 0 +loop { + i = + i 1 + print i + < i 10 +} + +===== output ===== +foo +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/src/globals.apfl b/src/globals.apfl index 079a68e..94b28a4 100644 --- a/src/globals.apfl +++ b/src/globals.apfl @@ -17,7 +17,7 @@ not := builtins.not len := builtins.len type := builtins.type - while := builtins.while + loop := builtins.loop gc := builtins.gc backtrace := builtins.backtrace fopen := builtins.fopen @@ -33,6 +33,38 @@ builtins.set-func-name f name } + while := { cond body -> + res := nil + loop { + if (cond) { + res = (body) + true + } { + false + } + } + res + } + + for := { + end body -> + for 0 end body + start end body -> + if (> start end) { + for start -1 end body + } { + for start 1 end body + } + start step end body -> + end-not-reached := if (> step 0) {{ < start end }} {{ > start end }} + out := nil + while end-not-reached { + out = body start + start = + start step + } + out + } + & := { ~strings -> join "" strings } @@ -58,6 +90,37 @@ !>= := -named '!>= (compose not >=) !<= := -named '!<= (compose not <=) + has := { + pred cmp y -> + { x -> + cmp (pred x) y + } + pred y -> + has pred == y + } + + keach := { + d?(has type 'dict) body -> + out := nil + builtins.iterate-dict d { k v -> + out = body k v + true + } + out + l?(has type 'list) body -> + out := nil + for (len l) { i -> + out = body i l@i + } + out + } + + each := { container body -> + keach container { _ v -> + body v + } + } + # Dictionary of exported functions [ 'if -> if @@ -78,7 +141,9 @@ 'not -> not 'len -> len 'type -> type + 'loop -> loop 'while -> while + 'for -> for 'gc -> gc 'backtrace -> backtrace 'fopen -> fopen @@ -92,10 +157,13 @@ '& -> & 'partial -> partial 'compose -> compose + 'has -> has '!= -> != '!> -> !> '!< -> !< '!>= -> !>= '!<= -> !<= + 'keach -> keach + 'each -> each ] }