Implement for / loop / [k]each loops

This commit is contained in:
Laria 2023-03-07 21:37:37 +01:00
parent 25c872f4ee
commit 1634b9439b
9 changed files with 310 additions and 29 deletions

View file

@ -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")

View file

@ -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);

View file

@ -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);
}

View file

@ -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)
{

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
]
}