diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d95e454..c669b50 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -136,6 +136,8 @@ functionaltest("get-optional") functionaltest("has-key") functionaltest("tonumber") functionaltest("re") +functionaltest("slice") +functionaltest("splice") install(TARGETS apfl DESTINATION lib) install(TARGETS apfl-bin DESTINATION bin) diff --git a/src/apfl.h b/src/apfl.h index fcee367..c585d62 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -770,6 +770,19 @@ void apfl_list_create(apfl_ctx, size_t initial_capacity); void apfl_list_append(apfl_ctx, apfl_stackidx list, apfl_stackidx value); // Append a list to another list. The second list will be dropped. void apfl_list_append_list(apfl_ctx, apfl_stackidx a, apfl_stackidx b); +// Splice a list into another list. Removes splice_len elements from list, +// starting at splice_off and inserts b_len elements from other (starting at +// b_off) at that place. list and other will be popped, a new list will be +// pushed. +void apfl_list_splice( + apfl_ctx, + apfl_stackidx list, + size_t splice_off, + size_t splice_len, + apfl_stackidx other, + size_t b_off, + size_t b_len +); // Create a new empty dict on the stack void apfl_dict_create(apfl_ctx); // Set a value in a dictionary. k and v will be dropped. @@ -913,6 +926,7 @@ struct apfl_messages { const char *invalid_matcher_state; const char *no_matching_subfunction; const char *invalid_call_stack_index; + const char *splice_arguments_out_of_range; }; extern const struct apfl_messages apfl_messages; diff --git a/src/builtins.c b/src/builtins.c index cbcbc9a..8288052 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -763,6 +763,93 @@ symbol(apfl_ctx ctx) } } +struct splice_info { + size_t off; + size_t len; +}; + +static apfl_number +getnumarg(apfl_ctx ctx, size_t arg) +{ + apfl_get_list_member_by_index(ctx, 0, arg); + return apfl_get_number(ctx, -1); +} + +static size_t +splice_off(apfl_ctx ctx, size_t list_len, size_t arg) +{ + size_t off; + apfl_number off_num = getnumarg(ctx, arg); + if (off_num < 0) { + off = (size_t)(-off_num); + off = off > list_len ? 0 : list_len - off; + } else { + off = (size_t)off_num; + if (off >= list_len) { + off = list_len; + } + } + return off; +} + +static size_t +splice_len(apfl_ctx ctx, size_t list_len, size_t off, size_t arg) +{ + assert(off <= list_len); + list_len -= off; + + apfl_get_list_member_by_index(ctx, 0, arg); + if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) { + apfl_drop(ctx, -1); + return list_len; + } + + apfl_number num = apfl_get_number(ctx, -1); + if (num < 0) { + size_t len = (size_t)(-num); + return len > list_len ? 0 : list_len - len; + } + + size_t len = (size_t)num; + return len > list_len ? list_len : len; +} + +static struct splice_info +splice_get_list(apfl_ctx ctx, size_t args_base) +{ + apfl_get_list_member_by_index(ctx, 0, args_base); + if (apfl_get_type(ctx, -1) != APFL_VALUE_LIST) { + apfl_raise_errorfmt( + ctx, + "Expected a list argument to splice, got {stack:type} instead", + -1 + ); + } + + size_t list_len = apfl_len(ctx, -1); + + size_t off = splice_off(ctx, list_len, args_base + 1); + size_t len = splice_len(ctx, list_len, off, args_base + 2); + + return (struct splice_info) { + .off = off, + .len = len, + }; +} + +static void +splice(apfl_ctx ctx) +{ + if (apfl_len(ctx, 0) != 6) { + apfl_raise_const_error(ctx, "splice needs exactly 6 arguments"); + } + + struct splice_info a = splice_get_list(ctx, 0); + struct splice_info b = splice_get_list(ctx, 3); + + apfl_list_splice(ctx, -2, a.off, a.len, -1, b.off, b.len); +} + static void add_builtin(apfl_ctx ctx, const char *name, apfl_cfunc func) { @@ -854,4 +941,5 @@ apfl_builtins(apfl_ctx ctx) add_builtin(ctx, "get-argv", get_argv); add_builtin(ctx, "cmod-searcher", cmod_searcher); add_builtin(ctx, "tonumber", tonumber); + add_builtin(ctx, "splice", splice); } diff --git a/src/context.c b/src/context.c index decddc3..70a4022 100644 --- a/src/context.c +++ b/src/context.c @@ -1205,7 +1205,7 @@ apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_ind bool ok; - if (!apfl_list_splice( + if (!apfl_list_splice_raw( &ctx->gc, &list_val->list, list_val->list->len, @@ -1243,7 +1243,7 @@ apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_i apfl_raise_const_error(ctx, apfl_messages.not_a_list); } - if (!apfl_list_splice( + if (!apfl_list_splice_raw( &ctx->gc, &dst_val->list, dst_val->list->len, @@ -1520,7 +1520,7 @@ apfl_set_list_member_by_index( struct apfl_value value = apfl_stack_must_get(ctx, value_index); - if (!apfl_list_splice( + if (!apfl_list_splice_raw( &ctx->gc, &list->list, index_in_list, @@ -1534,6 +1534,60 @@ apfl_set_list_member_by_index( apfl_drop(ctx, value_index); } +void +apfl_list_splice( + apfl_ctx ctx, + apfl_stackidx list, + size_t cut_off, + size_t cut_count, + apfl_stackidx other, + size_t other_off, + size_t other_count +) { + apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]) {other, list}); + + struct apfl_value *list_val = stack_get_pointer(ctx, -1); + struct apfl_value *other_val = stack_get_pointer(ctx, -2); + + if (list_val->type != VALUE_LIST) { + apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *list_val); + } + struct list_header** list_listp = &list_val->list; + if (other_val->type != VALUE_LIST) { + apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *other_val); + } + struct list_header* other_list = other_val->list; + + if (!apfl_resizable_check_cut_args((*list_listp)->len, cut_off, cut_count)) { + apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range); + } + + struct apfl_value *other_values = NULL; + if (other_count > 0) { + if ( + other_off >= other_list->len + || other_count > (other_list->len - other_off) + ) { + apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range); + } + + other_values = &(other_list->items[other_off]); + } + + if (!apfl_list_splice_raw( + &ctx->gc, + list_listp, + cut_off, + cut_count, + other_values, + other_count + )) { + apfl_raise_alloc_error(ctx); + } + + apfl_drop(ctx, -2); +} + size_t apfl_len(apfl_ctx ctx, apfl_stackidx index) { diff --git a/src/functional-tests/slice.at b/src/functional-tests/slice.at new file mode 100644 index 0000000..ffc5540 --- /dev/null +++ b/src/functional-tests/slice.at @@ -0,0 +1,27 @@ +===== script ===== +test := { ~args -> + print (& "[" (join " " (slice ~args)) "]") +} + +test 0 [1 2 3 4] +test 1 [1 2 3 4] +test -1 [1 2 3 4] +test 1 2 [1 2 3 4] +test 1 3 [1 2 3 4] +test 1 10000 [1 2 3 4] +test 1 -1 [1 2 3 4] +test 0 0 [1 2 3 4] +test 4 1 [1 2 3 4] +test -4 1 [1 2 3 4] + +===== output ===== +[1 2 3 4] +[2 3 4] +[4] +[2 3] +[2 3 4] +[2 3 4] +[2 3] +[] +[] +[1] diff --git a/src/functional-tests/splice.at b/src/functional-tests/splice.at new file mode 100644 index 0000000..a567464 --- /dev/null +++ b/src/functional-tests/splice.at @@ -0,0 +1,19 @@ +===== script ===== +test := { ~args -> + print (& "[" (join " " (splice ~args)) "]") +} + +test [1 2 3 4 5] 0 nil +test [1 2 3 4 5] 100 +test [1 2 3 4 5] 1 -2 +test [1 2 3 4 5] 1 -2 [100 200 300] +test [1 2 3 4 5] 1 -2 [100 200 300] 1 +test [1 2 3 4 5] 1 -2 [100 200 300] 1 -1 + +===== output ===== +[] +[1 2 3 4 5] +[1 4 5] +[1 100 200 300 4 5] +[1 200 300 4 5] +[1 200 4 5] diff --git a/src/globals.apfl b/src/globals.apfl index 151b7c9..5cdc1c8 100644 --- a/src/globals.apfl +++ b/src/globals.apfl @@ -29,6 +29,7 @@ get-optional := C.get-optional raise := C.raise symbol := C.symbol + splice := C.splice -serialize-bytecode := C.-serialize-bytecode -unserialize-bytecode := C.-unserialize-bytecode @@ -309,6 +310,20 @@ [(f x) ~(map f xs)] } + splice := { + a off -> splice a off nil + a off len -> splice a off len [] + a a-off a-len b -> splice a a-off a-len b 0 nil + a a-off a-len b b-off -> splice a a-off a-len b b-off nil + a a-off a-len b b-off b-len -> + C.splice a a-off a-len b b-off b-len + } + + slice := { + off l -> slice off nil l + off len l -> splice [] 0 nil l off len + } + # Dictionary of exported functions [ 'if -> if @@ -374,5 +389,7 @@ 'unwrap-some -> unwrap-some 'import -> modules.import 'map -> map + 'splice -> splice + 'slice -> slice ] } diff --git a/src/messages.c b/src/messages.c index 20fa4bb..1ec534e 100644 --- a/src/messages.c +++ b/src/messages.c @@ -19,4 +19,5 @@ const struct apfl_messages apfl_messages = { .invalid_matcher_state = "Matcher is in invalid state", .no_matching_subfunction = "No matching subfunction", .invalid_call_stack_index = "Invalid call stack index", + .splice_arguments_out_of_range = "Splice arguments out of range", }; diff --git a/src/value.c b/src/value.c index 9652c3b..ccab6d0 100644 --- a/src/value.c +++ b/src/value.c @@ -613,7 +613,7 @@ apfl_list_deinit(struct apfl_allocator allocator, struct list_header *list) } bool -apfl_list_splice( +apfl_list_splice_raw( struct gc *gc, struct list_header **dst_ptr, size_t cut_start, diff --git a/src/value.h b/src/value.h index 8941473..03b27cb 100644 --- a/src/value.h +++ b/src/value.h @@ -142,7 +142,7 @@ size_t apfl_list_len(struct list_header *); void apfl_list_deinit(struct apfl_allocator, struct list_header *); bool -apfl_list_splice( +apfl_list_splice_raw( struct gc *gc, struct list_header **dst_ptr, size_t cut_start,