From 4388be9eefab5e86e5e2aafb4f97341669a5199e Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 12 Dec 2024 19:54:18 -0800 Subject: [PATCH] Reimplement string interpolation for expression parser --- distr/flecs.c | 781 +++++++++++++++++++------- distr/flecs.h | 2 +- include/flecs/addons/script.h | 2 +- meson.build | 1 - src/addons/meta/cursor.c | 50 +- src/addons/script/ast.h | 2 +- src/addons/script/expr/ast.c | 38 +- src/addons/script/expr/ast.h | 19 + src/addons/script/expr/expr.h | 6 + src/addons/script/expr/parser.c | 41 +- src/addons/script/expr/util.c | 69 +++ src/addons/script/expr/visit_eval.c | 56 ++ src/addons/script/expr/visit_fold.c | 69 +++ src/addons/script/expr/visit_free.c | 24 + src/addons/script/expr/visit_to_str.c | 44 ++ src/addons/script/expr/visit_type.c | 135 ++++- src/addons/script/function.c | 3 + src/addons/script/interpolate.c | 176 ------ src/addons/script/parser.c | 2 + src/addons/script/script.h | 1 + src/addons/script/tokenizer.c | 42 +- src/addons/script/tokenizer.h | 5 + test/meta/src/Cursor.c | 4 +- test/script/project.json | 20 +- test/script/src/Eval.c | 2 +- test/script/src/Expr.c | 280 +++++++++ test/script/src/ExprAst.c | 125 +++++ test/script/src/main.c | 84 ++- 28 files changed, 1664 insertions(+), 419 deletions(-) delete mode 100644 src/addons/script/interpolate.c diff --git a/distr/flecs.c b/distr/flecs.c index 731dcdd53..9d71badab 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4516,6 +4516,7 @@ typedef struct ecs_script_impl_t { ecs_script_scope_t *root; ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; + char *token_remaining; /* Remaining space in token buffer */ const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; @@ -4620,6 +4621,11 @@ const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos); +const char* flecs_script_identifier( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out); + #endif @@ -4670,7 +4676,7 @@ typedef enum ecs_script_node_kind_t { EcsAstConst, EcsAstEntity, EcsAstPairScope, - EcsAstIf, + EcsAstIf } ecs_script_node_kind_t; typedef struct ecs_script_node_t { @@ -4951,6 +4957,7 @@ void flecs_expr_stack_pop( typedef enum ecs_expr_node_kind_t { EcsExprValue, + EcsExprInterpolatedString, EcsExprInitializer, EcsExprEmptyInitializer, EcsExprUnary, @@ -4979,6 +4986,15 @@ typedef struct ecs_expr_value_node_t { ecs_expr_small_value_t storage; } ecs_expr_value_node_t; +typedef struct ecs_expr_interpolated_string_t { + ecs_expr_node_t node; + char *value; /* modified by parser */ + char *buffer; /* for storing expr tokens */ + ecs_size_t buffer_size; + ecs_vec_t fragments; /* vec */ + ecs_vec_t expressions; /* vec */ +} ecs_expr_interpolated_string_t; + typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *value; @@ -5056,6 +5072,11 @@ ecs_expr_value_node_t* flecs_expr_value_from( ecs_expr_node_t *node, ecs_entity_t type); +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name); + ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); @@ -5076,6 +5097,10 @@ ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_script_parser_t *parser, + const char *value); + ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value); @@ -5195,6 +5220,12 @@ void flecs_expr_to_str_buf( ecs_strbuf_t *buf, bool colors); +bool flecs_string_is_interpolated( + const char *str); + +char* flecs_string_escape( + char *str); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ @@ -48040,6 +48071,7 @@ int ecs_meta_from_desc( * @brief API for assigning values of runtime types with reflection. */ +#include #include #ifdef FLECS_META @@ -48925,6 +48957,16 @@ int ecs_meta_set_bool( cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + case EcsOpString: { + char *result; + if (value) { + result = ecs_os_strdup("true"); + } else { + result = ecs_os_strdup("false"); + } + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_bool) { @@ -48941,7 +48983,6 @@ int ecs_meta_set_bool( case EcsOpPrimitive: case EcsOpF32: case EcsOpF64: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "bool"); return -1; default: @@ -48964,6 +49005,11 @@ int ecs_meta_set_char( switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); + case EcsOpString: { + char *result = flecs_asprintf("%c", value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -48996,7 +49042,6 @@ int ecs_meta_set_char( case EcsOpF32: case EcsOpF64: case EcsOpUPtr: - case EcsOpString: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "char"); @@ -49023,6 +49068,11 @@ int ecs_meta_set_int( cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRId64, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -49048,8 +49098,7 @@ int ecs_meta_set_int( case EcsOpPush: case EcsOpPop: case EcsOpScope: - case EcsOpPrimitive: - case EcsOpString: { + case EcsOpPrimitive: { if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "int"); return -1; @@ -49076,6 +49125,11 @@ int ecs_meta_set_uint( cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRIu64, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -49102,7 +49156,6 @@ int ecs_meta_set_uint( case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: - case EcsOpString: if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "uint"); return -1; @@ -49134,6 +49187,11 @@ int ecs_meta_set_float( cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%f", value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -49164,7 +49222,6 @@ int ecs_meta_set_float( case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "float"); return -1; default: @@ -49602,6 +49659,11 @@ int ecs_meta_set_entity( case EcsOpId: set_T(ecs_id_t, ptr, value); /* entities are valid ids */ break; + case EcsOpString: { + char *result = ecs_get_path(cursor->world, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_entity) { @@ -49634,7 +49696,6 @@ int ecs_meta_set_entity( case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "entity"); goto error; default: @@ -49659,6 +49720,11 @@ int ecs_meta_set_id( case EcsOpId: set_T(ecs_id_t, ptr, value); break; + case EcsOpString: { + char *result = ecs_id_str(cursor->world, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_id) { @@ -49691,7 +49757,6 @@ int ecs_meta_set_id( case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: - case EcsOpString: case EcsOpEntity: flecs_meta_conversion_error(cursor, op, "id"); goto error; @@ -55154,6 +55219,8 @@ ECS_MOVE(EcsScriptConstVar, dst, src, { if (dst->type_info->hooks.dtor) { dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); } + + ecs_os_free(dst->value.ptr); *dst = *src; @@ -55167,6 +55234,7 @@ ECS_DTOR(EcsScriptConstVar, ptr, { if (ptr->type_info->hooks.dtor) { ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); } + ecs_os_free(ptr->value.ptr); }) static @@ -55686,181 +55754,6 @@ void FlecsScriptMathImport( #endif -/** - * @file addons/script/interpolate.c - * @brief String interpolation. - */ - - -#ifdef FLECS_SCRIPT -#include - -static -const char* flecs_parse_var_name( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (isalpha(ch) || isdigit(ch) || ch == '_') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -static -const char* flecs_parse_interpolated_str( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (ch == '\\') { - if (ptr[1] == '}') { - *bptr = '}'; - bptr ++; - ptr += 2; - continue; - } - } - - if (ch != '}') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - ptr ++; - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -char* ecs_script_string_interpolate( - ecs_world_t *world, - const char *str, - const ecs_script_vars_t *vars) -{ - char token[ECS_MAX_TOKEN_SIZE]; - ecs_strbuf_t result = ECS_STRBUF_INIT; - const char *ptr; - char ch; - - for(ptr = str; (ch = *ptr); ptr++) { - if (ch == '\\') { - ptr ++; - if (ptr[0] == '$') { - ecs_strbuf_appendch(&result, '$'); - continue; - } - if (ptr[0] == '\\') { - ecs_strbuf_appendch(&result, '\\'); - continue; - } - if (ptr[0] == '{') { - ecs_strbuf_appendch(&result, '{'); - continue; - } - if (ptr[0] == '}') { - ecs_strbuf_appendch(&result, '}'); - continue; - } - ptr --; - } - - if (ch == '$') { - ptr = flecs_parse_var_name(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid variable name '%s'", ptr); - goto error; - } - - ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); - if (!var) { - ecs_parser_error(NULL, str, ptr - str, - "unresolved variable '%s'", token); - goto error; - } - - if (ecs_ptr_to_str_buf( - world, var->value.type, var->value.ptr, &result)) - { - goto error; - } - - ptr --; - } else if (ch == '{') { - ptr = flecs_parse_interpolated_str(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid interpolated expression"); - goto error; - } - - ecs_expr_eval_desc_t expr_desc = { - .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) - }; - - ecs_value_t expr_result = {0}; - if (!ecs_expr_run(world, token, &expr_result, &expr_desc)) { - goto error; - } - - if (ecs_ptr_to_str_buf( - world, expr_result.type, expr_result.ptr, &result)) - { - goto error; - } - - ecs_value_free(world, expr_result.type, expr_result.ptr); - - ptr --; - } else { - ecs_strbuf_appendch(&result, ch); - } - } - - return ecs_strbuf_get(&result); -error: - return NULL; -} - -#endif - /** * @file addons/script/parser.c * @brief Script grammar parser. @@ -56975,6 +56868,8 @@ ecs_script_t* ecs_script_parse( } } while (true); + impl->token_remaining = parser.token_cur; + return script; error: ecs_script_free(script); @@ -59383,21 +59278,24 @@ bool flecs_script_is_identifier( return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); } -static const char* flecs_script_identifier( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { - out->kind = EcsTokIdentifier; - out->value = parser->token_cur; + if (out) { + out->kind = EcsTokIdentifier; + out->value = parser->token_cur; + } ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; - char *outpos = parser->token_cur; - - if (parser->merge_variable_members) { - is_var = false; + char *outpos = NULL; + if (parser) { + outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } } do { @@ -59434,8 +59332,10 @@ const char* flecs_script_identifier( return NULL; } - *outpos = c; - outpos ++; + if (outpos) { + *outpos = c; + outpos ++; + } pos ++; if (!indent) { @@ -59443,8 +59343,10 @@ const char* flecs_script_identifier( } } while (true); - *outpos = '\0'; - parser->token_cur = outpos + 1; + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } return pos; } else if (c == '>') { ecs_parser_error(parser->script->pub.name, @@ -59453,14 +59355,19 @@ const char* flecs_script_identifier( "> without < in identifier"); return NULL; } else { - *outpos = '\0'; - parser->token_cur = outpos + 1; + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } return pos; } } - *outpos = *pos; - outpos ++; + if (outpos) { + *outpos = *pos; + outpos ++; + } + pos ++; } while (true); } @@ -74284,6 +74191,19 @@ ecs_expr_value_node_t* flecs_expr_value_from( return result; } +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name) +{ + ecs_expr_variable_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); + result->name = name; + result->node.kind = EcsExprVariable; + result->node.pos = node ? node->pos : NULL; + return result; +} + ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) @@ -74336,11 +74256,34 @@ ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value) { + char *str = ECS_CONST_CAST(char*, value); ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); - result->storage.string = ECS_CONST_CAST(char*, value); + result->storage.string = str; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); + + if (!flecs_string_escape(str)) { + return NULL; + } + + return result; +} + +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( + parser, ecs_expr_interpolated_string_t, EcsExprInterpolatedString); + result->value = ECS_CONST_CAST(char*, value); + result->buffer = flecs_strdup(&parser->script->allocator, value); + result->buffer_size = ecs_os_strlen(result->buffer) + 1; + result->node.type = ecs_id(ecs_string_t); + ecs_vec_init_t(&parser->script->allocator, &result->fragments, char*, 0); + ecs_vec_init_t(&parser->script->allocator, &result->expressions, + ecs_expr_node_t*, 0); + return result; } @@ -74801,7 +74744,12 @@ const char* flecs_script_parse_lhs( } case EcsTokString: { - *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + if (flecs_string_is_interpolated(Token(0))) { + *out = (ecs_expr_node_t*)flecs_expr_interpolated_string( + parser, Token(0)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + } break; } @@ -74818,7 +74766,7 @@ const char* flecs_script_parse_lhs( if (last_elem && last_elem[1] == '$') { /* Scoped global variable */ ecs_expr_variable_t *v = flecs_expr_variable(parser, expr); - memmove(&last_elem[1], &last_elem[2], + ecs_os_memmove(&last_elem[1], &last_elem[2], ecs_os_strlen(&last_elem[2]) + 1); v->node.kind = EcsExprGlobalVariable; *out = (ecs_expr_node_t*)v; @@ -74974,6 +74922,7 @@ ecs_script_t* ecs_expr_parse( } impl->next_token = ptr; + impl->token_remaining = parser.token_cur; if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; @@ -74987,7 +74936,7 @@ ecs_script_t* ecs_expr_parse( } } - // printf("%s\n", ecs_script_ast_to_str(script)); + // printf("%s\n", ecs_script_ast_to_str(script, true)); return script; error: @@ -75062,6 +75011,35 @@ const char* ecs_expr_run( return NULL; } +FLECS_API +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) +{ + if (!flecs_string_is_interpolated(str)) { + char *result = ecs_os_strdup(str); + if (!flecs_string_escape(result)) { + ecs_os_free(result); + return NULL; + } + return result; + } + + char *expr = flecs_asprintf("\"%s\"", str); + + ecs_expr_eval_desc_t desc = { .vars = vars }; + char *r = NULL; + if (!ecs_expr_run(world, expr, &ecs_value_ptr(ecs_string_t, &r), &desc)) { + ecs_os_free(expr); + return NULL; + } + + ecs_os_free(expr); + + return r; +} + #endif /** @@ -75409,6 +75387,75 @@ int flecs_value_binary( return 0; } +bool flecs_string_is_interpolated( + const char *value) +{ + const char *ptr = value; + + for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } + + if (isspace(ptr[1]) || !ptr[1]) { + continue; /* $ by itself */ + } + + return true; + } + + ptr = value; + + for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } + + return true; + } + + return false; +} + +char* flecs_string_escape( + char *str) +{ + const char *ptr; + char *out = str, ch; + + for (ptr = str; ptr[0]; ) { + if (ptr[0] == '\\') { + if (ptr[1] == '{') { /* Escape string interpolation delimiter */ + ch = '{'; + ptr += 2; + } else if (ptr[1] == '$') { /* Escape string interpolation var */ + ch = '$'; + ptr += 2; + } else { + ptr = flecs_chrparse(ptr, &ch); + if (!ptr) { + ecs_err("invalid escape sequence in string '%s'", str); + return NULL; + } + } + } else { + ch = ptr[0]; + ptr ++; + } + + out[0] = ch; + out ++; + } + + out[0] = '\0'; + + return out + 1; +} + #endif /** @@ -75445,6 +75492,53 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_interpolated_string_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_interpolated_string_t *node, + ecs_expr_value_t *out) +{ + ecs_assert(node->node.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + flecs_expr_stack_push(ctx->stack); + + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); + } else { + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + + ecs_expr_value_t *val = flecs_expr_stack_result(ctx->stack, + (ecs_expr_node_t*)node); + val->owned = true; + if (flecs_expr_visit_eval_priv(ctx, expr, val)) { + goto error; + } + + ecs_assert(val->value.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_appendstr(&buf, *(char**)val->value.ptr); + } + } + + *(char**)out->value.ptr = ecs_strbuf_get(&buf); + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, @@ -75692,6 +75786,8 @@ int flecs_expr_global_variable_visit_eval( ecs_expr_variable_t *node, ecs_expr_value_t *out) { + (void)ctx; + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); @@ -75949,6 +76045,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_eval( + ctx, (ecs_expr_interpolated_string_t*)node, out)) + { + goto error; + } + break; case EcsExprEmptyInitializer: break; case EcsExprInitializer: @@ -76278,6 +76381,70 @@ int flecs_expr_cast_visit_fold( return -1; } +static +int flecs_expr_interpolated_string_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_interpolated_string_t *node = + (ecs_expr_interpolated_string_t*)*node_ptr; + + bool can_fold = true; + + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (!fragment) { + ecs_expr_node_t **expr_ptr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++); + + if (flecs_expr_visit_fold(script, expr_ptr, desc)) { + goto error; + } + + if (expr_ptr[0]->kind != EcsExprValue) { + can_fold = false; + } + } + } + + if (can_fold) { + ecs_strbuf_t buf = ECS_STRBUF_INIT; + e = 0; + + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); + } else { + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + ecs_assert(expr->kind == EcsExprValue, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(expr->type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)expr; + ecs_strbuf_appendstr(&buf, *(char**)val->ptr); + } + } + + char **value = ecs_value_new(script->world, ecs_id(ecs_string_t)); + *value = ecs_strbuf_get(&buf); + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_string_t)); + result->ptr = value; + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_pre_fold( ecs_script_t *script, @@ -76558,6 +76725,11 @@ int flecs_expr_visit_fold( switch(node->kind) { case EcsExprValue: break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { @@ -76638,6 +76810,25 @@ void flecs_expr_value_visit_free( } } +static +void flecs_expr_interpolated_string_visit_free( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node) +{ + int32_t i, count = ecs_vec_count(&node->expressions); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); + for (i = 0; i < count; i ++) { + flecs_expr_visit_free(script, expressions[i]); + } + + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->fragments, char*); + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->expressions, ecs_expr_node_t*); + flecs_free_n(&flecs_script_impl(script)->allocator, + char, node->buffer_size, node->buffer); +} + static void flecs_expr_initializer_visit_free( ecs_script_t *script, @@ -76729,6 +76920,11 @@ void flecs_expr_visit_free( script, (ecs_expr_value_node_t*)node); flecs_free_t(a, ecs_expr_value_node_t, node); break; + case EcsExprInterpolatedString: + flecs_expr_interpolated_string_visit_free( + script, (ecs_expr_interpolated_string_t*)node); + flecs_free_t(a, ecs_expr_interpolated_string_t, node); + break; case EcsExprInitializer: case EcsExprEmptyInitializer: flecs_expr_initializer_visit_free( @@ -76822,6 +77018,43 @@ int flecs_expr_value_to_str( return ret; } +static +int flecs_expr_interpolated_string_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_interpolated_string_t *node) +{ + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); + + ecs_strbuf_appendlit(v->buf, "interpolated("); + + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + + if (i) { + ecs_strbuf_appendlit(v->buf, ", "); + } + + if (fragment) { + flecs_expr_color_to_str(v, ECS_YELLOW); + ecs_strbuf_appendlit(v->buf, "\""); + ecs_strbuf_appendstr(v->buf, fragment); + ecs_strbuf_appendlit(v->buf, "\""); + flecs_expr_color_to_str(v, ECS_NORMAL); + } else { + ecs_expr_node_t *expr = expressions[e ++]; + if (flecs_expr_node_to_str(v, expr)) { + return -1; + } + } + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +} + static int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, @@ -77018,6 +77251,13 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_to_str(v, + (const ecs_expr_interpolated_string_t*)node)) + { + goto error; + } + break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_to_str(v, @@ -77610,6 +77850,133 @@ int flecs_expr_type_for_oper( return 0; } +static +int flecs_expr_interpolated_string_visit_type( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + char *ptr, *frag = NULL; + char ch; + + for (ptr = node->value; (ch = ptr[0]); ptr ++) { + if (ch == '\\') { + ptr ++; + /* Next character is escaped, ignore */ + continue; + } + + if ((ch == '$') && (isspace(ptr[1]) || !ptr[1])) { + /* $ by itself */ + continue; + } + + if (ch == '$' || ch == '{') { + if (!frag) { + frag = node->value; + } + + char *frag_end = ptr; + + ecs_expr_node_t *result = NULL; + + if (ch == '$') { + char *var_name = ++ ptr; + ptr = ECS_CONST_CAST(char*, flecs_script_identifier( + NULL, ptr, NULL)); + if (!ptr) { + goto error; + } + + /* Fiddly, but reduces need for allocations */ + ecs_size_t offset = flecs_ito( + int32_t, node->buffer - node->value); + var_name = ECS_OFFSET(var_name, offset); + (*(char*)ECS_OFFSET(ptr, offset)) = '\0'; + + ecs_expr_variable_t *var = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, var_name); + if (!var) { + goto error; + } + + result = (ecs_expr_node_t*)var; + } else { + ecs_script_impl_t *impl = flecs_script_impl(script); + + ecs_script_parser_t parser = { + .script = impl, + .scope = impl->root, + .significant_newline = false, + .token_cur = impl->token_remaining + }; + + ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( + &parser, ptr + 1, 0, &result)); + if (!ptr) { + goto error; + } + + if (ptr[0] != '}') { + flecs_expr_visit_error(script, node, + "expected '}' at end of interpolated expression"); + goto error; + } + + ptr ++; + } + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_expr_eval_desc_t priv_desc = *desc; + priv_desc.type = ecs_id(ecs_string_t); /* String output */ + + if (flecs_expr_visit_type_priv(script, result, cur, &priv_desc)) { + flecs_expr_visit_free(script, result); + goto error; + } + + if (result->type != ecs_id(ecs_string_t)) { + result = (ecs_expr_node_t*)flecs_expr_cast(script, + (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); + } + + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->expressions, ecs_expr_node_t*)[0] = result; + + frag_end[0] = '\0'; + + if (frag != frag_end) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } + + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = NULL; + + frag = ptr; /* Point to next fragment */ + if (!ptr[0]) { + break; /* We already parsed the end of the string */ + } + } + } + + /* This would mean it's not an interpolated string, which means the parser + * messed up when creating the node. */ + ecs_assert(frag != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Add remaining fragment */ + if (frag != ptr) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_visit_type( ecs_script_t *script, @@ -78245,7 +78612,13 @@ int flecs_expr_visit_type_priv( switch(node->kind) { case EcsExprValue: - /* Value types are assigned by the AST */ + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_type( + script, (ecs_expr_interpolated_string_t*)node, cur, desc)) + { + goto error; + } break; case EcsExprEmptyInitializer: break; diff --git a/distr/flecs.h b/distr/flecs.h index a9a17c1d5..107949e8e 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14776,7 +14776,7 @@ typedef struct ecs_expr_eval_desc_t { const char *value, void *ctx); void *lookup_ctx; /**< Context passed to lookup function */ - ecs_script_vars_t *vars; /**< Variables accessible in expression */ + const ecs_script_vars_t *vars; /**< Variables accessible in expression */ ecs_entity_t type; /**< Type of parsed value (optional) */ bool disable_folding; /**< Disable constant folding (slower evaluation, faster parsing) */ ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index d642f0f38..520b291df 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -502,7 +502,7 @@ typedef struct ecs_expr_eval_desc_t { const char *value, void *ctx); void *lookup_ctx; /**< Context passed to lookup function */ - ecs_script_vars_t *vars; /**< Variables accessible in expression */ + const ecs_script_vars_t *vars; /**< Variables accessible in expression */ ecs_entity_t type; /**< Type of parsed value (optional) */ bool disable_folding; /**< Disable constant folding (slower evaluation, faster parsing) */ ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ diff --git a/meson.build b/meson.build index 932bf7e6d..fd76c47c7 100644 --- a/meson.build +++ b/meson.build @@ -62,7 +62,6 @@ flecs_src = files( 'src/addons/script/function.c', 'src/addons/script/functions_builtin.c', 'src/addons/script/functions_math.c', - 'src/addons/script/interpolate.c', 'src/addons/script/parser.c', 'src/addons/script/query_parser.c', 'src/addons/script/script.c', diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index e0e3f7429..5559c2ec1 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -4,6 +4,7 @@ */ #include "meta.h" +#include #include #ifdef FLECS_META @@ -890,6 +891,16 @@ int ecs_meta_set_bool( cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + case EcsOpString: { + char *result; + if (value) { + result = ecs_os_strdup("true"); + } else { + result = ecs_os_strdup("false"); + } + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_bool) { @@ -906,7 +917,6 @@ int ecs_meta_set_bool( case EcsOpPrimitive: case EcsOpF32: case EcsOpF64: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "bool"); return -1; default: @@ -929,6 +939,11 @@ int ecs_meta_set_char( switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); + case EcsOpString: { + char *result = flecs_asprintf("%c", value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -961,7 +976,6 @@ int ecs_meta_set_char( case EcsOpF32: case EcsOpF64: case EcsOpUPtr: - case EcsOpString: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "char"); @@ -988,6 +1002,11 @@ int ecs_meta_set_int( cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRId64, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -1013,8 +1032,7 @@ int ecs_meta_set_int( case EcsOpPush: case EcsOpPop: case EcsOpScope: - case EcsOpPrimitive: - case EcsOpString: { + case EcsOpPrimitive: { if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "int"); return -1; @@ -1041,6 +1059,11 @@ int ecs_meta_set_uint( cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRIu64, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -1067,7 +1090,6 @@ int ecs_meta_set_uint( case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: - case EcsOpString: if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "uint"); return -1; @@ -1099,6 +1121,11 @@ int ecs_meta_set_float( cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%f", value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, @@ -1129,7 +1156,6 @@ int ecs_meta_set_float( case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "float"); return -1; default: @@ -1567,6 +1593,11 @@ int ecs_meta_set_entity( case EcsOpId: set_T(ecs_id_t, ptr, value); /* entities are valid ids */ break; + case EcsOpString: { + char *result = ecs_get_path(cursor->world, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_entity) { @@ -1599,7 +1630,6 @@ int ecs_meta_set_entity( case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: - case EcsOpString: flecs_meta_conversion_error(cursor, op, "entity"); goto error; default: @@ -1624,6 +1654,11 @@ int ecs_meta_set_id( case EcsOpId: set_T(ecs_id_t, ptr, value); break; + case EcsOpString: { + char *result = ecs_id_str(cursor->world, value); + set_T(ecs_string_t, ptr, result); + break; + } case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_id) { @@ -1656,7 +1691,6 @@ int ecs_meta_set_id( case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: - case EcsOpString: case EcsOpEntity: flecs_meta_conversion_error(cursor, op, "id"); goto error; diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index 58126678a..0e35e437e 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -24,7 +24,7 @@ typedef enum ecs_script_node_kind_t { EcsAstConst, EcsAstEntity, EcsAstPairScope, - EcsAstIf, + EcsAstIf } ecs_script_node_kind_t; typedef struct ecs_script_node_t { diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 1882fe5f4..28fb95538 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -41,6 +41,19 @@ ecs_expr_value_node_t* flecs_expr_value_from( return result; } +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name) +{ + ecs_expr_variable_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); + result->name = name; + result->node.kind = EcsExprVariable; + result->node.pos = node ? node->pos : NULL; + return result; +} + ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) @@ -93,11 +106,34 @@ ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value) { + char *str = ECS_CONST_CAST(char*, value); ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); - result->storage.string = ECS_CONST_CAST(char*, value); + result->storage.string = str; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); + + if (!flecs_string_escape(str)) { + return NULL; + } + + return result; +} + +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( + parser, ecs_expr_interpolated_string_t, EcsExprInterpolatedString); + result->value = ECS_CONST_CAST(char*, value); + result->buffer = flecs_strdup(&parser->script->allocator, value); + result->buffer_size = ecs_os_strlen(result->buffer) + 1; + result->node.type = ecs_id(ecs_string_t); + ecs_vec_init_t(&parser->script->allocator, &result->fragments, char*, 0); + ecs_vec_init_t(&parser->script->allocator, &result->expressions, + ecs_expr_node_t*, 0); + return result; } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 6cf645478..90ab0ad5c 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -10,6 +10,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprValue, + EcsExprInterpolatedString, EcsExprInitializer, EcsExprEmptyInitializer, EcsExprUnary, @@ -38,6 +39,15 @@ typedef struct ecs_expr_value_node_t { ecs_expr_small_value_t storage; } ecs_expr_value_node_t; +typedef struct ecs_expr_interpolated_string_t { + ecs_expr_node_t node; + char *value; /* modified by parser */ + char *buffer; /* for storing expr tokens */ + ecs_size_t buffer_size; + ecs_vec_t fragments; /* vec */ + ecs_vec_t expressions; /* vec */ +} ecs_expr_interpolated_string_t; + typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *value; @@ -115,6 +125,11 @@ ecs_expr_value_node_t* flecs_expr_value_from( ecs_expr_node_t *node, ecs_entity_t type); +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name); + ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); @@ -135,6 +150,10 @@ ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_script_parser_t *parser, + const char *value); + ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value); diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 912521baa..8afe02009 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -51,6 +51,12 @@ void flecs_expr_to_str_buf( ecs_strbuf_t *buf, bool colors); +bool flecs_string_is_interpolated( + const char *str); + +char* flecs_string_escape( + char *str); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 39aea3361..a9041873b 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -358,7 +358,12 @@ const char* flecs_script_parse_lhs( } case EcsTokString: { - *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + if (flecs_string_is_interpolated(Token(0))) { + *out = (ecs_expr_node_t*)flecs_expr_interpolated_string( + parser, Token(0)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + } break; } @@ -375,7 +380,7 @@ const char* flecs_script_parse_lhs( if (last_elem && last_elem[1] == '$') { /* Scoped global variable */ ecs_expr_variable_t *v = flecs_expr_variable(parser, expr); - memmove(&last_elem[1], &last_elem[2], + ecs_os_memmove(&last_elem[1], &last_elem[2], ecs_os_strlen(&last_elem[2]) + 1); v->node.kind = EcsExprGlobalVariable; *out = (ecs_expr_node_t*)v; @@ -531,6 +536,7 @@ ecs_script_t* ecs_expr_parse( } impl->next_token = ptr; + impl->token_remaining = parser.token_cur; if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; @@ -544,7 +550,7 @@ ecs_script_t* ecs_expr_parse( } } - // printf("%s\n", ecs_script_ast_to_str(script)); + // printf("%s\n", ecs_script_ast_to_str(script, true)); return script; error: @@ -619,4 +625,33 @@ const char* ecs_expr_run( return NULL; } +FLECS_API +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) +{ + if (!flecs_string_is_interpolated(str)) { + char *result = ecs_os_strdup(str); + if (!flecs_string_escape(result)) { + ecs_os_free(result); + return NULL; + } + return result; + } + + char *expr = flecs_asprintf("\"%s\"", str); + + ecs_expr_eval_desc_t desc = { .vars = vars }; + char *r = NULL; + if (!ecs_expr_run(world, expr, &ecs_value_ptr(ecs_string_t, &r), &desc)) { + ecs_os_free(expr); + return NULL; + } + + ecs_os_free(expr); + + return r; +} + #endif diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 44cdfd162..4db0207c7 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -225,4 +225,73 @@ int flecs_value_binary( return 0; } +bool flecs_string_is_interpolated( + const char *value) +{ + const char *ptr = value; + + for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } + + if (isspace(ptr[1]) || !ptr[1]) { + continue; /* $ by itself */ + } + + return true; + } + + ptr = value; + + for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } + + return true; + } + + return false; +} + +char* flecs_string_escape( + char *str) +{ + const char *ptr; + char *out = str, ch; + + for (ptr = str; ptr[0]; ) { + if (ptr[0] == '\\') { + if (ptr[1] == '{') { /* Escape string interpolation delimiter */ + ch = '{'; + ptr += 2; + } else if (ptr[1] == '$') { /* Escape string interpolation var */ + ch = '$'; + ptr += 2; + } else { + ptr = flecs_chrparse(ptr, &ch); + if (!ptr) { + ecs_err("invalid escape sequence in string '%s'", str); + return NULL; + } + } + } else { + ch = ptr[0]; + ptr ++; + } + + out[0] = ch; + out ++; + } + + out[0] = '\0'; + + return out + 1; +} + #endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 4f99b80bb..95697b3d1 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -34,6 +34,53 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_interpolated_string_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_interpolated_string_t *node, + ecs_expr_value_t *out) +{ + ecs_assert(node->node.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + flecs_expr_stack_push(ctx->stack); + + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); + } else { + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + + ecs_expr_value_t *val = flecs_expr_stack_result(ctx->stack, + (ecs_expr_node_t*)node); + val->owned = true; + if (flecs_expr_visit_eval_priv(ctx, expr, val)) { + goto error; + } + + ecs_assert(val->value.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_appendstr(&buf, *(char**)val->value.ptr); + } + } + + *(char**)out->value.ptr = ecs_strbuf_get(&buf); + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, @@ -281,6 +328,8 @@ int flecs_expr_global_variable_visit_eval( ecs_expr_variable_t *node, ecs_expr_value_t *out) { + (void)ctx; + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); @@ -538,6 +587,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_eval( + ctx, (ecs_expr_interpolated_string_t*)node, out)) + { + goto error; + } + break; case EcsExprEmptyInitializer: break; case EcsExprInitializer: diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 79f6165d4..8482f6df6 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -164,6 +164,70 @@ int flecs_expr_cast_visit_fold( return -1; } +static +int flecs_expr_interpolated_string_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_interpolated_string_t *node = + (ecs_expr_interpolated_string_t*)*node_ptr; + + bool can_fold = true; + + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (!fragment) { + ecs_expr_node_t **expr_ptr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++); + + if (flecs_expr_visit_fold(script, expr_ptr, desc)) { + goto error; + } + + if (expr_ptr[0]->kind != EcsExprValue) { + can_fold = false; + } + } + } + + if (can_fold) { + ecs_strbuf_t buf = ECS_STRBUF_INIT; + e = 0; + + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); + } else { + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + ecs_assert(expr->kind == EcsExprValue, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(expr->type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)expr; + ecs_strbuf_appendstr(&buf, *(char**)val->ptr); + } + } + + char **value = ecs_value_new(script->world, ecs_id(ecs_string_t)); + *value = ecs_strbuf_get(&buf); + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_string_t)); + result->ptr = value; + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_pre_fold( ecs_script_t *script, @@ -444,6 +508,11 @@ int flecs_expr_visit_fold( switch(node->kind) { case EcsExprValue: break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index a6b213240..0d88ae938 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -18,6 +18,25 @@ void flecs_expr_value_visit_free( } } +static +void flecs_expr_interpolated_string_visit_free( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node) +{ + int32_t i, count = ecs_vec_count(&node->expressions); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); + for (i = 0; i < count; i ++) { + flecs_expr_visit_free(script, expressions[i]); + } + + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->fragments, char*); + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->expressions, ecs_expr_node_t*); + flecs_free_n(&flecs_script_impl(script)->allocator, + char, node->buffer_size, node->buffer); +} + static void flecs_expr_initializer_visit_free( ecs_script_t *script, @@ -109,6 +128,11 @@ void flecs_expr_visit_free( script, (ecs_expr_value_node_t*)node); flecs_free_t(a, ecs_expr_value_node_t, node); break; + case EcsExprInterpolatedString: + flecs_expr_interpolated_string_visit_free( + script, (ecs_expr_interpolated_string_t*)node); + flecs_free_t(a, ecs_expr_interpolated_string_t, node); + break; case EcsExprInitializer: case EcsExprEmptyInitializer: flecs_expr_initializer_visit_free( diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 98d02607e..b2ffb0e30 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -41,6 +41,43 @@ int flecs_expr_value_to_str( return ret; } +static +int flecs_expr_interpolated_string_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_interpolated_string_t *node) +{ + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); + + ecs_strbuf_appendlit(v->buf, "interpolated("); + + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + + if (i) { + ecs_strbuf_appendlit(v->buf, ", "); + } + + if (fragment) { + flecs_expr_color_to_str(v, ECS_YELLOW); + ecs_strbuf_appendlit(v->buf, "\""); + ecs_strbuf_appendstr(v->buf, fragment); + ecs_strbuf_appendlit(v->buf, "\""); + flecs_expr_color_to_str(v, ECS_NORMAL); + } else { + ecs_expr_node_t *expr = expressions[e ++]; + if (flecs_expr_node_to_str(v, expr)) { + return -1; + } + } + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +} + static int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, @@ -237,6 +274,13 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_to_str(v, + (const ecs_expr_interpolated_string_t*)node)) + { + goto error; + } + break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_to_str(v, diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 03822b09b..8c378bcf8 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -502,6 +502,133 @@ int flecs_expr_type_for_oper( return 0; } +static +int flecs_expr_interpolated_string_visit_type( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + char *ptr, *frag = NULL; + char ch; + + for (ptr = node->value; (ch = ptr[0]); ptr ++) { + if (ch == '\\') { + ptr ++; + /* Next character is escaped, ignore */ + continue; + } + + if ((ch == '$') && (isspace(ptr[1]) || !ptr[1])) { + /* $ by itself */ + continue; + } + + if (ch == '$' || ch == '{') { + if (!frag) { + frag = node->value; + } + + char *frag_end = ptr; + + ecs_expr_node_t *result = NULL; + + if (ch == '$') { + char *var_name = ++ ptr; + ptr = ECS_CONST_CAST(char*, flecs_script_identifier( + NULL, ptr, NULL)); + if (!ptr) { + goto error; + } + + /* Fiddly, but reduces need for allocations */ + ecs_size_t offset = flecs_ito( + int32_t, node->buffer - node->value); + var_name = ECS_OFFSET(var_name, offset); + (*(char*)ECS_OFFSET(ptr, offset)) = '\0'; + + ecs_expr_variable_t *var = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, var_name); + if (!var) { + goto error; + } + + result = (ecs_expr_node_t*)var; + } else { + ecs_script_impl_t *impl = flecs_script_impl(script); + + ecs_script_parser_t parser = { + .script = impl, + .scope = impl->root, + .significant_newline = false, + .token_cur = impl->token_remaining + }; + + ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( + &parser, ptr + 1, 0, &result)); + if (!ptr) { + goto error; + } + + if (ptr[0] != '}') { + flecs_expr_visit_error(script, node, + "expected '}' at end of interpolated expression"); + goto error; + } + + ptr ++; + } + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_expr_eval_desc_t priv_desc = *desc; + priv_desc.type = ecs_id(ecs_string_t); /* String output */ + + if (flecs_expr_visit_type_priv(script, result, cur, &priv_desc)) { + flecs_expr_visit_free(script, result); + goto error; + } + + if (result->type != ecs_id(ecs_string_t)) { + result = (ecs_expr_node_t*)flecs_expr_cast(script, + (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); + } + + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->expressions, ecs_expr_node_t*)[0] = result; + + frag_end[0] = '\0'; + + if (frag != frag_end) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } + + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = NULL; + + frag = ptr; /* Point to next fragment */ + if (!ptr[0]) { + break; /* We already parsed the end of the string */ + } + } + } + + /* This would mean it's not an interpolated string, which means the parser + * messed up when creating the node. */ + ecs_assert(frag != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Add remaining fragment */ + if (frag != ptr) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_visit_type( ecs_script_t *script, @@ -1137,7 +1264,13 @@ int flecs_expr_visit_type_priv( switch(node->kind) { case EcsExprValue: - /* Value types are assigned by the AST */ + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_type( + script, (ecs_expr_interpolated_string_t*)node, cur, desc)) + { + goto error; + } break; case EcsExprEmptyInitializer: break; diff --git a/src/addons/script/function.c b/src/addons/script/function.c index 1bc3e76fb..696e39b2e 100644 --- a/src/addons/script/function.c +++ b/src/addons/script/function.c @@ -24,6 +24,8 @@ ECS_MOVE(EcsScriptConstVar, dst, src, { if (dst->type_info->hooks.dtor) { dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); } + + ecs_os_free(dst->value.ptr); *dst = *src; @@ -37,6 +39,7 @@ ECS_DTOR(EcsScriptConstVar, ptr, { if (ptr->type_info->hooks.dtor) { ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); } + ecs_os_free(ptr->value.ptr); }) static diff --git a/src/addons/script/interpolate.c b/src/addons/script/interpolate.c deleted file mode 100644 index 9833f5b68..000000000 --- a/src/addons/script/interpolate.c +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @file addons/script/interpolate.c - * @brief String interpolation. - */ - -#include "flecs.h" - -#ifdef FLECS_SCRIPT -#include -#include "script.h" - -static -const char* flecs_parse_var_name( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (isalpha(ch) || isdigit(ch) || ch == '_') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -static -const char* flecs_parse_interpolated_str( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (ch == '\\') { - if (ptr[1] == '}') { - *bptr = '}'; - bptr ++; - ptr += 2; - continue; - } - } - - if (ch != '}') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - ptr ++; - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -char* ecs_script_string_interpolate( - ecs_world_t *world, - const char *str, - const ecs_script_vars_t *vars) -{ - char token[ECS_MAX_TOKEN_SIZE]; - ecs_strbuf_t result = ECS_STRBUF_INIT; - const char *ptr; - char ch; - - for(ptr = str; (ch = *ptr); ptr++) { - if (ch == '\\') { - ptr ++; - if (ptr[0] == '$') { - ecs_strbuf_appendch(&result, '$'); - continue; - } - if (ptr[0] == '\\') { - ecs_strbuf_appendch(&result, '\\'); - continue; - } - if (ptr[0] == '{') { - ecs_strbuf_appendch(&result, '{'); - continue; - } - if (ptr[0] == '}') { - ecs_strbuf_appendch(&result, '}'); - continue; - } - ptr --; - } - - if (ch == '$') { - ptr = flecs_parse_var_name(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid variable name '%s'", ptr); - goto error; - } - - ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); - if (!var) { - ecs_parser_error(NULL, str, ptr - str, - "unresolved variable '%s'", token); - goto error; - } - - if (ecs_ptr_to_str_buf( - world, var->value.type, var->value.ptr, &result)) - { - goto error; - } - - ptr --; - } else if (ch == '{') { - ptr = flecs_parse_interpolated_str(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid interpolated expression"); - goto error; - } - - ecs_expr_eval_desc_t expr_desc = { - .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) - }; - - ecs_value_t expr_result = {0}; - if (!ecs_expr_run(world, token, &expr_result, &expr_desc)) { - goto error; - } - - if (ecs_ptr_to_str_buf( - world, expr_result.type, expr_result.ptr, &result)) - { - goto error; - } - - ecs_value_free(world, expr_result.type, expr_result.ptr); - - ptr --; - } else { - ecs_strbuf_appendch(&result, ch); - } - } - - return ecs_strbuf_get(&result); -error: - return NULL; -} - -#endif diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 533cea40c..bddde1a61 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -850,6 +850,8 @@ ecs_script_t* ecs_script_parse( } } while (true); + impl->token_remaining = parser.token_cur; + return script; error: ecs_script_free(script); diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 6724fa2c7..616296347 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -21,6 +21,7 @@ typedef struct ecs_script_impl_t { ecs_script_scope_t *root; ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; + char *token_remaining; /* Remaining space in token buffer */ const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index a67143117..3179c45ea 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -202,21 +202,24 @@ bool flecs_script_is_identifier( return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); } -static const char* flecs_script_identifier( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { - out->kind = EcsTokIdentifier; - out->value = parser->token_cur; + if (out) { + out->kind = EcsTokIdentifier; + out->value = parser->token_cur; + } ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; - char *outpos = parser->token_cur; - - if (parser->merge_variable_members) { - is_var = false; + char *outpos = NULL; + if (parser) { + outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } } do { @@ -253,8 +256,10 @@ const char* flecs_script_identifier( return NULL; } - *outpos = c; - outpos ++; + if (outpos) { + *outpos = c; + outpos ++; + } pos ++; if (!indent) { @@ -262,8 +267,10 @@ const char* flecs_script_identifier( } } while (true); - *outpos = '\0'; - parser->token_cur = outpos + 1; + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } return pos; } else if (c == '>') { ecs_parser_error(parser->script->pub.name, @@ -272,14 +279,19 @@ const char* flecs_script_identifier( "> without < in identifier"); return NULL; } else { - *outpos = '\0'; - parser->token_cur = outpos + 1; + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } return pos; } } - *outpos = *pos; - outpos ++; + if (outpos) { + *outpos = *pos; + outpos ++; + } + pos ++; } while (true); } diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index 009110094..b81d4181e 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -93,4 +93,9 @@ const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos); +const char* flecs_script_identifier( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out); + #endif diff --git a/test/meta/src/Cursor.c b/test/meta/src/Cursor.c index f61cce8d0..ab62b6809 100644 --- a/test/meta/src/Cursor.c +++ b/test/meta/src/Cursor.c @@ -432,7 +432,7 @@ void Cursor_set_string_to_null_as_signed(void) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); test_ok( ecs_meta_set_int(&cur, 0) ); - test_str(value, 0); + test_str(value, "0"); ecs_fini(world); } @@ -445,7 +445,7 @@ void Cursor_set_string_to_null_as_unsigned(void) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); test_ok( ecs_meta_set_uint(&cur, 0) ); - test_str(value, 0); + test_str(value, "0"); ecs_fini(world); } diff --git a/test/script/project.json b/test/script/project.json index b49346e1d..3aed2a0c1 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -573,6 +573,18 @@ "interpolate_string_w_escape_curly_brackets", "interpolate_string_w_func", "interpolate_string_w_func_chain", + "interpolate_in_expr_var_name", + "interpolate_in_expr_var_name_w_pre", + "interpolate_in_expr_var_name_w_post", + "interpolate_in_expr_var_name_w_pre_post", + "interpolate_in_expr_var_name_bool", + "interpolate_in_expr_var_name_char", + "interpolate_in_expr_var_name_i32", + "interpolate_in_expr_var_name_u32", + "interpolate_in_expr_var_name_f32", + "interpolate_in_expr_var_name_entity", + "interpolate_in_expr_w_curly_brackets", + "interpolate_in_expr_w_curly_brackets_w_var", "iter_to_vars_no_data", "iter_to_vars_1_comp", "iter_to_vars_2_comps", @@ -611,7 +623,8 @@ "space_at_start", "newline_at_start", "global_const_var", - "scoped_global_const_var" + "scoped_global_const_var", + "escape_newline" ] }, { "id": "ExprAst", @@ -620,7 +633,10 @@ "binary_f32_var_add_int", "binary_f32_var_div_int", "binary_f32_var_add_flt", - "binary_f32_var_div_by_int_sub_int" + "binary_f32_var_div_by_int_sub_int", + "interpolated_string_var", + "interpolated_string_curly_brackets", + "interpolated_string_curly_brackets_w_var" ] }, { "id": "Vars", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 98799af35..ef6df5262 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -4426,7 +4426,7 @@ void Eval_multiline_string(void) { HEAD "Foo { String: {value: `start" LINE "Hello World" LINE "Foo Bar" - LINE "Special characters }{\"\"''," + LINE "Special characters }\\{\"\"''," LINE "`}}"; test_assert(ecs_script_run(world, NULL, expr) == 0); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 23b1e6a92..1472d3557 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -3825,6 +3825,273 @@ void Expr_interpolate_string_w_func_chain(void) { ecs_fini(world); } +void Expr_interpolate_in_expr_var_name(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_string_t); + test_assert(foo != NULL); + *(char**)foo->value.ptr = ecs_os_strdup("World"); + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "World"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_w_pre(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_string_t); + test_assert(foo != NULL); + *(char**)foo->value.ptr = ecs_os_strdup("World"); + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"Hello $foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello World"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_w_post(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_string_t); + test_assert(foo != NULL); + *(char**)foo->value.ptr = ecs_os_strdup("World"); + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo Hello\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "World Hello"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_w_pre_post(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_string_t); + test_assert(foo != NULL); + *(char**)foo->value.ptr = ecs_os_strdup("World"); + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"Hello $foo!\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello World!"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_bool(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_bool_t); + test_assert(foo != NULL); + *(bool*)foo->value.ptr = true; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "true"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_char(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_char_t); + test_assert(foo != NULL); + *(char*)foo->value.ptr = 'a'; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "a"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_i32(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_i32_t); + test_assert(foo != NULL); + *(int32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "10"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_u32(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_u32_t); + test_assert(foo != NULL); + *(ecs_u32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "10"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_f32(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_f32_t); + test_assert(foo != NULL); + *(ecs_f32_t*)foo->value.ptr = 10.5; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "10.500000"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_var_name_entity(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_entity_t); + test_assert(foo != NULL); + *(ecs_entity_t*)foo->value.ptr = EcsFlecsCore; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"$foo\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "flecs.core"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_w_curly_brackets(void) { + ecs_world_t *world = ecs_init(); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"{10 + 20}\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "30"); + ecs_os_free(v); + + ecs_fini(world); +} + +void Expr_interpolate_in_expr_w_curly_brackets_w_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define( + vars, "foo", ecs_i32_t); + test_assert(foo != NULL); + *(int32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + char *v = NULL; + test_assert(NULL != ecs_expr_run(world, "\"{$foo * 2}\"", + &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "20"); + ecs_os_free(v); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Expr_iter_to_vars_no_data(void) { ecs_world_t *world = ecs_init(); @@ -5147,3 +5414,16 @@ void Expr_scoped_global_const_var(void) { ecs_fini(world); } + +void Expr_escape_newline(void) { + ecs_world_t *world = ecs_init(); + + char *v = NULL; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "\"Hello\\nWorld\"", + &ecs_value_ptr(ecs_string_t, &v), &desc) != NULL); + test_str(v, "Hello\nWorld"); + ecs_os_free(v); + + ecs_fini(world); +} diff --git a/test/script/src/ExprAst.c b/test/script/src/ExprAst.c index 56674f23c..cfd32b1e5 100644 --- a/test/script/src/ExprAst.c +++ b/test/script/src/ExprAst.c @@ -121,3 +121,128 @@ void ExprAst_binary_f32_var_div_by_int_sub_int(void) { ecs_fini(world); } + +void ExprAst_interpolated_string_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(ecs_i32_t*)foo->value.ptr = 10; + + { + ecs_expr_eval_desc_t desc = { .vars = vars }; + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello $foo\"", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "interpolated(\"Hello \", string($foo))"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello 10"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + { + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = true }; + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello $foo\"", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "interpolated(\"Hello \", string($foo))"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello 10"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void ExprAst_interpolated_string_curly_brackets(void) { + ecs_world_t *world = ecs_init(); + + { + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello {10 + 20}\"", NULL); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "Hello 30"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), NULL)); + test_str(v, "Hello 30"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + { + ecs_expr_eval_desc_t desc = { .disable_folding = true }; + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello {10 + 20}\"", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "interpolated(\"Hello \", string((10 + 20)))"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello 30"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + ecs_fini(world); +} + +void ExprAst_interpolated_string_curly_brackets_w_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(ecs_i32_t*)foo->value.ptr = 10; + + { + ecs_expr_eval_desc_t desc = { .vars = vars }; + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello {$foo + 10}\"", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "interpolated(\"Hello \", string(($foo + 10)))"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello 20"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + { + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = true }; + ecs_script_t *expr = ecs_expr_parse(world, "\"Hello {$foo + 10}\"", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "interpolated(\"Hello \", string(($foo + i32(10))))"); + ecs_os_free(ast); + + char *v = NULL; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_string_t, &v), &desc)); + test_str(v, "Hello 20"); + ecs_os_free(v); + + ecs_script_free(expr); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index a278776aa..6d5948389 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -560,6 +560,18 @@ void Expr_interpolate_string_w_escape_var_operator(void); void Expr_interpolate_string_w_escape_curly_brackets(void); void Expr_interpolate_string_w_func(void); void Expr_interpolate_string_w_func_chain(void); +void Expr_interpolate_in_expr_var_name(void); +void Expr_interpolate_in_expr_var_name_w_pre(void); +void Expr_interpolate_in_expr_var_name_w_post(void); +void Expr_interpolate_in_expr_var_name_w_pre_post(void); +void Expr_interpolate_in_expr_var_name_bool(void); +void Expr_interpolate_in_expr_var_name_char(void); +void Expr_interpolate_in_expr_var_name_i32(void); +void Expr_interpolate_in_expr_var_name_u32(void); +void Expr_interpolate_in_expr_var_name_f32(void); +void Expr_interpolate_in_expr_var_name_entity(void); +void Expr_interpolate_in_expr_w_curly_brackets(void); +void Expr_interpolate_in_expr_w_curly_brackets_w_var(void); void Expr_iter_to_vars_no_data(void); void Expr_iter_to_vars_1_comp(void); void Expr_iter_to_vars_2_comps(void); @@ -599,6 +611,7 @@ void Expr_space_at_start(void); void Expr_newline_at_start(void); void Expr_global_const_var(void); void Expr_scoped_global_const_var(void); +void Expr_escape_newline(void); // Testsuite 'ExprAst' void ExprAst_binary_f32_var_add_f32_var(void); @@ -606,6 +619,9 @@ void ExprAst_binary_f32_var_add_int(void); void ExprAst_binary_f32_var_div_int(void); void ExprAst_binary_f32_var_add_flt(void); void ExprAst_binary_f32_var_div_by_int_sub_int(void); +void ExprAst_interpolated_string_var(void); +void ExprAst_interpolated_string_curly_brackets(void); +void ExprAst_interpolated_string_curly_brackets_w_var(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -2959,6 +2975,54 @@ bake_test_case Expr_testcases[] = { "interpolate_string_w_func_chain", Expr_interpolate_string_w_func_chain }, + { + "interpolate_in_expr_var_name", + Expr_interpolate_in_expr_var_name + }, + { + "interpolate_in_expr_var_name_w_pre", + Expr_interpolate_in_expr_var_name_w_pre + }, + { + "interpolate_in_expr_var_name_w_post", + Expr_interpolate_in_expr_var_name_w_post + }, + { + "interpolate_in_expr_var_name_w_pre_post", + Expr_interpolate_in_expr_var_name_w_pre_post + }, + { + "interpolate_in_expr_var_name_bool", + Expr_interpolate_in_expr_var_name_bool + }, + { + "interpolate_in_expr_var_name_char", + Expr_interpolate_in_expr_var_name_char + }, + { + "interpolate_in_expr_var_name_i32", + Expr_interpolate_in_expr_var_name_i32 + }, + { + "interpolate_in_expr_var_name_u32", + Expr_interpolate_in_expr_var_name_u32 + }, + { + "interpolate_in_expr_var_name_f32", + Expr_interpolate_in_expr_var_name_f32 + }, + { + "interpolate_in_expr_var_name_entity", + Expr_interpolate_in_expr_var_name_entity + }, + { + "interpolate_in_expr_w_curly_brackets", + Expr_interpolate_in_expr_w_curly_brackets + }, + { + "interpolate_in_expr_w_curly_brackets_w_var", + Expr_interpolate_in_expr_w_curly_brackets_w_var + }, { "iter_to_vars_no_data", Expr_iter_to_vars_no_data @@ -3114,6 +3178,10 @@ bake_test_case Expr_testcases[] = { { "scoped_global_const_var", Expr_scoped_global_const_var + }, + { + "escape_newline", + Expr_escape_newline } }; @@ -3137,6 +3205,18 @@ bake_test_case ExprAst_testcases[] = { { "binary_f32_var_div_by_int_sub_int", ExprAst_binary_f32_var_div_by_int_sub_int + }, + { + "interpolated_string_var", + ExprAst_interpolated_string_var + }, + { + "interpolated_string_curly_brackets", + ExprAst_interpolated_string_curly_brackets + }, + { + "interpolated_string_curly_brackets_w_var", + ExprAst_interpolated_string_curly_brackets_w_var } }; @@ -3820,7 +3900,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 193, + 206, Expr_testcases, 1, Expr_params @@ -3829,7 +3909,7 @@ static bake_test_suite suites[] = { "ExprAst", NULL, NULL, - 5, + 8, ExprAst_testcases }, {