Implement functions for concatenating strings
This commit is contained in:
parent
f9878b43d8
commit
e8a92a18b4
10 changed files with 206 additions and 14 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
115
src/context.c
115
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
13
src/functional-tests/concat.at
Normal file
13
src/functional-tests/concat.at
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
===== script =====
|
||||
dump (&)
|
||||
dump (& 'foo)
|
||||
dump (& 'foo 'bar)
|
||||
dump (& 'foo 'bar 'baz)
|
||||
dump (& 'foo 1 true [] [->])
|
||||
|
||||
===== output =====
|
||||
""
|
||||
"foo"
|
||||
"foobar"
|
||||
"foobarbaz"
|
||||
"foo1true[][->]"
|
||||
35
src/functional-tests/join.at
Normal file
35
src/functional-tests/join.at
Normal file
|
|
@ -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[->]
|
||||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
Loading…
Reference in a new issue