Implement global slice and splice functions

This also adds the apfl_list_splice() function for manipulating lists.
This commit is contained in:
Laria 2023-10-23 22:41:26 +02:00
parent 85d2c67404
commit ddd39f19ef
10 changed files with 227 additions and 5 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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",
};

View file

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

View file

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