From e8a92a18b40fd160e97c19cd4b2d836034942e0e Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sun, 20 Nov 2022 16:26:38 +0100 Subject: [PATCH] Implement functions for concatenating strings --- src/CMakeLists.txt | 2 + src/apfl.h | 5 ++ src/context.c | 115 ++++++++++++++++++++++++++++++--- src/eval.c | 7 +- src/functional-tests/concat.at | 13 ++++ src/functional-tests/join.at | 35 ++++++++++ src/globals.c | 23 +++++++ src/strings.c | 12 ++++ src/value.c | 7 ++ src/value.h | 1 + 10 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 src/functional-tests/concat.at create mode 100644 src/functional-tests/join.at diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17e391a..8582df5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,8 @@ functionaltest("dictionary-assignments") functionaltest("variadic-functions") functionaltest("predicate") functionaltest("compare") +functionaltest("concat") +functionaltest("join") install(TARGETS apfl DESTINATION lib) install(TARGETS apfl-bin DESTINATION bin) diff --git a/src/apfl.h b/src/apfl.h index 556b265..5e11830 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -108,6 +108,7 @@ struct apfl_string_builder { struct apfl_string_builder apfl_string_builder_init(struct apfl_allocator allocator); void apfl_string_builder_deinit(struct apfl_string_builder *); +bool apfl_string_builder_prealloc(struct apfl_string_builder *, size_t newcap); bool apfl_string_builder_append(struct apfl_string_builder *, struct apfl_string_view); bool apfl_string_builder_append_byte(struct apfl_string_builder *, char byte); bool apfl_string_builder_append_bytes(struct apfl_string_builder *, const char *bytes, size_t len); @@ -669,6 +670,10 @@ void apfl_push_number(apfl_ctx, apfl_number); void apfl_push_string_view_copy(apfl_ctx, struct apfl_string_view); // Push a constant string. void apfl_push_const_string(apfl_ctx, const char *); +// Joins all elements of the list parts together with glue into a string that is pushed onto the stack. +// glue and parts are popped off the stack. glue and the elements of parts are converted into a string if they are not +// already one. +void apfl_join_strings(apfl_ctx, apfl_stackidx glue, apfl_stackidx parts); // Create a new empty list on the stack void apfl_list_create(apfl_ctx, size_t initial_capacity); // Append a value to a list. The value will be dropped. diff --git a/src/context.c b/src/context.c index d321547..f024396 100644 --- a/src/context.c +++ b/src/context.c @@ -1156,10 +1156,9 @@ apfl_len(apfl_ctx ctx, apfl_stackidx index) return 0; } -struct apfl_string_view -apfl_get_string(apfl_ctx ctx, apfl_stackidx index) +static bool +get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value) { - struct apfl_value value = apfl_stack_must_get(ctx, index); switch (value.type) { case VALUE_NIL: case VALUE_BOOLEAN: @@ -1169,17 +1168,27 @@ apfl_get_string(apfl_ctx ctx, apfl_stackidx index) case VALUE_USERDATA: case VALUE_LIST: case VALUE_DICT: - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type); - goto error; + return false; case VALUE_STRING: - return apfl_string_view_from(*value.string); + *sv = apfl_string_view_from(*value.string); + return true; case VALUE_CONST_STRING: - return value.const_string; + *sv = value.const_string; + return true; } -error: assert(false); - return (struct apfl_string_view) {.bytes = NULL, .len = 0}; + return false; +} + +struct apfl_string_view +apfl_get_string(apfl_ctx ctx, apfl_stackidx index) +{ + struct apfl_string_view sv; + if (!get_string_view_of_value(&sv, apfl_stack_must_get(ctx, index))) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type); + } + return sv; } enum apfl_value_type @@ -1216,6 +1225,94 @@ apfl_tostring(apfl_ctx ctx, apfl_stackidx index) apfl_stack_drop(ctx, -2); // Drop original value } +static void +join_strings_inner(apfl_ctx ctx, apfl_stackidx _glue, apfl_stackidx _parts) +{ + struct apfl_value glue_val = apfl_stack_must_get(ctx, _glue); + struct apfl_value parts_val = apfl_stack_must_get(ctx, _parts); + + if (!apfl_value_add_as_tmproot(&ctx->gc, glue_val)) { + apfl_raise_alloc_error(ctx); + } + if (!apfl_value_add_as_tmproot(&ctx->gc, parts_val)) { + apfl_raise_alloc_error(ctx); + } + + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]) { _glue, _parts })); + + if (parts_val.type != VALUE_LIST) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list); + } + + struct list_header *parts = parts_val.list; + + apfl_stack_must_push(ctx, glue_val); + apfl_tostring(ctx, -1); + struct apfl_string_view glue_sv = apfl_get_string(ctx, -1); + + size_t total_length = 0; + + for (size_t i = 0; i < parts->len; i++) { + apfl_stack_must_push(ctx, parts->items[i]); + apfl_tostring(ctx, -1); + parts->items[i] = apfl_stack_must_pop(ctx, -1); + + struct apfl_string_view part_sv; + assert(get_string_view_of_value(&part_sv, parts->items[i]) /* Value should be a string at this point */); + + total_length += part_sv.len; + + if (i > 0) { + total_length += glue_sv.len; + } + } + + struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); + if (!apfl_string_builder_prealloc(&sb, total_length)) { + goto error; + } + + for (size_t i = 0; i < parts->len; i++) { + if (i > 0) { + if (!apfl_string_builder_append(&sb, glue_sv)) { + goto error; + } + } + + struct apfl_string_view part_sv; + assert(get_string_view_of_value(&part_sv, parts->items[i]) /* Value should be a string at this point */); + + if (!apfl_string_builder_append(&sb, part_sv)) { + goto error; + } + } + + + struct apfl_string str = apfl_string_builder_move_string(&sb); + apfl_string_builder_deinit(&sb); + if (!apfl_move_string_onto_stack(ctx, str)) { + apfl_string_deinit(ctx->gc.allocator, &str); + goto error; + } + + apfl_drop(ctx, -2); // Drop the glue from the stack + return; + +error: + apfl_drop(ctx, -1); // Drop the glue from the stack + apfl_raise_alloc_error(ctx); + +} + +void +apfl_join_strings(apfl_ctx ctx, apfl_stackidx glue, apfl_stackidx parts) +{ + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + join_strings_inner(ctx, glue, parts); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); +} + + bool apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index) { diff --git a/src/eval.c b/src/eval.c index 0f12c63..68a40c6 100644 --- a/src/eval.c +++ b/src/eval.c @@ -419,11 +419,8 @@ evaluate_until_call_stack_return(apfl_ctx ctx) static void must_tmproot_add_value(apfl_ctx ctx, struct apfl_value value) { - struct gc_object *obj = apfl_value_get_gc_object(value); - if (obj != NULL) { - if (!apfl_gc_tmproot_add(&ctx->gc, obj)) { - apfl_raise_alloc_error(ctx); - } + if (!apfl_value_add_as_tmproot(&ctx->gc, value)) { + apfl_raise_alloc_error(ctx); } } diff --git a/src/functional-tests/concat.at b/src/functional-tests/concat.at new file mode 100644 index 0000000..34413a2 --- /dev/null +++ b/src/functional-tests/concat.at @@ -0,0 +1,13 @@ +===== script ===== +dump (&) +dump (& 'foo) +dump (& 'foo 'bar) +dump (& 'foo 'bar 'baz) +dump (& 'foo 1 true [] [->]) + +===== output ===== +"" +"foo" +"foobar" +"foobarbaz" +"foo1true[][->]" diff --git a/src/functional-tests/join.at b/src/functional-tests/join.at new file mode 100644 index 0000000..ee8530d --- /dev/null +++ b/src/functional-tests/join.at @@ -0,0 +1,35 @@ +===== script ===== +print (join "," []) +print (join "," ['foo]) +print (join "," ['foo 'bar]) +print (join "," ['foo 'bar 'baz]) +print (join "," ['foo 1 true [] [->]]) + +print (join ":::" []) +print (join ":::" ['foo]) +print (join ":::" ['foo 'bar]) +print (join ":::" ['foo 'bar 'baz]) +print (join ":::" ['foo 1 true [] [->]]) + +print (join 1 []) +print (join 1 ['foo]) +print (join 1 ['foo 'bar]) +print (join 1 ['foo 'bar 'baz]) +print (join 1 ['foo 1 true [] [->]]) + +===== output ===== + +foo +foo,bar +foo,bar,baz +foo,1,true,[],[->] + +foo +foo:::bar +foo:::bar:::baz +foo:::1:::true:::[]:::[->] + +foo +foo1bar +foo1bar1baz +foo111true1[]1[->] diff --git a/src/globals.c b/src/globals.c index 8168bd4..01f7c02 100644 --- a/src/globals.c +++ b/src/globals.c @@ -169,6 +169,27 @@ op_div(apfl_ctx ctx, apfl_number a, apfl_number b) IMPL_MATH_OP_FUNC(impl_div, "/", single_identity, op_div) +static void +impl_concat(apfl_ctx ctx) +{ + apfl_push_const_string(ctx, ""); + apfl_join_strings(ctx, -1, -2); +} + +static void +impl_join(apfl_ctx ctx) +{ + size_t len = apfl_len(ctx, 0); + if (len != 2) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, "join expects exactly 2 arguments"); + } + + apfl_get_list_member_by_index(ctx, 0, 0); + apfl_get_list_member_by_index(ctx, 0, 1); + apfl_drop(ctx, 0); + apfl_join_strings(ctx, -2, -1); +} + static void print(apfl_ctx ctx) { @@ -366,6 +387,8 @@ static const struct global_def globals[] = { {"-", impl_minus}, {"*", impl_mult}, {"/", impl_div}, + {"&", impl_concat}, + {"join", impl_join}, {"print", print}, {"dump", dump}, {"disasm", disasm}, diff --git a/src/strings.c b/src/strings.c index 1f78cd2..0686882 100644 --- a/src/strings.c +++ b/src/strings.c @@ -133,6 +133,18 @@ apfl_string_builder_append_bytes(struct apfl_string_builder *builder, const char ); } +bool +apfl_string_builder_prealloc(struct apfl_string_builder *builder, size_t newcap) +{ + return apfl_resizable_ensure_cap( + builder->allocator, + sizeof(char), + (void **)&builder->bytes, + &builder->cap, + newcap + ); +} + bool apfl_string_builder_append(struct apfl_string_builder *builder, struct apfl_string_view view) { diff --git a/src/value.c b/src/value.c index bf4e171..3e271b4 100644 --- a/src/value.c +++ b/src/value.c @@ -724,6 +724,13 @@ apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque) } } +bool +apfl_value_add_as_tmproot(struct gc *gc, struct apfl_value value) +{ + struct gc_object *obj = apfl_value_get_gc_object(value); + return obj == NULL ? true : apfl_gc_tmproot_add(gc, obj); +} + void apfl_gc_list_traverse(struct list_header *list, gc_visitor cb, void *opaque) { diff --git a/src/value.h b/src/value.h index 546c9ed..5a9eb7c 100644 --- a/src/value.h +++ b/src/value.h @@ -144,6 +144,7 @@ void apfl_cfunction_deinit(struct apfl_allocator, struct cfunction *); // Functions for garbage collection struct gc_object *apfl_value_get_gc_object(struct apfl_value); void apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque); +bool apfl_value_add_as_tmproot(struct gc *, struct apfl_value); void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *); void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *); void apfl_gc_func_traverse(struct function*, gc_visitor, void *);