Implement for / loop / [k]each loops
This commit is contained in:
parent
25c872f4ee
commit
1634b9439b
9 changed files with 310 additions and 29 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
45
src/functional-tests/each.at
Normal file
45
src/functional-tests/each.at
Normal 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
|
||||
46
src/functional-tests/for.at
Normal file
46
src/functional-tests/for.at
Normal 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
|
||||
55
src/functional-tests/keach.at
Normal file
55
src/functional-tests/keach.at
Normal 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
|
||||
25
src/functional-tests/loop.at
Normal file
25
src/functional-tests/loop.at
Normal 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
|
||||
|
|
@ -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
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue