From 5126d7087f19494c71bda32133d0f5e8fe863751 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 19 Nov 2024 12:41:17 -0800 Subject: [PATCH 01/83] Initial commit for new expression parser --- distr/flecs.c | 842 ++++++++++++++++++++++++++++++-- distr/flecs.h | 14 + include/flecs/addons/script.h | 14 + src/addons/script/ast.c | 2 - src/addons/script/expr_ast.c | 132 +++++ src/addons/script/expr_ast.h | 111 +++++ src/addons/script/expr_to_str.c | 189 +++++++ src/addons/script/new_expr.c | 283 +++++++++++ src/addons/script/parser.c | 4 +- src/addons/script/parser.h | 35 +- src/addons/script/script.h | 1 + src/addons/script/tokenizer.c | 63 ++- src/addons/script/tokenizer.h | 22 +- 13 files changed, 1656 insertions(+), 56 deletions(-) create mode 100644 src/addons/script/expr_ast.c create mode 100644 src/addons/script/expr_ast.h create mode 100644 src/addons/script/expr_to_str.c create mode 100644 src/addons/script/new_expr.c diff --git a/distr/flecs.c b/distr/flecs.c index e6cfaa71f8..50dd064c30 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4546,20 +4546,33 @@ typedef enum ecs_script_token_kind_t { EcsTokParenClose = ')', EcsTokBracketOpen = '[', EcsTokBracketClose = ']', - EcsTokMul = '*', + EcsTokMember = '.', EcsTokComma = ',', EcsTokSemiColon = ';', EcsTokColon = ':', EcsTokAssign = '=', + EcsTokAdd = '+', + EcsTokSub = '-', + EcsTokMul = '*', + EcsTokDiv = '/', + EcsTokMod = '%', EcsTokBitwiseOr = '|', + EcsTokBitwiseAnd = '&', EcsTokNot = '!', EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', EcsTokEq, EcsTokNeq, - EcsTokMatch, + EcsTokGt, + EcsTokGtEq, + EcsTokLt, + EcsTokLtEq, + EcsTokAnd, EcsTokOr, + EcsTokMatch, + EcsTokShiftLeft, + EcsTokShiftRight, EcsTokIdentifier, EcsTokString, EcsTokNumber, @@ -4583,6 +4596,11 @@ typedef struct ecs_script_tokens_t { ecs_script_token_t tokens[256]; } ecs_script_tokens_t; +typedef struct ecs_script_tokenizer_t { + ecs_script_tokens_t stack; + ecs_script_token_t *tokens; +} ecs_script_tokenizer_t; + const char* flecs_script_expr( ecs_script_parser_t *parser, const char *ptr, @@ -4836,6 +4854,118 @@ ecs_script_if_t* flecs_script_insert_if( #endif +/** + * @file addons/script/expr_ast.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_AST_H +#define FLECS_SCRIPT_EXPR_AST_H + +typedef enum ecs_expr_node_kind_t { + EcsExprValue, + EcsExprUnary, + EcsExprBinary, + EcsExprIdentifier, + EcsExprVariable, + EcsExprFunction, + EcsExprMember, + EcsExprElement, + EcsExprCast +} ecs_expr_node_kind_t; + +struct ecs_expr_node_t { + ecs_expr_node_kind_t kind; + const char *pos; +}; + +typedef struct ecs_expr_val_t { + ecs_expr_node_t node; + ecs_value_t value; + union { + bool bool_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + double f32; + double f64; + const char *string; + } storage; +} ecs_expr_val_t; + +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; +} ecs_expr_identifier_t; + +typedef struct ecs_expr_variable_t { + ecs_expr_node_t node; + const char *value; +} ecs_expr_variable_t; + +typedef struct ecs_expr_binary_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *right; + ecs_script_token_kind_t operator; +} ecs_expr_binary_t; + +typedef struct ecs_expr_member_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *member_name; +} ecs_expr_member_t; + +typedef struct ecs_expr_element_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *index; +} ecs_expr_element_t; + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value); + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value); + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value); + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value); + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser); + +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser); + +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser); + +#endif + /** * @file addons/script/visit.h * @brief Script AST visitor utilities. @@ -54240,8 +54370,6 @@ bool ecs_using_task_threads( #ifdef FLECS_SCRIPT -#define flecs_ast_strdup(parser, str)\ - (str ? flecs_strdup(&parser->script->allocator, str) : NULL) #define flecs_ast_new(parser, T, kind)\ (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) #define flecs_ast_vec(parser, vec, T) \ @@ -56297,6 +56425,325 @@ const char* ecs_script_expr_run( #endif +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ + + +#ifdef FLECS_SCRIPT + +#define flecs_expr_ast_new(parser, T, kind)\ + (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) + +static +void* flecs_expr_ast_new_( + ecs_script_parser_t *parser, + ecs_size_t size, + ecs_expr_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_expr_node_t *result = flecs_calloc(a, size); + result->kind = kind; + result->pos = parser->pos; + return result; +} + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.bool_ = value; + result->value.ptr = &result->storage.bool_; + result->value.type = ecs_id(ecs_bool_t); + return result; +} + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.i64 = value; + result->value.ptr = &result->storage.i64; + result->value.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.u64 = value; + result->value.ptr = &result->storage.u64; + result->value.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.f64 = value; + result->value.ptr = &result->storage.f64; + result->value.type = ecs_id(ecs_f64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.string = value; + result->value.ptr = &result->storage.string; + result->value.type = ecs_id(ecs_string_t); + return result; +} + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_identifier_t *result = flecs_expr_ast_new( + parser, ecs_expr_identifier_t, EcsExprIdentifier); + result->value = value; + return result; +} + +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_variable_t *result = flecs_expr_ast_new( + parser, ecs_expr_variable_t, EcsExprVariable); + result->value = value; + return result; +} + +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprBinary); + return result; +} + +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser) +{ + ecs_expr_member_t *result = flecs_expr_ast_new( + parser, ecs_expr_member_t, EcsExprMember); + return result; +} + +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser) +{ + ecs_expr_element_t *result = flecs_expr_ast_new( + parser, ecs_expr_element_t, EcsExprElement); + return result; +} + +#endif + +/** + * @file addons/script/expr_to_str.c + * @brief Script expression AST to string visitor. + */ + + +#ifdef FLECS_SCRIPT + +typedef struct ecs_expr_str_visitor_t { + const ecs_world_t *world; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_expr_str_visitor_t; + +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node); + +int flecs_expr_value_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_val_t *node) +{ + return ecs_ptr_to_str_buf( + v->world, node->value.type, node->value.ptr, v->buf); +} + +int flecs_expr_binary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_binary_t *node) +{ + ecs_strbuf_appendlit(v->buf, "("); + + if (flecs_expr_node_to_str(v, node->left)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, " "); + + switch(node->operator) { + case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; + case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; + case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; + case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; + case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; + case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; + case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; + case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; + case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; + case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; + case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; + case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; + case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; + case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; + case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; + case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; + case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; + default: + ecs_err("invalid operator in expression"); + return -1; + }; + + ecs_strbuf_appendlit(v->buf, " "); + + if (flecs_expr_node_to_str(v, node->right)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_identifier_t *node) +{ + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_variable_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_variable_t *node) +{ + ecs_strbuf_appendlit(v->buf, "$"); + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_member_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_member_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "."); + ecs_strbuf_appendstr(v->buf, node->member_name); + return 0; +} + +int flecs_expr_element_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_element_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "["); + if (flecs_expr_node_to_str(v, node->index)) { + return -1; + } + ecs_strbuf_appendlit(v->buf, "]"); + return 0; +} + +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + goto error; + } + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + if (flecs_expr_node_to_str(&v, expr)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif + /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -56473,8 +56920,8 @@ char* ecs_script_string_interpolate( #endif /** - * @file addons/script/parser.c - * @brief Script grammar parser. + * @file addons/script/expr.c + * @brief Script expression parser. */ @@ -56518,9 +56965,9 @@ char* ecs_script_string_interpolate( /* Definitions for parser functions */ #define ParserBegin\ - ecs_script_tokens_t token_stack = {0};\ - ecs_script_token_t *tokens = token_stack.tokens;\ - (void)tokens + ecs_script_tokenizer_t _tokenizer = {{0}};\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_script_tokenizer_t *tokenizer = &_tokenizer; #define ParserEnd\ Error("unexpected end of rule (parser error)");\ @@ -56528,7 +56975,14 @@ char* ecs_script_string_interpolate( return NULL /* Get token */ -#define Token(n) (tokens[n].value) +#define Token(n) (tokenizer->tokens[n].value) + +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; + +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; /* Error */ #define Error(...)\ @@ -56539,8 +56993,8 @@ char* ecs_script_string_interpolate( /* Parse expression */ #define Expr(until, ...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ goto error;\ }\ @@ -56554,8 +57008,8 @@ char* ecs_script_string_interpolate( /* Parse token until character */ #define Until(until, ...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_until(parser, pos, t, until))) {\ goto error;\ }\ @@ -56565,8 +57019,8 @@ char* ecs_script_string_interpolate( /* Parse next token */ #define Parse(...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_token(parser, pos, t, false))) {\ goto error;\ }\ @@ -56637,11 +57091,11 @@ char* ecs_script_string_interpolate( ecs_script_token_t lookahead_token;\ const char *old_lh_token_cur = parser->token_cur;\ if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ - token_stack.tokens[token_stack.count ++] = lookahead_token;\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ switch(lookahead_token.kind) {\ __VA_ARGS__\ default:\ - token_stack.count --;\ + tokenizer->stack.count --;\ break;\ }\ if (old_lh_token_cur > parser->token_keep) {\ @@ -56693,9 +57147,9 @@ char* ecs_script_string_interpolate( /* Parser loop */ #define Loop(...)\ - int32_t token_stack_count = token_stack.count;\ + int32_t token_stack_count = tokenizer->stack.count;\ do {\ - token_stack.count = token_stack_count;\ + tokenizer->stack.count = token_stack_count;\ __VA_ARGS__\ } while (true); @@ -56704,6 +57158,287 @@ char* ecs_script_string_interpolate( #endif +/* From https://en.cppreference.com/w/c/language/operator_precedence */ + +static int flecs_expr_precedence[] = { + [EcsTokParenOpen] = 1, + [EcsTokMember] = 1, + [EcsTokBracketOpen] = 1, + [EcsTokMul] = 3, + [EcsTokDiv] = 3, + [EcsTokMod] = 3, + [EcsTokAdd] = 4, + [EcsTokSub] = 4, + [EcsTokShiftLeft] = 5, + [EcsTokShiftRight] = 5, + [EcsTokGt] = 6, + [EcsTokGtEq] = 6, + [EcsTokLt] = 6, + [EcsTokLtEq] = 6, + [EcsTokEq] = 7, + [EcsTokNeq] = 7, + [EcsTokBitwiseAnd] = 8, + [EcsTokBitwiseOr] = 10, + [EcsTokAnd] = 11, + [EcsTokOr] = 12, +}; + +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +static +bool flecs_has_precedence( + ecs_script_token_kind_t first, + ecs_script_token_kind_t second) +{ + if (!flecs_expr_precedence[first]) { + return false; + } + return flecs_expr_precedence[first] < flecs_expr_precedence[second]; +} + +static +const char* flecs_script_parse_rhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_expr_node_t *left, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + const char *last_pos = pos; + + TokenFramePush(); + + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; + + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } + + /* Consume lookahead token */ + pos = lookahead; + + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; + + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } + + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); + + break; + } + + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; + + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); + + break; + } + + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; + } + }; + + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; + + break; + } + ) + + if (pos[0] && (pos != last_pos)) { + pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); + } + + TokenFramePop(); + + return pos; +error: + return NULL; +} + +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + TokenFramePush(); + + Parse( + case EcsTokNumber: { + const char *expr = Token(0); + if (strchr(expr, '.') || strchr(expr, 'e')) { + *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); + } else if (expr[0] == '-') { + *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + } + break; + } + + case EcsTokString: { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + break; + } + + case EcsTokIdentifier: { + const char *expr = Token(0); + if (expr[0] == '$') { + *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); + } else if (!ecs_os_strcmp(expr, "true")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); + } else if (!ecs_os_strcmp(expr, "false")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); + } else { + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + } + break; + } + + case EcsTokParenOpen: { + pos = flecs_script_parse_expr(parser, pos, 0, out); + Parse_1(EcsTokParenClose, { + break; + }) + break; + } + ) + + TokenFramePop(); + + /* Parse right-hand side of expression if there is one */ + return flecs_script_parse_rhs( + parser, pos, tokenizer, *out, left_oper, out); +error: + return NULL; +} + +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + ParserBegin; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, 0, out); + + EndOfRule; + + ParserEnd; +} + +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr) +{ + if (!script) { + script = flecs_script_new(world); + } + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + return out; +error: + return NULL; +} + +#endif + +/** + * @file addons/script/parser.c + * @brief Script grammar parser. + */ + + +#ifdef FLECS_SCRIPT + #define EcsTokEndOfStatement\ case ';':\ case '\n':\ @@ -57015,13 +57750,13 @@ insert_tag: { if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { Error( "invalid context for variable component '%s': must be " - "part of entity", tokens[0].value); + "part of entity", tokenizer->tokens[0].value); } } else { if (!flecs_script_insert_tag(parser, Token(0))) { Error( "invalid context for tag '%s': must be part of entity", - tokens[0].value); + tokenizer->tokens[0].value); } } @@ -59553,15 +60288,27 @@ const char* flecs_script_token_kind_str( case EcsTokAnnotation: case EcsTokComma: case EcsTokSemiColon: - case EcsTokMul: case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: case EcsTokNot: case EcsTokOptional: case EcsTokEq: case EcsTokNeq: - case EcsTokMatch: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: case EcsTokOr: + case EcsTokMatch: + case EcsTokShiftLeft: + case EcsTokShiftRight: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: @@ -59729,12 +60476,35 @@ const char* flecs_script_number( { out->kind = EcsTokNumber; out->value = parser->token_cur; + + bool dot_parsed = false; + bool e_parsed = false; ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; do { char c = pos[0]; - if (!isdigit(c)) { + bool valid_number = false; + + if (c == '.') { + if (!dot_parsed && !e_parsed) { + if (isdigit(pos[1])) { + dot_parsed = true; + valid_number = true; + } + } + } else if (c == 'e') { + if (!e_parsed) { + if (isdigit(pos[1])) { + e_parsed = true; + valid_number = true; + } + } + } else if (isdigit(c)) { + valid_number = true; + } + + if (!valid_number) { *outpos = '\0'; parser->token_cur = outpos + 1; break; @@ -60000,17 +60770,31 @@ const char* flecs_script_token( Operator ("@", EcsTokAnnotation) Operator (",", EcsTokComma) Operator (";", EcsTokSemiColon) + Operator ("+", EcsTokAdd) + Operator ("-", EcsTokSub) Operator ("*", EcsTokMul) + Operator ("/", EcsTokDiv) + Operator ("%%", EcsTokMod) Operator ("?", EcsTokOptional) + Operator (".", EcsTokMember) OperatorMultiChar ("==", EcsTokEq) OperatorMultiChar ("!=", EcsTokNeq) - OperatorMultiChar ("~=", EcsTokMatch) + OperatorMultiChar ("<<", EcsTokShiftLeft) + OperatorMultiChar (">>", EcsTokShiftRight) + OperatorMultiChar (">=", EcsTokGtEq) + OperatorMultiChar ("<=", EcsTokLtEq) + + OperatorMultiChar ("&&", EcsTokAnd) OperatorMultiChar ("||", EcsTokOr) + OperatorMultiChar ("~=", EcsTokMatch) - OperatorMultiChar ("!", EcsTokNot) - OperatorMultiChar ("=", EcsTokAssign) - OperatorMultiChar ("|", EcsTokBitwiseOr) + Operator ("!", EcsTokNot) + Operator ("=", EcsTokAssign) + Operator ("&", EcsTokBitwiseAnd) + Operator ("|", EcsTokBitwiseOr) + Operator (">", EcsTokGt) + Operator ("<", EcsTokLt) Keyword ("with", EcsTokKeywordWith) Keyword ("using", EcsTokKeywordUsing) diff --git a/distr/flecs.h b/distr/flecs.h index d8f1b7e771..338a5dc67b 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14677,6 +14677,20 @@ int ecs_ptr_to_str_buf( const void *data, ecs_strbuf_t *buf); +typedef struct ecs_expr_node_t ecs_expr_node_t; + +FLECS_API +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr); + +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr); + /** Script module import function. * Usage: * @code diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index f123249b1e..c31bc7e3d6 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -498,6 +498,20 @@ int ecs_ptr_to_str_buf( const void *data, ecs_strbuf_t *buf); +typedef struct ecs_expr_node_t ecs_expr_node_t; + +FLECS_API +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr); + +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr); + /** Script module import function. * Usage: * @code diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index e45bbf0b3b..941043b11d 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -8,8 +8,6 @@ #ifdef FLECS_SCRIPT #include "script.h" -#define flecs_ast_strdup(parser, str)\ - (str ? flecs_strdup(&parser->script->allocator, str) : NULL) #define flecs_ast_new(parser, T, kind)\ (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) #define flecs_ast_vec(parser, vec, T) \ diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr_ast.c new file mode 100644 index 0000000000..6f59104e4d --- /dev/null +++ b/src/addons/script/expr_ast.c @@ -0,0 +1,132 @@ +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +#define flecs_expr_ast_new(parser, T, kind)\ + (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) + +static +void* flecs_expr_ast_new_( + ecs_script_parser_t *parser, + ecs_size_t size, + ecs_expr_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_expr_node_t *result = flecs_calloc(a, size); + result->kind = kind; + result->pos = parser->pos; + return result; +} + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.bool_ = value; + result->value.ptr = &result->storage.bool_; + result->value.type = ecs_id(ecs_bool_t); + return result; +} + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.i64 = value; + result->value.ptr = &result->storage.i64; + result->value.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.u64 = value; + result->value.ptr = &result->storage.u64; + result->value.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.f64 = value; + result->value.ptr = &result->storage.f64; + result->value.type = ecs_id(ecs_f64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.string = value; + result->value.ptr = &result->storage.string; + result->value.type = ecs_id(ecs_string_t); + return result; +} + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_identifier_t *result = flecs_expr_ast_new( + parser, ecs_expr_identifier_t, EcsExprIdentifier); + result->value = value; + return result; +} + +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_variable_t *result = flecs_expr_ast_new( + parser, ecs_expr_variable_t, EcsExprVariable); + result->value = value; + return result; +} + +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprBinary); + return result; +} + +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser) +{ + ecs_expr_member_t *result = flecs_expr_ast_new( + parser, ecs_expr_member_t, EcsExprMember); + return result; +} + +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser) +{ + ecs_expr_element_t *result = flecs_expr_ast_new( + parser, ecs_expr_element_t, EcsExprElement); + return result; +} + +#endif diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h new file mode 100644 index 0000000000..f15c3dee65 --- /dev/null +++ b/src/addons/script/expr_ast.h @@ -0,0 +1,111 @@ +/** + * @file addons/script/expr_ast.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_AST_H +#define FLECS_SCRIPT_EXPR_AST_H + +typedef enum ecs_expr_node_kind_t { + EcsExprValue, + EcsExprUnary, + EcsExprBinary, + EcsExprIdentifier, + EcsExprVariable, + EcsExprFunction, + EcsExprMember, + EcsExprElement, + EcsExprCast +} ecs_expr_node_kind_t; + +struct ecs_expr_node_t { + ecs_expr_node_kind_t kind; + const char *pos; +}; + +typedef struct ecs_expr_val_t { + ecs_expr_node_t node; + ecs_value_t value; + union { + bool bool_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + double f32; + double f64; + const char *string; + } storage; +} ecs_expr_val_t; + +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; +} ecs_expr_identifier_t; + +typedef struct ecs_expr_variable_t { + ecs_expr_node_t node; + const char *value; +} ecs_expr_variable_t; + +typedef struct ecs_expr_binary_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *right; + ecs_script_token_kind_t operator; +} ecs_expr_binary_t; + +typedef struct ecs_expr_member_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *member_name; +} ecs_expr_member_t; + +typedef struct ecs_expr_element_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *index; +} ecs_expr_element_t; + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value); + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value); + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value); + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value); + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value); + +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser); + +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser); + +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser); + +#endif diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr_to_str.c new file mode 100644 index 0000000000..b9ff3c5d22 --- /dev/null +++ b/src/addons/script/expr_to_str.c @@ -0,0 +1,189 @@ +/** + * @file addons/script/expr_to_str.c + * @brief Script expression AST to string visitor. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +typedef struct ecs_expr_str_visitor_t { + const ecs_world_t *world; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_expr_str_visitor_t; + +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node); + +int flecs_expr_value_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_val_t *node) +{ + return ecs_ptr_to_str_buf( + v->world, node->value.type, node->value.ptr, v->buf); +} + +int flecs_expr_binary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_binary_t *node) +{ + ecs_strbuf_appendlit(v->buf, "("); + + if (flecs_expr_node_to_str(v, node->left)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, " "); + + switch(node->operator) { + case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; + case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; + case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; + case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; + case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; + case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; + case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; + case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; + case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; + case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; + case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; + case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; + case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; + case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; + case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; + case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; + case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; + default: + ecs_err("invalid operator in expression"); + return -1; + }; + + ecs_strbuf_appendlit(v->buf, " "); + + if (flecs_expr_node_to_str(v, node->right)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_identifier_t *node) +{ + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_variable_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_variable_t *node) +{ + ecs_strbuf_appendlit(v->buf, "$"); + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_member_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_member_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "."); + ecs_strbuf_appendstr(v->buf, node->member_name); + return 0; +} + +int flecs_expr_element_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_element_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "["); + if (flecs_expr_node_to_str(v, node->index)) { + return -1; + } + ecs_strbuf_appendlit(v->buf, "]"); + return 0; +} + +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + goto error; + } + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + if (flecs_expr_node_to_str(&v, expr)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif diff --git a/src/addons/script/new_expr.c b/src/addons/script/new_expr.c new file mode 100644 index 0000000000..269792387f --- /dev/null +++ b/src/addons/script/new_expr.c @@ -0,0 +1,283 @@ +/** + * @file addons/script/expr.c + * @brief Script expression parser. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" +#include "parser.h" + +/* From https://en.cppreference.com/w/c/language/operator_precedence */ + +static int flecs_expr_precedence[] = { + [EcsTokParenOpen] = 1, + [EcsTokMember] = 1, + [EcsTokBracketOpen] = 1, + [EcsTokMul] = 3, + [EcsTokDiv] = 3, + [EcsTokMod] = 3, + [EcsTokAdd] = 4, + [EcsTokSub] = 4, + [EcsTokShiftLeft] = 5, + [EcsTokShiftRight] = 5, + [EcsTokGt] = 6, + [EcsTokGtEq] = 6, + [EcsTokLt] = 6, + [EcsTokLtEq] = 6, + [EcsTokEq] = 7, + [EcsTokNeq] = 7, + [EcsTokBitwiseAnd] = 8, + [EcsTokBitwiseOr] = 10, + [EcsTokAnd] = 11, + [EcsTokOr] = 12, +}; + +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +static +bool flecs_has_precedence( + ecs_script_token_kind_t first, + ecs_script_token_kind_t second) +{ + if (!flecs_expr_precedence[first]) { + return false; + } + return flecs_expr_precedence[first] < flecs_expr_precedence[second]; +} + +static +const char* flecs_script_parse_rhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_expr_node_t *left, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + const char *last_pos = pos; + + TokenFramePush(); + + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; + + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } + + /* Consume lookahead token */ + pos = lookahead; + + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; + + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } + + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); + + break; + } + + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; + + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); + + break; + } + + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; + } + }; + + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; + + break; + } + ) + + if (pos[0] && (pos != last_pos)) { + pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); + } + + TokenFramePop(); + + return pos; +error: + return NULL; +} + +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + TokenFramePush(); + + Parse( + case EcsTokNumber: { + const char *expr = Token(0); + if (strchr(expr, '.') || strchr(expr, 'e')) { + *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); + } else if (expr[0] == '-') { + *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + } + break; + } + + case EcsTokString: { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + break; + } + + case EcsTokIdentifier: { + const char *expr = Token(0); + if (expr[0] == '$') { + *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); + } else if (!ecs_os_strcmp(expr, "true")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); + } else if (!ecs_os_strcmp(expr, "false")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); + } else { + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + } + break; + } + + case EcsTokParenOpen: { + pos = flecs_script_parse_expr(parser, pos, 0, out); + Parse_1(EcsTokParenClose, { + break; + }) + break; + } + ) + + TokenFramePop(); + + /* Parse right-hand side of expression if there is one */ + return flecs_script_parse_rhs( + parser, pos, tokenizer, *out, left_oper, out); +error: + return NULL; +} + +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + ParserBegin; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, 0, out); + + EndOfRule; + + ParserEnd; +} + +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr) +{ + if (!script) { + script = flecs_script_new(world); + } + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + return out; +error: + return NULL; +} + +#endif diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 45fff5a430..9812840c82 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -320,13 +320,13 @@ insert_tag: { if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { Error( "invalid context for variable component '%s': must be " - "part of entity", tokens[0].value); + "part of entity", tokenizer->tokens[0].value); } } else { if (!flecs_script_insert_tag(parser, Token(0))) { Error( "invalid context for tag '%s': must be part of entity", - tokens[0].value); + tokenizer->tokens[0].value); } } diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index de2c490df2..277102a665 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -37,9 +37,9 @@ /* Definitions for parser functions */ #define ParserBegin\ - ecs_script_tokens_t token_stack = {0};\ - ecs_script_token_t *tokens = token_stack.tokens;\ - (void)tokens + ecs_script_tokenizer_t _tokenizer = {{0}};\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_script_tokenizer_t *tokenizer = &_tokenizer; #define ParserEnd\ Error("unexpected end of rule (parser error)");\ @@ -47,7 +47,14 @@ return NULL /* Get token */ -#define Token(n) (tokens[n].value) +#define Token(n) (tokenizer->tokens[n].value) + +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; + +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; /* Error */ #define Error(...)\ @@ -58,8 +65,8 @@ /* Parse expression */ #define Expr(until, ...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ goto error;\ }\ @@ -73,8 +80,8 @@ /* Parse token until character */ #define Until(until, ...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_until(parser, pos, t, until))) {\ goto error;\ }\ @@ -84,8 +91,8 @@ /* Parse next token */ #define Parse(...)\ {\ - ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ if (!(pos = flecs_script_token(parser, pos, t, false))) {\ goto error;\ }\ @@ -156,11 +163,11 @@ ecs_script_token_t lookahead_token;\ const char *old_lh_token_cur = parser->token_cur;\ if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ - token_stack.tokens[token_stack.count ++] = lookahead_token;\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ switch(lookahead_token.kind) {\ __VA_ARGS__\ default:\ - token_stack.count --;\ + tokenizer->stack.count --;\ break;\ }\ if (old_lh_token_cur > parser->token_keep) {\ @@ -212,9 +219,9 @@ /* Parser loop */ #define Loop(...)\ - int32_t token_stack_count = token_stack.count;\ + int32_t token_stack_count = tokenizer->stack.count;\ do {\ - token_stack.count = token_stack_count;\ + tokenizer->stack.count = token_stack_count;\ __VA_ARGS__\ } while (true); diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 90949b2a03..59b104ccb9 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -45,6 +45,7 @@ struct ecs_script_parser_t { }; #include "ast.h" +#include "expr_ast.h" #include "visit.h" #include "visit_eval.h" diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 037ca2013f..7627620b87 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -42,15 +42,27 @@ const char* flecs_script_token_kind_str( case EcsTokAnnotation: case EcsTokComma: case EcsTokSemiColon: - case EcsTokMul: case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: case EcsTokNot: case EcsTokOptional: case EcsTokEq: case EcsTokNeq: - case EcsTokMatch: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: case EcsTokOr: + case EcsTokMatch: + case EcsTokShiftLeft: + case EcsTokShiftRight: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: @@ -218,12 +230,35 @@ const char* flecs_script_number( { out->kind = EcsTokNumber; out->value = parser->token_cur; + + bool dot_parsed = false; + bool e_parsed = false; ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; do { char c = pos[0]; - if (!isdigit(c)) { + bool valid_number = false; + + if (c == '.') { + if (!dot_parsed && !e_parsed) { + if (isdigit(pos[1])) { + dot_parsed = true; + valid_number = true; + } + } + } else if (c == 'e') { + if (!e_parsed) { + if (isdigit(pos[1])) { + e_parsed = true; + valid_number = true; + } + } + } else if (isdigit(c)) { + valid_number = true; + } + + if (!valid_number) { *outpos = '\0'; parser->token_cur = outpos + 1; break; @@ -489,17 +524,31 @@ const char* flecs_script_token( Operator ("@", EcsTokAnnotation) Operator (",", EcsTokComma) Operator (";", EcsTokSemiColon) + Operator ("+", EcsTokAdd) + Operator ("-", EcsTokSub) Operator ("*", EcsTokMul) + Operator ("/", EcsTokDiv) + Operator ("%%", EcsTokMod) Operator ("?", EcsTokOptional) + Operator (".", EcsTokMember) OperatorMultiChar ("==", EcsTokEq) OperatorMultiChar ("!=", EcsTokNeq) - OperatorMultiChar ("~=", EcsTokMatch) + OperatorMultiChar ("<<", EcsTokShiftLeft) + OperatorMultiChar (">>", EcsTokShiftRight) + OperatorMultiChar (">=", EcsTokGtEq) + OperatorMultiChar ("<=", EcsTokLtEq) + + OperatorMultiChar ("&&", EcsTokAnd) OperatorMultiChar ("||", EcsTokOr) + OperatorMultiChar ("~=", EcsTokMatch) - OperatorMultiChar ("!", EcsTokNot) - OperatorMultiChar ("=", EcsTokAssign) - OperatorMultiChar ("|", EcsTokBitwiseOr) + Operator ("!", EcsTokNot) + Operator ("=", EcsTokAssign) + Operator ("&", EcsTokBitwiseAnd) + Operator ("|", EcsTokBitwiseOr) + Operator (">", EcsTokGt) + Operator ("<", EcsTokLt) Keyword ("with", EcsTokKeywordWith) Keyword ("using", EcsTokKeywordUsing) diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index 8c21b39439..960f9cd6a9 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -16,20 +16,33 @@ typedef enum ecs_script_token_kind_t { EcsTokParenClose = ')', EcsTokBracketOpen = '[', EcsTokBracketClose = ']', - EcsTokMul = '*', + EcsTokMember = '.', EcsTokComma = ',', EcsTokSemiColon = ';', EcsTokColon = ':', EcsTokAssign = '=', + EcsTokAdd = '+', + EcsTokSub = '-', + EcsTokMul = '*', + EcsTokDiv = '/', + EcsTokMod = '%', EcsTokBitwiseOr = '|', + EcsTokBitwiseAnd = '&', EcsTokNot = '!', EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', EcsTokEq, EcsTokNeq, - EcsTokMatch, + EcsTokGt, + EcsTokGtEq, + EcsTokLt, + EcsTokLtEq, + EcsTokAnd, EcsTokOr, + EcsTokMatch, + EcsTokShiftLeft, + EcsTokShiftRight, EcsTokIdentifier, EcsTokString, EcsTokNumber, @@ -53,6 +66,11 @@ typedef struct ecs_script_tokens_t { ecs_script_token_t tokens[256]; } ecs_script_tokens_t; +typedef struct ecs_script_tokenizer_t { + ecs_script_tokens_t stack; + ecs_script_token_t *tokens; +} ecs_script_tokenizer_t; + const char* flecs_script_expr( ecs_script_parser_t *parser, const char *ptr, From 0ea208179eec9f95220fc1b700aab3acec1fddad Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 20 Nov 2024 20:42:15 -0800 Subject: [PATCH 02/83] Implement type visitor for binary expressions --- distr/flecs.c | 364 +++++++++++++++++++++++++++- src/addons/script/expr.c | 3 +- src/addons/script/expr_ast.c | 20 +- src/addons/script/expr_ast.h | 3 +- src/addons/script/expr_to_str.c | 8 +- src/addons/script/expr_visit.h | 13 + src/addons/script/expr_visit_type.c | 315 ++++++++++++++++++++++++ src/addons/script/new_expr.c | 2 + src/addons/script/script.h | 1 + 9 files changed, 703 insertions(+), 26 deletions(-) create mode 100644 src/addons/script/expr_visit.h create mode 100644 src/addons/script/expr_visit_type.c diff --git a/distr/flecs.c b/distr/flecs.c index 50dd064c30..0913afd7da 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4876,12 +4876,13 @@ typedef enum ecs_expr_node_kind_t { struct ecs_expr_node_t { ecs_expr_node_kind_t kind; + ecs_entity_t type; const char *pos; }; typedef struct ecs_expr_val_t { ecs_expr_node_t node; - ecs_value_t value; + void *ptr; union { bool bool_; int8_t i8; @@ -4966,6 +4967,20 @@ ecs_expr_element_t* flecs_expr_element( #endif +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_EXPR_SCRIPT_VISIT_H +#define FLECS_EXPR_SCRIPT_VISIT_H + +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node); + +#endif + /** * @file addons/script/visit.h * @brief Script AST visitor utilities. @@ -55822,7 +55837,8 @@ const char* flecs_binary_expr_parse( ptr = flecs_parse_ws_eol(ptr); ecs_value_t rvalue = {0}; - const char *rptr = flecs_script_expr_run(world, stack, ptr, &rvalue, op, desc); + const char *rptr = flecs_script_expr_run( + world, stack, ptr, &rvalue, op, desc); if (!rptr) { return NULL; } @@ -56457,8 +56473,8 @@ ecs_expr_val_t* flecs_expr_bool( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.bool_ = value; - result->value.ptr = &result->storage.bool_; - result->value.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + result->node.type = ecs_id(ecs_bool_t); return result; } @@ -56469,8 +56485,8 @@ ecs_expr_val_t* flecs_expr_int( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.i64 = value; - result->value.ptr = &result->storage.i64; - result->value.type = ecs_id(ecs_i64_t); + result->ptr = &result->storage.i64; + result->node.type = ecs_id(ecs_i64_t); return result; } @@ -56481,8 +56497,8 @@ ecs_expr_val_t* flecs_expr_uint( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.u64 = value; - result->value.ptr = &result->storage.u64; - result->value.type = ecs_id(ecs_i64_t); + result->ptr = &result->storage.u64; + result->node.type = ecs_id(ecs_i64_t); return result; } @@ -56493,8 +56509,8 @@ ecs_expr_val_t* flecs_expr_float( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.f64 = value; - result->value.ptr = &result->storage.f64; - result->value.type = ecs_id(ecs_f64_t); + result->ptr = &result->storage.f64; + result->node.type = ecs_id(ecs_f64_t); return result; } @@ -56505,8 +56521,8 @@ ecs_expr_val_t* flecs_expr_string( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; - result->value.ptr = &result->storage.string; - result->value.type = ecs_id(ecs_string_t); + result->ptr = &result->storage.string; + result->node.type = ecs_id(ecs_string_t); return result; } @@ -56580,7 +56596,7 @@ int flecs_expr_value_to_str( const ecs_expr_val_t *node) { return ecs_ptr_to_str_buf( - v->world, node->value.type, node->value.ptr, v->buf); + v->world, node->node.type, node->ptr, v->buf); } int flecs_expr_binary_to_str( @@ -56683,6 +56699,12 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + if (node->type) { + ecs_strbuf_appendlit(v->buf, "("); + ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + ecs_strbuf_appendlit(v->buf, ")"); + } + switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { @@ -56744,6 +56766,320 @@ char* ecs_script_expr_to_str( #endif +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ + + +#ifdef FLECS_SCRIPT + +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); + +static +bool flecs_expr_operator_is_equality( + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return true; + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } +error: + return false; +} + +static +bool flecs_expr_is_type_number( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +static +ecs_entity_t flecs_expr_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_expr_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) +{ + if (type == ecs_id(ecs_u64_t)) { + return promote_to; + } + if (promote_to == ecs_id(ecs_u64_t)) { + return type; + } + if (type == ecs_id(ecs_f64_t)) { + return type; + } + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); +} + +static +bool flecs_expr_oper_valid_for_type( + ecs_entity_t type, + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + return flecs_expr_is_type_number(type); + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + return type == ecs_id(ecs_u64_t) || + type == ecs_id(ecs_u32_t) || + type == ecs_id(ecs_u16_t) || + type == ecs_id(ecs_u8_t); + case EcsTokEq: + case EcsTokNeq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return flecs_expr_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +int flecs_expr_type_for_oper( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) +{ + ecs_world_t *world = script->world; + ecs_expr_node_t *left = node->left, *right = node->right; + + switch(node->operator) { + case EcsTokDiv: + /* Result type of a division is always a float */ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + return 0; + case EcsTokAnd: + case EcsTokOr: + /* Result type of a condition operator is always a bool */ + *operand_type = ecs_id(ecs_bool_t); + *result_type = ecs_id(ecs_bool_t); + return 0; + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + *result_type = ecs_id(ecs_bool_t); + break; + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } + + const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + char *lname = ecs_get_path(world, left->type); + char *rname = ecs_get_path(world, right->type); + flecs_expr_visit_error(script, node, + "invalid non-primitive type in binary expression (%s, %s)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } + + ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + if (ltype == rtype) { + *operand_type = ltype; + goto done; + } + + if (flecs_expr_operator_is_equality(node->operator)) { + char *lname = ecs_id_str(world, ltype); + char *rname = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "mismatching types in equality expression (%s vs %s)", + lname, rname); + ecs_os_free(rname); + ecs_os_free(lname); + return 0; + } + + if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { + flecs_expr_visit_error(script, node, + "incompatible types in binary expression"); + return 0; + } + + *operand_type = flecs_expr_promote_type(ltype, rtype); + +done: + if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + /* Result of subtracting two unsigned ints can be negative */ + *operand_type = ecs_id(ecs_i64_t); + } + + if (!*result_type) { + *result_type = *operand_type; + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_binary_visit_type( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + /* Operands must be of this type or casted to it */ + ecs_entity_t operand_type = 0; + + /* Resulting type of binary expression */ + ecs_entity_t result_type = 0; + + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + if (flecs_script_expr_visit_type(script, node->right)) { + goto error; + } + + if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + goto error; + } + + if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + flecs_expr_visit_error(script, node, "invalid operator for type"); + goto error; + } + + node->node.type = result_type; + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +#endif + /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -57424,6 +57760,8 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } + flecs_script_expr_visit_type(script, out); + return out; error: return NULL; diff --git a/src/addons/script/expr.c b/src/addons/script/expr.c index c0cafe8880..424f29a2d7 100644 --- a/src/addons/script/expr.c +++ b/src/addons/script/expr.c @@ -1162,7 +1162,8 @@ const char* flecs_binary_expr_parse( ptr = flecs_parse_ws_eol(ptr); ecs_value_t rvalue = {0}; - const char *rptr = flecs_script_expr_run(world, stack, ptr, &rvalue, op, desc); + const char *rptr = flecs_script_expr_run( + world, stack, ptr, &rvalue, op, desc); if (!rptr) { return NULL; } diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr_ast.c index 6f59104e4d..a3af830056 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr_ast.c @@ -32,8 +32,8 @@ ecs_expr_val_t* flecs_expr_bool( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.bool_ = value; - result->value.ptr = &result->storage.bool_; - result->value.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + result->node.type = ecs_id(ecs_bool_t); return result; } @@ -44,8 +44,8 @@ ecs_expr_val_t* flecs_expr_int( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.i64 = value; - result->value.ptr = &result->storage.i64; - result->value.type = ecs_id(ecs_i64_t); + result->ptr = &result->storage.i64; + result->node.type = ecs_id(ecs_i64_t); return result; } @@ -56,8 +56,8 @@ ecs_expr_val_t* flecs_expr_uint( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.u64 = value; - result->value.ptr = &result->storage.u64; - result->value.type = ecs_id(ecs_i64_t); + result->ptr = &result->storage.u64; + result->node.type = ecs_id(ecs_i64_t); return result; } @@ -68,8 +68,8 @@ ecs_expr_val_t* flecs_expr_float( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.f64 = value; - result->value.ptr = &result->storage.f64; - result->value.type = ecs_id(ecs_f64_t); + result->ptr = &result->storage.f64; + result->node.type = ecs_id(ecs_f64_t); return result; } @@ -80,8 +80,8 @@ ecs_expr_val_t* flecs_expr_string( ecs_expr_val_t *result = flecs_expr_ast_new( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; - result->value.ptr = &result->storage.string; - result->value.type = ecs_id(ecs_string_t); + result->ptr = &result->storage.string; + result->node.type = ecs_id(ecs_string_t); return result; } diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h index f15c3dee65..e901abea7e 100644 --- a/src/addons/script/expr_ast.h +++ b/src/addons/script/expr_ast.h @@ -20,12 +20,13 @@ typedef enum ecs_expr_node_kind_t { struct ecs_expr_node_t { ecs_expr_node_kind_t kind; + ecs_entity_t type; const char *pos; }; typedef struct ecs_expr_val_t { ecs_expr_node_t node; - ecs_value_t value; + void *ptr; union { bool bool_; int8_t i8; diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr_to_str.c index b9ff3c5d22..6a0eac6fde 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr_to_str.c @@ -24,7 +24,7 @@ int flecs_expr_value_to_str( const ecs_expr_val_t *node) { return ecs_ptr_to_str_buf( - v->world, node->value.type, node->value.ptr, v->buf); + v->world, node->node.type, node->ptr, v->buf); } int flecs_expr_binary_to_str( @@ -127,6 +127,12 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + if (node->type) { + ecs_strbuf_appendlit(v->buf, "("); + ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + ecs_strbuf_appendlit(v->buf, ")"); + } + switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { diff --git a/src/addons/script/expr_visit.h b/src/addons/script/expr_visit.h new file mode 100644 index 0000000000..60c054761e --- /dev/null +++ b/src/addons/script/expr_visit.h @@ -0,0 +1,13 @@ +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_EXPR_SCRIPT_VISIT_H +#define FLECS_EXPR_SCRIPT_VISIT_H + +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node); + +#endif diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr_visit_type.c new file mode 100644 index 0000000000..34376ceaea --- /dev/null +++ b/src/addons/script/expr_visit_type.c @@ -0,0 +1,315 @@ +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); + +static +bool flecs_expr_operator_is_equality( + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return true; + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } +error: + return false; +} + +static +bool flecs_expr_is_type_number( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +static +ecs_entity_t flecs_expr_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_expr_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) +{ + if (type == ecs_id(ecs_u64_t)) { + return promote_to; + } + if (promote_to == ecs_id(ecs_u64_t)) { + return type; + } + if (type == ecs_id(ecs_f64_t)) { + return type; + } + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); +} + +static +bool flecs_expr_oper_valid_for_type( + ecs_entity_t type, + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + return flecs_expr_is_type_number(type); + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + return type == ecs_id(ecs_u64_t) || + type == ecs_id(ecs_u32_t) || + type == ecs_id(ecs_u16_t) || + type == ecs_id(ecs_u8_t); + case EcsTokEq: + case EcsTokNeq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return flecs_expr_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +int flecs_expr_type_for_oper( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) +{ + ecs_world_t *world = script->world; + ecs_expr_node_t *left = node->left, *right = node->right; + + switch(node->operator) { + case EcsTokDiv: + /* Result type of a division is always a float */ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + return 0; + case EcsTokAnd: + case EcsTokOr: + /* Result type of a condition operator is always a bool */ + *operand_type = ecs_id(ecs_bool_t); + *result_type = ecs_id(ecs_bool_t); + return 0; + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + *result_type = ecs_id(ecs_bool_t); + break; + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } + + const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + char *lname = ecs_get_path(world, left->type); + char *rname = ecs_get_path(world, right->type); + flecs_expr_visit_error(script, node, + "invalid non-primitive type in binary expression (%s, %s)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } + + ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + if (ltype == rtype) { + *operand_type = ltype; + goto done; + } + + if (flecs_expr_operator_is_equality(node->operator)) { + char *lname = ecs_id_str(world, ltype); + char *rname = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "mismatching types in equality expression (%s vs %s)", + lname, rname); + ecs_os_free(rname); + ecs_os_free(lname); + return 0; + } + + if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { + flecs_expr_visit_error(script, node, + "incompatible types in binary expression"); + return 0; + } + + *operand_type = flecs_expr_promote_type(ltype, rtype); + +done: + if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + /* Result of subtracting two unsigned ints can be negative */ + *operand_type = ecs_id(ecs_i64_t); + } + + if (!*result_type) { + *result_type = *operand_type; + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_binary_visit_type( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + /* Operands must be of this type or casted to it */ + ecs_entity_t operand_type = 0; + + /* Resulting type of binary expression */ + ecs_entity_t result_type = 0; + + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + if (flecs_script_expr_visit_type(script, node->right)) { + goto error; + } + + if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + goto error; + } + + if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + flecs_expr_visit_error(script, node, "invalid operator for type"); + goto error; + } + + node->node.type = result_type; + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +#endif diff --git a/src/addons/script/new_expr.c b/src/addons/script/new_expr.c index 269792387f..10505c8c49 100644 --- a/src/addons/script/new_expr.c +++ b/src/addons/script/new_expr.c @@ -275,6 +275,8 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } + flecs_script_expr_visit_type(script, out); + return out; error: return NULL; diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 59b104ccb9..8053440020 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -46,6 +46,7 @@ struct ecs_script_parser_t { #include "ast.h" #include "expr_ast.h" +#include "expr_visit.h" #include "visit.h" #include "visit_eval.h" From 2b256c7764feb018e4d3b466252ec1e09f40a0fa Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 21 Nov 2024 17:24:03 -0800 Subject: [PATCH 03/83] Implement type visitor for remaining nodes --- distr/flecs.c | 164 +++++++++++++++++++++++++++- src/addons/script/expr_ast.h | 1 + src/addons/script/expr_to_str.c | 8 +- src/addons/script/expr_visit_type.c | 155 ++++++++++++++++++++++++++ 4 files changed, 324 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 0913afd7da..879cbac124 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4902,6 +4902,7 @@ typedef struct ecs_expr_val_t { typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; + ecs_entity_t id; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { @@ -56700,9 +56701,9 @@ int flecs_expr_node_to_str( ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); if (node->type) { - ecs_strbuf_appendlit(v->buf, "("); + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_appendlit(v->buf, ")"); + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); } switch(node->kind) { @@ -56744,6 +56745,10 @@ int flecs_expr_node_to_str( break; } + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } + return 0; error: return -1; @@ -57043,6 +57048,149 @@ int flecs_expr_binary_visit_type( return -1; } +static +int flecs_expr_identifier_visit( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + node->id = ecs_lookup(script->world, node->value); + if (!node->id) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_variable_visit( + ecs_script_t *script, + ecs_expr_variable_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + return 0; +} + +static +int flecs_expr_member_visit( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } + + if (type->kind != EcsStructType) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on non-struct type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); + ecs_meta_push(&cur); /* { */ + int prev_log = ecs_log_set_level(-4); + if (ecs_meta_dotmember(&cur, node->member_name)) { + ecs_log_set_level(prev_log); + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "unresolved member '%s' for type '%s'", + node->member_name, type_str); + ecs_os_free(type_str); + goto error; + } + ecs_log_set_level(prev_log); + + node->node.type = ecs_meta_get_type(&cur); + ecs_meta_pop(&cur); /* } */ + + return 0; +error: + return -1; +} + +static +int flecs_expr_element_visit( + ecs_script_t *script, + ecs_expr_element_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + if (flecs_script_expr_visit_type(script, node->index)) { + goto error; + } + + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot use [] on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } + + bool is_entity_type = false; + + if (type->kind == EcsPrimitiveType) { + const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); + if (ptype->kind == EcsEntity) { + is_entity_type = true; + } + } + + if (is_entity_type) { + if (node->index->kind == EcsExprIdentifier) { + node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + } else { + flecs_expr_visit_error(script, node, + "invalid component expression"); + goto error; + } + } else if (type->kind == EcsArrayType) { + const EcsArray *type_array = ecs_get(world, left_type, EcsArray); + ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_array->type; + } else if (type->kind == EcsVectorType) { + const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); + ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_vector->type; + } else { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node) @@ -57060,14 +57208,26 @@ int flecs_script_expr_visit_type( } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit(script, (ecs_expr_identifier_t*)node)) { + goto error; + } break; case EcsExprVariable: + if (flecs_expr_variable_visit(script, (ecs_expr_variable_t*)node)) { + goto error; + } break; case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit(script, (ecs_expr_member_t*)node)) { + goto error; + } break; case EcsExprElement: + if (flecs_expr_element_visit(script, (ecs_expr_element_t*)node)) { + goto error; + } break; case EcsExprCast: break; diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h index e901abea7e..8f2612e985 100644 --- a/src/addons/script/expr_ast.h +++ b/src/addons/script/expr_ast.h @@ -46,6 +46,7 @@ typedef struct ecs_expr_val_t { typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; + ecs_entity_t id; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr_to_str.c index 6a0eac6fde..2faf23c773 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr_to_str.c @@ -128,9 +128,9 @@ int flecs_expr_node_to_str( ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); if (node->type) { - ecs_strbuf_appendlit(v->buf, "("); + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_appendlit(v->buf, ")"); + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); } switch(node->kind) { @@ -172,6 +172,10 @@ int flecs_expr_node_to_str( break; } + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } + return 0; error: return -1; diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr_visit_type.c index 34376ceaea..b062b729e4 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr_visit_type.c @@ -277,6 +277,149 @@ int flecs_expr_binary_visit_type( return -1; } +static +int flecs_expr_identifier_visit( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + node->id = ecs_lookup(script->world, node->value); + if (!node->id) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_variable_visit( + ecs_script_t *script, + ecs_expr_variable_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + return 0; +} + +static +int flecs_expr_member_visit( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } + + if (type->kind != EcsStructType) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on non-struct type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); + ecs_meta_push(&cur); /* { */ + int prev_log = ecs_log_set_level(-4); + if (ecs_meta_dotmember(&cur, node->member_name)) { + ecs_log_set_level(prev_log); + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "unresolved member '%s' for type '%s'", + node->member_name, type_str); + ecs_os_free(type_str); + goto error; + } + ecs_log_set_level(prev_log); + + node->node.type = ecs_meta_get_type(&cur); + ecs_meta_pop(&cur); /* } */ + + return 0; +error: + return -1; +} + +static +int flecs_expr_element_visit( + ecs_script_t *script, + ecs_expr_element_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } + + if (flecs_script_expr_visit_type(script, node->index)) { + goto error; + } + + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot use [] on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } + + bool is_entity_type = false; + + if (type->kind == EcsPrimitiveType) { + const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); + if (ptype->kind == EcsEntity) { + is_entity_type = true; + } + } + + if (is_entity_type) { + if (node->index->kind == EcsExprIdentifier) { + node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + } else { + flecs_expr_visit_error(script, node, + "invalid component expression"); + goto error; + } + } else if (type->kind == EcsArrayType) { + const EcsArray *type_array = ecs_get(world, left_type, EcsArray); + ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_array->type; + } else if (type->kind == EcsVectorType) { + const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); + ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_vector->type; + } else { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node) @@ -294,14 +437,26 @@ int flecs_script_expr_visit_type( } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit(script, (ecs_expr_identifier_t*)node)) { + goto error; + } break; case EcsExprVariable: + if (flecs_expr_variable_visit(script, (ecs_expr_variable_t*)node)) { + goto error; + } break; case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit(script, (ecs_expr_member_t*)node)) { + goto error; + } break; case EcsExprElement: + if (flecs_expr_element_visit(script, (ecs_expr_element_t*)node)) { + goto error; + } break; case EcsExprCast: break; From 8e5c0232097e399e24153b606e16ef3fc16c5e53 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 21 Nov 2024 19:28:34 -0800 Subject: [PATCH 04/83] Add unary expression --- distr/flecs.c | 74 ++++++++++++++++++++++++++++- src/addons/script/expr_ast.c | 8 ++++ src/addons/script/expr_ast.h | 9 ++++ src/addons/script/expr_to_str.c | 25 +++++++++- src/addons/script/expr_visit_type.c | 21 ++++++++ src/addons/script/new_expr.c | 11 ++++- 6 files changed, 144 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 879cbac124..058954e08a 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4910,6 +4910,12 @@ typedef struct ecs_expr_variable_t { const char *value; } ecs_expr_variable_t; +typedef struct ecs_expr_unary_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_script_token_kind_t operator; +} ecs_expr_unary_t; + typedef struct ecs_expr_binary_t { ecs_expr_node_t node; ecs_expr_node_t *left; @@ -4957,6 +4963,9 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_script_parser_t *parser, const char *value); +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser); + ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser); @@ -56547,6 +56556,14 @@ ecs_expr_variable_t* flecs_expr_variable( return result; } +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprUnary); + return result; +} + ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser) { @@ -56600,6 +56617,26 @@ int flecs_expr_value_to_str( v->world, node->node.type, node->ptr, v->buf); } +int flecs_expr_unary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_unary_t *node) +{ + switch(node->operator) { + case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; + default: + ecs_err("invalid operator for unary expression"); + return -1; + }; + + if (flecs_expr_node_to_str(v, node->expr)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -56631,7 +56668,7 @@ int flecs_expr_binary_to_str( case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; default: - ecs_err("invalid operator in expression"); + ecs_err("invalid operator for binary expression"); return -1; }; @@ -56713,6 +56750,9 @@ int flecs_expr_node_to_str( } break; case EcsExprUnary: + if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { @@ -57013,6 +57053,23 @@ int flecs_expr_type_for_oper( return -1; } +static +int flecs_expr_unary_visit_type( + ecs_script_t *script, + ecs_expr_unary_t *node) +{ + if (flecs_script_expr_visit_type(script, node->expr)) { + goto error; + } + + /* The only supported unary expression is not (!) which returns a bool */ + node->node.type = ecs_id(ecs_bool_t); + + return 0; +error: + return -1; +} + static int flecs_expr_binary_visit_type( ecs_script_t *script, @@ -57199,8 +57256,12 @@ int flecs_script_expr_visit_type( switch(node->kind) { case EcsExprValue: + /* Value types are assigned by the AST */ break; case EcsExprUnary: + if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { @@ -57660,6 +57721,7 @@ static int flecs_expr_precedence[] = { [EcsTokParenOpen] = 1, [EcsTokMember] = 1, [EcsTokBracketOpen] = 1, + [EcsTokNot] = 2, [EcsTokMul] = 3, [EcsTokDiv] = 3, [EcsTokMod] = 3, @@ -57863,6 +57925,14 @@ const char* flecs_script_parse_lhs( }) break; } + + case EcsTokNot: { + ecs_expr_unary_t *unary = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); + unary->operator = EcsTokNot; + *out = (ecs_expr_node_t*)unary; + break; + } ) TokenFramePop(); @@ -57883,7 +57953,7 @@ const char* flecs_script_parse_expr( { ParserBegin; - pos = flecs_script_parse_lhs(parser, pos, tokenizer, 0, out); + pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); EndOfRule; diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr_ast.c index a3af830056..79fe41a055 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr_ast.c @@ -105,6 +105,14 @@ ecs_expr_variable_t* flecs_expr_variable( return result; } +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprUnary); + return result; +} + ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser) { diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h index 8f2612e985..fd7c80d250 100644 --- a/src/addons/script/expr_ast.h +++ b/src/addons/script/expr_ast.h @@ -54,6 +54,12 @@ typedef struct ecs_expr_variable_t { const char *value; } ecs_expr_variable_t; +typedef struct ecs_expr_unary_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_script_token_kind_t operator; +} ecs_expr_unary_t; + typedef struct ecs_expr_binary_t { ecs_expr_node_t node; ecs_expr_node_t *left; @@ -101,6 +107,9 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_script_parser_t *parser, const char *value); +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser); + ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser); diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr_to_str.c index 2faf23c773..e822a35829 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr_to_str.c @@ -27,6 +27,26 @@ int flecs_expr_value_to_str( v->world, node->node.type, node->ptr, v->buf); } +int flecs_expr_unary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_unary_t *node) +{ + switch(node->operator) { + case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; + default: + ecs_err("invalid operator for unary expression"); + return -1; + }; + + if (flecs_expr_node_to_str(v, node->expr)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -58,7 +78,7 @@ int flecs_expr_binary_to_str( case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; default: - ecs_err("invalid operator in expression"); + ecs_err("invalid operator for binary expression"); return -1; }; @@ -140,6 +160,9 @@ int flecs_expr_node_to_str( } break; case EcsExprUnary: + if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr_visit_type.c index b062b729e4..bde3737238 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr_visit_type.c @@ -242,6 +242,23 @@ int flecs_expr_type_for_oper( return -1; } +static +int flecs_expr_unary_visit_type( + ecs_script_t *script, + ecs_expr_unary_t *node) +{ + if (flecs_script_expr_visit_type(script, node->expr)) { + goto error; + } + + /* The only supported unary expression is not (!) which returns a bool */ + node->node.type = ecs_id(ecs_bool_t); + + return 0; +error: + return -1; +} + static int flecs_expr_binary_visit_type( ecs_script_t *script, @@ -428,8 +445,12 @@ int flecs_script_expr_visit_type( switch(node->kind) { case EcsExprValue: + /* Value types are assigned by the AST */ break; case EcsExprUnary: + if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { diff --git a/src/addons/script/new_expr.c b/src/addons/script/new_expr.c index 10505c8c49..465d3e3692 100644 --- a/src/addons/script/new_expr.c +++ b/src/addons/script/new_expr.c @@ -15,6 +15,7 @@ static int flecs_expr_precedence[] = { [EcsTokParenOpen] = 1, [EcsTokMember] = 1, [EcsTokBracketOpen] = 1, + [EcsTokNot] = 2, [EcsTokMul] = 3, [EcsTokDiv] = 3, [EcsTokMod] = 3, @@ -218,6 +219,14 @@ const char* flecs_script_parse_lhs( }) break; } + + case EcsTokNot: { + ecs_expr_unary_t *unary = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); + unary->operator = EcsTokNot; + *out = (ecs_expr_node_t*)unary; + break; + } ) TokenFramePop(); @@ -238,7 +247,7 @@ const char* flecs_script_parse_expr( { ParserBegin; - pos = flecs_script_parse_lhs(parser, pos, tokenizer, 0, out); + pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); EndOfRule; From ec80c7e9ed89f99c8e93ded707ce169cc0a914cd Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 21 Nov 2024 21:25:52 -0800 Subject: [PATCH 05/83] Implement folding for unary and binary expressions --- distr/flecs.c | 246 +++++++++++++++++++++++++-- src/addons/script/expr_ast.c | 4 +- src/addons/script/expr_ast.h | 3 + src/addons/script/expr_visit.h | 9 + src/addons/script/expr_visit_fold.c | 250 ++++++++++++++++++++++++++++ src/addons/script/expr_visit_type.c | 21 +-- src/addons/script/new_expr.c | 1 + 7 files changed, 504 insertions(+), 30 deletions(-) create mode 100644 src/addons/script/expr_visit_fold.c diff --git a/distr/flecs.c b/distr/flecs.c index 058954e08a..ab964fd43e 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4935,6 +4935,9 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t *index; } ecs_expr_element_t; +ecs_expr_val_t* flecs_expr_value( + ecs_script_parser_t *parser); + ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); @@ -4985,10 +4988,19 @@ ecs_expr_element_t* flecs_expr_element( #ifndef FLECS_EXPR_SCRIPT_VISIT_H #define FLECS_EXPR_SCRIPT_VISIT_H +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); + int flecs_script_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node); +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node); + #endif /** @@ -56559,8 +56571,8 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_unary_t* flecs_expr_unary( ecs_script_parser_t *parser) { - ecs_expr_binary_t *result = flecs_expr_ast_new( - parser, ecs_expr_binary_t, EcsExprUnary); + ecs_expr_unary_t *result = flecs_expr_ast_new( + parser, ecs_expr_unary_t, EcsExprUnary); return result; } @@ -56590,6 +56602,214 @@ ecs_expr_element_t* flecs_expr_element( #endif +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + + +#ifdef FLECS_SCRIPT + +#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->right)) { + goto error; + } + + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + + switch(node->operator) { + case EcsTokAdd: + ECS_BINARY_OP(node->left, node->right, result, +); + break; + case EcsTokSub: + ECS_BINARY_OP(node->left, node->right, result, -); + break; + case EcsTokMul: + ECS_BINARY_OP(node->left, node->right, result, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(node->left, node->right, result, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(node->left, node->right, result, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(node->left, node->right, result, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(node->left, node->right, result, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); + break; + case EcsTokShiftLeft: + ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + break; + default: + flecs_expr_visit_error(script, node->left, "unsupported operator"); + goto error; + } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; + + switch(node->kind) { + case EcsExprValue: + /* Value types are assigned by the AST */ + break; + case EcsExprUnary: + // if (flecs_expr_unary_visit_fold(script, (ecs_expr_unary_t*)node)) { + // goto error; + // } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +#endif + /** * @file addons/script/expr_to_str.c * @brief Script expression AST to string visitor. @@ -56819,11 +57039,6 @@ char* ecs_script_expr_to_str( #ifdef FLECS_SCRIPT -#define flecs_expr_visit_error(script, node, ...) \ - ecs_parser_error( \ - script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ - __VA_ARGS__); - static bool flecs_expr_operator_is_equality( ecs_script_token_kind_t op) @@ -57106,7 +57321,7 @@ int flecs_expr_binary_visit_type( } static -int flecs_expr_identifier_visit( +int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node) { @@ -57124,7 +57339,7 @@ int flecs_expr_identifier_visit( } static -int flecs_expr_variable_visit( +int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node) { @@ -57133,7 +57348,7 @@ int flecs_expr_variable_visit( } static -int flecs_expr_member_visit( +int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node) { @@ -57186,7 +57401,7 @@ int flecs_expr_member_visit( } static -int flecs_expr_element_visit( +int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node) { @@ -57269,24 +57484,24 @@ int flecs_script_expr_visit_type( } break; case EcsExprIdentifier: - if (flecs_expr_identifier_visit(script, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_visit(script, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { goto error; } break; case EcsExprFunction: break; case EcsExprMember: - if (flecs_expr_member_visit(script, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { goto error; } break; case EcsExprElement: - if (flecs_expr_element_visit(script, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { goto error; } break; @@ -57991,6 +58206,7 @@ ecs_expr_node_t* ecs_script_parse_expr( } flecs_script_expr_visit_type(script, out); + flecs_script_expr_visit_fold(script, &out); return out; error: diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr_ast.c index 79fe41a055..047b706812 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr_ast.c @@ -108,8 +108,8 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_unary_t* flecs_expr_unary( ecs_script_parser_t *parser) { - ecs_expr_binary_t *result = flecs_expr_ast_new( - parser, ecs_expr_binary_t, EcsExprUnary); + ecs_expr_unary_t *result = flecs_expr_ast_new( + parser, ecs_expr_unary_t, EcsExprUnary); return result; } diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h index fd7c80d250..0b5bf6a2d6 100644 --- a/src/addons/script/expr_ast.h +++ b/src/addons/script/expr_ast.h @@ -79,6 +79,9 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t *index; } ecs_expr_element_t; +ecs_expr_val_t* flecs_expr_value( + ecs_script_parser_t *parser); + ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); diff --git a/src/addons/script/expr_visit.h b/src/addons/script/expr_visit.h index 60c054761e..889a3bc868 100644 --- a/src/addons/script/expr_visit.h +++ b/src/addons/script/expr_visit.h @@ -6,8 +6,17 @@ #ifndef FLECS_EXPR_SCRIPT_VISIT_H #define FLECS_EXPR_SCRIPT_VISIT_H +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); + int flecs_script_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node); +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node); + #endif diff --git a/src/addons/script/expr_visit_fold.c b/src/addons/script/expr_visit_fold.c new file mode 100644 index 0000000000..cb2e08fb48 --- /dev/null +++ b/src/addons/script/expr_visit_fold.c @@ -0,0 +1,250 @@ +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +int flecs_expr_unary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; + + if (node->operator != EcsTokNot) { + flecs_expr_visit_error(script, node, + "operator invalid for unary expression"); + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } + + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + if (node->node.type != ecs_id(ecs_bool_t)) { + char *type_str = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "! operator cannot be applied to value of type '%s' (must be bool)"); + ecs_os_free(type_str); + goto error; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); + + return 0; +error: + return -1; +} + +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->right)) { + goto error; + } + + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + + switch(node->operator) { + case EcsTokAdd: + ECS_BINARY_OP(node->left, node->right, result, +); + break; + case EcsTokSub: + ECS_BINARY_OP(node->left, node->right, result, -); + break; + case EcsTokMul: + ECS_BINARY_OP(node->left, node->right, result, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(node->left, node->right, result, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(node->left, node->right, result, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(node->left, node->right, result, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(node->left, node->right, result, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); + break; + case EcsTokShiftLeft: + ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + break; + default: + flecs_expr_visit_error(script, node->left, "unsupported operator"); + goto error; + } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; + + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + break; + } + + return 0; +error: + return -1; +} + +#endif diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr_visit_type.c index bde3737238..280813c7ee 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr_visit_type.c @@ -8,11 +8,6 @@ #ifdef FLECS_SCRIPT #include "script.h" -#define flecs_expr_visit_error(script, node, ...) \ - ecs_parser_error( \ - script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ - __VA_ARGS__); - static bool flecs_expr_operator_is_equality( ecs_script_token_kind_t op) @@ -295,7 +290,7 @@ int flecs_expr_binary_visit_type( } static -int flecs_expr_identifier_visit( +int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node) { @@ -313,7 +308,7 @@ int flecs_expr_identifier_visit( } static -int flecs_expr_variable_visit( +int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node) { @@ -322,7 +317,7 @@ int flecs_expr_variable_visit( } static -int flecs_expr_member_visit( +int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node) { @@ -375,7 +370,7 @@ int flecs_expr_member_visit( } static -int flecs_expr_element_visit( +int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node) { @@ -458,24 +453,24 @@ int flecs_script_expr_visit_type( } break; case EcsExprIdentifier: - if (flecs_expr_identifier_visit(script, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_visit(script, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { goto error; } break; case EcsExprFunction: break; case EcsExprMember: - if (flecs_expr_member_visit(script, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { goto error; } break; case EcsExprElement: - if (flecs_expr_element_visit(script, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { goto error; } break; diff --git a/src/addons/script/new_expr.c b/src/addons/script/new_expr.c index 465d3e3692..571080828f 100644 --- a/src/addons/script/new_expr.c +++ b/src/addons/script/new_expr.c @@ -285,6 +285,7 @@ ecs_expr_node_t* ecs_script_parse_expr( } flecs_script_expr_visit_type(script, out); + flecs_script_expr_visit_fold(script, &out); return out; error: From 9955a1c0d4eaea375d24393df1bde44dc385f77d Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 21 Nov 2024 22:02:46 -0800 Subject: [PATCH 06/83] Implement cast expressions --- distr/flecs.c | 539 +++++++++++++++++----------- src/addons/script/expr_ast.c | 14 + src/addons/script/expr_ast.h | 10 + src/addons/script/expr_to_str.c | 10 + src/addons/script/expr_visit_fold.c | 46 +++ src/addons/script/expr_visit_type.c | 10 + 6 files changed, 425 insertions(+), 204 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index ab964fd43e..1054e1b081 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4935,6 +4935,11 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t *index; } ecs_expr_element_t; +typedef struct ecs_expr_cast_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; +} ecs_expr_cast_t; + ecs_expr_val_t* flecs_expr_value( ecs_script_parser_t *parser); @@ -4978,6 +4983,11 @@ ecs_expr_member_t* flecs_expr_member( ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); + #endif /** @@ -56600,212 +56610,18 @@ ecs_expr_element_t* flecs_expr_element( return result; } -#endif - -/** - * @file addons/script/expr_fold.c - * @brief Script expression constant folding. - */ - - -#ifdef FLECS_SCRIPT - -#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -int flecs_expr_binary_visit_fold( +ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - - if (flecs_script_expr_visit_fold(script, &node->left)) { - goto error; - } - - if (flecs_script_expr_visit_fold(script, &node->right)) { - goto error; - } - - if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->ptr = &result->storage.u64; - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; - - switch(node->operator) { - case EcsTokAdd: - ECS_BINARY_OP(node->left, node->right, result, +); - break; - case EcsTokSub: - ECS_BINARY_OP(node->left, node->right, result, -); - break; - case EcsTokMul: - ECS_BINARY_OP(node->left, node->right, result, *); - break; - case EcsTokDiv: - ECS_BINARY_OP(node->left, node->right, result, /); - break; - case EcsTokMod: - ECS_BINARY_INT_OP(node->left, node->right, result, %); - break; - case EcsTokEq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); - break; - case EcsTokNeq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); - break; - case EcsTokGt: - ECS_BINARY_COND_OP(node->left, node->right, result, >); - break; - case EcsTokGtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, >=); - break; - case EcsTokLt: - ECS_BINARY_COND_OP(node->left, node->right, result, <); - break; - case EcsTokLtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, <=); - break; - case EcsTokAnd: - ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); - break; - case EcsTokOr: - ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); - break; - case EcsTokShiftLeft: - ECS_BINARY_UINT_OP(node->left, node->right, result, <<); - break; - case EcsTokShiftRight: - ECS_BINARY_UINT_OP(node->left, node->right, result, >>); - break; - default: - flecs_expr_visit_error(script, node->left, "unsupported operator"); - goto error; - } - - *node_ptr = (ecs_expr_node_t*)result; - - return 0; -error: - return -1; -} - -int flecs_script_expr_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t *expr, + ecs_entity_t type) { - ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_expr_node_t *node = *node_ptr; - - switch(node->kind) { - case EcsExprValue: - /* Value types are assigned by the AST */ - break; - case EcsExprUnary: - // if (flecs_expr_unary_visit_fold(script, (ecs_expr_unary_t*)node)) { - // goto error; - // } - break; - case EcsExprBinary: - if (flecs_expr_binary_visit_fold(script, node_ptr)) { - goto error; - } - break; - case EcsExprIdentifier: - break; - case EcsExprVariable: - break; - case EcsExprFunction: - break; - case EcsExprMember: - break; - case EcsExprElement: - break; - case EcsExprCast: - break; - } - - return 0; -error: - return -1; + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; + ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); + result->node.kind = EcsExprCast; + result->node.pos = expr->pos; + result->node.type = type; + result->expr = expr; + return result; } #endif @@ -56951,6 +56767,13 @@ int flecs_expr_element_to_str( return 0; } +int flecs_expr_cast_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_cast_t *node) +{ + return flecs_expr_node_to_str(v, node->expr); +} + int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) @@ -57002,6 +56825,9 @@ int flecs_expr_node_to_str( } break; case EcsExprCast: + if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + goto error; + } break; } @@ -57031,6 +56857,301 @@ char* ecs_script_expr_to_str( #endif +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + + +#ifdef FLECS_SCRIPT + +#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +int flecs_expr_unary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; + + if (node->operator != EcsTokNot) { + flecs_expr_visit_error(script, node, + "operator invalid for unary expression"); + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } + + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + if (node->node.type != ecs_id(ecs_bool_t)) { + char *type_str = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "! operator cannot be applied to value of type '%s' (must be bool)"); + ecs_os_free(type_str); + goto error; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); + + return 0; +error: + return -1; +} + +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->right)) { + goto error; + } + + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + + switch(node->operator) { + case EcsTokAdd: + ECS_BINARY_OP(node->left, node->right, result, +); + break; + case EcsTokSub: + ECS_BINARY_OP(node->left, node->right, result, -); + break; + case EcsTokMul: + ECS_BINARY_OP(node->left, node->right, result, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(node->left, node->right, result, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(node->left, node->right, result, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(node->left, node->right, result, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(node->left, node->right, result, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); + break; + case EcsTokShiftLeft: + ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + break; + default: + flecs_expr_visit_error(script, node->left, "unsupported operator"); + goto error; + } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + +int flecs_expr_cast_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } + + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + + /* Reuse existing node to hold casted value */ + *node_ptr = (ecs_expr_node_t*)expr; + + ecs_entity_t dst_type = node->node.type; + ecs_entity_t src_type = expr->node.type; + + if (dst_type == src_type) { + /* No cast necessary if types are equal */ + return 0; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + ecs_value_t value = { + .type = src_type, + .ptr = expr->ptr + }; + + ecs_meta_set_value(&cur, &value); + + expr->node.type = dst_type; + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; + + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + if (flecs_expr_cast_visit_fold(script, node_ptr)) { + goto error; + } + break; + } + + return 0; +error: + return -1; +} + +#endif + /** * @file addons/script/expr_ast.c * @brief Script expression AST implementation. @@ -57313,6 +57434,16 @@ int flecs_expr_binary_visit_type( goto error; } + if (operand_type != node->left->type) { + node->left = (ecs_expr_node_t*)flecs_expr_cast( + script, node->left, operand_type); + } + + if (operand_type != node->right->type) { + node->right = (ecs_expr_node_t*)flecs_expr_cast( + script, node->right, operand_type); + } + node->node.type = result_type; return 0; diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr_ast.c index 047b706812..51680cbd6c 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr_ast.c @@ -137,4 +137,18 @@ ecs_expr_element_t* flecs_expr_element( return result; } +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *expr, + ecs_entity_t type) +{ + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; + ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); + result->node.kind = EcsExprCast; + result->node.pos = expr->pos; + result->node.type = type; + result->expr = expr; + return result; +} + #endif diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr_ast.h index 0b5bf6a2d6..de99896231 100644 --- a/src/addons/script/expr_ast.h +++ b/src/addons/script/expr_ast.h @@ -79,6 +79,11 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t *index; } ecs_expr_element_t; +typedef struct ecs_expr_cast_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; +} ecs_expr_cast_t; + ecs_expr_val_t* flecs_expr_value( ecs_script_parser_t *parser); @@ -122,4 +127,9 @@ ecs_expr_member_t* flecs_expr_member( ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); + #endif diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr_to_str.c index e822a35829..a91fa616a6 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr_to_str.c @@ -141,6 +141,13 @@ int flecs_expr_element_to_str( return 0; } +int flecs_expr_cast_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_cast_t *node) +{ + return flecs_expr_node_to_str(v, node->expr); +} + int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) @@ -192,6 +199,9 @@ int flecs_expr_node_to_str( } break; case EcsExprCast: + if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + goto error; + } break; } diff --git a/src/addons/script/expr_visit_fold.c b/src/addons/script/expr_visit_fold.c index cb2e08fb48..f98b0be7de 100644 --- a/src/addons/script/expr_visit_fold.c +++ b/src/addons/script/expr_visit_fold.c @@ -208,6 +208,49 @@ int flecs_expr_binary_visit_fold( return -1; } +int flecs_expr_cast_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) +{ + ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } + + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } + + ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + + /* Reuse existing node to hold casted value */ + *node_ptr = (ecs_expr_node_t*)expr; + + ecs_entity_t dst_type = node->node.type; + ecs_entity_t src_type = expr->node.type; + + if (dst_type == src_type) { + /* No cast necessary if types are equal */ + return 0; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + ecs_value_t value = { + .type = src_type, + .ptr = expr->ptr + }; + + ecs_meta_set_value(&cur, &value); + + expr->node.type = dst_type; + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr) @@ -239,6 +282,9 @@ int flecs_script_expr_visit_fold( case EcsExprElement: break; case EcsExprCast: + if (flecs_expr_cast_visit_fold(script, node_ptr)) { + goto error; + } break; } diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr_visit_type.c index 280813c7ee..cf610593f6 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr_visit_type.c @@ -282,6 +282,16 @@ int flecs_expr_binary_visit_type( goto error; } + if (operand_type != node->left->type) { + node->left = (ecs_expr_node_t*)flecs_expr_cast( + script, node->left, operand_type); + } + + if (operand_type != node->right->type) { + node->right = (ecs_expr_node_t*)flecs_expr_cast( + script, node->right, operand_type); + } + node->node.type = result_type; return 0; From ba89187f5f46c72bcf030e6167723f37465e9f47 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 21 Nov 2024 22:22:57 -0800 Subject: [PATCH 07/83] Move expression implementation to its own folder --- distr/flecs.c | 24384 ++++++++-------- src/addons/script/{expr_ast.c => expr/ast.c} | 2 +- src/addons/script/{expr_ast.h => expr/ast.h} | 0 .../script/{new_expr.c => expr/parser.c} | 6 +- .../script/{expr_visit.h => expr/visit.h} | 0 .../{expr_visit_fold.c => expr/visit_fold.c} | 8 +- .../{expr_to_str.c => expr/visit_to_str.c} | 2 +- .../{expr_visit_type.c => expr/visit_type.c} | 6 +- src/addons/script/script.h | 4 +- 9 files changed, 12212 insertions(+), 12200 deletions(-) rename src/addons/script/{expr_ast.c => expr/ast.c} (99%) rename src/addons/script/{expr_ast.h => expr/ast.h} (100%) rename src/addons/script/{new_expr.c => expr/parser.c} (98%) rename src/addons/script/{expr_visit.h => expr/visit.h} (100%) rename src/addons/script/{expr_visit_fold.c => expr/visit_fold.c} (97%) rename src/addons/script/{expr_to_str.c => expr/visit_to_str.c} (99%) rename src/addons/script/{expr_visit_type.c => expr/visit_type.c} (98%) diff --git a/distr/flecs.c b/distr/flecs.c index 1054e1b081..584c072168 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56474,1885 +56474,418 @@ const char* ecs_script_expr_run( #endif /** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. + * @file addons/script/interpolate.c + * @brief String interpolation. */ #ifdef FLECS_SCRIPT - -#define flecs_expr_ast_new(parser, T, kind)\ - (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) +#include static -void* flecs_expr_ast_new_( - ecs_script_parser_t *parser, - ecs_size_t size, - ecs_expr_node_kind_t kind) +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) { - ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_allocator_t *a = &parser->script->allocator; - ecs_expr_node_t *result = flecs_calloc(a, size); - result->kind = kind; - result->pos = parser->pos; - return result; -} + char ch, *bptr = token_out; -ecs_expr_val_t* flecs_expr_bool( - ecs_script_parser_t *parser, - bool value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.bool_ = value; - result->ptr = &result->storage.bool_; - result->node.type = ecs_id(ecs_bool_t); - return result; -} + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } -ecs_expr_val_t* flecs_expr_int( - ecs_script_parser_t *parser, - int64_t value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.i64 = value; - result->ptr = &result->storage.i64; - result->node.type = ecs_id(ecs_i64_t); - return result; -} + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; + } + } -ecs_expr_val_t* flecs_expr_uint( - ecs_script_parser_t *parser, - uint64_t value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.u64 = value; - result->ptr = &result->storage.u64; - result->node.type = ecs_id(ecs_i64_t); - return result; -} + if (bptr == token_out) { + goto error; + } -ecs_expr_val_t* flecs_expr_float( - ecs_script_parser_t *parser, - double value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.f64 = value; - result->ptr = &result->storage.f64; - result->node.type = ecs_id(ecs_f64_t); - return result; -} + *bptr = '\0'; -ecs_expr_val_t* flecs_expr_string( - ecs_script_parser_t *parser, - const char *value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.string = value; - result->ptr = &result->storage.string; - result->node.type = ecs_id(ecs_string_t); - return result; + return ptr; +error: + return NULL; } -ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_parser_t *parser, - const char *value) +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) { - ecs_expr_identifier_t *result = flecs_expr_ast_new( - parser, ecs_expr_identifier_t, EcsExprIdentifier); - result->value = value; - return result; -} + char ch, *bptr = token_out; -ecs_expr_variable_t* flecs_expr_variable( - ecs_script_parser_t *parser, - const char *value) -{ - ecs_expr_variable_t *result = flecs_expr_ast_new( - parser, ecs_expr_variable_t, EcsExprVariable); - result->value = value; - return result; -} + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } -ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser) -{ - ecs_expr_unary_t *result = flecs_expr_ast_new( - parser, ecs_expr_unary_t, EcsExprUnary); - return result; -} + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } -ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser) -{ - ecs_expr_binary_t *result = flecs_expr_ast_new( - parser, ecs_expr_binary_t, EcsExprBinary); - return result; -} + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } + } -ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser) -{ - ecs_expr_member_t *result = flecs_expr_ast_new( - parser, ecs_expr_member_t, EcsExprMember); - return result; -} + if (bptr == token_out) { + goto error; + } -ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser) -{ - ecs_expr_element_t *result = flecs_expr_ast_new( - parser, ecs_expr_element_t, EcsExprElement); - return result; + *bptr = '\0'; + + return ptr; +error: + return NULL; } -ecs_expr_cast_t* flecs_expr_cast( - ecs_script_t *script, - ecs_expr_node_t *expr, - ecs_entity_t type) +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) { - ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; - ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); - result->node.kind = EcsExprCast; - result->node.pos = expr->pos; - result->node.type = type; - result->expr = expr; - return result; -} + char token[ECS_MAX_TOKEN_SIZE]; + ecs_strbuf_t result = ECS_STRBUF_INIT; + const char *ptr; + char ch; -#endif + 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 --; + } -/** - * @file addons/script/expr_to_str.c - * @brief Script expression AST to string visitor. - */ + 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; + } -#ifdef FLECS_SCRIPT + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; + } -typedef struct ecs_expr_str_visitor_t { - const ecs_world_t *world; - ecs_strbuf_t *buf; - int32_t depth; - bool newline; -} ecs_expr_str_visitor_t; + 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; + } -int flecs_expr_node_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_node_t *node); + ecs_script_expr_run_desc_t expr_desc = { + .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) + }; -int flecs_expr_value_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_val_t *node) -{ - return ecs_ptr_to_str_buf( - v->world, node->node.type, node->ptr, v->buf); -} + ecs_value_t expr_result = {0}; + if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { + goto error; + } -int flecs_expr_unary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_unary_t *node) -{ - switch(node->operator) { - case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; - default: - ecs_err("invalid operator for unary expression"); - return -1; - }; + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } - if (flecs_expr_node_to_str(v, node->expr)) { - goto error; + ecs_value_free(world, expr_result.type, expr_result.ptr); + + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } } - return 0; + return ecs_strbuf_get(&result); error: - return -1; + return NULL; } -int flecs_expr_binary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_binary_t *node) -{ - ecs_strbuf_appendlit(v->buf, "("); +#endif - if (flecs_expr_node_to_str(v, node->left)) { - goto error; - } +/** + * @file addons/script/parser.c + * @brief Script grammar parser. + */ - ecs_strbuf_appendlit(v->buf, " "); - switch(node->operator) { - case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; - case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; - case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; - case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; - case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; - case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; - case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; - case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; - case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; - case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; - case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; - case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; - case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; - case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; - case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; - case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; - case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; - default: - ecs_err("invalid operator for binary expression"); - return -1; - }; +#ifdef FLECS_SCRIPT +/** + * @file addons/script/parser.h + * @brief Script grammar parser. + * + * Macro utilities that facilitate a simple recursive descent parser. + */ - ecs_strbuf_appendlit(v->buf, " "); +#ifndef FLECS_SCRIPT_PARSER_H +#define FLECS_SCRIPT_PARSER_H - if (flecs_expr_node_to_str(v, node->right)) { - goto error; +#if defined(ECS_TARGET_CLANG) +/* Ignore unused enum constants in switch as it would blow up the parser code */ +#pragma clang diagnostic ignored "-Wswitch-enum" +/* To allow for nested Parse statements */ +#pragma clang diagnostic ignored "-Wshadow" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wshadow" +#elif defined(ECS_TARGET_MSVC) +/* Allow for variable shadowing */ +#pragma warning(disable : 4456) +#endif + +/* Create script & parser structs with static token buffer */ +#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ + ecs_script_impl_t script = {\ + .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ + .pub.name = script_name,\ + .pub.code = expr\ + };\ + ecs_script_parser_t parser = {\ + .script = flecs_script_impl(&script),\ + .pos = expr,\ + .token_cur = tokens\ } - ecs_strbuf_appendlit(v->buf, ")"); +/* Definitions for parser functions */ +#define ParserBegin\ + ecs_script_tokenizer_t _tokenizer = {{0}};\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_script_tokenizer_t *tokenizer = &_tokenizer; - return 0; -error: - return -1; -} +#define ParserEnd\ + Error("unexpected end of rule (parser error)");\ + error:\ + return NULL -int flecs_expr_identifier_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_identifier_t *node) -{ - ecs_strbuf_appendstr(v->buf, node->value); - return 0; -} +/* Get token */ +#define Token(n) (tokenizer->tokens[n].value) -int flecs_expr_variable_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_variable_t *node) -{ - ecs_strbuf_appendlit(v->buf, "$"); - ecs_strbuf_appendstr(v->buf, node->value); - return 0; -} +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; -int flecs_expr_member_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_member_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; - ecs_strbuf_appendlit(v->buf, "."); - ecs_strbuf_appendstr(v->buf, node->member_name); - return 0; -} +/* Error */ +#define Error(...)\ + ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ + (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ + goto error -int flecs_expr_element_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_element_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } +/* Parse expression */ +#define Expr(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ + goto error;\ + }\ + if (!t->value[0] && (until == '\n' || until == '{')) {\ + pos ++;\ + Error("empty expression");\ + }\ + }\ + Parse_1(until, __VA_ARGS__) - ecs_strbuf_appendlit(v->buf, "["); - if (flecs_expr_node_to_str(v, node->index)) { - return -1; - } - ecs_strbuf_appendlit(v->buf, "]"); - return 0; -} +/* Parse token until character */ +#define Until(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_until(parser, pos, t, until))) {\ + goto error;\ + }\ + }\ + Parse_1(until, __VA_ARGS__) -int flecs_expr_cast_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_cast_t *node) -{ - return flecs_expr_node_to_str(v, node->expr); -} +/* Parse next token */ +#define Parse(...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_token(parser, pos, t, false))) {\ + goto error;\ + }\ + switch(t->kind) {\ + __VA_ARGS__\ + default:\ + if (t->value) {\ + Error("unexpected %s'%s'", \ + flecs_script_token_kind_str(t->kind), t->value);\ + } else {\ + Error("unexpected %s", \ + flecs_script_token_kind_str(t->kind));\ + }\ + }\ + } -int flecs_expr_node_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_node_t *node) -{ - ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); +/* Parse N consecutive tokens */ +#define Parse_1(tok, ...)\ + Parse(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } +#define Parse_2(tok1, tok2, ...)\ + Parse_1(tok1, \ + Parse(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + ) - switch(node->kind) { - case EcsExprValue: - if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { - goto error; - } - break; - case EcsExprUnary: - if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { - goto error; - } - break; - case EcsExprIdentifier: - if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { - goto error; - } - break; - case EcsExprVariable: - if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { - goto error; - } - break; - case EcsExprFunction: - break; - case EcsExprMember: - if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { - goto error; - } - break; - case EcsExprElement: - if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { - goto error; - } - break; - case EcsExprCast: - if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { - goto error; - } - break; - } - - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } - - return 0; -error: - return -1; -} - -FLECS_API -char* ecs_script_expr_to_str( - const ecs_world_t *world, - const ecs_expr_node_t *expr) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; - if (flecs_expr_node_to_str(&v, expr)) { - ecs_strbuf_reset(&buf); - return NULL; - } - - return ecs_strbuf_get(&buf); -} - -#endif - -/** - * @file addons/script/expr_fold.c - * @brief Script expression constant folding. - */ - - -#ifdef FLECS_SCRIPT - -#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -int flecs_expr_unary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; - - if (node->operator != EcsTokNot) { - flecs_expr_visit_error(script, node, - "operator invalid for unary expression"); - goto error; - } - - if (flecs_script_expr_visit_fold(script, &node->expr)) { - goto error; - } - - if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - if (node->node.type != ecs_id(ecs_bool_t)) { - char *type_str = ecs_get_path(script->world, node->node.type); - flecs_expr_visit_error(script, node, - "! operator cannot be applied to value of type '%s' (must be bool)"); - ecs_os_free(type_str); - goto error; - } - - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = ecs_id(ecs_bool_t); - result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); - - return 0; -error: - return -1; -} - -int flecs_expr_binary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - - if (flecs_script_expr_visit_fold(script, &node->left)) { - goto error; - } - - if (flecs_script_expr_visit_fold(script, &node->right)) { - goto error; - } - - if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->ptr = &result->storage.u64; - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; - - switch(node->operator) { - case EcsTokAdd: - ECS_BINARY_OP(node->left, node->right, result, +); - break; - case EcsTokSub: - ECS_BINARY_OP(node->left, node->right, result, -); - break; - case EcsTokMul: - ECS_BINARY_OP(node->left, node->right, result, *); - break; - case EcsTokDiv: - ECS_BINARY_OP(node->left, node->right, result, /); - break; - case EcsTokMod: - ECS_BINARY_INT_OP(node->left, node->right, result, %); - break; - case EcsTokEq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); - break; - case EcsTokNeq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); - break; - case EcsTokGt: - ECS_BINARY_COND_OP(node->left, node->right, result, >); - break; - case EcsTokGtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, >=); - break; - case EcsTokLt: - ECS_BINARY_COND_OP(node->left, node->right, result, <); - break; - case EcsTokLtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, <=); - break; - case EcsTokAnd: - ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); - break; - case EcsTokOr: - ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); - break; - case EcsTokShiftLeft: - ECS_BINARY_UINT_OP(node->left, node->right, result, <<); - break; - case EcsTokShiftRight: - ECS_BINARY_UINT_OP(node->left, node->right, result, >>); - break; - default: - flecs_expr_visit_error(script, node->left, "unsupported operator"); - goto error; - } - - *node_ptr = (ecs_expr_node_t*)result; - - return 0; -error: - return -1; -} - -int flecs_expr_cast_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - - if (flecs_script_expr_visit_fold(script, &node->expr)) { - goto error; - } - - if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; - - /* Reuse existing node to hold casted value */ - *node_ptr = (ecs_expr_node_t*)expr; - - ecs_entity_t dst_type = node->node.type; - ecs_entity_t src_type = expr->node.type; - - if (dst_type == src_type) { - /* No cast necessary if types are equal */ - return 0; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); - ecs_value_t value = { - .type = src_type, - .ptr = expr->ptr - }; - - ecs_meta_set_value(&cur, &value); - - expr->node.type = dst_type; - - return 0; -error: - return -1; -} - -int flecs_script_expr_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_expr_node_t *node = *node_ptr; - - switch(node->kind) { - case EcsExprValue: - break; - case EcsExprUnary: - if (flecs_expr_unary_visit_fold(script, node_ptr)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_visit_fold(script, node_ptr)) { - goto error; - } - break; - case EcsExprIdentifier: - break; - case EcsExprVariable: - break; - case EcsExprFunction: - break; - case EcsExprMember: - break; - case EcsExprElement: - break; - case EcsExprCast: - if (flecs_expr_cast_visit_fold(script, node_ptr)) { - goto error; - } - break; - } - - return 0; -error: - return -1; -} - -#endif - -/** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -static -bool flecs_expr_operator_is_equality( - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return true; - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - return false; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - -static -bool flecs_expr_is_type_number( - ecs_entity_t type) -{ - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -static -ecs_entity_t flecs_expr_largest_type( - const EcsPrimitive *type) -{ - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -/** Promote type to most expressive (f64 > i64 > u64) */ -static -ecs_entity_t flecs_expr_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) -{ - if (type == ecs_id(ecs_u64_t)) { - return promote_to; - } - if (promote_to == ecs_id(ecs_u64_t)) { - return type; - } - if (type == ecs_id(ecs_f64_t)) { - return type; - } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; - } - return ecs_id(ecs_i64_t); -} - -static -bool flecs_expr_oper_valid_for_type( - ecs_entity_t type, - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - return flecs_expr_is_type_number(type); - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - return type == ecs_id(ecs_u64_t) || - type == ecs_id(ecs_u32_t) || - type == ecs_id(ecs_u16_t) || - type == ecs_id(ecs_u8_t); - case EcsTokEq: - case EcsTokNeq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return flecs_expr_is_type_number(type) || - (type == ecs_id(ecs_bool_t)) || - (type == ecs_id(ecs_char_t)) || - (type == ecs_id(ecs_entity_t)); - default: - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } -} - -static -int flecs_expr_type_for_oper( - ecs_script_t *script, - ecs_expr_binary_t *node, - ecs_entity_t *operand_type, - ecs_entity_t *result_type) -{ - ecs_world_t *world = script->world; - ecs_expr_node_t *left = node->left, *right = node->right; - - switch(node->operator) { - case EcsTokDiv: - /* Result type of a division is always a float */ - *operand_type = ecs_id(ecs_f64_t); - *result_type = ecs_id(ecs_f64_t); - return 0; - case EcsTokAnd: - case EcsTokOr: - /* Result type of a condition operator is always a bool */ - *operand_type = ecs_id(ecs_bool_t); - *result_type = ecs_id(ecs_bool_t); - return 0; - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - /* Result type of equality operator is always bool, but operand types - * should not be casted to bool */ - *result_type = ecs_id(ecs_bool_t); - break; - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - break; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } - - const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); - const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); - if (!ltype_ptr || !rtype_ptr) { - char *lname = ecs_get_path(world, left->type); - char *rname = ecs_get_path(world, right->type); - flecs_expr_visit_error(script, node, - "invalid non-primitive type in binary expression (%s, %s)", - lname, rname); - ecs_os_free(lname); - ecs_os_free(rname); - return 0; - } - - ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); - if (ltype == rtype) { - *operand_type = ltype; - goto done; - } - - if (flecs_expr_operator_is_equality(node->operator)) { - char *lname = ecs_id_str(world, ltype); - char *rname = ecs_id_str(world, rtype); - flecs_expr_visit_error(script, node, - "mismatching types in equality expression (%s vs %s)", - lname, rname); - ecs_os_free(rname); - ecs_os_free(lname); - return 0; - } - - if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { - flecs_expr_visit_error(script, node, - "incompatible types in binary expression"); - return 0; - } - - *operand_type = flecs_expr_promote_type(ltype, rtype); - -done: - if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { - /* Result of subtracting two unsigned ints can be negative */ - *operand_type = ecs_id(ecs_i64_t); - } - - if (!*result_type) { - *result_type = *operand_type; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_unary_visit_type( - ecs_script_t *script, - ecs_expr_unary_t *node) -{ - if (flecs_script_expr_visit_type(script, node->expr)) { - goto error; - } - - /* The only supported unary expression is not (!) which returns a bool */ - node->node.type = ecs_id(ecs_bool_t); - - return 0; -error: - return -1; -} - -static -int flecs_expr_binary_visit_type( - ecs_script_t *script, - ecs_expr_binary_t *node) -{ - /* Operands must be of this type or casted to it */ - ecs_entity_t operand_type = 0; - - /* Resulting type of binary expression */ - ecs_entity_t result_type = 0; - - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - if (flecs_script_expr_visit_type(script, node->right)) { - goto error; - } - - if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { - goto error; - } - - if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { - flecs_expr_visit_error(script, node, "invalid operator for type"); - goto error; - } - - if (operand_type != node->left->type) { - node->left = (ecs_expr_node_t*)flecs_expr_cast( - script, node->left, operand_type); - } - - if (operand_type != node->right->type) { - node->right = (ecs_expr_node_t*)flecs_expr_cast( - script, node->right, operand_type); - } - - node->node.type = result_type; - - return 0; -error: - return -1; -} - -static -int flecs_expr_identifier_visit_type( - ecs_script_t *script, - ecs_expr_identifier_t *node) -{ - node->node.type = ecs_id(ecs_entity_t); - node->id = ecs_lookup(script->world, node->value); - if (!node->id) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_variable_visit_type( - ecs_script_t *script, - ecs_expr_variable_t *node) -{ - node->node.type = ecs_id(ecs_entity_t); - return 0; -} - -static -int flecs_expr_member_visit_type( - ecs_script_t *script, - ecs_expr_member_t *node) -{ - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - - const EcsType *type = ecs_get(world, left_type, EcsType); - if (!type) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot resolve member on value of type '%s' (missing reflection data)", - type_str); - ecs_os_free(type_str); - goto error; - } - - if (type->kind != EcsStructType) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot resolve member on non-struct type '%s'", - type_str); - ecs_os_free(type_str); - goto error; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); - ecs_meta_push(&cur); /* { */ - int prev_log = ecs_log_set_level(-4); - if (ecs_meta_dotmember(&cur, node->member_name)) { - ecs_log_set_level(prev_log); - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "unresolved member '%s' for type '%s'", - node->member_name, type_str); - ecs_os_free(type_str); - goto error; - } - ecs_log_set_level(prev_log); - - node->node.type = ecs_meta_get_type(&cur); - ecs_meta_pop(&cur); /* } */ - - return 0; -error: - return -1; -} - -static -int flecs_expr_element_visit_type( - ecs_script_t *script, - ecs_expr_element_t *node) -{ - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - if (flecs_script_expr_visit_type(script, node->index)) { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - const EcsType *type = ecs_get(world, left_type, EcsType); - if (!type) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot use [] on value of type '%s' (missing reflection data)", - type_str); - ecs_os_free(type_str); - goto error; - } - - bool is_entity_type = false; - - if (type->kind == EcsPrimitiveType) { - const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); - if (ptype->kind == EcsEntity) { - is_entity_type = true; - } - } - - if (is_entity_type) { - if (node->index->kind == EcsExprIdentifier) { - node->node.type = ((ecs_expr_identifier_t*)node->index)->id; - } else { - flecs_expr_visit_error(script, node, - "invalid component expression"); - goto error; - } - } else if (type->kind == EcsArrayType) { - const EcsArray *type_array = ecs_get(world, left_type, EcsArray); - ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_array->type; - } else if (type->kind == EcsVectorType) { - const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); - ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_vector->type; - } else { - char *type_str = ecs_get_path(script->world, node->left->type); - flecs_expr_visit_error(script, node, - "invalid usage of [] on non collection/entity type '%s'", type_str); - ecs_os_free(type_str); - goto error; - } - - return 0; -error: - return -1; -} - -int flecs_script_expr_visit_type( - ecs_script_t *script, - ecs_expr_node_t *node) -{ - ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - - switch(node->kind) { - case EcsExprValue: - /* Value types are assigned by the AST */ - break; - case EcsExprUnary: - if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { - goto error; - } - break; - case EcsExprIdentifier: - if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { - goto error; - } - break; - case EcsExprVariable: - if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { - goto error; - } - break; - case EcsExprFunction: - break; - case EcsExprMember: - if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { - goto error; - } - break; - case EcsExprElement: - if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { - goto error; - } - break; - case EcsExprCast: - break; - } - - return 0; -error: - return -1; -} - -#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_script_expr_run_desc_t expr_desc = { - .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) - }; - - ecs_value_t expr_result = {0}; - if (!ecs_script_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/expr.c - * @brief Script expression parser. - */ - - -#ifdef FLECS_SCRIPT -/** - * @file addons/script/parser.h - * @brief Script grammar parser. - * - * Macro utilities that facilitate a simple recursive descent parser. - */ - -#ifndef FLECS_SCRIPT_PARSER_H -#define FLECS_SCRIPT_PARSER_H - -#if defined(ECS_TARGET_CLANG) -/* Ignore unused enum constants in switch as it would blow up the parser code */ -#pragma clang diagnostic ignored "-Wswitch-enum" -/* To allow for nested Parse statements */ -#pragma clang diagnostic ignored "-Wshadow" -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#pragma GCC diagnostic ignored "-Wswitch-enum" -#pragma GCC diagnostic ignored "-Wshadow" -#elif defined(ECS_TARGET_MSVC) -/* Allow for variable shadowing */ -#pragma warning(disable : 4456) -#endif - -/* Create script & parser structs with static token buffer */ -#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ - ecs_script_impl_t script = {\ - .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ - .pub.name = script_name,\ - .pub.code = expr\ - };\ - ecs_script_parser_t parser = {\ - .script = flecs_script_impl(&script),\ - .pos = expr,\ - .token_cur = tokens\ - } - -/* Definitions for parser functions */ -#define ParserBegin\ - ecs_script_tokenizer_t _tokenizer = {{0}};\ - _tokenizer.tokens = _tokenizer.stack.tokens;\ - ecs_script_tokenizer_t *tokenizer = &_tokenizer; - -#define ParserEnd\ - Error("unexpected end of rule (parser error)");\ - error:\ - return NULL - -/* Get token */ -#define Token(n) (tokenizer->tokens[n].value) - -/* Push/pop token frame (allows token stack reuse in recursive functions) */ -#define TokenFramePush() \ - tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; - -#define TokenFramePop() \ - tokenizer->tokens = tokenizer->stack.tokens; - -/* Error */ -#define Error(...)\ - ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ - goto error - -/* Parse expression */ -#define Expr(until, ...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ - goto error;\ - }\ - if (!t->value[0] && (until == '\n' || until == '{')) {\ - pos ++;\ - Error("empty expression");\ - }\ - }\ - Parse_1(until, __VA_ARGS__) - -/* Parse token until character */ -#define Until(until, ...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_until(parser, pos, t, until))) {\ - goto error;\ - }\ - }\ - Parse_1(until, __VA_ARGS__) - -/* Parse next token */ -#define Parse(...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_token(parser, pos, t, false))) {\ - goto error;\ - }\ - switch(t->kind) {\ - __VA_ARGS__\ - default:\ - if (t->value) {\ - Error("unexpected %s'%s'", \ - flecs_script_token_kind_str(t->kind), t->value);\ - } else {\ - Error("unexpected %s", \ - flecs_script_token_kind_str(t->kind));\ - }\ - }\ - } - -/* Parse N consecutive tokens */ -#define Parse_1(tok, ...)\ - Parse(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) - -#define Parse_2(tok1, tok2, ...)\ - Parse_1(tok1, \ - Parse(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_3(tok1, tok2, tok3, ...)\ - Parse_2(tok1, tok2, \ - Parse(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_4(tok1, tok2, tok3, tok4, ...)\ - Parse_3(tok1, tok2, tok3, \ - Parse(\ - case tok4: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ - Parse_4(tok1, tok2, tok3, tok4, \ - Parse(\ - case tok5: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define LookAhead_Keep() \ - pos = lookahead;\ - parser->token_keep = parser->token_cur - -/* Same as Parse, but doesn't error out if token is not in handled cases */ -#define LookAhead(...)\ - const char *lookahead;\ - ecs_script_token_t lookahead_token;\ - const char *old_lh_token_cur = parser->token_cur;\ - if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ - tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ - switch(lookahead_token.kind) {\ - __VA_ARGS__\ - default:\ - tokenizer->stack.count --;\ - break;\ - }\ - if (old_lh_token_cur > parser->token_keep) {\ - parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ - } else {\ - parser->token_cur = parser->token_keep;\ - }\ - } - -/* Lookahead N consecutive tokens */ -#define LookAhead_1(tok, ...)\ - LookAhead(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) - -#define LookAhead_2(tok1, tok2, ...)\ - LookAhead_1(tok1, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - pos = old_ptr;\ - ) - -#define LookAhead_3(tok1, tok2, tok3, ...)\ - LookAhead_2(tok1, tok2, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - pos = old_ptr;\ - ) - -/* Open scope */ -#define Scope(s, ...) {\ - ecs_script_scope_t *old_scope = parser->scope;\ - parser->scope = s;\ - __VA_ARGS__\ - parser->scope = old_scope;\ - } - -/* Parser loop */ -#define Loop(...)\ - int32_t token_stack_count = tokenizer->stack.count;\ - do {\ - tokenizer->stack.count = token_stack_count;\ - __VA_ARGS__\ - } while (true); - -#define EndOfRule return pos - -#endif - - -/* From https://en.cppreference.com/w/c/language/operator_precedence */ - -static int flecs_expr_precedence[] = { - [EcsTokParenOpen] = 1, - [EcsTokMember] = 1, - [EcsTokBracketOpen] = 1, - [EcsTokNot] = 2, - [EcsTokMul] = 3, - [EcsTokDiv] = 3, - [EcsTokMod] = 3, - [EcsTokAdd] = 4, - [EcsTokSub] = 4, - [EcsTokShiftLeft] = 5, - [EcsTokShiftRight] = 5, - [EcsTokGt] = 6, - [EcsTokGtEq] = 6, - [EcsTokLt] = 6, - [EcsTokLtEq] = 6, - [EcsTokEq] = 7, - [EcsTokNeq] = 7, - [EcsTokBitwiseAnd] = 8, - [EcsTokBitwiseOr] = 10, - [EcsTokAnd] = 11, - [EcsTokOr] = 12, -}; - -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -static -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -static -bool flecs_has_precedence( - ecs_script_token_kind_t first, - ecs_script_token_kind_t second) -{ - if (!flecs_expr_precedence[first]) { - return false; - } - return flecs_expr_precedence[first] < flecs_expr_precedence[second]; -} - -static -const char* flecs_script_parse_rhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_expr_node_t *left, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - const char *last_pos = pos; - - TokenFramePush(); - - LookAhead( - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBracketOpen: - case EcsTokMember: - { - ecs_script_token_kind_t oper = lookahead_token.kind; - - /* Only consume more tokens if operator has precedence */ - if (flecs_has_precedence(left_oper, oper)) { - break; - } - - /* Consume lookahead token */ - pos = lookahead; - - switch(oper) { - case EcsTokBracketOpen: { - ecs_expr_element_t *result = flecs_expr_element(parser); - result->left = *out; - - pos = flecs_script_parse_lhs( - parser, pos, tokenizer, 0, &result->index); - if (!pos) { - goto error; - } - - Parse_1(']', { - *out = (ecs_expr_node_t*)result; - break; - }); - - break; - } - - case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; - - Parse_1(EcsTokIdentifier, { - result->member_name = Token(1); - *out = (ecs_expr_node_t*)result; - break; - }); - - break; - } - - default: { - ecs_expr_binary_t *result = flecs_expr_binary(parser); - result->left = *out; - result->operator = oper; - - pos = flecs_script_parse_lhs(parser, pos, tokenizer, - result->operator, &result->right); - if (!pos) { - goto error; - } - *out = (ecs_expr_node_t*)result; - break; - } - }; - - /* Ensures lookahead tokens in token buffer don't get overwritten */ - parser->token_keep = parser->token_cur; - - break; - } +#define Parse_3(tok1, tok2, tok3, ...)\ + Parse_2(tok1, tok2, \ + Parse(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ ) - if (pos[0] && (pos != last_pos)) { - pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); - } - - TokenFramePop(); - - return pos; -error: - return NULL; -} - -static -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - TokenFramePush(); - - Parse( - case EcsTokNumber: { - const char *expr = Token(0); - if (strchr(expr, '.') || strchr(expr, 'e')) { - *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); - } else if (expr[0] == '-') { - *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); - } else { - *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); - } - break; - } - - case EcsTokString: { - *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); - break; - } - - case EcsTokIdentifier: { - const char *expr = Token(0); - if (expr[0] == '$') { - *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); - } else if (!ecs_os_strcmp(expr, "true")) { - *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); - } else if (!ecs_os_strcmp(expr, "false")) { - *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); - } else { - *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); - } - break; - } - - case EcsTokParenOpen: { - pos = flecs_script_parse_expr(parser, pos, 0, out); - Parse_1(EcsTokParenClose, { - break; - }) - break; - } - - case EcsTokNot: { - ecs_expr_unary_t *unary = flecs_expr_unary(parser); - pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); - unary->operator = EcsTokNot; - *out = (ecs_expr_node_t*)unary; - break; - } +#define Parse_4(tok1, tok2, tok3, tok4, ...)\ + Parse_3(tok1, tok2, tok3, \ + Parse(\ + case tok4: {\ + __VA_ARGS__\ + }\ + )\ ) - TokenFramePop(); - - /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs( - parser, pos, tokenizer, *out, left_oper, out); -error: - return NULL; -} - -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - ParserBegin; - - pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); - - EndOfRule; +#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ + Parse_4(tok1, tok2, tok3, tok4, \ + Parse(\ + case tok5: {\ + __VA_ARGS__\ + }\ + )\ + ) - ParserEnd; -} +#define LookAhead_Keep() \ + pos = lookahead;\ + parser->token_keep = parser->token_cur -ecs_expr_node_t* ecs_script_parse_expr( - ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr) -{ - if (!script) { - script = flecs_script_new(world); +/* Same as Parse, but doesn't error out if token is not in handled cases */ +#define LookAhead(...)\ + const char *lookahead;\ + ecs_script_token_t lookahead_token;\ + const char *old_lh_token_cur = parser->token_cur;\ + if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ + switch(lookahead_token.kind) {\ + __VA_ARGS__\ + default:\ + tokenizer->stack.count --;\ + break;\ + }\ + if (old_lh_token_cur > parser->token_keep) {\ + parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ + } else {\ + parser->token_cur = parser->token_keep;\ + }\ } - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, - .significant_newline = false - }; - - ecs_script_impl_t *impl = flecs_script_impl(script); +/* Lookahead N consecutive tokens */ +#define LookAhead_1(tok, ...)\ + LookAhead(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); - parser.token_cur = impl->token_buffer; +#define LookAhead_2(tok1, tok2, ...)\ + LookAhead_1(tok1, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + pos = old_ptr;\ + ) - ecs_expr_node_t *out = NULL; +#define LookAhead_3(tok1, tok2, tok3, ...)\ + LookAhead_2(tok1, tok2, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + pos = old_ptr;\ + ) - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { - goto error; +/* Open scope */ +#define Scope(s, ...) {\ + ecs_script_scope_t *old_scope = parser->scope;\ + parser->scope = s;\ + __VA_ARGS__\ + parser->scope = old_scope;\ } - flecs_script_expr_visit_type(script, out); - flecs_script_expr_visit_fold(script, &out); +/* Parser loop */ +#define Loop(...)\ + int32_t token_stack_count = tokenizer->stack.count;\ + do {\ + tokenizer->stack.count = token_stack_count;\ + __VA_ARGS__\ + } while (true); - return out; -error: - return NULL; -} +#define EndOfRule return pos #endif -/** - * @file addons/script/parser.c - * @brief Script grammar parser. - */ - - -#ifdef FLECS_SCRIPT #define EcsTokEndOfStatement\ case ';':\ @@ -62722,17 +61255,238 @@ int flecs_script_eval_tag( return 0; } - ecs_entity_t src = flecs_script_get_src( - v, v->entity->eval, node->id.eval); - ecs_add_id(v->world, src, node->id.eval); + ecs_entity_t src = flecs_script_get_src( + v, v->entity->eval, node->id.eval); + ecs_add_id(v->world, src, node->id.eval); + + return 0; +} + +static +int flecs_script_eval_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (v->template) { + return 0; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->template) { + return 0; + } + + ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + + if (node->expr && node->expr[0]) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + if (!ti) { + return -1; + } + + const EcsType *type = ecs_get(v->world, ti->component, EcsType); + if (type) { + bool is_collection = false; + + switch(type->kind) { + case EcsPrimitiveType: + case EcsBitmaskType: + case EcsEnumType: + case EcsStructType: + case EcsOpaqueType: + break; + case EcsArrayType: + case EcsVectorType: + is_collection = true; + break; + } + + if (node->is_collection != is_collection) { + char *id_str = ecs_id_str(v->world, ti->component); + if (node->is_collection && !is_collection) { + flecs_script_eval_error(v, node, + "type %s is not a collection (use '%s: {...}')", + id_str, id_str); + } else { + flecs_script_eval_error(v, node, + "type %s is a collection (use '%s: [...]')", + id_str, id_str); + } + ecs_os_free(id_str); + return -1; + } + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, src, node->id.eval), + .type = ti->component + }; + + /* Assign entire value, including members not set by expression. This + * prevents uninitialized or unexpected values. */ + if (!ti->hooks.ctor) { + ecs_os_memset(value.ptr, 0, ti->size); + } else if (ti->hooks.ctor) { + if (ti->hooks.dtor) { + ti->hooks.dtor(value.ptr, 1, ti); + } + ti->hooks.ctor(value.ptr, 1, ti); + } + + if (ecs_os_strcmp(node->expr, "{}")) { + if (flecs_script_eval_expr(v, node->expr, &value)) { + return -1; + } + } + + ecs_modified_id(v->world, src, node->id.eval); + } else { + ecs_add_id(v->world, src, node->id.eval); + } + + return 0; +} + +static +int flecs_script_eval_var_component( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + if (v->template) { + return 0; + } + + ecs_id_t var_id = var->value.type; + + if (var->value.ptr) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, var_id); + if (!ti) { + return -1; + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id), + .type = var_id + }; + + ecs_value_copy_w_type_info(v->world, ti, value.ptr, var->value.ptr); + + ecs_modified_id(v->world, v->entity->eval, var_id); + } else { + ecs_add_id(v->world, v->entity->eval, var_id); + } + + return 0; +} + +static +int flecs_script_eval_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + if (v->template) { + return 0; + } + + ecs_script_scope_t *scope = ecs_script_current_scope(v); + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); + scope = scope->parent; + + if (!scope) { + flecs_script_eval_error(v, node, + "entity '%s' is in root scope which cannot have a default type", + v->entity->name); + return -1; + } + + ecs_id_t default_type = scope->default_component_eval; + if (!default_type) { + flecs_script_eval_error(v, node, + "scope for entity '%s' does not have a default type", + v->entity->name); + return -1; + } + + if (ecs_get_type_info(v->world, default_type) == NULL) { + char *id_str = ecs_id_str(v->world, default_type); + flecs_script_eval_error(v, node, + "cannot use tag '%s' as default type in assignment", + id_str); + ecs_os_free(id_str); + return -1; + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), + .type = default_type + }; + + if (flecs_script_eval_expr(v, node->expr, &value)) { + return -1; + } + + ecs_modified_id(v->world, v->entity->eval, default_type); + + return 0; +} + +static +int flecs_script_eval_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + if (v->template) { + return 0; + } + + ecs_allocator_t *a = v->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types + *value = var->value; return 0; } static -int flecs_script_eval_component( +int flecs_script_eval_with_tag( ecs_script_eval_visitor_t *v, - ecs_script_component_t *node) + ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; @@ -62742,14 +61496,20 @@ int flecs_script_eval_component( return 0; } - if (!v->entity) { - if (node->id.second) { - flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", - node->id.first, node->id.second); - } else { - flecs_script_eval_error(v, node, "missing entity for component %s", - node->id.first); - } + ecs_allocator_t *a = v->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = node->id.eval; + value->ptr = NULL; + + return 0; +} + +static +int flecs_script_eval_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { return -1; } @@ -62757,5919 +61517,6704 @@ int flecs_script_eval_component( return 0; } - ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + ecs_allocator_t *a = v->allocator; + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + + ecs_value_t *value = flecs_script_with_append(a, v, ti); + value->type = node->id.eval; + value->ptr = NULL; if (node->expr && node->expr[0]) { - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, node->id.eval); if (!ti) { return -1; } - const EcsType *type = ecs_get(v->world, ti->component, EcsType); - if (type) { - bool is_collection = false; + value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + value->type = ti->component; // Expression parser needs actual type - switch(type->kind) { - case EcsPrimitiveType: - case EcsBitmaskType: - case EcsEnumType: - case EcsStructType: - case EcsOpaqueType: - break; - case EcsArrayType: - case EcsVectorType: - is_collection = true; - break; - } + if (ti->hooks.ctor) { + ti->hooks.ctor(value->ptr, 1, ti); + } - if (node->is_collection != is_collection) { - char *id_str = ecs_id_str(v->world, ti->component); - if (node->is_collection && !is_collection) { - flecs_script_eval_error(v, node, - "type %s is not a collection (use '%s: {...}')", - id_str, id_str); - } else { - flecs_script_eval_error(v, node, - "type %s is a collection (use '%s: [...]')", - id_str, id_str); - } - ecs_os_free(id_str); - return -1; - } + if (flecs_script_eval_expr(v, node->expr, value)) { + return -1; } - - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, src, node->id.eval), - .type = ti->component - }; - /* Assign entire value, including members not set by expression. This - * prevents uninitialized or unexpected values. */ - if (!ti->hooks.ctor) { - ecs_os_memset(value.ptr, 0, ti->size); - } else if (ti->hooks.ctor) { - if (ti->hooks.dtor) { - ti->hooks.dtor(value.ptr, 1, ti); + value->type = node->id.eval; // Restore so we're adding actual id + } + + return 0; +} + +static +int flecs_script_eval_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + ecs_allocator_t *a = v->allocator; + int32_t prev_with_count = flecs_script_with_count(v); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); + int result = 0; + + if (ecs_script_visit_scope(v, node->expressions)) { + result = -1; + goto error; + } + + ecs_value_t *value = flecs_script_with_last(v); + if (!value->ptr) { + if (ecs_is_valid(v->world, value->type)) { + node->scope->default_component_eval = value->type; + } + } + + if (ecs_script_visit_scope(v, node->scope)) { + result = -1; + goto error; + } + +error: + flecs_script_with_set_count(a, v, prev_with_count); + flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); + return result; +} + +static +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + ecs_allocator_t *a = v->allocator; + int32_t len = ecs_os_strlen(node->name); + + if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { + char *path = flecs_strdup(a, node->name); + path[len - 2] = '\0'; + + ecs_entity_t from = ecs_lookup(v->world, path); + if (!from) { + flecs_script_eval_error(v, node, + "unresolved path '%s' in using statement", path); + flecs_strfree(a, path); + return -1; + } + + /* Add each child of the scope to using stack */ + ecs_iter_t it = ecs_children(v->world, from); + while (ecs_children_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_vec_append_t( + a, &v->using, ecs_entity_t)[0] = it.entities[i]; } - ti->hooks.ctor(value.ptr, 1, ti); } - if (ecs_os_strcmp(node->expr, "{}")) { - if (flecs_script_eval_expr(v, node->expr, &value)) { + flecs_strfree(a, path); + } else { + ecs_entity_t from = ecs_lookup_path_w_sep( + v->world, 0, node->name, NULL, NULL, false); + if (!from) { + from = ecs_entity(v->world, { + .name = node->name, + .root_sep = "" + }); + + if (!from) { return -1; } } - ecs_modified_id(v->world, src, node->id.eval); - } else { - ecs_add_id(v->world, src, node->id.eval); + ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; } return 0; } static -int flecs_script_eval_var_component( +int flecs_script_eval_module( ecs_script_eval_visitor_t *v, - ecs_script_var_component_t *node) + ecs_script_module_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); + ecs_entity_t m = flecs_script_create_entity(v, node->name); + if (!m) { return -1; } - if (v->template) { - return 0; + ecs_add_id(v->world, m, EcsModule); + + v->module = m; + v->parent = m; + + return 0; +} + +static +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "variable '%s' redeclared", node->name); + return -1; } - ecs_id_t var_id = var->value.type; + if (node->type) { + ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + if (!type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for const variable '%s'", + node->type, node->name); + return -1; + } - if (var->value.ptr) { - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, var_id); + const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); if (!ti) { + flecs_script_eval_error(v, node, + "failed to retrieve type info for '%s' for const variable '%s'", + node->type, node->name); return -1; } - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id), - .type = var_id - }; + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = type; + var->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + if (flecs_script_eval_expr(v, node->expr, &var->value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } + } else { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } + + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_value_copy_w_type_info(v->world, ti, value.ptr, var->value.ptr); + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = value.type; + var->type_info = ti; - ecs_modified_id(v->world, v->entity->eval, var_id); - } else { - ecs_add_id(v->world, v->entity->eval, var_id); + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); } return 0; } static -int flecs_script_eval_default_component( +int flecs_script_eval_pair_scope( ecs_script_eval_visitor_t *v, - ecs_script_default_component_t *node) + ecs_script_pair_scope_t *node) { - if (!v->entity) { - flecs_script_eval_error(v, node, - "missing entity for default component"); - return -1; + ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); + if (!first) { + first = flecs_script_create_entity(v, node->id.first); + if (!first) { + return -1; + } } - if (v->template) { - return 0; + ecs_entity_t second = flecs_script_create_entity(v, node->id.second); + if (!second) { + return -1; } - ecs_script_scope_t *scope = ecs_script_current_scope(v); - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); - scope = scope->parent; + ecs_allocator_t *a = v->allocator; + ecs_entity_t prev_first = v->with_relationship; + ecs_entity_t prev_second = 0; + int32_t prev_with_relationship_sp = v->with_relationship_sp; - if (!scope) { - flecs_script_eval_error(v, node, - "entity '%s' is in root scope which cannot have a default type", - v->entity->name); - return -1; - } + v->with_relationship = first; - ecs_id_t default_type = scope->default_component_eval; - if (!default_type) { - flecs_script_eval_error(v, node, - "scope for entity '%s' does not have a default type", - v->entity->name); - return -1; + if (prev_first != first) { + /* Append new element to with stack */ + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = ecs_pair(first, second); + value->ptr = NULL; + v->with_relationship_sp = flecs_script_with_count(v) - 1; + } else { + /* Get existing with element for current relationhip stack */ + ecs_value_t *value = ecs_vec_get_t( + &v->with, ecs_value_t, v->with_relationship_sp); + ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, + ECS_INTERNAL_ERROR, NULL); + prev_second = ECS_PAIR_SECOND(value->type); + value->type = ecs_pair(first, second); + value->ptr = NULL; } - if (ecs_get_type_info(v->world, default_type) == NULL) { - char *id_str = ecs_id_str(v->world, default_type); - flecs_script_eval_error(v, node, - "cannot use tag '%s' as default type in assignment", - id_str); - ecs_os_free(id_str); + if (ecs_script_visit_scope(v, node->scope)) { return -1; } - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), - .type = default_type - }; - - if (flecs_script_eval_expr(v, node->expr, &value)) { - return -1; + if (prev_second) { + ecs_value_t *value = ecs_vec_get_t( + &v->with, ecs_value_t, v->with_relationship_sp); + value->type = ecs_pair(first, prev_second); + } else { + flecs_script_with_set_count(a, v, v->with_relationship_sp); } - ecs_modified_id(v->world, v->entity->eval, default_type); + v->with_relationship = prev_first; + v->with_relationship_sp = prev_with_relationship_sp; return 0; } static -int flecs_script_eval_with_var( +int flecs_script_eval_if( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_if_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); + ecs_value_t condval = { .type = 0, .ptr = NULL }; + if (flecs_script_eval_expr(v, node->expr, &condval)) { return -1; } - if (v->template) { - return 0; + bool cond; + if (condval.type == ecs_id(ecs_bool_t)) { + cond = *(bool*)(condval.ptr); + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor( + v->world, condval.type, condval.ptr); + cond = ecs_meta_get_bool(&cur); } - ecs_allocator_t *a = v->allocator; - ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types - *value = var->value; + ecs_value_free(v->world, condval.type, condval.ptr); + + if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { + return -1; + } return 0; } static -int flecs_script_eval_with_tag( +int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, - ecs_script_tag_t *node) + ecs_script_annot_t *node) { - if (flecs_script_eval_id(v, node, &node->id)) { + if (!v->base.next) { + flecs_script_eval_error(v, node, + "annotation '%s' is not applied to anything", node->name); return -1; } - if (v->template) { - return 0; + ecs_script_node_kind_t kind = v->base.next->kind; + if (kind != EcsAstEntity && kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "annotation must be applied to an entity"); + return -1; } ecs_allocator_t *a = v->allocator; - ecs_value_t *value = flecs_script_with_append(a, v, NULL); - value->type = node->id.eval; - value->ptr = NULL; + ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; return 0; } -static -int flecs_script_eval_with_component( +int flecs_script_eval_node( ecs_script_eval_visitor_t *v, - ecs_script_component_t *node) + ecs_script_node_t *node) { - if (flecs_script_eval_id(v, node, &node->id)) { - return -1; - } - - if (v->template) { + switch(node->kind) { + case EcsAstScope: + return flecs_script_eval_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_eval_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_eval_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_eval_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_eval_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_eval_with_var( + v, (ecs_script_var_node_t*)node); + case EcsAstWithTag: + return flecs_script_eval_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_eval_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_eval_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_eval_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return flecs_script_eval_module( + v, (ecs_script_module_t*)node); + case EcsAstAnnotation: + return flecs_script_eval_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return flecs_script_eval_template( + v, (ecs_script_template_node_t*)node); + case EcsAstProp: return 0; + case EcsAstConst: + return flecs_script_eval_const( + v, (ecs_script_var_node_t*)node); + case EcsAstEntity: + return flecs_script_eval_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_eval_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_eval_if( + v, (ecs_script_if_t*)node); } - ecs_allocator_t *a = v->allocator; - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, node->id.eval); + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} - ecs_value_t *value = flecs_script_with_append(a, v, ti); - value->type = node->id.eval; - value->ptr = NULL; +void flecs_script_eval_visit_init( + ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v) +{ + *v = (ecs_script_eval_visitor_t){ + .base = { + .script = script, + .visit = (ecs_visit_action_t)flecs_script_eval_node + }, + .world = script->pub.world, + .allocator = &script->allocator + }; - if (node->expr && node->expr[0]) { - if (!ti) { - return -1; - } + flecs_stack_init(&v->stack); + ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); + ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); + ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); + ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); + + /* Always include flecs.meta */ + ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = + ecs_lookup(v->world, "flecs.meta"); +} + +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v) +{ + ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); + ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); + ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); + ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); + flecs_stack_fini(&v->stack); +} + +int ecs_script_eval( + ecs_script_t *script) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl(script); + flecs_script_eval_visit_init(impl, &v); + int result = ecs_script_visit(impl, &v, flecs_script_eval_node); + flecs_script_eval_visit_fini(&v); + return result; +} - value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); - value->type = ti->component; // Expression parser needs actual type +#endif - if (ti->hooks.ctor) { - ti->hooks.ctor(value->ptr, 1, ti); - } +/** + * @file addons/script/visit_free.c + * @brief Script free visitor (frees AST resources). + */ - if (flecs_script_eval_expr(v, node->expr, value)) { - return -1; - } - value->type = node->id.eval; // Restore so we're adding actual id - } +#ifdef FLECS_SCRIPT - return 0; +static +void flecs_script_scope_free( + ecs_script_visit_t *v, + ecs_script_scope_t *node) +{ + ecs_script_visit_scope(v, node); + ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); + flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); } static -int flecs_script_eval_with( - ecs_script_eval_visitor_t *v, +void flecs_script_with_free( + ecs_script_visit_t *v, ecs_script_with_t *node) { - ecs_allocator_t *a = v->allocator; - int32_t prev_with_count = flecs_script_with_count(v); - ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); - int result = 0; - - if (ecs_script_visit_scope(v, node->expressions)) { - result = -1; - goto error; - } - - ecs_value_t *value = flecs_script_with_last(v); - if (!value->ptr) { - if (ecs_is_valid(v->world, value->type)) { - node->scope->default_component_eval = value->type; - } - } - - if (ecs_script_visit_scope(v, node->scope)) { - result = -1; - goto error; - } - -error: - flecs_script_with_set_count(a, v, prev_with_count); - flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); - return result; + flecs_script_scope_free(v, node->expressions); + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_using( - ecs_script_eval_visitor_t *v, - ecs_script_using_t *node) +void flecs_script_template_free( + ecs_script_visit_t *v, + ecs_script_template_node_t *node) { - ecs_allocator_t *a = v->allocator; - int32_t len = ecs_os_strlen(node->name); - - if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { - char *path = flecs_strdup(a, node->name); - path[len - 2] = '\0'; - - ecs_entity_t from = ecs_lookup(v->world, path); - if (!from) { - flecs_script_eval_error(v, node, - "unresolved path '%s' in using statement", path); - flecs_strfree(a, path); - return -1; - } - - /* Add each child of the scope to using stack */ - ecs_iter_t it = ecs_children(v->world, from); - while (ecs_children_next(&it)) { - int32_t i, count = it.count; - for (i = 0; i < count; i ++) { - ecs_vec_append_t( - a, &v->using, ecs_entity_t)[0] = it.entities[i]; - } - } - - flecs_strfree(a, path); - } else { - ecs_entity_t from = ecs_lookup_path_w_sep( - v->world, 0, node->name, NULL, NULL, false); - if (!from) { - from = ecs_entity(v->world, { - .name = node->name, - .root_sep = "" - }); - - if (!from) { - return -1; - } - } - - ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; - } - - return 0; + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_module( - ecs_script_eval_visitor_t *v, - ecs_script_module_t *node) +void flecs_script_entity_free( + ecs_script_visit_t *v, + ecs_script_entity_t *node) { - ecs_entity_t m = flecs_script_create_entity(v, node->name); - if (!m) { - return -1; - } - - ecs_add_id(v->world, m, EcsModule); - - v->module = m; - v->parent = m; - - return 0; + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_const( - ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) +void flecs_script_pair_scope_free( + ecs_script_visit_t *v, + ecs_script_pair_scope_t *node) { - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "variable '%s' redeclared", node->name); - return -1; - } - - if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); - if (!type) { - flecs_script_eval_error(v, node, - "unresolved type '%s' for const variable '%s'", - node->type, node->name); - return -1; - } - - const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); - if (!ti) { - flecs_script_eval_error(v, node, - "failed to retrieve type info for '%s' for const variable '%s'", - node->type, node->name); - return -1; - } - - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); - var->value.type = type; - var->type_info = ti; - - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } + flecs_script_scope_free(v, node->scope); +} - if (flecs_script_eval_expr(v, node->expr, &var->value)) { - flecs_script_eval_error(v, node, - "failed to evaluate expression for const variable '%s'", - node->name); - return -1; - } - } else { - /* We don't know the type yet, so we can't create a storage for it yet. - * Run the expression first to deduce the type. */ - ecs_value_t value = {0}; - if (flecs_script_eval_expr(v, node->expr, &value)) { - flecs_script_eval_error(v, node, - "failed to evaluate expression for const variable '%s'", - node->name); - return -1; - } +static +void flecs_script_if_free( + ecs_script_visit_t *v, + ecs_script_if_t *node) +{ + flecs_script_scope_free(v, node->if_true); + flecs_script_scope_free(v, node->if_false); +} - ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); +static +int flecs_script_stmt_free( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + ecs_allocator_t *a = &v->script->allocator; + switch(node->kind) { + case EcsAstScope: + flecs_script_scope_free(v, (ecs_script_scope_t*)node); + break; + case EcsAstWith: + flecs_script_with_free(v, (ecs_script_with_t*)node); + flecs_free_t(a, ecs_script_with_t, node); + break; + case EcsAstTemplate: + flecs_script_template_free(v, (ecs_script_template_node_t*)node); + flecs_free_t(a, ecs_script_template_node_t, node); + break; + case EcsAstEntity: + flecs_script_entity_free(v, (ecs_script_entity_t*)node); + flecs_free_t(a, ecs_script_entity_t, node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); + flecs_free_t(a, ecs_script_pair_scope_t, node); + break; + case EcsAstIf: + flecs_script_if_free(v, (ecs_script_if_t*)node); + flecs_free_t(a, ecs_script_if_t, node); + break; + case EcsAstTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstComponent: + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstDefaultComponent: + flecs_free_t(a, ecs_script_default_component_t, node); + break; + case EcsAstVarComponent: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithVar: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstWithComponent: + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstUsing: + flecs_free_t(a, ecs_script_using_t, node); + break; + case EcsAstModule: + flecs_free_t(a, ecs_script_module_t, node); + break; + case EcsAstAnnotation: + flecs_free_t(a, ecs_script_annot_t, node); + break; + case EcsAstProp: + case EcsAstConst: + flecs_free_t(a, ecs_script_var_node_t, node); + break; + } - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); - var->value.type = value.type; - var->type_info = ti; + return 0; +} - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } +int flecs_script_visit_free( + ecs_script_t *script) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_visit_t v = { + .script = flecs_script_impl(script) + }; - ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); - ecs_value_fini_w_type_info(v->world, ti, value.ptr); - flecs_free(&v->world->allocator, ti->size, value.ptr); + if (ecs_script_visit( + flecs_script_impl(script), &v, flecs_script_stmt_free)) + { + goto error; } return 0; +error: + return - 1; } -static -int flecs_script_eval_pair_scope( - ecs_script_eval_visitor_t *v, - ecs_script_pair_scope_t *node) -{ - ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); - if (!first) { - first = flecs_script_create_entity(v, node->id.first); - if (!first) { - return -1; - } - } +#endif - ecs_entity_t second = flecs_script_create_entity(v, node->id.second); - if (!second) { - return -1; - } +/** + * @file addons/script/visit_to_str.c + * @brief Script AST to string visitor. + */ - ecs_allocator_t *a = v->allocator; - ecs_entity_t prev_first = v->with_relationship; - ecs_entity_t prev_second = 0; - int32_t prev_with_relationship_sp = v->with_relationship_sp; - v->with_relationship = first; +#ifdef FLECS_SCRIPT - if (prev_first != first) { - /* Append new element to with stack */ - ecs_value_t *value = flecs_script_with_append(a, v, NULL); - value->type = ecs_pair(first, second); - value->ptr = NULL; - v->with_relationship_sp = flecs_script_with_count(v) - 1; - } else { - /* Get existing with element for current relationhip stack */ - ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); - ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, - ECS_INTERNAL_ERROR, NULL); - prev_second = ECS_PAIR_SECOND(value->type); - value->type = ecs_pair(first, second); - value->ptr = NULL; - } +typedef struct ecs_script_str_visitor_t { + ecs_script_visit_t base; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_script_str_visitor_t; - if (ecs_script_visit_scope(v, node->scope)) { - return -1; - } +static +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope); - if (prev_second) { - ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); - value->type = ecs_pair(first, prev_second); - } else { - flecs_script_with_set_count(a, v, v->with_relationship_sp); +static +void flecs_scriptbuf_append( + ecs_script_str_visitor_t *v, + const char *fmt, + ...) +{ + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; } - v->with_relationship = prev_first; - v->with_relationship_sp = prev_with_relationship_sp; + va_list args; + va_start(args, fmt); + ecs_strbuf_vappend(v->buf, fmt, args); + va_end(args); - return 0; + if (fmt[strlen(fmt) - 1] == '\n') { + v->newline = true; + } } static -int flecs_script_eval_if( - ecs_script_eval_visitor_t *v, - ecs_script_if_t *node) +void flecs_scriptbuf_appendstr( + ecs_script_str_visitor_t *v, + const char *str) { - ecs_value_t condval = { .type = 0, .ptr = NULL }; - if (flecs_script_eval_expr(v, node->expr, &condval)) { - return -1; - } - - bool cond; - if (condval.type == ecs_id(ecs_bool_t)) { - cond = *(bool*)(condval.ptr); - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor( - v->world, condval.type, condval.ptr); - cond = ecs_meta_get_bool(&cur); + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; } - ecs_value_free(v->world, condval.type, condval.ptr); + ecs_strbuf_appendstr(v->buf, str); - if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { - return -1; + if (str[strlen(str) - 1] == '\n') { + v->newline = true; } - - return 0; } static -int flecs_script_eval_annot( - ecs_script_eval_visitor_t *v, - ecs_script_annot_t *node) +void flecs_script_id_to_str( + ecs_script_str_visitor_t *v, + ecs_script_id_t *id) { - if (!v->base.next) { - flecs_script_eval_error(v, node, - "annotation '%s' is not applied to anything", node->name); - return -1; + if (id->flag) { + if (id->flag == ECS_AUTO_OVERRIDE) { + flecs_scriptbuf_appendstr(v, "auto_override | "); + } else { + flecs_scriptbuf_appendstr(v, "??? | "); + } } - ecs_script_node_kind_t kind = v->base.next->kind; - if (kind != EcsAstEntity && kind != EcsAstAnnotation) { - flecs_script_eval_error(v, node, - "annotation must be applied to an entity"); - return -1; + if (id->second) { + flecs_scriptbuf_append(v, "(%s, %s)", + id->first, id->second); + } else { + flecs_scriptbuf_appendstr(v, id->first); } +} - ecs_allocator_t *a = v->allocator; - ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; - - return 0; +static +void flecs_script_expr_to_str( + ecs_script_str_visitor_t *v, + const char *expr) +{ + if (expr) { + flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); + } else { + flecs_scriptbuf_appendstr(v, "{}"); + } } -int flecs_script_eval_node( - ecs_script_eval_visitor_t *v, +static +const char* flecs_script_node_to_str( ecs_script_node_t *node) { switch(node->kind) { - case EcsAstScope: - return flecs_script_eval_scope( - v, (ecs_script_scope_t*)node); - case EcsAstTag: - return flecs_script_eval_tag( - v, (ecs_script_tag_t*)node); - case EcsAstComponent: - return flecs_script_eval_component( - v, (ecs_script_component_t*)node); - case EcsAstVarComponent: - return flecs_script_eval_var_component( - v, (ecs_script_var_component_t*)node); - case EcsAstDefaultComponent: - return flecs_script_eval_default_component( - v, (ecs_script_default_component_t*)node); - case EcsAstWithVar: - return flecs_script_eval_with_var( - v, (ecs_script_var_node_t*)node); + case EcsAstScope: return "scope"; case EcsAstWithTag: - return flecs_script_eval_with_tag( - v, (ecs_script_tag_t*)node); + case EcsAstTag: return "tag"; case EcsAstWithComponent: - return flecs_script_eval_with_component( - v, (ecs_script_component_t*)node); - case EcsAstWith: - return flecs_script_eval_with( - v, (ecs_script_with_t*)node); - case EcsAstUsing: - return flecs_script_eval_using( - v, (ecs_script_using_t*)node); - case EcsAstModule: - return flecs_script_eval_module( - v, (ecs_script_module_t*)node); - case EcsAstAnnotation: - return flecs_script_eval_annot( - v, (ecs_script_annot_t*)node); - case EcsAstTemplate: - return flecs_script_eval_template( - v, (ecs_script_template_node_t*)node); - case EcsAstProp: - return 0; - case EcsAstConst: - return flecs_script_eval_const( - v, (ecs_script_var_node_t*)node); - case EcsAstEntity: - return flecs_script_eval_entity( - v, (ecs_script_entity_t*)node); - case EcsAstPairScope: - return flecs_script_eval_pair_scope( - v, (ecs_script_pair_scope_t*)node); - case EcsAstIf: - return flecs_script_eval_if( - v, (ecs_script_if_t*)node); + case EcsAstComponent: return "component"; + case EcsAstWithVar: + case EcsAstVarComponent: return "var"; + case EcsAstDefaultComponent: return "default_component"; + case EcsAstWith: return "with"; + case EcsAstUsing: return "using"; + case EcsAstModule: return "module"; + case EcsAstAnnotation: return "annot"; + case EcsAstTemplate: return "template"; + case EcsAstProp: return "prop"; + case EcsAstConst: return "const"; + case EcsAstEntity: return "entity"; + case EcsAstPairScope: return "pair_scope"; + case EcsAstIf: return "if"; } + return "???"; +} - ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +static +void flecs_scriptbuf_node( + ecs_script_str_visitor_t *v, + ecs_script_node_t *node) +{ + flecs_scriptbuf_append(v, "%s%s%s: ", + ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); } -void flecs_script_eval_visit_init( - ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v) +static +void flecs_script_tag_to_str( + ecs_script_str_visitor_t *v, + ecs_script_tag_t *node) { - *v = (ecs_script_eval_visitor_t){ - .base = { - .script = script, - .visit = (ecs_visit_action_t)flecs_script_eval_node - }, - .world = script->pub.world, - .allocator = &script->allocator - }; - - flecs_stack_init(&v->stack); - ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); - ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); - ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); - ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); - - /* Always include flecs.meta */ - ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = - ecs_lookup(v->world, "flecs.meta"); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, "\n"); } -void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v) +static +void flecs_script_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_component_t *node) { - ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); - ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); - ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); - ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); - flecs_stack_fini(&v->stack); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + if (node->expr) { + flecs_scriptbuf_appendstr(v, ": "); + flecs_script_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); } -int ecs_script_eval( - ecs_script_t *script) +static +void flecs_script_default_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_default_component_t *node) { - ecs_script_eval_visitor_t v; - ecs_script_impl_t *impl = flecs_script_impl(script); - flecs_script_eval_visit_init(impl, &v); - int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v); - return result; + flecs_scriptbuf_node(v, &node->node); + if (node->expr) { + flecs_script_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); } -#endif - -/** - * @file addons/script/visit_free.c - * @brief Script free visitor (frees AST resources). - */ +static +void flecs_script_with_var_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_component_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_scriptbuf_appendstr(v, "\n"); +} +static +void flecs_script_with_to_str( + ecs_script_str_visitor_t *v, + ecs_script_with_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + + flecs_scriptbuf_appendstr(v, "{\n"); + v->depth ++; + flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->expressions); + flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} -#ifdef FLECS_SCRIPT +static +void flecs_script_using_to_str( + ecs_script_str_visitor_t *v, + ecs_script_using_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); +} static -void flecs_script_scope_free( - ecs_script_visit_t *v, - ecs_script_scope_t *node) +void flecs_script_module_to_str( + ecs_script_str_visitor_t *v, + ecs_script_module_t *node) { - ecs_script_visit_scope(v, node); - ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); - flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); } static -void flecs_script_with_free( - ecs_script_visit_t *v, - ecs_script_with_t *node) +void flecs_script_annot_to_str( + ecs_script_str_visitor_t *v, + ecs_script_annot_t *node) { - flecs_script_scope_free(v, node->expressions); - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, + ECS_GREEN, node->expr, ECS_NORMAL); + flecs_scriptbuf_appendstr(v, "\n"); } static -void flecs_script_template_free( - ecs_script_visit_t *v, +void flecs_script_template_to_str( + ecs_script_str_visitor_t *v, ecs_script_template_node_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_script_scope_to_str(v, node->scope); } static -void flecs_script_entity_free( - ecs_script_visit_t *v, +void flecs_script_var_node_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_node_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + if (node->type) { + flecs_scriptbuf_append(v, "%s : %s = ", + node->name, + node->type); + } else { + flecs_scriptbuf_append(v, "%s = ", + node->name); + } + flecs_script_expr_to_str(v, node->expr); + flecs_scriptbuf_appendstr(v, "\n"); +} + +static +void flecs_script_entity_to_str( + ecs_script_str_visitor_t *v, ecs_script_entity_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + if (node->kind) { + flecs_scriptbuf_append(v, "%s ", node->kind); + } + if (node->name) { + flecs_scriptbuf_append(v, "%s ", node->name); + } else { + flecs_scriptbuf_appendstr(v, " "); + } + + if (!flecs_scope_is_empty(node->scope)) { + flecs_script_scope_to_str(v, node->scope); + } else { + flecs_scriptbuf_appendstr(v, "\n"); + } } static -void flecs_script_pair_scope_free( - ecs_script_visit_t *v, +void flecs_script_pair_scope_to_str( + ecs_script_str_visitor_t *v, ecs_script_pair_scope_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, " "); + flecs_script_scope_to_str(v, node->scope); } static -void flecs_script_if_free( - ecs_script_visit_t *v, +void flecs_script_if_to_str( + ecs_script_str_visitor_t *v, ecs_script_if_t *node) { - flecs_script_scope_free(v, node->if_true); - flecs_script_scope_free(v, node->if_false); + flecs_scriptbuf_node(v, &node->node); + flecs_script_expr_to_str(v, node->expr); + + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_true); + flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_false); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); } static -int flecs_script_stmt_free( - ecs_script_visit_t *v, +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope) +{ + if (!ecs_vec_count(&scope->stmts)) { + flecs_scriptbuf_appendstr(v, "{}\n"); + return 0; + } + + flecs_scriptbuf_appendstr(v, "{\n"); + + v->depth ++; + + if (ecs_script_visit_scope(v, scope)) { + return -1; + } + + v->depth --; + + flecs_scriptbuf_appendstr(v, "}\n"); + + return 0; +} + +static +int flecs_script_stmt_to_str( + ecs_script_str_visitor_t *v, ecs_script_node_t *node) { - ecs_allocator_t *a = &v->script->allocator; switch(node->kind) { case EcsAstScope: - flecs_script_scope_free(v, (ecs_script_scope_t*)node); - break; - case EcsAstWith: - flecs_script_with_free(v, (ecs_script_with_t*)node); - flecs_free_t(a, ecs_script_with_t, node); - break; - case EcsAstTemplate: - flecs_script_template_free(v, (ecs_script_template_node_t*)node); - flecs_free_t(a, ecs_script_template_node_t, node); - break; - case EcsAstEntity: - flecs_script_entity_free(v, (ecs_script_entity_t*)node); - flecs_free_t(a, ecs_script_entity_t, node); - break; - case EcsAstPairScope: - flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); - flecs_free_t(a, ecs_script_pair_scope_t, node); - break; - case EcsAstIf: - flecs_script_if_free(v, (ecs_script_if_t*)node); - flecs_free_t(a, ecs_script_if_t, node); + if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { + return -1; + } break; case EcsAstTag: - flecs_free_t(a, ecs_script_tag_t, node); + case EcsAstWithTag: + flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); break; case EcsAstComponent: - flecs_free_t(a, ecs_script_component_t, node); - break; - case EcsAstDefaultComponent: - flecs_free_t(a, ecs_script_default_component_t, node); + case EcsAstWithComponent: + flecs_script_component_to_str(v, (ecs_script_component_t*)node); break; case EcsAstVarComponent: - flecs_free_t(a, ecs_script_var_component_t, node); - break; case EcsAstWithVar: - flecs_free_t(a, ecs_script_var_component_t, node); + flecs_script_with_var_to_str(v, + (ecs_script_var_component_t*)node); break; - case EcsAstWithTag: - flecs_free_t(a, ecs_script_tag_t, node); + case EcsAstDefaultComponent: + flecs_script_default_component_to_str(v, + (ecs_script_default_component_t*)node); break; - case EcsAstWithComponent: - flecs_free_t(a, ecs_script_component_t, node); + case EcsAstWith: + flecs_script_with_to_str(v, (ecs_script_with_t*)node); break; case EcsAstUsing: - flecs_free_t(a, ecs_script_using_t, node); + flecs_script_using_to_str(v, (ecs_script_using_t*)node); break; case EcsAstModule: - flecs_free_t(a, ecs_script_module_t, node); + flecs_script_module_to_str(v, (ecs_script_module_t*)node); break; case EcsAstAnnotation: - flecs_free_t(a, ecs_script_annot_t, node); + flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); + break; + case EcsAstTemplate: + flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); break; - case EcsAstProp: case EcsAstConst: - flecs_free_t(a, ecs_script_var_node_t, node); + case EcsAstProp: + flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); + break; + case EcsAstEntity: + flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); + break; + case EcsAstIf: + flecs_script_if_to_str(v, (ecs_script_if_t*)node); break; } return 0; } -int flecs_script_visit_free( - ecs_script_t *script) +int ecs_script_ast_to_buf( + ecs_script_t *script, + ecs_strbuf_t *buf) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_visit_t v = { - .script = flecs_script_impl(script) - }; - - if (ecs_script_visit( - flecs_script_impl(script), &v, flecs_script_stmt_free)) - { + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = buf }; + if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { goto error; } return 0; error: + ecs_strbuf_reset(buf); return - 1; } +char* ecs_script_ast_to_str( + ecs_script_t *script) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + if (ecs_script_ast_to_buf(script, &buf)) { + goto error; + } + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + #endif /** - * @file addons/script/visit_to_str.c - * @brief Script AST to string visitor. + * @file addons/monitor.c + * @brief Stats addon module. */ +/** + * @file addons/stats/stats.h + * @brief Internal functions/types for stats addon. + */ -#ifdef FLECS_SCRIPT +#ifndef FLECS_STATS_PRIVATE_H +#define FLECS_STATS_PRIVATE_H -typedef struct ecs_script_str_visitor_t { - ecs_script_visit_t base; - ecs_strbuf_t *buf; - int32_t depth; - bool newline; -} ecs_script_str_visitor_t; -static -int flecs_script_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_scope_t *scope); +typedef struct { + /* Statistics API interface */ + void (*copy_last)(void *stats, void *src); + void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); + void (*reduce)(void *stats, void *src); + void (*reduce_last)(void *stats, void *last, int32_t reduce_count); + void (*repeat_last)(void* stats); + void (*set_t)(void *stats, int32_t t); + void (*fini)(void *stats); -static -void flecs_scriptbuf_append( - ecs_script_str_visitor_t *v, - const char *fmt, - ...) -{ - if (v->newline) { - ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); - v->newline = false; - } + /* Size of statistics type */ + ecs_size_t stats_size; - va_list args; - va_start(args, fmt); - ecs_strbuf_vappend(v->buf, fmt, args); - va_end(args); + /* Id of component that contains the statistics */ + ecs_entity_t monitor_component_id; - if (fmt[strlen(fmt) - 1] == '\n') { - v->newline = true; - } -} + /* Id of component used to query for monitored resources (optional) */ + ecs_id_t query_component_id; +} ecs_stats_api_t; + +void flecs_stats_api_import( + ecs_world_t *world, + ecs_stats_api_t *api); + +void FlecsWorldSummaryImport( + ecs_world_t *world); + +void FlecsWorldMonitorImport( + ecs_world_t *world); + +void FlecsSystemMonitorImport( + ecs_world_t *world); + +void FlecsPipelineMonitorImport( + ecs_world_t *world); + +#endif + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(FlecsStats); + +ecs_entity_t EcsPeriod1s = 0; +ecs_entity_t EcsPeriod1m = 0; +ecs_entity_t EcsPeriod1h = 0; +ecs_entity_t EcsPeriod1d = 0; +ecs_entity_t EcsPeriod1w = 0; + +#define FlecsDayIntervalCount (24) +#define FlecsWeekIntervalCount (168) + +typedef struct { + ecs_stats_api_t api; + ecs_query_t *query; +} ecs_monitor_stats_ctx_t; + +typedef struct { + ecs_stats_api_t api; +} ecs_reduce_stats_ctx_t; + +typedef struct { + ecs_stats_api_t api; + int32_t interval; +} ecs_aggregate_stats_ctx_t; static -void flecs_scriptbuf_appendstr( - ecs_script_str_visitor_t *v, - const char *str) -{ - if (v->newline) { - ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); - v->newline = false; +void MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + ecs_monitor_stats_ctx_t *ctx = it->ctx; + + EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 0); + + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(hdr->elapsed * 60); + int32_t i, dif = t_next - t_last; + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + if (!dif) { + hdr->reduce_count ++; } - ecs_strbuf_appendstr(v->buf, str); + ecs_iter_t qit; + int32_t cur = -1, count = 0; + void *stats = NULL; + ecs_map_t *stats_map = NULL; - if (str[strlen(str) - 1] == '\n') { - v->newline = true; + if (ctx->query) { + /* Query results are stored in a map */ + qit = ecs_query_iter(it->world, ctx->query); + stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); + } else { + /* No query, so tracking stats for single element */ + stats = ECS_OFFSET_T(hdr, EcsStatsHeader); } -} -static -void flecs_script_id_to_str( - ecs_script_str_visitor_t *v, - ecs_script_id_t *id) -{ - if (id->flag) { - if (id->flag == ECS_AUTO_OVERRIDE) { - flecs_scriptbuf_appendstr(v, "auto_override | "); - } else { - flecs_scriptbuf_appendstr(v, "??? | "); + do { + ecs_entity_t res = 0; + if (ctx->query) { + /* Query, fetch resource entity & stats pointer */ + if (cur == (count - 1)) { + if (!ecs_query_next(&qit)) { + break; + } + + cur = 0; + count = qit.count; + if (!count) { + cur = -1; + continue; + } + } else { + cur ++; + } + + res = qit.entities[cur]; + stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); + ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); + } + + if (!dif) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, stats); + } + + ctx->api.get(world, res, stats); + + if (!dif) { + /* Still in same interval, combine with last measurement */ + ctx->api.reduce_last(stats, last, hdr->reduce_count); + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + ctx->api.repeat_last(stats); + } + } + + if (last && ctx->api.fini) { + ctx->api.fini(last); } - } - if (id->second) { - flecs_scriptbuf_append(v, "(%s, %s)", - id->first, id->second); - } else { - flecs_scriptbuf_appendstr(v, id->first); - } -} + if (!ctx->query) { + break; + } + } while (true); -static -void flecs_script_expr_to_str( - ecs_script_str_visitor_t *v, - const char *expr) -{ - if (expr) { - flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); - } else { - flecs_scriptbuf_appendstr(v, "{}"); + if (dif > 1) { + hdr->reduce_count = 0; } } static -const char* flecs_script_node_to_str( - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstScope: return "scope"; - case EcsAstWithTag: - case EcsAstTag: return "tag"; - case EcsAstWithComponent: - case EcsAstComponent: return "component"; - case EcsAstWithVar: - case EcsAstVarComponent: return "var"; - case EcsAstDefaultComponent: return "default_component"; - case EcsAstWith: return "with"; - case EcsAstUsing: return "using"; - case EcsAstModule: return "module"; - case EcsAstAnnotation: return "annot"; - case EcsAstTemplate: return "template"; - case EcsAstProp: return "prop"; - case EcsAstConst: return "const"; - case EcsAstEntity: return "entity"; - case EcsAstPairScope: return "pair_scope"; - case EcsAstIf: return "if"; +void ReduceStats(ecs_iter_t *it) { + ecs_reduce_stats_ctx_t *ctx = it->ctx; + + void *dst = ecs_field_w_size(it, 0, 0); + void *src = ecs_field_w_size(it, 0, 1); + + dst = ECS_OFFSET_T(dst, EcsStatsHeader); + src = ECS_OFFSET_T(src, EcsStatsHeader); + + if (!ctx->api.query_component_id) { + ctx->api.reduce(dst, src); + } else { + ecs_map_iter_t mit = ecs_map_iter(src); + while (ecs_map_next(&mit)) { + void *src_el = ecs_map_ptr(&mit); + void *dst_el = ecs_map_ensure_alloc( + dst, ctx->api.stats_size, ecs_map_key(&mit)); + ctx->api.reduce(dst_el, src_el); + } } - return "???"; } static -void flecs_scriptbuf_node( - ecs_script_str_visitor_t *v, - ecs_script_node_t *node) -{ - flecs_scriptbuf_append(v, "%s%s%s: ", - ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); -} +void AggregateStats(ecs_iter_t *it) { + ecs_aggregate_stats_ctx_t *ctx = it->ctx; + int32_t interval = ctx->interval; -static -void flecs_script_tag_to_str( - ecs_script_str_visitor_t *v, - ecs_script_tag_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - flecs_scriptbuf_appendstr(v, "\n"); -} + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 1); -static -void flecs_script_component_to_str( - ecs_script_str_visitor_t *v, - ecs_script_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - if (node->expr) { - flecs_scriptbuf_appendstr(v, ": "); - flecs_script_expr_to_str(v, node->expr); + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); + void *dst_map = NULL; + void *src_map = NULL; + if (ctx->api.query_component_id) { + dst_map = dst; + src_map = src; + dst = NULL; + src = NULL; } - flecs_scriptbuf_appendstr(v, "\n"); -} -static -void flecs_script_default_component_to_str( - ecs_script_str_visitor_t *v, - ecs_script_default_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->expr) { - flecs_script_expr_to_str(v, node->expr); + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + ecs_map_iter_t mit; + if (src_map) { + mit = ecs_map_iter(src_map); } - flecs_scriptbuf_appendstr(v, "\n"); -} -static -void flecs_script_with_var_to_str( - ecs_script_str_visitor_t *v, - ecs_script_var_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s ", node->name); - flecs_scriptbuf_appendstr(v, "\n"); -} + do { + if (src_map) { + if (!ecs_map_next(&mit)) { + break; + } -static -void flecs_script_with_to_str( - ecs_script_str_visitor_t *v, - ecs_script_with_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - - flecs_scriptbuf_appendstr(v, "{\n"); - v->depth ++; - flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->expressions); - flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->scope); - v->depth --; - flecs_scriptbuf_appendstr(v, "}\n"); -} + src = ecs_map_ptr(&mit); + dst = ecs_map_ensure_alloc( + dst_map, ctx->api.stats_size, ecs_map_key(&mit)); + } -static -void flecs_script_using_to_str( - ecs_script_str_visitor_t *v, - ecs_script_using_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s\n", node->name); -} + if (dst_hdr->reduce_count != 0) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, dst); + } -static -void flecs_script_module_to_str( - ecs_script_str_visitor_t *v, - ecs_script_module_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s\n", node->name); -} + /* Reduce from minutes to the current day */ + ctx->api.reduce(dst, src); -static -void flecs_script_annot_to_str( - ecs_script_str_visitor_t *v, - ecs_script_annot_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, - ECS_GREEN, node->expr, ECS_NORMAL); - flecs_scriptbuf_appendstr(v, "\n"); -} + if (dst_hdr->reduce_count != 0) { + ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); + } -static -void flecs_script_template_to_str( - ecs_script_str_visitor_t *v, - ecs_script_template_node_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s ", node->name); - flecs_script_scope_to_str(v, node->scope); -} + if (last && ctx->api.fini != NULL) { + ctx->api.fini(last); + } -static -void flecs_script_var_node_to_str( - ecs_script_str_visitor_t *v, - ecs_script_var_node_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->type) { - flecs_scriptbuf_append(v, "%s : %s = ", - node->name, - node->type); - } else { - flecs_scriptbuf_append(v, "%s = ", - node->name); - } - flecs_script_expr_to_str(v, node->expr); - flecs_scriptbuf_appendstr(v, "\n"); -} + if (!src_map) { + break; + } + } while (true); -static -void flecs_script_entity_to_str( - ecs_script_str_visitor_t *v, - ecs_script_entity_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->kind) { - flecs_scriptbuf_append(v, "%s ", node->kind); - } - if (node->name) { - flecs_scriptbuf_append(v, "%s ", node->name); - } else { - flecs_scriptbuf_appendstr(v, " "); + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 0; } +} - if (!flecs_scope_is_empty(node->scope)) { - flecs_script_scope_to_str(v, node->scope); - } else { - flecs_scriptbuf_appendstr(v, "\n"); +static +void flecs_monitor_ctx_free( + void *ptr) +{ + ecs_monitor_stats_ctx_t *ctx = ptr; + if (ctx->query) { + ecs_query_fini(ctx->query); } + ecs_os_free(ctx); } static -void flecs_script_pair_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_pair_scope_t *node) +void flecs_reduce_ctx_free( + void *ptr) { - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - flecs_scriptbuf_appendstr(v, " "); - flecs_script_scope_to_str(v, node->scope); + ecs_os_free(ptr); } static -void flecs_script_if_to_str( - ecs_script_str_visitor_t *v, - ecs_script_if_t *node) +void flecs_aggregate_ctx_free( + void *ptr) { - flecs_scriptbuf_node(v, &node->node); - flecs_script_expr_to_str(v, node->expr); - - flecs_scriptbuf_appendstr(v, " {\n"); - v->depth ++; - flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->if_true); - flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->if_false); - v->depth --; - flecs_scriptbuf_appendstr(v, "}\n"); + ecs_os_free(ptr); } -static -int flecs_script_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_scope_t *scope) +void flecs_stats_api_import( + ecs_world_t *world, + ecs_stats_api_t *api) { - if (!ecs_vec_count(&scope->stmts)) { - flecs_scriptbuf_appendstr(v, "{}\n"); - return 0; + ecs_entity_t kind = api->monitor_component_id; + ecs_entity_t prev = ecs_set_scope(world, kind); + + ecs_query_t *q = NULL; + if (api->query_component_id) { + q = ecs_query(world, { + .terms = {{ .id = api->query_component_id }}, + .cache_kind = EcsQueryCacheNone, + .flags = EcsQueryMatchDisabled + }); } - flecs_scriptbuf_appendstr(v, "{\n"); + // Called each frame, collects 60 measurements per second + { + ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); + ctx->api = *api; + ctx->query = q; - v->depth ++; + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = MonitorStats, + .ctx = ctx, + .ctx_free = flecs_monitor_ctx_free + }); + } - if (ecs_script_visit_scope(v, scope)) { - return -1; + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m; + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; + + mw1m = ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .interval = 1.0, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); } - v->depth --; + // Called each minute, reduces into 60 measurements per hour + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; - flecs_scriptbuf_appendstr(v, "}\n"); + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); + } - return 0; -} + // Called each minute, reduces into 60 measurements per day + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsDayIntervalCount; -static -int flecs_script_stmt_to_str( - ecs_script_str_visitor_t *v, - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstScope: - if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { - return -1; - } - break; - case EcsAstTag: - case EcsAstWithTag: - flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); - break; - case EcsAstComponent: - case EcsAstWithComponent: - flecs_script_component_to_str(v, (ecs_script_component_t*)node); - break; - case EcsAstVarComponent: - case EcsAstWithVar: - flecs_script_with_var_to_str(v, - (ecs_script_var_component_t*)node); - break; - case EcsAstDefaultComponent: - flecs_script_default_component_to_str(v, - (ecs_script_default_component_t*)node); - break; - case EcsAstWith: - flecs_script_with_to_str(v, (ecs_script_with_t*)node); - break; - case EcsAstUsing: - flecs_script_using_to_str(v, (ecs_script_using_t*)node); - break; - case EcsAstModule: - flecs_script_module_to_str(v, (ecs_script_module_t*)node); - break; - case EcsAstAnnotation: - flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); - break; - case EcsAstTemplate: - flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); - break; - case EcsAstConst: - case EcsAstProp: - flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); - break; - case EcsAstEntity: - flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); - break; - case EcsAstPairScope: - flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); - break; - case EcsAstIf: - flecs_script_if_to_str(v, (ecs_script_if_t*)node); - break; + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1d), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); } - return 0; + // Called each hour, reduces into 60 measurements per week + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsWeekIntervalCount; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1w), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); + } + + ecs_set_scope(world, prev); + + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); } -int ecs_script_ast_to_buf( - ecs_script_t *script, - ecs_strbuf_t *buf) +void FlecsStatsImport( + ecs_world_t *world) { - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = buf }; - if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { - goto error; - } + ECS_MODULE_DEFINE(world, FlecsStats); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_UNITS + ECS_IMPORT(world, FlecsUnits); +#endif +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsStats), + "Module that automatically monitors statistics for the world & systems"); +#endif - return 0; -error: - ecs_strbuf_reset(buf); - return - 1; -} + ecs_set_name_prefix(world, "Ecs"); -char* ecs_script_ast_to_str( - ecs_script_t *script) -{ - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (ecs_script_ast_to_buf(script, &buf)) { - goto error; - } + EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); + EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); + EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); + EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); + EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); - return ecs_strbuf_get(&buf); -error: - return NULL; + FlecsWorldSummaryImport(world); + FlecsWorldMonitorImport(world); + FlecsSystemMonitorImport(world); + FlecsPipelineMonitorImport(world); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); + } } #endif /** - * @file addons/monitor.c - * @brief Stats addon module. - */ - -/** - * @file addons/stats/stats.h - * @brief Internal functions/types for stats addon. + * @file addons/stats/pipeline_monitor.c + * @brief Stats addon pipeline monitor */ -#ifndef FLECS_STATS_PRIVATE_H -#define FLECS_STATS_PRIVATE_H - - -typedef struct { - /* Statistics API interface */ - void (*copy_last)(void *stats, void *src); - void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); - void (*reduce)(void *stats, void *src); - void (*reduce_last)(void *stats, void *last, int32_t reduce_count); - void (*repeat_last)(void* stats); - void (*set_t)(void *stats, int32_t t); - void (*fini)(void *stats); - - /* Size of statistics type */ - ecs_size_t stats_size; - - /* Id of component that contains the statistics */ - ecs_entity_t monitor_component_id; - - /* Id of component used to query for monitored resources (optional) */ - ecs_id_t query_component_id; -} ecs_stats_api_t; -void flecs_stats_api_import( - ecs_world_t *world, - ecs_stats_api_t *api); +#ifdef FLECS_STATS -void FlecsWorldSummaryImport( - ecs_world_t *world); +ECS_COMPONENT_DECLARE(EcsPipelineStats); -void FlecsWorldMonitorImport( - ecs_world_t *world); +static +void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); + ecs_pipeline_stats_fini(stats); + ecs_os_free(stats); + } + ecs_map_fini(&ptr->stats); +} -void FlecsSystemMonitorImport( - ecs_world_t *world); +static ECS_CTOR(EcsPipelineStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) -void FlecsPipelineMonitorImport( - ecs_world_t *world); +static ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) -#endif +static ECS_MOVE(EcsPipelineStats, dst, src, { + flecs_pipeline_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) +static ECS_DTOR(EcsPipelineStats, ptr, { + flecs_pipeline_monitor_dtor(ptr); +}) -#ifdef FLECS_STATS +static +void flecs_pipeline_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_pipeline_stats_t*)stats)->t = t; +} -ECS_COMPONENT_DECLARE(FlecsStats); -ecs_entity_t EcsPeriod1s = 0; -ecs_entity_t EcsPeriod1m = 0; -ecs_entity_t EcsPeriod1h = 0; -ecs_entity_t EcsPeriod1d = 0; -ecs_entity_t EcsPeriod1w = 0; +static +void flecs_pipeline_stats_copy_last( + void *stats, + void *src) +{ + ecs_pipeline_stats_copy_last(stats, src); +} -#define FlecsDayIntervalCount (24) -#define FlecsWeekIntervalCount (168) +static +void flecs_pipeline_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) +{ + ecs_pipeline_stats_get(world, res, stats); +} -typedef struct { - ecs_stats_api_t api; - ecs_query_t *query; -} ecs_monitor_stats_ctx_t; +static +void flecs_pipeline_stats_reduce( + void *stats, + void *src) +{ + ecs_pipeline_stats_reduce(stats, src); +} -typedef struct { - ecs_stats_api_t api; -} ecs_reduce_stats_ctx_t; +static +void flecs_pipeline_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) +{ + ecs_pipeline_stats_reduce_last(stats, last, reduce_count); +} -typedef struct { - ecs_stats_api_t api; - int32_t interval; -} ecs_aggregate_stats_ctx_t; +static +void flecs_pipeline_stats_repeat_last( + void* stats) +{ + ecs_pipeline_stats_repeat_last(stats); +} static -void MonitorStats(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; - ecs_monitor_stats_ctx_t *ctx = it->ctx; +void flecs_pipeline_stats_fini( + void *stats) +{ + ecs_pipeline_stats_fini(stats); +} - EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 0); +void FlecsPipelineMonitorImport( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); - ecs_ftime_t elapsed = hdr->elapsed; - hdr->elapsed += it->delta_time; + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_ctor(EcsPipelineStats), + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); - int32_t t_last = (int32_t)(elapsed * 60); - int32_t t_next = (int32_t)(hdr->elapsed * 60); - int32_t i, dif = t_next - t_last; - void *stats_storage = ecs_os_alloca(ctx->api.stats_size); - void *last = NULL; + ecs_stats_api_t api = { + .copy_last = flecs_pipeline_stats_copy_last, + .get = flecs_pipeline_stats_get, + .reduce = flecs_pipeline_stats_reduce, + .reduce_last = flecs_pipeline_stats_reduce_last, + .repeat_last = flecs_pipeline_stats_repeat_last, + .set_t = flecs_pipeline_stats_set_t, + .fini = flecs_pipeline_stats_fini, + .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), + .monitor_component_id = ecs_id(EcsPipelineStats), + .query_component_id = ecs_id(EcsPipeline) + }; - if (!dif) { - hdr->reduce_count ++; - } + flecs_stats_api_import(world, &api); +} - ecs_iter_t qit; - int32_t cur = -1, count = 0; - void *stats = NULL; - ecs_map_t *stats_map = NULL; +#endif - if (ctx->query) { - /* Query results are stored in a map */ - qit = ecs_query_iter(it->world, ctx->query); - stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); - } else { - /* No query, so tracking stats for single element */ - stats = ECS_OFFSET_T(hdr, EcsStatsHeader); - } +/** + * @file addons/stats.c + * @brief Stats addon. + */ - do { - ecs_entity_t res = 0; - if (ctx->query) { - /* Query, fetch resource entity & stats pointer */ - if (cur == (count - 1)) { - if (!ecs_query_next(&qit)) { - break; - } - cur = 0; - count = qit.count; - if (!count) { - cur = -1; - continue; - } - } else { - cur ++; - } - res = qit.entities[cur]; - stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); - ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); - } +#ifdef FLECS_STATS - if (!dif) { - /* Copy last value so we can pass it to reduce_last */ - last = stats_storage; - ecs_os_memset(last, 0, ctx->api.stats_size); - ctx->api.copy_last(last, stats); - } +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (ecs_float_t)(value)) - ctx->api.get(world, res, stats); +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (double)(value)) - if (!dif) { - /* Still in same interval, combine with last measurement */ - ctx->api.reduce_last(stats, last, hdr->reduce_count); - } else if (dif > 1) { - /* More than 16ms has passed, backfill */ - for (i = 1; i < dif; i ++) { - ctx->api.repeat_last(stats); - } - } +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) - if (last && ctx->api.fini) { - ctx->api.fini(last); - } +#define ECS_METRIC_LAST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) - if (!ctx->query) { - break; - } - } while (true); +static +int32_t t_next( + int32_t t) +{ + return (t + 1) % ECS_STAT_WINDOW; +} - if (dif > 1) { - hdr->reduce_count = 0; - } +static +int32_t t_prev( + int32_t t) +{ + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static -void ReduceStats(ecs_iter_t *it) { - ecs_reduce_stats_ctx_t *ctx = it->ctx; +void flecs_gauge_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) +{ + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; +} - void *dst = ecs_field_w_size(it, 0, 0); - void *src = ecs_field_w_size(it, 0, 1); +static +double flecs_counter_record( + ecs_metric_t *m, + int32_t t, + double value) +{ + int32_t tp = t_prev(t); + double prev = m->counter.value[tp]; + m->counter.value[t] = value; + double gauge_value = value - prev; + if (gauge_value < 0) { + gauge_value = 0; /* Counters are monotonically increasing */ + } + flecs_gauge_record(m, t, (ecs_float_t)gauge_value); + return gauge_value; +} - dst = ECS_OFFSET_T(dst, EcsStatsHeader); - src = ECS_OFFSET_T(src, EcsStatsHeader); +static +void flecs_metric_print( + const char *name, + ecs_float_t value) +{ + ecs_size_t len = ecs_os_strlen(name); + ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); +} - if (!ctx->api.query_component_id) { - ctx->api.reduce(dst, src); - } else { - ecs_map_iter_t mit = ecs_map_iter(src); - while (ecs_map_next(&mit)) { - void *src_el = ecs_map_ptr(&mit); - void *dst_el = ecs_map_ensure_alloc( - dst, ctx->api.stats_size, ecs_map_key(&mit)); - ctx->api.reduce(dst_el, src_el); - } - } +static +void flecs_gauge_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->gauge.avg[t]); } static -void AggregateStats(ecs_iter_t *it) { - ecs_aggregate_stats_ctx_t *ctx = it->ctx; - int32_t interval = ctx->interval; +void flecs_counter_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->counter.rate.avg[t]); +} - EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); - EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 1); +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src) +{ + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); - void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); - void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); - void *dst_map = NULL; - void *src_map = NULL; - if (ctx->api.query_component_id) { - dst_map = dst; - src_map = src; - dst = NULL; - src = NULL; + bool min_set = false; + dst->gauge.avg[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.max[t_dst] = 0; + + ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; + + if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { + dst->gauge.min[t_dst] = src->gauge.min[t]; + min_set = true; + } + if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { + dst->gauge.max[t_dst] = src->gauge.max[t]; + } } - void *stats_storage = ecs_os_alloca(ctx->api.stats_size); - void *last = NULL; + dst->counter.value[t_dst] = src->counter.value[t_src]; - ecs_map_iter_t mit; - if (src_map) { - mit = ecs_map_iter(src_map); +error: + return; +} + +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t prev, + int32_t count) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = t_next(prev); + + if (m->gauge.min[t] < m->gauge.min[prev]) { + m->gauge.min[prev] = m->gauge.min[t]; } - do { - if (src_map) { - if (!ecs_map_next(&mit)) { - break; - } + if (m->gauge.max[t] > m->gauge.max[prev]) { + m->gauge.max[prev] = m->gauge.max[t]; + } - src = ecs_map_ptr(&mit); - dst = ecs_map_ensure_alloc( - dst_map, ctx->api.stats_size, ecs_map_key(&mit)); - } + ecs_float_t fcount = (ecs_float_t)(count + 1); + ecs_float_t cur = m->gauge.avg[prev]; + ecs_float_t next = m->gauge.avg[t]; - if (dst_hdr->reduce_count != 0) { - /* Copy last value so we can pass it to reduce_last */ - last = stats_storage; - ecs_os_memset(last, 0, ctx->api.stats_size); - ctx->api.copy_last(last, dst); - } + cur *= ((fcount - 1) / fcount); + next *= 1 / fcount; - /* Reduce from minutes to the current day */ - ctx->api.reduce(dst, src); + m->gauge.avg[prev] = cur + next; + m->counter.value[prev] = m->counter.value[t]; - if (dst_hdr->reduce_count != 0) { - ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); - } +error: + return; +} - if (last && ctx->api.fini != NULL) { - ctx->api.fini(last); - } +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - if (!src_map) { - break; - } - } while (true); + m->gauge.avg[dst] = m->gauge.avg[src]; + m->gauge.min[dst] = m->gauge.min[src]; + m->gauge.max[dst] = m->gauge.max[src]; + m->counter.value[dst] = m->counter.value[src]; - /* A day has 60 24 minute intervals */ - dst_hdr->reduce_count ++; - if (dst_hdr->reduce_count >= interval) { - dst_hdr->reduce_count = 0; - } +error: + return; } static -void flecs_monitor_ctx_free( - void *ptr) +void flecs_stats_reduce( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) { - ecs_monitor_stats_ctx_t *ctx = ptr; - if (ctx->query) { - ecs_query_fini(ctx->query); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } - ecs_os_free(ctx); } static -void flecs_reduce_ctx_free( - void *ptr) +void flecs_stats_reduce_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src, + int32_t count) { - ecs_os_free(ptr); + int32_t t_dst_next = t_next(t_dst); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + /* Reduce into previous value */ + ecs_metric_reduce_last(dst_cur, t_dst, count); + + /* Restore old value */ + dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; + } } static -void flecs_aggregate_ctx_free( - void *ptr) +void flecs_stats_repeat_last( + ecs_metric_t *cur, + ecs_metric_t *last, + int32_t t) { - ecs_os_free(ptr); + int32_t prev = t_prev(t); + for (; cur <= last; cur ++) { + ecs_metric_copy(cur, t, prev); + } } -void flecs_stats_api_import( - ecs_world_t *world, - ecs_stats_api_t *api) +static +void flecs_stats_copy_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) { - ecs_entity_t kind = api->monitor_component_id; - ecs_entity_t prev = ecs_set_scope(world, kind); - - ecs_query_t *q = NULL; - if (api->query_component_id) { - q = ecs_query(world, { - .terms = {{ .id = api->query_component_id }}, - .cache_kind = EcsQueryCacheNone, - .flags = EcsQueryMatchDisabled - }); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } +} - // Called each frame, collects 60 measurements per second - { - ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); - ctx->api = *api; - ctx->query = q; - - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = MonitorStats, - .ctx = ctx, - .ctx_free = flecs_monitor_ctx_free - }); - } +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - // Called each second, reduces into 60 measurements per minute - ecs_entity_t mw1m; - { - ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); - ctx->api = *api; + world = ecs_get_world(world); - mw1m = ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .interval = 1.0, - .ctx = ctx, - .ctx_free = flecs_reduce_ctx_free - }); - } + int32_t t = s->t = t_next(s->t); - // Called each minute, reduces into 60 measurements per hour - { - ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); - ctx->api = *api; + double delta_frame_count = + ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); + ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); + ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); + ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_reduce_ctx_free - }); + double delta_world_time = + ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); + ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); + ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); + ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); + if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { + ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); + } else { + ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } - // Called each minute, reduces into 60 measurements per day - { - ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); - ctx->api = *api; - ctx->interval = FlecsDayIntervalCount; - - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1d), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_aggregate_ctx_free - }); - } + ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); + ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); - // Called each hour, reduces into 60 measurements per week - { - ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); - ctx->api = *api; - ctx->interval = FlecsWeekIntervalCount; + ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_map_count(&world->type_info)); + ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1w), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_aggregate_ctx_free - }); + ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); + ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); + if (ecs_is_alive(world, EcsSystem)) { + ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } + ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); + ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); + ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); - ecs_set_scope(world, prev); - - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); -} - -void FlecsStatsImport( - ecs_world_t *world) -{ - ECS_MODULE_DEFINE(world, FlecsStats); - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsTimer); -#ifdef FLECS_META - ECS_IMPORT(world, FlecsMeta); -#endif -#ifdef FLECS_UNITS - ECS_IMPORT(world, FlecsUnits); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsStats), - "Module that automatically monitors statistics for the world & systems"); -#endif + ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); + ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); + ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); + ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); + ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); + ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); + ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); + ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); + ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); + ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); + ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); - ecs_set_name_prefix(world, "Ecs"); + int64_t outstanding_allocs = ecs_os_api_malloc_count + + ecs_os_api_calloc_count - ecs_os_api_free_count; + ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); + ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); + ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); + ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); - EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); - EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); - EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); - EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); - EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); + outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); - FlecsWorldSummaryImport(world); - FlecsWorldMonitorImport(world); - FlecsSystemMonitorImport(world); - FlecsPipelineMonitorImport(world); - - if (ecs_os_has_time()) { - ecs_measure_frame_time(world, true); - ecs_measure_system_time(world, true); - } -} + outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); +#ifdef FLECS_HTTP + ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); + ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); + ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); + ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); + ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); + ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); + ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); + ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); + ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif -/** - * @file addons/stats/pipeline_monitor.c - * @brief Stats addon pipeline monitor - */ +error: + return; +} +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} -#ifdef FLECS_STATS +void ecs_world_stats_reduce_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +} -ECS_COMPONENT_DECLARE(EcsPipelineStats); +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} -static -void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { - ecs_map_iter_t it = ecs_map_iter(&ptr->stats); - while (ecs_map_next(&it)) { - ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); - ecs_pipeline_stats_fini(stats); - ecs_os_free(stats); - } - ecs_map_fini(&ptr->stats); +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static ECS_CTOR(EcsPipelineStats, ptr, { - ecs_os_zeromem(ptr); - ecs_map_init(&ptr->stats, NULL); -}) +void ecs_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; -static ECS_COPY(EcsPipelineStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); -}) + int32_t t = s->t = t_next(s->t); + ecs_query_count_t counts = ecs_query_count(query); + ECS_GAUGE_RECORD(&s->result_count, t, counts.results); + ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); + ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); -static ECS_MOVE(EcsPipelineStats, dst, src, { - flecs_pipeline_monitor_dtor(dst); - ecs_os_memcpy_t(dst, src, EcsPipelineStats); - ecs_os_zeromem(src); -}) +error: + return; +} -static ECS_DTOR(EcsPipelineStats, ptr, { - flecs_pipeline_monitor_dtor(ptr); -}) +void ecs_query_cache_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} -static -void flecs_pipeline_stats_set_t( - void *stats, int32_t t) +void ecs_query_cache_stats_reduce_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src, + int32_t count) { - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_pipeline_stats_t*)stats)->t = t; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } +void ecs_query_cache_stats_repeat_last( + ecs_query_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} -static -void flecs_pipeline_stats_copy_last( - void *stats, - void *src) +void ecs_query_cache_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) { - ecs_pipeline_stats_copy_last(stats, src); + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static -void flecs_pipeline_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) +#ifdef FLECS_SYSTEM + +bool ecs_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) { - ecs_pipeline_stats_get(world, res, stats); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); + if (!ptr) { + return false; + } + + ecs_query_stats_get(world, ptr->query, &s->query); + int32_t t = s->query.t; + + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + + s->task = !(ptr->query->flags & EcsQueryMatchThis); + + return true; +error: + return false; } -static -void flecs_pipeline_stats_reduce( - void *stats, - void *src) +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_pipeline_stats_reduce(stats, src); + ecs_query_cache_stats_reduce(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } -static -void flecs_pipeline_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) +void ecs_system_stats_reduce_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src, + int32_t count) { - ecs_pipeline_stats_reduce_last(stats, last, reduce_count); + ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); + dst->task = src->task; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } -static -void flecs_pipeline_stats_repeat_last( - void* stats) +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats) { - ecs_pipeline_stats_repeat_last(stats); + ecs_query_cache_stats_repeat_last(&stats->query); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->query.t)); } -static -void flecs_pipeline_stats_fini( - void *stats) +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_pipeline_stats_fini(stats); + ecs_query_cache_stats_copy_last(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } -void FlecsPipelineMonitorImport( - ecs_world_t *world) +#endif + +#ifdef FLECS_PIPELINE + +bool ecs_pipeline_stats_get( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) { - ECS_COMPONENT_DEFINE(world, EcsPipelineStats); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - ecs_set_hooks(world, EcsPipelineStats, { - .ctor = ecs_ctor(EcsPipelineStats), - .copy = ecs_copy(EcsPipelineStats), - .move = ecs_move(EcsPipelineStats), - .dtor = ecs_dtor(EcsPipelineStats) - }); + const ecs_world_t *world = ecs_get_world(stage); + const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); + if (!pqc) { + return false; + } + ecs_pipeline_state_t *pq = pqc->state; + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_stats_api_t api = { - .copy_last = flecs_pipeline_stats_copy_last, - .get = flecs_pipeline_stats_get, - .reduce = flecs_pipeline_stats_reduce, - .reduce_last = flecs_pipeline_stats_reduce_last, - .repeat_last = flecs_pipeline_stats_repeat_last, - .set_t = flecs_pipeline_stats_set_t, - .fini = flecs_pipeline_stats_fini, - .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), - .monitor_component_id = ecs_id(EcsPipelineStats), - .query_component_id = ecs_id(EcsPipeline) - }; + int32_t sys_count = 0, active_sys_count = 0; - flecs_stats_api_import(world, &api); -} + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + active_sys_count += it.count; + } + + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } + + /* Also count synchronization points */ + ecs_vec_t *ops = &pq->ops; + ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); + int32_t pip_count = active_sys_count + ecs_vec_count(ops); -#endif + if (!sys_count) { + return false; + } -/** - * @file addons/stats.c - * @brief Stats addon. - */ + if (op) { + ecs_entity_t *systems = NULL; + if (pip_count) { + ecs_vec_init_if_t(&s->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); + systems = ecs_vec_first_t(&s->systems, ecs_entity_t); + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(stage, pq->query); + + int32_t i, i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + for (i = 0; i < it.count; i ++) { + systems[i_system ++] = it.entities[i]; + ran_since_merge ++; + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + systems[i_system ++] = 0; /* 0 indicates a merge point */ + } + } + } -#ifdef FLECS_STATS + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); + } -#define ECS_GAUGE_RECORD(m, t, value)\ - flecs_gauge_record(m, t, (ecs_float_t)(value)) + /* Get sync point statistics */ + int32_t i, count = ecs_vec_count(ops); + if (count) { + ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); + op = ecs_vec_first_t(ops, ecs_pipeline_op_t); -#define ECS_COUNTER_RECORD(m, t, value)\ - flecs_counter_record(m, t, (double)(value)) + for (i = 0; i < count; i ++) { + ecs_pipeline_op_t *cur = &op[i]; + ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, + ecs_sync_stats_t, i); -#define ECS_METRIC_FIRST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) + ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); + ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, + cur->commands_enqueued); -#define ECS_METRIC_LAST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + el->system_count = cur->count; + el->multi_threaded = cur->multi_threaded; + el->immediate = cur->immediate; + } + } + } -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; -} + s->t = t_next(s->t); -static -int32_t t_prev( - int32_t t) -{ - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; + return true; +error: + return false; } -static -void flecs_gauge_record( - ecs_metric_t *m, - int32_t t, - ecs_float_t value) +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) { - m->gauge.avg[t] = value; - m->gauge.min[t] = value; - m->gauge.max[t] = value; + ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); + ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } -static -double flecs_counter_record( - ecs_metric_t *m, - int32_t t, - double value) +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - int32_t tp = t_prev(t); - double prev = m->counter.value[tp]; - m->counter.value[t] = value; - double gauge_value = value - prev; - if (gauge_value < 0) { - gauge_value = 0; /* Counters are monotonically increasing */ + int32_t system_count = ecs_vec_count(&src->systems); + ecs_vec_init_if_t(&dst->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); + ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); + ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); + ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; } - flecs_gauge_record(m, t, (ecs_float_t)gauge_value); - return gauge_value; + + dst->t = t_next(dst->t); } -static -void flecs_metric_print( - const char *name, - ecs_float_t value) +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src, + int32_t count) { - ecs_size_t len = ecs_os_strlen(name); - ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t, count); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; + } + + dst->t = t_prev(dst->t); } -static -void flecs_gauge_print( - const char *name, - int32_t t, - const ecs_metric_t *m) +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats) { - flecs_metric_print(name, m->gauge.avg[t]); + int32_t i, sync_count = ecs_vec_count(&stats->sync_points); + ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *el = &syncs[i]; + flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), + (stats->t)); + } + + stats->t = t_next(stats->t); } -static -void flecs_counter_print( - const char *name, - int32_t t, - const ecs_metric_t *m) +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - flecs_metric_print(name, m->counter.rate.avg[t]); + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; + } } -void ecs_metric_reduce( - ecs_metric_t *dst, - const ecs_metric_t *src, - int32_t t_dst, - int32_t t_src) +#endif + +void ecs_world_stats_log( + const ecs_world_t *world, + const ecs_world_stats_t *s) { - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = s->t; - bool min_set = false; - dst->gauge.avg[t_dst] = 0; - dst->gauge.min[t_dst] = 0; - dst->gauge.max[t_dst] = 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + world = ecs_get_world(world); + + flecs_counter_print("Frame", t, &s->frame.frame_count); + ecs_trace("-------------------------------------"); + flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); + flecs_counter_print("systems ran", t, &s->frame.systems_ran); + ecs_trace(""); + flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); + flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); + ecs_trace(""); + flecs_gauge_print("actual FPS", t, &s->performance.fps); + flecs_counter_print("frame time", t, &s->performance.frame_time); + flecs_counter_print("system time", t, &s->performance.system_time); + flecs_counter_print("merge time", t, &s->performance.merge_time); + flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); + ecs_trace(""); + flecs_gauge_print("tag id count", t, &s->components.tag_count); + flecs_gauge_print("component id count", t, &s->components.component_count); + flecs_gauge_print("pair id count", t, &s->components.pair_count); + flecs_gauge_print("type count", t, &s->components.type_count); + flecs_counter_print("id create count", t, &s->components.create_count); + flecs_counter_print("id delete count", t, &s->components.delete_count); + ecs_trace(""); + flecs_gauge_print("alive entity count", t, &s->entities.count); + flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); + ecs_trace(""); + flecs_gauge_print("query count", t, &s->queries.query_count); + flecs_gauge_print("observer count", t, &s->queries.observer_count); + flecs_gauge_print("system count", t, &s->queries.system_count); + ecs_trace(""); + flecs_gauge_print("table count", t, &s->tables.count); + flecs_gauge_print("empty table count", t, &s->tables.empty_count); + flecs_counter_print("table create count", t, &s->tables.create_count); + flecs_counter_print("table delete count", t, &s->tables.delete_count); + ecs_trace(""); + flecs_counter_print("add commands", t, &s->commands.add_count); + flecs_counter_print("remove commands", t, &s->commands.remove_count); + flecs_counter_print("delete commands", t, &s->commands.delete_count); + flecs_counter_print("clear commands", t, &s->commands.clear_count); + flecs_counter_print("set commands", t, &s->commands.set_count); + flecs_counter_print("ensure commands", t, &s->commands.ensure_count); + flecs_counter_print("modified commands", t, &s->commands.modified_count); + flecs_counter_print("other commands", t, &s->commands.other_count); + flecs_counter_print("discarded commands", t, &s->commands.discard_count); + flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); + flecs_counter_print("batched commands", t, &s->commands.batched_count); + ecs_trace(""); + +error: + return; +} - int32_t i; - for (i = 0; i < ECS_STAT_WINDOW; i ++) { - int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; +#endif - if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { - dst->gauge.min[t_dst] = src->gauge.min[t]; - min_set = true; - } - if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { - dst->gauge.max[t_dst] = src->gauge.max[t]; - } - } +/** + * @file addons/stats/system_monitor.c + * @brief Stats addon system monitor + */ - dst->counter.value[t_dst] = src->counter.value[t_src]; -error: - return; -} +#ifdef FLECS_STATS -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t prev, - int32_t count) -{ - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t t = t_next(prev); +ECS_COMPONENT_DECLARE(EcsSystemStats); - if (m->gauge.min[t] < m->gauge.min[prev]) { - m->gauge.min[prev] = m->gauge.min[t]; +static +void flecs_system_monitor_dtor(EcsSystemStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *stats = ecs_map_ptr(&it); + ecs_os_free(stats); } + ecs_map_fini(&ptr->stats); +} - if (m->gauge.max[t] > m->gauge.max[prev]) { - m->gauge.max[prev] = m->gauge.max[t]; - } +static ECS_CTOR(EcsSystemStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) - ecs_float_t fcount = (ecs_float_t)(count + 1); - ecs_float_t cur = m->gauge.avg[prev]; - ecs_float_t next = m->gauge.avg[t]; +static ECS_COPY(EcsSystemStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); +}) - cur *= ((fcount - 1) / fcount); - next *= 1 / fcount; +static ECS_MOVE(EcsSystemStats, dst, src, { + flecs_system_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsSystemStats); + ecs_os_zeromem(src); +}) - m->gauge.avg[prev] = cur + next; - m->counter.value[prev] = m->counter.value[t]; +static ECS_DTOR(EcsSystemStats, ptr, { + flecs_system_monitor_dtor(ptr); +}) -error: - return; +static +void flecs_system_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_system_stats_t*)stats)->query.t = t; } -void ecs_metric_copy( - ecs_metric_t *m, - int32_t dst, - int32_t src) +static +void flecs_system_stats_copy_last( + void *stats, + void *src) { - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - - m->gauge.avg[dst] = m->gauge.avg[src]; - m->gauge.min[dst] = m->gauge.min[src]; - m->gauge.max[dst] = m->gauge.max[src]; - m->counter.value[dst] = m->counter.value[src]; - -error: - return; + ecs_system_stats_copy_last(stats, src); } static -void flecs_stats_reduce( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) +void flecs_system_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) { - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); - } + ecs_system_stats_get(world, res, stats); } static -void flecs_stats_reduce_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src, - int32_t count) +void flecs_system_stats_reduce( + void *stats, + void *src) { - int32_t t_dst_next = t_next(t_dst); - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - /* Reduce into previous value */ - ecs_metric_reduce_last(dst_cur, t_dst, count); - - /* Restore old value */ - dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; - } + ecs_system_stats_reduce(stats, src); } static -void flecs_stats_repeat_last( - ecs_metric_t *cur, - ecs_metric_t *last, - int32_t t) +void flecs_system_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - int32_t prev = t_prev(t); - for (; cur <= last; cur ++) { - ecs_metric_copy(cur, t, prev); - } + ecs_system_stats_reduce_last(stats, last, reduce_count); } static -void flecs_stats_copy_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) +void flecs_system_stats_repeat_last( + void* stats) { - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; - } + ecs_system_stats_repeat_last(stats); } -void ecs_world_stats_get( - const ecs_world_t *world, - ecs_world_stats_t *s) +void FlecsSystemMonitorImport( + ecs_world_t *world) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - int32_t t = s->t = t_next(s->t); - - double delta_frame_count = - ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); - ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); - ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); - ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); - ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); - ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); - ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); + ECS_COMPONENT_DEFINE(world, EcsSystemStats); - double delta_world_time = - ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); - ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); - ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); - ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); - ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); - ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); - ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); - ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); - if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { - ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); - } else { - ECS_GAUGE_RECORD(&s->performance.fps, t, 0); - } + ecs_set_hooks(world, EcsSystemStats, { + .ctor = ecs_ctor(EcsSystemStats), + .copy = ecs_copy(EcsSystemStats), + .move = ecs_move(EcsSystemStats), + .dtor = ecs_dtor(EcsSystemStats) + }); - ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); - ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); + ecs_stats_api_t api = { + .copy_last = flecs_system_stats_copy_last, + .get = flecs_system_stats_get, + .reduce = flecs_system_stats_reduce, + .reduce_last = flecs_system_stats_reduce_last, + .repeat_last = flecs_system_stats_repeat_last, + .set_t = flecs_system_stats_set_t, + .stats_size = ECS_SIZEOF(ecs_system_stats_t), + .monitor_component_id = ecs_id(EcsSystemStats), + .query_component_id = EcsSystem + }; - ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); - ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); - ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); - ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_map_count(&world->type_info)); - ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); - ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); + flecs_stats_api_import(world, &api); +} - ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); - ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); - if (ecs_is_alive(world, EcsSystem)) { - ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); - } - ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); - ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); - ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); - ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); +#endif - ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); - ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); - ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); - ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); - ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); - ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); - ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); - ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); - ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); - ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); - ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); +/** + * @file addons/stats/world_monitor.c + * @brief Stats addon world monitor. + */ - int64_t outstanding_allocs = ecs_os_api_malloc_count + - ecs_os_api_calloc_count - ecs_os_api_free_count; - ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); - ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); - ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); - ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); - outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; - ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); - ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); - ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); +#ifdef FLECS_STATS - outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; - ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); - ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); - ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); +ECS_COMPONENT_DECLARE(EcsWorldStats); -#ifdef FLECS_HTTP - ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); - ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); - ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); - ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); - ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); - ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); - ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); - ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); - ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); -#endif +static +void flecs_world_stats_get( + ecs_world_t *world, ecs_entity_t res, void *stats) +{ + (void)res; + ecs_world_stats_get(world, stats); +} -error: - return; +static +void flecs_world_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_world_stats_t*)stats)->t = t; } -void ecs_world_stats_reduce( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) +static +void flecs_world_stats_copy_last( + void *stats, + void *src) { - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); + ecs_world_stats_copy_last(stats, src); } -void ecs_world_stats_reduce_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src, - int32_t count) +static +void flecs_world_stats_reduce( + void *stats, + void *src) { - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); + ecs_world_stats_reduce(stats, src); } -void ecs_world_stats_repeat_last( - ecs_world_stats_t *stats) +static +void flecs_world_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); + ecs_world_stats_reduce_last(stats, last, reduce_count); } -void ecs_world_stats_copy_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) +static +void flecs_world_stats_repeat_last( + void* stats) { - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); + ecs_world_stats_repeat_last(stats); } -void ecs_query_stats_get( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *s) +void FlecsWorldMonitorImport( + ecs_world_t *world) { - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; + ECS_COMPONENT_DEFINE(world, EcsWorldStats); - int32_t t = s->t = t_next(s->t); - ecs_query_count_t counts = ecs_query_count(query); - ECS_GAUGE_RECORD(&s->result_count, t, counts.results); - ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); - ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); + ecs_set_hooks(world, EcsWorldStats, { + .ctor = flecs_default_ctor + }); -error: - return; + ecs_stats_api_t api = { + .copy_last = flecs_world_stats_copy_last, + .get = flecs_world_stats_get, + .reduce = flecs_world_stats_reduce, + .reduce_last = flecs_world_stats_reduce_last, + .repeat_last = flecs_world_stats_repeat_last, + .set_t = flecs_world_stats_set_t, + .fini = NULL, + .stats_size = ECS_SIZEOF(ecs_world_stats_t), + .monitor_component_id = ecs_id(EcsWorldStats) + }; + + flecs_stats_api_import(world, &api); } -void ecs_query_cache_stats_reduce( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) +#endif + +/** + * @file addons/world_summary.c + * @brief Monitor addon. + */ + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(EcsWorldSummary); + +static +void flecs_copy_world_summary( + ecs_world_t *world, + EcsWorldSummary *dst) { - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); + const ecs_world_info_t *info = ecs_get_world_info(world); + + dst->target_fps = (double)info->target_fps; + dst->time_scale = (double)info->time_scale; + + dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; + dst->system_time_last = (double)info->system_time_total - dst->system_time_total; + dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; + + dst->frame_time_total = (double)info->frame_time_total; + dst->system_time_total = (double)info->system_time_total; + dst->merge_time_total = (double)info->merge_time_total; + + dst->frame_count ++; + dst->command_count += + info->cmd.add_count + + info->cmd.remove_count + + info->cmd.delete_count + + info->cmd.clear_count + + info->cmd.set_count + + info->cmd.ensure_count + + info->cmd.modified_count + + info->cmd.discard_count + + info->cmd.event_count + + info->cmd.other_count; + + dst->build_info = *ecs_get_build_info(); } -void ecs_query_cache_stats_reduce_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src, - int32_t count) -{ - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +static +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_copy_world_summary(it->world, &summary[i]); + } } -void ecs_query_cache_stats_repeat_last( - ecs_query_stats_t *stats) -{ - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); +static +void OnSetWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); + ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); + } } -void ecs_query_cache_stats_copy_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) +void FlecsWorldSummaryImport( + ecs_world_t *world) { - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); -} + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); -#ifdef FLECS_SYSTEM +#if defined(FLECS_META) && defined(FLECS_UNITS) + ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); + ecs_struct(world, { + .entity = ecs_id(EcsWorldSummary), + .members = { + { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, + { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, + { .name = "command_count", .type = ecs_id(ecs_u64_t) }, + { .name = "build_info", .type = build_info } + } + }); +#endif + const ecs_world_info_t *info = ecs_get_world_info(world); -bool ecs_system_stats_get( - const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *s) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "UpdateWorldSummary", + .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) + }), + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = UpdateWorldSummary + }); + + ecs_observer(world, { + .entity = ecs_entity(world, { + .name = "OnSetWorldSummary" + }), + .events = { EcsOnSet }, + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = OnSetWorldSummary + }); + + ecs_set(world, EcsWorld, EcsWorldSummary, { + .target_fps = (double)info->target_fps, + .time_scale = (double)info->time_scale + }); + + EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); + flecs_copy_world_summary(world, summary); + ecs_modified(world, EcsWorld, EcsWorldSummary); +} - world = ecs_get_world(world); +#endif - const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); - if (!ptr) { - return false; - } +/** + * @file addons/system/system.c + * @brief System addon. + */ - ecs_query_stats_get(world, ptr->query, &s->query); - int32_t t = s->query.t; - ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); +#ifdef FLECS_SYSTEM - s->task = !(ptr->query->flags & EcsQueryMatchThis); - return true; -error: - return false; -} +ecs_mixins_t ecs_system_t_mixins = { + .type_name = "ecs_system_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_system_t, world), + [EcsMixinEntity] = offsetof(ecs_system_t, entity), + [EcsMixinDtor] = offsetof(ecs_system_t, dtor) + } +}; -void ecs_system_stats_reduce( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - ecs_query_cache_stats_reduce(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t); -} +/* -- Public API -- */ -void ecs_system_stats_reduce_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src, - int32_t count) +ecs_entity_t flecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) { - ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); - dst->task = src->task; - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); -} + ecs_ftime_t time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; -void ecs_system_stats_repeat_last( - ecs_system_stats_t *stats) -{ - ecs_query_cache_stats_repeat_last(&stats->query); - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->query.t)); -} + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } -void ecs_system_stats_copy_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - ecs_query_cache_stats_copy_last(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); -} + if (tick_source) { + const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); -#endif + if (tick) { + time_elapsed = tick->time_elapsed; -#ifdef FLECS_PIPELINE + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } -bool ecs_pipeline_stats_get( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) -{ - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + ecs_os_perf_trace_push(system_data->name); - const ecs_world_t *world = ecs_get_world(stage); - const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); - if (!pqc) { - return false; + if (ecs_should_log_3()) { + char *path = ecs_get_path(world, system); + ecs_dbg_3("worker %d: %s", stage_index, path); + ecs_os_free(path); } - ecs_pipeline_state_t *pq = pqc->state; - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t sys_count = 0, active_sys_count = 0; + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; - } - active_sys_count += it.count; + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } else { + stage = world->stages[0]; } - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } + /* Prepare the query iterator */ + ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; - /* Also count synchronization points */ - ecs_vec_t *ops = &pq->ops; - ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); - int32_t pip_count = active_sys_count + ecs_vec_count(ops); + qit.system = system; + qit.delta_time = delta_time; + qit.delta_system_time = time_elapsed; + qit.param = param; + qit.ctx = system_data->ctx; + qit.callback_ctx = system_data->callback_ctx; + qit.run_ctx = system_data->run_ctx; - if (!sys_count) { - return false; + flecs_defer_begin(world, stage); + + if (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; } - if (op) { - ecs_entity_t *systems = NULL; - if (pip_count) { - ecs_vec_init_if_t(&s->systems, ecs_entity_t); - ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); - systems = ecs_vec_first_t(&s->systems, ecs_entity_t); + ecs_entity_t old_system = flecs_stage_set_system(stage, system); + ecs_iter_action_t action = system_data->action; + it->callback = action; - /* Populate systems vector, keep track of sync points */ - it = ecs_query_iter(stage, pq->query); - - int32_t i, i_system = 0, ran_since_merge = 0; - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; + ecs_run_action_t run = system_data->run; + if (run) { + /* If system query matches nothing, the system run callback doesn't have + * anything to iterate, so the iterator resources don't get cleaned up + * automatically, so clean it up here. */ + if (system_data->query->flags & EcsQueryMatchNothing) { + it->next = flecs_default_next_callback; /* Return once */ + run(it); + ecs_iter_fini(&qit); + } else { + run(it); + } + } else { + if (system_data->query->term_count) { + if (it == &qit) { + while (ecs_query_next(&qit)) { + action(&qit); } - - for (i = 0; i < it.count; i ++) { - systems[i_system ++] = it.entities[i]; - ran_since_merge ++; - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; - op++; - systems[i_system ++] = 0; /* 0 indicates a merge point */ - } + } else { + while (ecs_iter_next(it)) { + action(it); } } - - systems[i_system ++] = 0; /* Last merge */ - ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { - ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); + action(&qit); + ecs_iter_fini(&qit); } + } - /* Get sync point statistics */ - int32_t i, count = ecs_vec_count(ops); - if (count) { - ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); - op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + flecs_stage_set_system(stage, old_system); + + if (measure_time) { + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); + } + + flecs_defer_end(world, stage); + + ecs_os_perf_trace_pop(system_data->name); + + return it->interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + return flecs_run_intern( + world, stage, system, system_data, stage_index, stage_count, + delta_time, param); +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_run_intern( + world, stage, system, system_data, 0, 0, delta_time, param); +} - for (i = 0; i < count; i ++) { - ecs_pipeline_op_t *cur = &op[i]; - ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, - ecs_sync_stats_t, i); +/* System deinitialization */ +static +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); + } - ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); - ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, - cur->commands_enqueued); + if (sys->callback_ctx_free) { + sys->callback_ctx_free(sys->callback_ctx); + } - el->system_count = cur->count; - el->multi_threaded = cur->multi_threaded; - el->immediate = cur->immediate; - } - } + if (sys->run_ctx_free) { + sys->run_ctx_free(sys->run_ctx); } - s->t = t_next(s->t); + /* Safe cast, type owns name */ + ecs_os_free(ECS_CONST_CAST(char*, sys->name)); - return true; -error: - return false; + flecs_poly_free(sys, ecs_system_t); } -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) +/* ecs_poly_dtor_t-compatible wrapper */ +static +void flecs_system_poly_fini(void *sys) { - ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); - ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); + flecs_system_fini(sys); } -void ecs_pipeline_stats_reduce( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) +static +int flecs_system_init_timer( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_system_desc_t *desc) { - int32_t system_count = ecs_vec_count(&src->systems); - ecs_vec_init_if_t(&dst->systems, ecs_entity_t); - ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); - ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); - ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); - ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { + char *name = ecs_get_path(world, entity); + ecs_err("system %s cannot have both interval and rate set", name); + ecs_os_free(name); + return -1; + } - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, src->t); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; + if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || + ECS_NEQZERO(desc->tick_source)) + { +#ifdef FLECS_TIMER + if (ECS_NEQZERO(desc->interval)) { + ecs_set_interval(world, entity, desc->interval); + } + + if (desc->rate) { + ecs_entity_t tick_source = desc->tick_source; + ecs_set_rate(world, entity, desc->rate, tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + (void)world; + (void)entity; + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif } - dst->t = t_next(dst->t); + return 0; } -void ecs_pipeline_stats_reduce_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src, - int32_t count) +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) { - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_system_desc_t was not initialized to zero"); + ecs_assert(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, src->t, count); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_entity(world, {0}); } - dst->t = t_prev(dst->t); -} + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); + if (!poly->poly) { + ecs_system_t *system = flecs_poly_new(ecs_system_t); + ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); + + poly->poly = system; + system->world = world; + system->dtor = flecs_system_poly_fini; + system->entity = entity; -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats) -{ - int32_t i, sync_count = ecs_vec_count(&stats->sync_points); - ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = entity; - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *el = &syncs[i]; - flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), - (stats->t)); - } + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; + } - stats->t = t_next(stats->t); -} + /* Prevent the system from moving while we're initializing */ + flecs_defer_begin(world, world->stages[0]); -void ecs_pipeline_stats_copy_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) -{ - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + system->query = query; + system->query_entity = query->entity; - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; - } -} + system->run = desc->run; + system->action = desc->callback; -#endif + system->ctx = desc->ctx; + system->callback_ctx = desc->callback_ctx; + system->run_ctx = desc->run_ctx; -void ecs_world_stats_log( - const ecs_world_t *world, - const ecs_world_stats_t *s) -{ - int32_t t = s->t; + system->ctx_free = desc->ctx_free; + system->callback_ctx_free = desc->callback_ctx_free; + system->run_ctx_free = desc->run_ctx_free; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + system->tick_source = desc->tick_source; - world = ecs_get_world(world); - - flecs_counter_print("Frame", t, &s->frame.frame_count); - ecs_trace("-------------------------------------"); - flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); - flecs_counter_print("systems ran", t, &s->frame.systems_ran); - ecs_trace(""); - flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); - flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); - ecs_trace(""); - flecs_gauge_print("actual FPS", t, &s->performance.fps); - flecs_counter_print("frame time", t, &s->performance.frame_time); - flecs_counter_print("system time", t, &s->performance.system_time); - flecs_counter_print("merge time", t, &s->performance.merge_time); - flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); - ecs_trace(""); - flecs_gauge_print("tag id count", t, &s->components.tag_count); - flecs_gauge_print("component id count", t, &s->components.component_count); - flecs_gauge_print("pair id count", t, &s->components.pair_count); - flecs_gauge_print("type count", t, &s->components.type_count); - flecs_counter_print("id create count", t, &s->components.create_count); - flecs_counter_print("id delete count", t, &s->components.delete_count); - ecs_trace(""); - flecs_gauge_print("alive entity count", t, &s->entities.count); - flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); - ecs_trace(""); - flecs_gauge_print("query count", t, &s->queries.query_count); - flecs_gauge_print("observer count", t, &s->queries.observer_count); - flecs_gauge_print("system count", t, &s->queries.system_count); - ecs_trace(""); - flecs_gauge_print("table count", t, &s->tables.count); - flecs_gauge_print("empty table count", t, &s->tables.empty_count); - flecs_counter_print("table create count", t, &s->tables.create_count); - flecs_counter_print("table delete count", t, &s->tables.delete_count); - ecs_trace(""); - flecs_counter_print("add commands", t, &s->commands.add_count); - flecs_counter_print("remove commands", t, &s->commands.remove_count); - flecs_counter_print("delete commands", t, &s->commands.delete_count); - flecs_counter_print("clear commands", t, &s->commands.clear_count); - flecs_counter_print("set commands", t, &s->commands.set_count); - flecs_counter_print("ensure commands", t, &s->commands.ensure_count); - flecs_counter_print("modified commands", t, &s->commands.modified_count); - flecs_counter_print("other commands", t, &s->commands.other_count); - flecs_counter_print("discarded commands", t, &s->commands.discard_count); - flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); - flecs_counter_print("batched commands", t, &s->commands.batched_count); - ecs_trace(""); - -error: - return; -} + system->multi_threaded = desc->multi_threaded; + system->immediate = desc->immediate; + + system->name = ecs_get_path(world, entity); + + if (flecs_system_init_timer(world, entity, desc)) { + ecs_delete(world, entity); + ecs_defer_end(world); + goto error; + } -#endif + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } -/** - * @file addons/stats/system_monitor.c - * @brief Stats addon system monitor - */ + ecs_defer_end(world); + } else { + flecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t *system = (ecs_system_t*)poly->poly; + if (system->ctx_free) { + if (system->ctx && system->ctx != desc->ctx) { + system->ctx_free(system->ctx); + } + } -#ifdef FLECS_STATS + if (system->callback_ctx_free) { + if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { + system->callback_ctx_free(system->callback_ctx); + system->callback_ctx_free = NULL; + system->callback_ctx = NULL; + } + } -ECS_COMPONENT_DECLARE(EcsSystemStats); + if (system->run_ctx_free) { + if (system->run_ctx && system->run_ctx != desc->run_ctx) { + system->run_ctx_free(system->run_ctx); + system->run_ctx_free = NULL; + system->run_ctx = NULL; + } + } -static -void flecs_system_monitor_dtor(EcsSystemStats *ptr) { - ecs_map_iter_t it = ecs_map_iter(&ptr->stats); - while (ecs_map_next(&it)) { - ecs_system_stats_t *stats = ecs_map_ptr(&it); - ecs_os_free(stats); - } - ecs_map_fini(&ptr->stats); -} + if (desc->run) { + system->run = desc->run; + if (!desc->callback) { + system->action = NULL; + } + } -static ECS_CTOR(EcsSystemStats, ptr, { - ecs_os_zeromem(ptr); - ecs_map_init(&ptr->stats, NULL); -}) + if (desc->callback) { + system->action = desc->callback; + if (!desc->run) { + system->run = NULL; + } + } -static ECS_COPY(EcsSystemStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); -}) + if (desc->ctx) { + system->ctx = desc->ctx; + } -static ECS_MOVE(EcsSystemStats, dst, src, { - flecs_system_monitor_dtor(dst); - ecs_os_memcpy_t(dst, src, EcsSystemStats); - ecs_os_zeromem(src); -}) + if (desc->callback_ctx) { + system->callback_ctx = desc->callback_ctx; + } -static ECS_DTOR(EcsSystemStats, ptr, { - flecs_system_monitor_dtor(ptr); -}) + if (desc->run_ctx) { + system->run_ctx = desc->run_ctx; + } -static -void flecs_system_stats_set_t( - void *stats, int32_t t) -{ - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_system_stats_t*)stats)->query.t = t; -} + if (desc->ctx_free) { + system->ctx_free = desc->ctx_free; + } -static -void flecs_system_stats_copy_last( - void *stats, - void *src) -{ - ecs_system_stats_copy_last(stats, src); -} + if (desc->callback_ctx_free) { + system->callback_ctx_free = desc->callback_ctx_free; + } -static -void flecs_system_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) -{ - ecs_system_stats_get(world, res, stats); -} + if (desc->run_ctx_free) { + system->run_ctx_free = desc->run_ctx_free; + } -static -void flecs_system_stats_reduce( - void *stats, - void *src) -{ - ecs_system_stats_reduce(stats, src); -} + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } -static -void flecs_system_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_system_stats_reduce_last(stats, last, reduce_count); + if (desc->immediate) { + system->immediate = desc->immediate; + } + + if (flecs_system_init_timer(world, entity, desc)) { + return 0; + } + } + + flecs_poly_modified(world, entity, ecs_system_t); + + return entity; +error: + return 0; } -static -void flecs_system_stats_repeat_last( - void* stats) +const ecs_system_t* ecs_system_get( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_system_stats_repeat_last(stats); + return flecs_poly_get(world, entity, ecs_system_t); } -void FlecsSystemMonitorImport( +void FlecsSystemImport( ecs_world_t *world) { - ECS_COMPONENT_DEFINE(world, EcsSystemStats); + ECS_MODULE(world, FlecsSystem); +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsSystem), + "Module that implements Flecs systems"); +#endif - ecs_set_hooks(world, EcsSystemStats, { - .ctor = ecs_ctor(EcsSystemStats), - .copy = ecs_copy(EcsSystemStats), - .move = ecs_move(EcsSystemStats), - .dtor = ecs_dtor(EcsSystemStats) - }); + ecs_set_name_prefix(world, "Ecs"); - ecs_stats_api_t api = { - .copy_last = flecs_system_stats_copy_last, - .get = flecs_system_stats_get, - .reduce = flecs_system_stats_reduce, - .reduce_last = flecs_system_stats_reduce_last, - .repeat_last = flecs_system_stats_repeat_last, - .set_t = flecs_system_stats_set_t, - .stats_size = ECS_SIZEOF(ecs_system_stats_t), - .monitor_component_id = ecs_id(EcsSystemStats), - .query_component_id = EcsSystem - }; + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); - flecs_stats_api_import(world, &api); + /* Make sure to never inherit system component. This makes sure that any + * term created for the System component will default to 'self' traversal, + * which improves efficiency of the query. */ + ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); } #endif /** - * @file addons/stats/world_monitor.c - * @brief Stats addon world monitor. + * @file query/compiler/compile.c + * @brief Compile query program from query. */ -#ifdef FLECS_STATS +static +bool flecs_query_var_is_anonymous( + const ecs_query_impl_t *query, + ecs_var_id_t var_id) +{ + ecs_query_var_t *var = &query->vars[var_id]; + return var->anonymous; +} + +ecs_var_id_t flecs_query_add_var( + ecs_query_impl_t *query, + const char *name, + ecs_vec_t *vars, + ecs_var_kind_t kind) +{ + const char *dot = NULL; + if (name) { + dot = strchr(name, '.'); + if (dot) { + kind = EcsVarEntity; /* lookup variables are always entities */ + } + } + + ecs_hashmap_t *var_index = NULL; + ecs_var_id_t var_id = EcsVarNone; + if (name) { + if (kind == EcsVarAny) { + var_id = flecs_query_find_var_id(query, name, EcsVarEntity); + if (var_id != EcsVarNone) { + return var_id; + } + + var_id = flecs_query_find_var_id(query, name, EcsVarTable); + if (var_id != EcsVarNone) { + return var_id; + } + + kind = EcsVarTable; + } else { + var_id = flecs_query_find_var_id(query, name, kind); + if (var_id != EcsVarNone) { + return var_id; + } + } + + if (kind == EcsVarTable) { + var_index = &query->tvar_index; + } else { + var_index = &query->evar_index; + } + + /* If we're creating an entity var, check if it has a table variant */ + if (kind == EcsVarEntity && var_id == EcsVarNone) { + var_id = flecs_query_find_var_id(query, name, EcsVarTable); + } + } + + ecs_query_var_t *var; + ecs_var_id_t result; + if (vars) { + var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); + result = var->id = flecs_itovar(ecs_vec_count(vars)); + } else { + ecs_dbg_assert(query->var_count < query->var_size, + ECS_INTERNAL_ERROR, NULL); + var = &query->vars[query->var_count]; + result = var->id = flecs_itovar(query->var_count); + query->var_count ++; + } -ECS_COMPONENT_DECLARE(EcsWorldStats); + var->kind = flecs_ito(int8_t, kind); + var->name = name; + var->table_id = var_id; + var->base_id = 0; + var->lookup = NULL; + flecs_set_var_label(var, NULL); -static -void flecs_world_stats_get( - ecs_world_t *world, ecs_entity_t res, void *stats) -{ - (void)res; - ecs_world_stats_get(world, stats); -} + if (name) { + flecs_name_index_init_if(var_index, NULL); + flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name[0] == '_'; -static -void flecs_world_stats_set_t( - void *stats, int32_t t) -{ - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_world_stats_t*)stats)->t = t; -} + /* Handle variables that require a by-name lookup, e.g. $this.wheel */ + if (dot != NULL) { + ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); + var->lookup = dot + 1; + } + } -static -void flecs_world_stats_copy_last( - void *stats, - void *src) -{ - ecs_world_stats_copy_last(stats, src); + return result; } static -void flecs_world_stats_reduce( - void *stats, - void *src) +ecs_var_id_t flecs_query_add_var_for_term_id( + ecs_query_impl_t *query, + ecs_term_ref_t *term_id, + ecs_vec_t *vars, + ecs_var_kind_t kind) { - ecs_world_stats_reduce(stats, src); -} + const char *name = flecs_term_ref_var_name(term_id); + if (!name) { + return EcsVarNone; + } -static -void flecs_world_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_world_stats_reduce_last(stats, last, reduce_count); + return flecs_query_add_var(query, name, vars, kind); } +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ static -void flecs_world_stats_repeat_last( - void* stats) -{ - ecs_world_stats_repeat_last(stats); -} - -void FlecsWorldMonitorImport( - ecs_world_t *world) +int flecs_query_discover_vars( + ecs_stage_t *stage, + ecs_query_impl_t *query) { - ECS_COMPONENT_DEFINE(world, EcsWorldStats); + ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ + ecs_vec_reset_t(NULL, vars, ecs_query_var_t); - ecs_set_hooks(world, EcsWorldStats, { - .ctor = flecs_default_ctor - }); + ecs_term_t *terms = query->pub.terms; + int32_t a, i, anonymous_count = 0, count = query->pub.term_count; + int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; + bool table_this = false, entity_before_table_this = false; - ecs_stats_api_t api = { - .copy_last = flecs_world_stats_copy_last, - .get = flecs_world_stats_get, - .reduce = flecs_world_stats_reduce, - .reduce_last = flecs_world_stats_reduce_last, - .repeat_last = flecs_world_stats_repeat_last, - .set_t = flecs_world_stats_set_t, - .fini = NULL, - .stats_size = ECS_SIZEOF(ecs_world_stats_t), - .monitor_component_id = ecs_id(EcsWorldStats) - }; + /* For This table lookups during discovery. This will be overwritten after + * discovery with whether the query actually has a This table variable. */ + query->pub.flags |= EcsQueryHasTableThisVar; - flecs_stats_api_import(world, &api); -} + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + ecs_term_ref_t *src = &term->src; -#endif + if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { + /* Keep track of which variables are first used in scope, so that we + * can mark them as anonymous. Terms inside a scope are collapsed + * into a single result, which means that outside of the scope the + * value of those variables is undefined. */ + if (!scope) { + scoped_var_index = ecs_vec_count(vars); + } + scope ++; + continue; + } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { + if (!--scope) { + /* Any new variables declared after entering a scope should be + * marked as anonymous. */ + int32_t v; + for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { + ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; + } + } + continue; + } -/** - * @file addons/world_summary.c - * @brief Monitor addon. - */ + ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( + query, first, vars, EcsVarEntity); + if (first_var_id == EcsVarNone) { + /* If first is not a variable, check if we need to insert anonymous + * variable for resolving component inheritance */ + if (term->flags_ & EcsTermIdInherited) { + anonymous_count += 2; /* table & entity variable */ + } + /* If first is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(first)) { + anonymous_count ++; + } + } -#ifdef FLECS_STATS + if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { + const char *var_name = flecs_term_ref_var_name(src); + if (var_name) { + ecs_var_id_t var_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (var_id == EcsVarNone || var_id == first_var_id) { + var_id = flecs_query_add_var( + query, var_name, vars, EcsVarEntity); + } -ECS_COMPONENT_DECLARE(EcsWorldSummary); + if (var_id != EcsVarNone) { + /* Mark variable as one for which we need to create a table + * variable. Don't create table variable now, so that we can + * store it in the non-public part of the variable array. */ + ecs_query_var_t *var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)var_id - 1); + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + if (!var->lookup) { + var->kind = EcsVarAny; + anonymous_table_count ++; + } -static -void flecs_copy_world_summary( - ecs_world_t *world, - EcsWorldSummary *dst) -{ - const ecs_world_info_t *info = ecs_get_world_info(world); + if (((1llu << term->field_index) & query->pub.data_fields)) { + /* Can't have an anonymous variable as source of a term + * that returns a component. We need to return each + * instance of the component, whereas anonymous + * variables are not guaranteed to be resolved to + * individual entities. */ + if (var->anonymous) { + ecs_err( + "can't use anonymous variable '%s' as source of " + "data term", var->name); + goto error; + } + } - dst->target_fps = (double)info->target_fps; - dst->time_scale = (double)info->time_scale; + /* Track which variable ids are used as field source */ + if (!query->src_vars) { + query->src_vars = flecs_calloc_n(&stage->allocator, + ecs_var_id_t, query->pub.field_count); + } - dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; - dst->system_time_last = (double)info->system_time_total - dst->system_time_total; - dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; + query->src_vars[term->field_index] = var_id; + } + } else { + if (flecs_term_ref_is_wildcard(src)) { + anonymous_count ++; + } + } + } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { + if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); + } + } - dst->frame_time_total = (double)info->frame_time_total; - dst->system_time_total = (double)info->system_time_total; - dst->merge_time_total = (double)info->merge_time_total; + if (flecs_query_add_var_for_term_id( + query, second, vars, EcsVarEntity) == EcsVarNone) + { + /* If second is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(second)) { + anonymous_count ++; + } + } - dst->frame_count ++; - dst->command_count += - info->cmd.add_count + - info->cmd.remove_count + - info->cmd.delete_count + - info->cmd.clear_count + - info->cmd.set_count + - info->cmd.ensure_count + - info->cmd.modified_count + - info->cmd.discard_count + - info->cmd.event_count + - info->cmd.other_count; + if (src->id & EcsIsVariable && second->id & EcsIsVariable) { + if (term->flags_ & EcsTermTransitive) { + /* Anonymous variable to store temporary id for finding + * targets for transitive relationship, see compile_term. */ + anonymous_count ++; + } + } - dst->build_info = *ecs_get_build_info(); -} + /* If member term, make sure source is available as entity */ + if (term->flags_ & EcsTermIsMember) { + flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); + } -static -void UpdateWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + /* Track if a This entity variable is used before a potential This table + * variable. If this happens, the query has no This table variable */ + if (ECS_TERM_REF_ID(src) == EcsThis) { + table_this = true; + } - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - flecs_copy_world_summary(it->world, &summary[i]); + if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { + if (!table_this) { + entity_before_table_this = true; + } + } } -} -static -void OnSetWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + int32_t var_count = ecs_vec_count(vars); + ecs_var_id_t placeholder = EcsVarNone - 1; + bool replace_placeholders = false; - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); - ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); - } -} + /* Ensure lookup variables have table and/or entity variables */ + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->lookup) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; -void FlecsWorldSummaryImport( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsWorldSummary); + ecs_var_id_t base_table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + if (base_table_id != EcsVarNone) { + var->table_id = base_table_id; + } else if (anonymous_table_count) { + /* Scan for implicit anonymous table variables that haven't been + * inserted yet (happens after this step). Doing this here vs. + * ensures that anonymous variables are appended at the end of + * the variable array, while also ensuring that variable ids are + * stable (no swapping of table var ids that are in use). */ + for (a = 0; a < var_count; a ++) { + ecs_query_var_t *avar = ecs_vec_get_t( + vars, ecs_query_var_t, a); + if (avar->kind == EcsVarAny) { + if (!ecs_os_strcmp(avar->name, var_name)) { + base_table_id = (ecs_var_id_t)(a + 1); + break; + } + } + } + if (base_table_id != EcsVarNone) { + /* Set marker so we can set the new table id afterwards */ + var->table_id = placeholder; + replace_placeholders = true; + } + } -#if defined(FLECS_META) && defined(FLECS_UNITS) - ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); - ecs_struct(world, { - .entity = ecs_id(EcsWorldSummary), - .members = { - { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, - { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, - { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, - { .name = "command_count", .type = ecs_id(ecs_u64_t) }, - { .name = "build_info", .type = build_info } - } - }); -#endif - const ecs_world_info_t *info = ecs_get_world_info(world); + ecs_var_id_t base_entity_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (base_entity_id == EcsVarNone) { + /* Get name from table var (must exist). We can't use allocated + * name since variables don't own names. */ + const char *base_name = NULL; + if (base_table_id != EcsVarNone && base_table_id) { + ecs_query_var_t *base_table_var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)base_table_id - 1); + base_name = base_table_var->name; + } else { + base_name = EcsThisName; + } - ecs_system(world, { - .entity = ecs_entity(world, { - .name = "UpdateWorldSummary", - .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) - }), - .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, - .callback = UpdateWorldSummary - }); + base_entity_id = flecs_query_add_var( + query, base_name, vars, EcsVarEntity); + var = ecs_vec_get_t(vars, ecs_query_var_t, i); + } - ecs_observer(world, { - .entity = ecs_entity(world, { - .name = "OnSetWorldSummary" - }), - .events = { EcsOnSet }, - .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, - .callback = OnSetWorldSummary - }); + var->base_id = base_entity_id; - ecs_set(world, EcsWorld, EcsWorldSummary, { - .target_fps = (double)info->target_fps, - .time_scale = (double)info->time_scale - }); + ecs_os_free(var_name); + } + } - EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); - flecs_copy_world_summary(world, summary); - ecs_modified(world, EcsWorld, EcsWorldSummary); -} + var_count = ecs_vec_count(vars); -#endif + /* Add non-This table variables */ + if (anonymous_table_count) { + anonymous_table_count = 0; + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->kind == EcsVarAny) { + var->kind = EcsVarEntity; -/** - * @file addons/system/system.c - * @brief System addon. - */ + ecs_var_id_t var_id = flecs_query_add_var( + query, var->name, vars, EcsVarTable); + ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; + anonymous_table_count ++; + } + } + var_count = ecs_vec_count(vars); + } -#ifdef FLECS_SYSTEM + /* If any forward references to newly added anonymous tables exist, replace + * them with the actual table variable ids. */ + if (replace_placeholders) { + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->table_id == placeholder) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; + var->table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + ecs_assert(var->table_id != EcsVarNone, + ECS_INTERNAL_ERROR, NULL); -ecs_mixins_t ecs_system_t_mixins = { - .type_name = "ecs_system_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_system_t, world), - [EcsMixinEntity] = offsetof(ecs_system_t, entity), - [EcsMixinDtor] = offsetof(ecs_system_t, dtor) + ecs_os_free(var_name); + } + } } -}; -/* -- Public API -- */ - -ecs_entity_t flecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - ecs_system_t *system_data, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) -{ - ecs_ftime_t time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; + /* Always include spot for This variable, even if query doesn't use it */ + var_count ++; - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; + ecs_query_var_t *query_vars = &flecs_this_array; + if ((var_count + anonymous_count) > 1) { + query_vars = flecs_alloc(&stage->allocator, + (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + (var_count + anonymous_count)); } - if (tick_source) { - const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); + query->vars = query_vars; + query->var_count = var_count; + query->pub.var_count = flecs_ito(int8_t, var_count); + ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, + !entity_before_table_this); + query->var_size = var_count + anonymous_count; - if (tick) { - time_elapsed = tick->time_elapsed; + char **var_names; + if (query_vars != &flecs_this_array) { + query_vars[0].kind = EcsVarTable; + query_vars[0].name = NULL; + flecs_set_var_label(&query_vars[0], NULL); + query_vars[0].id = 0; + query_vars[0].table_id = EcsVarNone; + query_vars[0].lookup = NULL; - /* If timer hasn't fired we shouldn't run the system */ - if (!tick->tick) { - return 0; - } - } else { - /* If a timer has been set but the timer entity does not have the - * EcsTimer component, don't run the system. This can be the result - * of a single-shot timer that has fired already. Not resetting the - * timer field of the system will ensure that the system won't be - * ran after the timer has fired. */ - return 0; - } + var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), + var_count + anonymous_count); + var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); + } else { + var_names = &flecs_this_name_array; } - ecs_os_perf_trace_push(system_data->name); + query->pub.vars = (char**)var_names; - if (ecs_should_log_3()) { - char *path = ecs_get_path(world, system); - ecs_dbg_3("worker %d: %s", stage_index, path); - ecs_os_free(path); + query_vars ++; + var_names ++; + var_count --; + + if (var_count) { + ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); + ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); + for (i = 0; i < var_count; i ++) { + ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], + ECS_INTERNAL_ERROR, NULL); + var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); + } } - ecs_time_t time_start; - bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); - if (measure_time) { - ecs_os_get_time(&time_start); - } + /* Hide anonymous table variables from application */ + query->pub.var_count = + flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; - } else { - stage = world->stages[0]; + /* Sanity check to make sure that the public part of the variable array only + * contains entity variables. */ +#ifdef FLECS_DEBUG + for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { + ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } +#endif - /* Prepare the query iterator */ - ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); - ecs_iter_t *it = &qit; + return 0; +error: + return -1; +} - qit.system = system; - qit.delta_time = delta_time; - qit.delta_system_time = time_elapsed; - qit.param = param; - qit.ctx = system_data->ctx; - qit.callback_ctx = system_data->callback_ctx; - qit.run_ctx = system_data->run_ctx; +static +bool flecs_query_var_is_unknown( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_var_t *vars = query->vars; + if (ctx->written & (1ull << var_id)) { + return false; + } else { + ecs_var_id_t table_var = vars[var_id].table_id; + if (table_var != EcsVarNone) { + return flecs_query_var_is_unknown(query, table_var, ctx); + } + } + return true; +} - flecs_defer_begin(world, stage); +/* Returns whether term is unknown. A term is unknown when it has variable + * elements (first, second, src) that are all unknown. */ +static +bool flecs_query_term_is_unknown( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t dummy = {0}; + flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, + &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, + &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, + &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); - if (stage_count > 1 && system_data->multi_threaded) { - wit = ecs_worker_iter(it, stage_index, stage_count); - it = &wit; + bool has_vars = dummy.flags & + ((EcsQueryIsVar << EcsQueryFirst) | + (EcsQueryIsVar << EcsQuerySecond) | + (EcsQueryIsVar << EcsQuerySrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; } - ecs_entity_t old_system = flecs_stage_set_system(stage, system); - ecs_iter_action_t action = system_data->action; - it->callback = action; - - ecs_run_action_t run = system_data->run; - if (run) { - /* If system query matches nothing, the system run callback doesn't have - * anything to iterate, so the iterator resources don't get cleaned up - * automatically, so clean it up here. */ - if (system_data->query->flags & EcsQueryMatchNothing) { - it->next = flecs_default_next_callback; /* Return once */ - run(it); - ecs_iter_fini(&qit); - } else { - run(it); + if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { + return false; } - } else { - if (system_data->query->term_count) { - if (it == &qit) { - while (ecs_query_next(&qit)) { - action(&qit); - } - } else { - while (ecs_iter_next(it)) { - action(it); - } - } - } else { - action(&qit); - ecs_iter_fini(&qit); + } + if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { + return false; + } + } + if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { + return false; } } - flecs_stage_set_system(stage, old_system); + return true; +} - if (measure_time) { - system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); - } +/* Find the next known term from specified offset. This function is used to find + * a term that can be evaluated before a term that is unknown. Evaluating known + * before unknown terms can significantly decrease the search space. */ +static +int32_t flecs_query_term_next_known( + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) +{ + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - flecs_defer_end(world, stage); + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1ull << i)) { + continue; + } - ecs_os_perf_trace_pop(system_data->name); + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + continue; + } - return it->interrupted_by; -} + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; + } -/* -- Public API -- */ + if (flecs_query_term_is_unknown(query, term, ctx)) { + continue; + } -ecs_entity_t ecs_run_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return i; + } - return flecs_run_intern( - world, stage, system, system_data, stage_index, stage_count, - delta_time, param); + return -1; } -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - void *param) +/* If the first part of a query contains more than one trivial term, insert a + * special instruction which batch-evaluates multiple terms. */ +static +void flecs_query_insert_trivial_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_run_intern( - world, stage, system, system_data, 0, 0, delta_time, param); -} + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; + ecs_flags64_t trivial_set = 0; -/* System deinitialization */ -static -void flecs_system_fini(ecs_system_t *sys) { - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); + /* Trivial search always ignores prefabs and disabled entities */ + if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { + return; } - if (sys->callback_ctx_free) { - sys->callback_ctx_free(sys->callback_ctx); - } + /* Find trivial terms, which can be handled in single instruction */ + int32_t trivial_wildcard_terms = 0; + int32_t trivial_terms = 0; - if (sys->run_ctx_free) { - sys->run_ctx_free(sys->run_ctx); - } + for (i = 0; i < term_count; i ++) { + /* Term is already compiled */ + if (*compiled & (1ull << i)) { + continue; + } - /* Safe cast, type owns name */ - ecs_os_free(ECS_CONST_CAST(char*, sys->name)); + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsTrivial)) { + continue; + } - flecs_poly_free(sys, ecs_system_t); -} + /* We can only add trivial terms to plan if they no up traversal */ + if ((term->src.id & EcsTraverseFlags) != EcsSelf) { + continue; + } -/* ecs_poly_dtor_t-compatible wrapper */ -static -void flecs_system_poly_fini(void *sys) -{ - flecs_system_fini(sys); -} + /* Wildcards are not supported for trivial queries */ + if (ecs_id_is_wildcard(term->id)) { + continue; + } -static -int flecs_system_init_timer( - ecs_world_t *world, - ecs_entity_t entity, - const ecs_system_desc_t *desc) -{ - if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { - char *name = ecs_get_path(world, entity); - ecs_err("system %s cannot have both interval and rate set", name); - ecs_os_free(name); - return -1; + trivial_set |= (1llu << i); + + trivial_terms ++; } - if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || - ECS_NEQZERO(desc->tick_source)) - { -#ifdef FLECS_TIMER - if (ECS_NEQZERO(desc->interval)) { - ecs_set_interval(world, entity, desc->interval); + if (trivial_terms >= 2) { + /* Mark terms as compiled & populated */ + for (i = 0; i < q->term_count; i ++) { + if (trivial_set & (1llu << i)) { + *compiled |= (1ull << i); + } } - if (desc->rate) { - ecs_entity_t tick_source = desc->tick_source; - ecs_set_rate(world, entity, desc->rate, tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, entity, desc->tick_source); + /* If there's more than 1 trivial term, batch them in trivial search */ + ecs_query_op_t trivial = {0}; + if (!trivial_wildcard_terms) { + trivial.kind = EcsQueryTriv; } -#else - (void)world; - (void)entity; - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } - return 0; + /* Store the bitset with trivial terms on the instruction */ + trivial.src.entity = trivial_set; + flecs_query_op_insert(&trivial, ctx); + + /* Mark $this as written */ + ctx->written |= (1llu << 0); + } } -ecs_entity_t ecs_system_init( - ecs_world_t *world, - const ecs_system_desc_t *desc) +static +void flecs_query_insert_cache_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_system_desc_t was not initialized to zero"); - ecs_assert(!(world->flags & EcsWorldReadonly), - ECS_INVALID_WHILE_READONLY, NULL); - - ecs_entity_t entity = desc->entity; - if (!entity) { - entity = ecs_entity(world, {0}); + if (!query->cache) { + return; } - EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); - if (!poly->poly) { - ecs_system_t *system = flecs_poly_new(ecs_system_t); - ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); - - poly->poly = system; - system->world = world; - system->dtor = flecs_system_poly_fini; - system->entity = entity; + ecs_query_t *q = &query->pub; - ecs_query_desc_t query_desc = desc->query; - query_desc.entity = entity; + if (q->cache_kind == EcsQueryCacheAll) { + /* If all terms are cacheable, make sure no other terms are compiled */ + *compiled = 0xFFFFFFFFFFFFFFFF; + } else if (q->cache_kind == EcsQueryCacheAuto) { + /* The query is partially cacheable */ + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, entity); - return 0; - } + for (i = 0; i < count; i ++) { + if ((*compiled) & (1ull << i)) { + continue; + } - /* Prevent the system from moving while we're initializing */ - flecs_defer_begin(world, world->stages[0]); + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsCacheable)) { + continue; + } - system->query = query; - system->query_entity = query->entity; + *compiled |= (1ull << i); + } + } - system->run = desc->run; - system->action = desc->callback; + /* Insert the operation for cache traversal */ + ecs_query_op_t op = {0}; - system->ctx = desc->ctx; - system->callback_ctx = desc->callback_ctx; - system->run_ctx = desc->run_ctx; + if (q->flags & EcsQueryIsCacheable) { + op.kind = EcsQueryIsCache; + } else { + op.kind = EcsQueryCache; + } - system->ctx_free = desc->ctx_free; - system->callback_ctx_free = desc->callback_ctx_free; - system->run_ctx_free = desc->run_ctx_free; + flecs_query_write(0, &op.written); + flecs_query_write_ctx(0, ctx, false); + flecs_query_op_insert(&op, ctx); +} - system->tick_source = desc->tick_source; +static +bool flecs_term_ref_match_multiple( + ecs_term_ref_t *ref) +{ + return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); +} - system->multi_threaded = desc->multi_threaded; - system->immediate = desc->immediate; +static +bool flecs_term_match_multiple( + ecs_term_t *term) +{ + return flecs_term_ref_match_multiple(&term->first) || + flecs_term_ref_match_multiple(&term->second); +} - system->name = ecs_get_path(world, entity); +static +int flecs_query_insert_toggle( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &impl->pub; + int32_t i, j, term_count = q->term_count; + ecs_term_t *terms = q->terms; + ecs_flags64_t fields_done = 0; - if (flecs_system_init_timer(world, entity, desc)) { - ecs_delete(world, entity); - ecs_defer_end(world); - goto error; + for (i = 0; i < term_count; i ++) { + if (fields_done & (1llu << i)) { + continue; } - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, entity)); - } + ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsToggle) { + ecs_query_op_t cur = {0}; + flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, + &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); - ecs_defer_end(world); - } else { - flecs_poly_assert(poly->poly, ecs_system_t); - ecs_system_t *system = (ecs_system_t*)poly->poly; + ecs_flags64_t and_toggles = 0; + ecs_flags64_t not_toggles = 0; + ecs_flags64_t optional_toggles = 0; - if (system->ctx_free) { - if (system->ctx && system->ctx != desc->ctx) { - system->ctx_free(system->ctx); - } - } + for (j = i; j < term_count; j ++) { + if (fields_done & (1llu << j)) { + continue; + } - if (system->callback_ctx_free) { - if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { - system->callback_ctx_free(system->callback_ctx); - system->callback_ctx_free = NULL; - system->callback_ctx = NULL; - } - } + /* Also includes term[i], so flags get set correctly */ + term = &terms[j]; - if (system->run_ctx_free) { - if (system->run_ctx && system->run_ctx != desc->run_ctx) { - system->run_ctx_free(system->run_ctx); - system->run_ctx_free = NULL; - system->run_ctx = NULL; - } - } + /* If term is not for the same src, skip */ + ecs_query_op_t next = {0}; + flecs_query_compile_term_ref(NULL, impl, &next, &term->src, + &next.src, EcsQuerySrc, EcsVarAny, ctx, false); + if (next.src.entity != cur.src.entity || + next.flags != cur.flags) + { + continue; + } - if (desc->run) { - system->run = desc->run; - if (!desc->callback) { - system->action = NULL; + /* Source matches, set flag */ + if (term->oper == EcsNot) { + not_toggles |= (1llu << j); + } else if (term->oper == EcsOptional) { + optional_toggles |= (1llu << j); + } else { + and_toggles |= (1llu << j); + } + + fields_done |= (1llu << j); } - } - if (desc->callback) { - system->action = desc->callback; - if (!desc->run) { - system->run = NULL; + if (and_toggles || not_toggles) { + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggle; + op.src = cur.src; + op.flags = cur.flags; + + if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_write(op.src.var, &op.written); + } + + /* Encode fields: + * - first.entity is the fields that match enabled bits + * - second.entity is the fields that match disabled bits + */ + op.first.entity = and_toggles; + op.second.entity = not_toggles; + flecs_query_op_insert(&op, ctx); } - } - if (desc->ctx) { - system->ctx = desc->ctx; - } + /* Insert separate instructions for optional terms. To make sure + * entities are returned in batches where fields are never partially + * set or unset, the result must be split up into batches that have + * the exact same toggle masks. Instead of complicating the toggle + * instruction with code to scan for blocks that have the same bits + * set, separate instructions let the query engine backtrack to get + * the right results. */ + if (optional_toggles) { + for (j = i; j < term_count; j ++) { + uint64_t field_bit = 1ull << j; + if (!(optional_toggles & field_bit)) { + continue; + } - if (desc->callback_ctx) { - system->callback_ctx = desc->callback_ctx; + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggleOption; + op.src = cur.src; + op.first.entity = field_bit; + op.flags = cur.flags; + flecs_query_op_insert(&op, ctx); + } + } } + } - if (desc->run_ctx) { - system->run_ctx = desc->run_ctx; - } + return 0; +} - if (desc->ctx_free) { - system->ctx_free = desc->ctx_free; - } +static +int flecs_query_insert_fixed_src_terms( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &impl->pub; + int32_t i, term_count = q->term_count; + ecs_term_t *terms = q->terms; - if (desc->callback_ctx_free) { - system->callback_ctx_free = desc->callback_ctx_free; - } + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - if (desc->run_ctx_free) { - system->run_ctx_free = desc->run_ctx_free; + if (term->oper == EcsNot) { + /* If term has not operator and variables for first/second, we can't + * put the term first as this could prevent us from getting back + * valid results. For example: + * !$var(e), Tag($var) + * + * Here, the first term would evaluate to false (and cause the + * entire query not to match) if 'e' has any components. + * + * However, when reordering we get results: + * Tag($var), !$var(e) + * + * Now the query returns all entities with Tag, that 'e' does not + * have as component. For this reason, queries should never use + * unwritten variables in not terms- and we should also not reorder + * terms in a way that results in doing this. */ + if (flecs_term_match_multiple(term)) { + continue; + } } - if (desc->multi_threaded) { - system->multi_threaded = desc->multi_threaded; + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; } - if (desc->immediate) { - system->immediate = desc->immediate; - } + if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { + if (flecs_query_compile_term(world, impl, term, ctx)) { + return -1; + } - if (flecs_system_init_timer(world, entity, desc)) { - return 0; + *compiled |= (1llu << i); } } - flecs_poly_modified(world, entity, ecs_system_t); - - return entity; -error: return 0; } -const ecs_system_t* ecs_system_get( - const ecs_world_t *world, - ecs_entity_t entity) +int flecs_query_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_query_impl_t *query) { - return flecs_poly_get(world, entity, ecs_system_t); -} + /* Compile query to operations. Only necessary for non-trivial queries, as + * trivial queries use trivial iterators that don't use query ops. */ + bool needs_plan = true; + ecs_flags32_t flags = query->pub.flags; + ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; + if ((flags & trivial_flags) == trivial_flags) { + if (query->cache) { + if (flags & EcsQueryIsCacheable) { + needs_plan = false; + } + } else { + if (!(flags & EcsQueryMatchWildcards)) { + needs_plan = false; + } + } + } -void FlecsSystemImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsSystem); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsSystem), - "Module that implements Flecs systems"); -#endif + if (!needs_plan) { + /* Initialize space for $this variable */ + query->pub.var_count = 1; + query->var_count = 1; + query->var_size = 1; + query->vars = &flecs_this_array; + query->pub.vars = &flecs_this_name_array; + query->pub.flags |= EcsQueryHasTableThisVar; + return 0; + } - ecs_set_name_prefix(world, "Ecs"); + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + ecs_query_compile_ctx_t ctx = {0}; + ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); + ctx.ops = &stage->operations; + ctx.cur = ctx.ctrlflow; + ctx.cur->lbl_begin = -1; + ctx.cur->lbl_begin = -1; + ecs_vec_clear(ctx.ops); - flecs_bootstrap_tag(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); + /* Find all variables defined in query */ + if (flecs_query_discover_vars(stage, query)) { + return -1; + } - /* Make sure to never inherit system component. This makes sure that any - * term created for the System component will default to 'self' traversal, - * which improves efficiency of the query. */ - ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); -} + /* If query contains fixed source terms, insert operation to set sources */ + int32_t i, term_count = q->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.id & EcsIsEntity) { + ecs_query_op_t set_fixed = {0}; + set_fixed.kind = EcsQuerySetFixed; + flecs_query_op_insert(&set_fixed, &ctx); + break; + } + } -#endif + /* If the query contains terms with fixed ids (no wildcards, variables), + * insert instruction that initializes ecs_iter_t::ids. This allows for the + * insertion of simpler instructions later on. + * If the query is entirely cacheable, ids are populated by the cache. */ + if (q->cache_kind != EcsQueryCacheAll) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_term_is_fixed_id(q, term) || + (term->src.id & EcsIsEntity && + !(term->src.id & ~EcsTermRefFlags))) + { + ecs_query_op_t set_ids = {0}; + set_ids.kind = EcsQuerySetIds; + flecs_query_op_insert(&set_ids, &ctx); + break; + } + } + } -/** - * @file query/compiler/compile.c - * @brief Compile query program from query. - */ + ecs_flags64_t compiled = 0; + /* Always evaluate terms with fixed source before other terms */ + flecs_query_insert_fixed_src_terms( + world, query, &compiled, &ctx); -static -bool flecs_query_var_is_anonymous( - const ecs_query_impl_t *query, - ecs_var_id_t var_id) -{ - ecs_query_var_t *var = &query->vars[var_id]; - return var->anonymous; -} + /* Compile cacheable terms */ + flecs_query_insert_cache_search(query, &compiled, &ctx); -ecs_var_id_t flecs_query_add_var( - ecs_query_impl_t *query, - const char *name, - ecs_vec_t *vars, - ecs_var_kind_t kind) -{ - const char *dot = NULL; - if (name) { - dot = strchr(name, '.'); - if (dot) { - kind = EcsVarEntity; /* lookup variables are always entities */ + /* Insert trivial term search if query allows for it */ + flecs_query_insert_trivial_search(query, &compiled, &ctx); + + /* If a query starts with one or more optional terms, first compile the non + * optional terms. This prevents having to insert an instruction that + * matches the query against every entity in the storage. + * Only skip optional terms at the start of the query so that any + * short-circuiting behavior isn't affected (a non-optional term can become + * optional if it uses a variable set in an optional term). */ + int32_t start_term = 0; + for (; start_term < term_count; start_term ++) { + if (terms[start_term].oper != EcsOptional) { + break; } } - ecs_hashmap_t *var_index = NULL; - ecs_var_id_t var_id = EcsVarNone; - if (name) { - if (kind == EcsVarAny) { - var_id = flecs_query_find_var_id(query, name, EcsVarEntity); - if (var_id != EcsVarNone) { - return var_id; + do { + /* Compile remaining query terms to instructions */ + for (i = start_term; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t compile = i; + + if (compiled & (1ull << i)) { + continue; /* Already compiled */ } - var_id = flecs_query_find_var_id(query, name, EcsVarTable); - if (var_id != EcsVarNone) { - return var_id; + if (term->oper == EcsOptional && start_term) { + /* Don't reorder past the first optional term that's not in the + * initial list of optional terms. This protects short + * circuiting branching in the query. + * A future algorithm could look at which variables are + * accessed by optional terms, and continue reordering terms + * that don't access those variables. */ + break; } - kind = EcsVarTable; - } else { - var_id = flecs_query_find_var_id(query, name, kind); - if (var_id != EcsVarNone) { - return var_id; + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + can_reorder = false; + } + + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_query_term_is_unknown(query, term, &ctx)) + { + int32_t term_index = flecs_query_term_next_known( + query, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &q->terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } + + if (flecs_query_compile_term(world, query, term, &ctx)) { + return -1; } + + compiled |= (1ull << compile); } - if (kind == EcsVarTable) { - var_index = &query->tvar_index; + if (start_term) { + start_term = 0; /* Repeat, now also insert optional terms */ } else { - var_index = &query->evar_index; + break; } + } while (true); - /* If we're creating an entity var, check if it has a table variant */ - if (kind == EcsVarEntity && var_id == EcsVarNone) { - var_id = flecs_query_find_var_id(query, name, EcsVarTable); + ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); + if (this_id != EcsVarNone) { + /* If This variable has been written as entity, insert an operation to + * assign it to it.entities for consistency. */ + if (ctx.written & (1ull << this_id)) { + ecs_query_op_t set_this = {0}; + set_this.kind = EcsQuerySetThis; + set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); + set_this.first.var = this_id; + flecs_query_op_insert(&set_this, &ctx); } } - ecs_query_var_t *var; - ecs_var_id_t result; - if (vars) { - var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); - result = var->id = flecs_itovar(ecs_vec_count(vars)); - } else { - ecs_dbg_assert(query->var_count < query->var_size, - ECS_INTERNAL_ERROR, NULL); - var = &query->vars[query->var_count]; - result = var->id = flecs_itovar(query->var_count); - query->var_count ++; + /* Make sure non-This variables are written as entities */ + if (query->vars) { + for (i = 0; i < query->var_count; i ++) { + ecs_query_var_t *var = &query->vars[i]; + if (var->id && var->kind == EcsVarTable && var->name) { + ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, + EcsVarEntity); + if (!flecs_query_is_written(var_id, ctx.written)) { + /* Skip anonymous variables */ + if (!flecs_query_var_is_anonymous(query, var_id)) { + flecs_query_insert_each(var->id, var_id, &ctx, false); + } + } + } + } } - var->kind = flecs_ito(int8_t, kind); - var->name = name; - var->table_id = var_id; - var->base_id = 0; - var->lookup = NULL; - flecs_set_var_label(var, NULL); + /* If query contains non-This variables as term source, build lookup array */ + if (query->src_vars) { + ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); + bool only_anonymous = true; - if (name) { - flecs_name_index_init_if(var_index, NULL); - flecs_name_index_ensure(var_index, var->id, name, 0, 0); - var->anonymous = name[0] == '_'; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } - /* Handle variables that require a by-name lookup, e.g. $this.wheel */ - if (dot != NULL) { - ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); - var->lookup = dot + 1; + if (!flecs_query_var_is_anonymous(query, var_id)) { + only_anonymous = false; + break; + } else { + /* Don't fetch component data for anonymous variables. Because + * not all metadata (such as it.sources) is initialized for + * anonymous variables, and because they may only be available + * as table variables (each is not guaranteed to be inserted for + * anonymous variables) the iterator may not have sufficient + * information to resolve component data. */ + for (int32_t t = 0; t < q->term_count; t ++) { + ecs_term_t *term = &q->terms[t]; + if (term->field_index == i) { + term->inout = EcsInOutNone; + } + } + } } - } - return result; -} - -static -ecs_var_id_t flecs_query_add_var_for_term_id( - ecs_query_impl_t *query, - ecs_term_ref_t *term_id, - ecs_vec_t *vars, - ecs_var_kind_t kind) -{ - const char *name = flecs_term_ref_var_name(term_id); - if (!name) { - return EcsVarNone; - } + /* Don't insert setvar instruction if all vars are anonymous */ + if (!only_anonymous) { + ecs_query_op_t set_vars = {0}; + set_vars.kind = EcsQuerySetVars; + flecs_query_op_insert(&set_vars, &ctx); + } - return flecs_query_add_var(query, name, vars, kind); -} + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } -/* This function walks over terms to discover which variables are used in the - * query. It needs to provide the following functionality: - * - create table vars for all variables used as source - * - create entity vars for all variables not used as source - * - create entity vars for all non-$this vars - * - create anonymous vars to store the content of wildcards - * - create anonymous vars to store result of lookups (for $var.child_name) - * - create anonymous vars for resolving component inheritance - * - create array that stores the source variable for each field - * - ensure table vars for non-$this variables are anonymous - * - ensure variables created inside scopes are anonymous - * - place anonymous variables after public variables in vars array - */ -static -int flecs_query_discover_vars( - ecs_stage_t *stage, - ecs_query_impl_t *query) -{ - ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ - ecs_vec_reset_t(NULL, vars, ecs_query_var_t); + if (query->vars[var_id].kind == EcsVarTable) { + var_id = flecs_query_find_var_id(query, query->vars[var_id].name, + EcsVarEntity); - ecs_term_t *terms = query->pub.terms; - int32_t a, i, anonymous_count = 0, count = query->pub.term_count; - int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; - bool table_this = false, entity_before_table_this = false; + /* Variables used as source that aren't This must be entities */ + ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } - /* For This table lookups during discovery. This will be overwritten after - * discovery with whether the query actually has a This table variable. */ - query->pub.flags |= EcsQueryHasTableThisVar; + query->src_vars[i] = var_id; + } + } - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *second = &term->second; - ecs_term_ref_t *src = &term->src; + ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { - /* Keep track of which variables are first used in scope, so that we - * can mark them as anonymous. Terms inside a scope are collapsed - * into a single result, which means that outside of the scope the - * value of those variables is undefined. */ - if (!scope) { - scoped_var_index = ecs_vec_count(vars); - } - scope ++; - continue; - } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { - if (!--scope) { - /* Any new variables declared after entering a scope should be - * marked as anonymous. */ - int32_t v; - for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { - ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; - } - } - continue; + /* If query is empty, insert Nothing instruction */ + if (!(term_count - ctx.skipped)) { + ecs_vec_clear(ctx.ops); + ecs_query_op_t nothing = {0}; + nothing.kind = EcsQueryNothing; + flecs_query_op_insert(¬hing, &ctx); + } else { + /* If query contains terms for toggleable components, insert toggle */ + if (!(q->flags & EcsQueryTableOnly)) { + flecs_query_insert_toggle(query, &ctx); } - ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( - query, first, vars, EcsVarEntity); - if (first_var_id == EcsVarNone) { - /* If first is not a variable, check if we need to insert anonymous - * variable for resolving component inheritance */ - if (term->flags_ & EcsTermIdInherited) { - anonymous_count += 2; /* table & entity variable */ - } + /* Insert yield. If program reaches this operation, a result was found */ + ecs_query_op_t yield = {0}; + yield.kind = EcsQueryYield; + flecs_query_op_insert(&yield, &ctx); + } - /* If first is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(first)) { - anonymous_count ++; - } - } + int32_t op_count = ecs_vec_count(ctx.ops); + if (op_count) { + query->op_count = op_count; + query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); + ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); + ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); + } - if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { - const char *var_name = flecs_term_ref_var_name(src); - if (var_name) { - ecs_var_id_t var_id = flecs_query_find_var_id( - query, var_name, EcsVarEntity); - if (var_id == EcsVarNone || var_id == first_var_id) { - var_id = flecs_query_add_var( - query, var_name, vars, EcsVarEntity); - } + return 0; +} - if (var_id != EcsVarNone) { - /* Mark variable as one for which we need to create a table - * variable. Don't create table variable now, so that we can - * store it in the non-public part of the variable array. */ - ecs_query_var_t *var = ecs_vec_get_t( - vars, ecs_query_var_t, (int32_t)var_id - 1); - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - if (!var->lookup) { - var->kind = EcsVarAny; - anonymous_table_count ++; - } +/** + * @file query/compiler/compiler_term.c + * @brief Compile query term. + */ - if (((1llu << term->field_index) & query->pub.data_fields)) { - /* Can't have an anonymous variable as source of a term - * that returns a component. We need to return each - * instance of the component, whereas anonymous - * variables are not guaranteed to be resolved to - * individual entities. */ - if (var->anonymous) { - ecs_err( - "can't use anonymous variable '%s' as source of " - "data term", var->name); - goto error; - } - } - /* Track which variable ids are used as field source */ - if (!query->src_vars) { - query->src_vars = flecs_calloc_n(&stage->allocator, - ecs_var_id_t, query->pub.field_count); - } +#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + +ecs_var_id_t flecs_query_find_var_id( + const ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - query->src_vars[term->field_index] = var_id; - } + if (kind == EcsVarTable) { + if (!ecs_os_strcmp(name, EcsThisName)) { + if (query->pub.flags & EcsQueryHasTableThisVar) { + return 0; } else { - if (flecs_term_ref_is_wildcard(src)) { - anonymous_count ++; - } - } - } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { - if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { - flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); - } - } - - if (flecs_query_add_var_for_term_id( - query, second, vars, EcsVarEntity) == EcsVarNone) - { - /* If second is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(second)) { - anonymous_count ++; + return EcsVarNone; } } - if (src->id & EcsIsVariable && second->id & EcsIsVariable) { - if (term->flags_ & EcsTermTransitive) { - /* Anonymous variable to store temporary id for finding - * targets for transitive relationship, see compile_term. */ - anonymous_count ++; - } + if (!flecs_name_index_is_init(&query->tvar_index)) { + return EcsVarNone; } - /* If member term, make sure source is available as entity */ - if (term->flags_ & EcsTermIsMember) { - flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); + uint64_t index = flecs_name_index_find( + &query->tvar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } + return flecs_utovar(index); + } - /* Track if a This entity variable is used before a potential This table - * variable. If this happens, the query has no This table variable */ - if (ECS_TERM_REF_ID(src) == EcsThis) { - table_this = true; + if (kind == EcsVarEntity) { + if (!flecs_name_index_is_init(&query->evar_index)) { + return EcsVarNone; } - if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { - if (!table_this) { - entity_before_table_this = true; - } + uint64_t index = flecs_name_index_find( + &query->evar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } + return flecs_utovar(index); } - int32_t var_count = ecs_vec_count(vars); - ecs_var_id_t placeholder = EcsVarNone - 1; - bool replace_placeholders = false; + ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); - /* Ensure lookup variables have table and/or entity variables */ - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->lookup) { - char *var_name = ecs_os_strdup(var->name); - var_name[var->lookup - var->name - 1] = '\0'; + /* If searching for any kind of variable, start with most specific */ + ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); + if (index != EcsVarNone) { + return index; + } - ecs_var_id_t base_table_id = flecs_query_find_var_id( - query, var_name, EcsVarTable); - if (base_table_id != EcsVarNone) { - var->table_id = base_table_id; - } else if (anonymous_table_count) { - /* Scan for implicit anonymous table variables that haven't been - * inserted yet (happens after this step). Doing this here vs. - * ensures that anonymous variables are appended at the end of - * the variable array, while also ensuring that variable ids are - * stable (no swapping of table var ids that are in use). */ - for (a = 0; a < var_count; a ++) { - ecs_query_var_t *avar = ecs_vec_get_t( - vars, ecs_query_var_t, a); - if (avar->kind == EcsVarAny) { - if (!ecs_os_strcmp(avar->name, var_name)) { - base_table_id = (ecs_var_id_t)(a + 1); - break; - } - } - } - if (base_table_id != EcsVarNone) { - /* Set marker so we can set the new table id afterwards */ - var->table_id = placeholder; - replace_placeholders = true; - } - } + return flecs_query_find_var_id(query, name, EcsVarTable); +} - ecs_var_id_t base_entity_id = flecs_query_find_var_id( - query, var_name, EcsVarEntity); - if (base_entity_id == EcsVarNone) { - /* Get name from table var (must exist). We can't use allocated - * name since variables don't own names. */ - const char *base_name = NULL; - if (base_table_id != EcsVarNone && base_table_id) { - ecs_query_var_t *base_table_var = ecs_vec_get_t( - vars, ecs_query_var_t, (int32_t)base_table_id - 1); - base_name = base_table_var->name; - } else { - base_name = EcsThisName; - } +static +ecs_var_id_t flecs_query_most_specific_var( + ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx) +{ + if (kind == EcsVarTable || kind == EcsVarEntity) { + return flecs_query_find_var_id(query, name, kind); + } - base_entity_id = flecs_query_add_var( - query, base_name, vars, EcsVarEntity); - var = ecs_vec_get_t(vars, ecs_query_var_t, i); - } + ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); + if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { + /* If entity variable is available and written to, it contains the most + * specific result and should be used. */ + return evar; + } - var->base_id = base_entity_id; + ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } - ecs_os_free(var_name); + /* If table var is written, and entity var doesn't exist or is not written, + * return table var */ + if (tvar != EcsVarNone) { + return tvar; + } else { + return evar; + } +} + +ecs_query_lbl_t flecs_query_op_insert( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); + int32_t count = ecs_vec_count(ctx->ops); + *elem = *op; + if (count > 1) { + if (ctx->cur->lbl_begin == -1) { + /* Variables written by previous instruction can't be written by + * this instruction, except when this is part of an OR chain. */ + elem->written &= ~elem[-1].written; } } - var_count = ecs_vec_count(vars); + elem->next = flecs_itolbl(count); + elem->prev = flecs_itolbl(count - 2); + return flecs_itolbl(count - 1); +} - /* Add non-This table variables */ - if (anonymous_table_count) { - anonymous_table_count = 0; - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->kind == EcsVarAny) { - var->kind = EcsVarEntity; +ecs_query_op_t* flecs_query_begin_block( + ecs_query_op_kind_t kind, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t op = {0}; + op.kind = flecs_ito(uint8_t, kind); + ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); + return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); +} - ecs_var_id_t var_id = flecs_query_add_var( - query, var->name, vars, EcsVarTable); - ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; - anonymous_table_count ++; - } - } +void flecs_query_end_block( + ecs_query_compile_ctx_t *ctx, + bool reset) +{ + ecs_query_op_t new_op = {0}; + new_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); + + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_begin].next = end; - var_count = ecs_vec_count(vars); + ecs_query_op_t *end_op = &ops[end]; + if (reset && ctx->cur->lbl_query != -1) { + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op->prev = ctx->cur->lbl_begin; + end_op->src = query_op->src; + end_op->first = query_op->first; + end_op->second = query_op->second; + end_op->flags = query_op->flags; + end_op->field_index = query_op->field_index; + } else { + end_op->prev = ctx->cur->lbl_begin; + end_op->field_index = -1; } - /* If any forward references to newly added anonymous tables exist, replace - * them with the actual table variable ids. */ - if (replace_placeholders) { - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->table_id == placeholder) { - char *var_name = ecs_os_strdup(var->name); - var_name[var->lookup - var->name - 1] = '\0'; + ctx->cur->lbl_begin = -1; +} - var->table_id = flecs_query_find_var_id( - query, var_name, EcsVarTable); - ecs_assert(var->table_id != EcsVarNone, - ECS_INTERNAL_ERROR, NULL); +static +void flecs_query_begin_block_cond_eval( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + ecs_write_flags_t cond_write_state) +{ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + ecs_write_flags_t cond_mask = 0; - ecs_os_free(var_name); - } - } + if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { + first_var = op->first.var; + ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << first_var); + } + if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { + second_var = op->second.var; + ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << second_var); } - - /* Always include spot for This variable, even if query doesn't use it */ - var_count ++; - - ecs_query_var_t *query_vars = &flecs_this_array; - if ((var_count + anonymous_count) > 1) { - query_vars = flecs_alloc(&stage->allocator, - (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * - (var_count + anonymous_count)); + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { + src_var = op->src.var; + ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << src_var); } - query->vars = query_vars; - query->var_count = var_count; - query->pub.var_count = flecs_ito(int8_t, var_count); - ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, - !entity_before_table_this); - query->var_size = var_count + anonymous_count; - - char **var_names; - if (query_vars != &flecs_this_array) { - query_vars[0].kind = EcsVarTable; - query_vars[0].name = NULL; - flecs_set_var_label(&query_vars[0], NULL); - query_vars[0].id = 0; - query_vars[0].table_id = EcsVarNone; - query_vars[0].lookup = NULL; - - var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), - var_count + anonymous_count); - var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); - } else { - var_names = &flecs_this_name_array; + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; } - query->pub.vars = (char**)var_names; + /* If this term uses conditionally set variables, insert instruction that + * jumps over the term if the variables weren't set yet. */ + if (cond_mask & cond_write_state) { + ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); - query_vars ++; - var_names ++; - var_count --; + ecs_query_op_t jmp_op = {0}; + jmp_op.kind = EcsQueryIfVar; - if (var_count) { - ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); - ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); - for (i = 0; i < var_count; i ++) { - ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], - ECS_INTERNAL_ERROR, NULL); - var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); + if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); + jmp_op.first.var = first_var; + } + if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); + jmp_op.second.var = second_var; + } + if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + jmp_op.src.var = src_var; } - } - - /* Hide anonymous table variables from application */ - query->pub.var_count = - flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - /* Sanity check to make sure that the public part of the variable array only - * contains entity variables. */ -#ifdef FLECS_DEBUG - for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { - ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); + flecs_query_op_insert(&jmp_op, ctx); + } else { + ctx->cur->lbl_cond_eval = -1; } -#endif - - return 0; -error: - return -1; } static -bool flecs_query_var_is_unknown( - ecs_query_impl_t *query, - ecs_var_id_t var_id, +void flecs_query_end_block_cond_eval( ecs_query_compile_ctx_t *ctx) { - ecs_query_var_t *vars = query->vars; - if (ctx->written & (1ull << var_id)) { - return false; - } else { - ecs_var_id_t table_var = vars[var_id].table_id; - if (table_var != EcsVarNone) { - return flecs_query_var_is_unknown(query, table_var, ctx); - } + if (ctx->cur->lbl_cond_eval == -1) { + return; } - return true; + + ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_query_op_t end_op = {0}; + end_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); + + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_cond_eval].next = end; + + ecs_query_op_t *end_op_ptr = &ops[end]; + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op_ptr->prev = ctx->cur->lbl_cond_eval; + end_op_ptr->src = query_op->src; + end_op_ptr->first = query_op->first; + end_op_ptr->second = query_op->second; + end_op_ptr->flags = query_op->flags; + end_op_ptr->field_index = query_op->field_index; } -/* Returns whether term is unknown. A term is unknown when it has variable - * elements (first, second, src) that are all unknown. */ static -bool flecs_query_term_is_unknown( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) +void flecs_query_begin_block_or( + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) { - ecs_query_op_t dummy = {0}; - flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, - &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, - &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, - &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); - - bool has_vars = dummy.flags & - ((EcsQueryIsVar << EcsQueryFirst) | - (EcsQueryIsVar << EcsQuerySecond) | - (EcsQueryIsVar << EcsQuerySrc)); - if (!has_vars) { - /* If term has no variables (typically terms with a static src) there - * can't be anything that's unknown. */ - return false; - } + ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); + or_op->kind = EcsQueryOr; + or_op->field_index = term->field_index; - if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { - if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { - return false; - } - } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { - return false; + /* Set the source of the evaluate terms as source of the Or instruction. + * This lets the engine determine whether the variable has already been + * written. When the source is not yet written, an OR operation needs to + * take the union of all the terms in the OR chain. When the variable is + * known, it will return after the first matching term. + * + * In case a term in the OR expression is an equality predicate which + * compares the left hand side with a variable, the variable acts as an + * alias, so we can always assume that it's written. */ + bool add_src = true; + if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { + if (!(flecs_query_is_written(op->src.var, ctx->written))) { + add_src = false; } } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { - return false; + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (add_src) { + or_op->flags = (EcsQueryIsVar << EcsQuerySrc); + or_op->src = op->src; + ctx->cur->src_or = op->src; } - } - return true; + ctx->cur->src_written_or = flecs_query_is_written( + op->src.var, ctx->written); + } } -/* Find the next known term from specified offset. This function is used to find - * a term that can be evaluated before a term that is unknown. Evaluating known - * before unknown terms can significantly decrease the search space. */ static -int32_t flecs_query_term_next_known( - ecs_query_impl_t *query, - ecs_query_compile_ctx_t *ctx, - int32_t offset, - ecs_flags64_t compiled) +void flecs_query_end_block_or( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) { - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; + ecs_query_op_t op = {0}; + op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); - for (i = offset; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (compiled & (1ull << i)) { - continue; + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + int32_t i, prev_or = ctx->cur->lbl_begin + 1; + for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { + if (ops[i].next == FlecsRuleOrMarker) { + if (i == (end - 1)) { + ops[prev_or].prev = ctx->cur->lbl_begin; + } else { + ops[prev_or].prev = flecs_itolbl(i + 1); + } + + ops[i].next = flecs_itolbl(end); + + prev_or = i + 1; } + } - /* Only evaluate And terms */ - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - continue; + ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; + bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); + first->next = flecs_itolbl(end); + ops[end].prev = ctx->cur->lbl_begin; + ops[end - 1].prev = ctx->cur->lbl_begin; + + ctx->ctrlflow->in_or = false; + ctx->cur->lbl_begin = -1; + if (src_is_var) { + ecs_var_id_t src_var = first->src.var; + ctx->written |= (1llu << src_var); + + /* If src is a table variable, it is possible that this was resolved to + * an entity variable in all of the OR terms. If this is the case, mark + * entity variable as written as well. */ + ecs_query_var_t *var = &impl->vars[src_var]; + if (var->kind == EcsVarTable) { + const char *name = var->name; + if (!name) { + name = "this"; + } + + ecs_var_id_t evar = flecs_query_find_var_id( + impl, name, EcsVarEntity); + if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { + ctx->written |= (1llu << evar); + ctx->cond_written &= ~(1llu << evar); + } } + } + ctx->written |= ctx->cond_written; + + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { + /* Skip variable if it's the source for the OR chain */ + if (src_is_var && (i == first->src.var)) { continue; } - if (flecs_query_term_is_unknown(query, term, ctx)) { - continue; + if (!prev && cur) { + ecs_query_op_t reset_op = {0}; + reset_op.kind = EcsQueryReset; + reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + reset_op.src.var = flecs_itovar(i); + flecs_query_op_insert(&reset_op, ctx); } - - return i; } +} - return -1; +void flecs_query_insert_each( + ecs_var_id_t tvar, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t each = {0}; + each.kind = EcsQueryEach; + each.src.var = evar; + each.first.var = tvar; + each.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &each.written); + flecs_query_op_insert(&each, ctx); } -/* If the first part of a query contains more than one trivial term, insert a - * special instruction which batch-evaluates multiple terms. */ static -void flecs_query_insert_trivial_search( +void flecs_query_insert_lookup( + ecs_var_id_t base_var, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t lookup = {0}; + lookup.kind = EcsQueryLookup; + lookup.src.var = evar; + lookup.first.var = base_var; + lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &lookup.written); + flecs_query_op_insert(&lookup, ctx); +} + +static +void flecs_query_insert_unconstrained_transitive( ecs_query_impl_t *query, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) { - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - int32_t i, term_count = q->term_count; - ecs_flags64_t trivial_set = 0; + /* Create anonymous variable to store the target ids. This will return the + * list of targets without constraining the variable of the term, which + * needs to stay variable to find all transitive relationships for a src. */ + ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); - /* Trivial search always ignores prefabs and disabled entities */ - if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { - return; - } + /* First, find ids to start traversal from. This fixes op.second. */ + ecs_query_op_t find_ids = {0}; + find_ids.kind = EcsQueryIdsRight; + find_ids.field_index = -1; + find_ids.first = op->first; + find_ids.second = op->second; + find_ids.flags = op->flags; + find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); + find_ids.second.var = tgt; + flecs_query_write_ctx(tgt, ctx, cond_write); + flecs_query_write(tgt, &find_ids.written); + flecs_query_op_insert(&find_ids, ctx); - /* Find trivial terms, which can be handled in single instruction */ - int32_t trivial_wildcard_terms = 0; - int32_t trivial_terms = 0; + /* Next, iterate all tables for the ids. This fixes op.src */ + ecs_query_op_t and_op = {0}; + and_op.kind = EcsQueryAnd; + and_op.field_index = op->field_index; + and_op.first = op->first; + and_op.second = op->second; + and_op.src = op->src; + and_op.flags = op->flags | EcsQueryIsSelf; + and_op.second.var = tgt; + flecs_query_write_ctx(and_op.src.var, ctx, cond_write); + flecs_query_write(and_op.src.var, &and_op.written); + flecs_query_op_insert(&and_op, ctx); +} - for (i = 0; i < term_count; i ++) { - /* Term is already compiled */ - if (*compiled & (1ull << i)) { - continue; - } +static +void flecs_query_insert_inheritance( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + /* Anonymous variable to store the resolved component ids */ + ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsTrivial)) { - continue; - } + flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); + flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); - /* We can only add trivial terms to plan if they no up traversal */ - if ((term->src.id & EcsTraverseFlags) != EcsSelf) { - continue; - } + ecs_query_op_t trav_op = {0}; + trav_op.kind = EcsQueryTrav; + trav_op.field_index = -1; + trav_op.first.entity = EcsIsA; + trav_op.second.entity = ECS_TERM_REF_ID(&term->first); + trav_op.src.var = tvar; + trav_op.flags = EcsQueryIsSelf; + trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); + trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + trav_op.written |= (1ull << tvar); + if (term->first.id & EcsSelf) { + trav_op.match_flags |= EcsTermReflexive; + } + flecs_query_op_insert(&trav_op, ctx); + flecs_query_insert_each(tvar, evar, ctx, cond_write); - /* Wildcards are not supported for trivial queries */ - if (ecs_id_is_wildcard(term->id)) { - continue; - } + ecs_query_ref_t r = { .var = evar }; + op->first = r; + op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op->flags |= (EcsQueryIsVar << EcsQueryFirst); +} - trivial_set |= (1llu << i); +void flecs_query_compile_term_ref( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_term_ref_t *term_ref, + ecs_query_ref_t *ref, + ecs_flags8_t ref_kind, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx, + bool create_wildcard_vars) +{ + (void)world; - trivial_terms ++; + if (!ecs_term_ref_is_set(term_ref)) { + return; } - if (trivial_terms >= 2) { - /* Mark terms as compiled & populated */ - for (i = 0; i < q->term_count; i ++) { - if (trivial_set & (1llu << i)) { - *compiled |= (1ull << i); + if (term_ref->id & EcsIsVariable) { + op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); + const char *name = flecs_term_ref_var_name(term_ref); + if (name) { + ref->var = flecs_query_most_specific_var(query, name, kind, ctx); + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if (create_wildcard_vars) { + bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); + if (is_wildcard && (kind == EcsVarAny)) { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + } else { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); } + if (is_wildcard) { + flecs_set_var_label(&query->vars[ref->var], + ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); + } + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } + } - /* If there's more than 1 trivial term, batch them in trivial search */ - ecs_query_op_t trivial = {0}; - if (!trivial_wildcard_terms) { - trivial.kind = EcsQueryTriv; - } - - /* Store the bitset with trivial terms on the instruction */ - trivial.src.entity = trivial_set; - flecs_query_op_insert(&trivial, ctx); - - /* Mark $this as written */ - ctx->written |= (1llu << 0); + if (term_ref->id & EcsIsEntity) { + op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); + ref->entity = ECS_TERM_REF_ID(term_ref); } } static -void flecs_query_insert_cache_search( +int flecs_query_compile_ensure_vars( ecs_query_impl_t *query, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx, + bool cond_write, + bool *written_out) { - if (!query->cache) { - return; - } + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + bool written = false; - ecs_query_t *q = &query->pub; + if (flags & EcsQueryIsVar) { + ecs_var_id_t var_id = ref->var; + ecs_query_var_t *var = &query->vars[var_id]; - if (q->cache_kind == EcsQueryCacheAll) { - /* If all terms are cacheable, make sure no other terms are compiled */ - *compiled = 0xFFFFFFFFFFFFFFFF; - } else if (q->cache_kind == EcsQueryCacheAuto) { - /* The query is partially cacheable */ - ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; + if (var->kind == EcsVarEntity && + !flecs_query_is_written(var_id, ctx->written)) + { + /* If entity variable is not yet written but a table variant exists + * that has been written, insert each operation to translate from + * entity variable to table */ + ecs_var_id_t tvar = var->table_id; + if ((tvar != EcsVarNone) && + flecs_query_is_written(tvar, ctx->written)) + { + if (var->lookup) { + if (!flecs_query_is_written(tvar, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } + + if (!flecs_query_is_written(var->base_id, ctx->written)) { + flecs_query_insert_each( + tvar, var->base_id, ctx, cond_write); + } + } else { + flecs_query_insert_each(tvar, var_id, ctx, cond_write); + } - for (i = 0; i < count; i ++) { - if ((*compiled) & (1ull << i)) { - continue; + /* Variable was written, just not as entity */ + written = true; + } else if (var->lookup) { + if (!flecs_query_is_written(var->base_id, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } } + } - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsCacheable)) { - continue; - } + written |= flecs_query_is_written(var_id, ctx->written); + } else { + /* If it's not a variable, it's always written */ + written = true; + } - *compiled |= (1ull << i); - } + if (written_out) { + *written_out = written; } - /* Insert the operation for cache traversal */ - ecs_query_op_t op = {0}; + return 0; +} - if (q->flags & EcsQueryIsCacheable) { - op.kind = EcsQueryIsCache; +static +bool flecs_query_compile_lookup( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_var_t *var = &query->vars[var_id]; + if (var->lookup) { + flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); + return true; } else { - op.kind = EcsQueryCache; + return false; } - - flecs_query_write(0, &op.written); - flecs_query_write_ctx(0, ctx, false); - flecs_query_op_insert(&op, ctx); } static -bool flecs_term_ref_match_multiple( - ecs_term_ref_t *ref) +void flecs_query_insert_contains( + ecs_query_impl_t *query, + ecs_var_id_t src_var, + ecs_var_id_t other_var, + ecs_query_compile_ctx_t *ctx) { - return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); + ecs_query_op_t contains = {0}; + if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { + contains.kind = EcsQueryContain; + contains.src.var = src_var; + contains.first.var = other_var; + contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_op_insert(&contains, ctx); + } } static -bool flecs_term_match_multiple( - ecs_term_t *term) +void flecs_query_insert_pair_eq( + int32_t field_index, + ecs_query_compile_ctx_t *ctx) { - return flecs_term_ref_match_multiple(&term->first) || - flecs_term_ref_match_multiple(&term->second); + ecs_query_op_t contains = {0}; + contains.kind = EcsQueryPairEq; + contains.field_index = flecs_ito(int8_t, field_index); + flecs_query_op_insert(&contains, ctx); } static -int flecs_query_insert_toggle( - ecs_query_impl_t *impl, - ecs_query_compile_ctx_t *ctx) +int flecs_query_compile_builtin_pred( + ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_write_flags_t write_state) { - ecs_query_t *q = &impl->pub; - int32_t i, j, term_count = q->term_count; - ecs_term_t *terms = q->terms; - ecs_flags64_t fields_done = 0; + ecs_entity_t id = ECS_TERM_REF_ID(&term->first); - for (i = 0; i < term_count; i ++) { - if (fields_done & (1llu << i)) { - continue; - } + ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; + ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; + ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; + + ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_term_t *term = &terms[i]; - if (term->flags_ & EcsTermIsToggle) { - ecs_query_op_t cur = {0}; - flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, - &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); + if (id == EcsPredEq) { + if (term->second.id & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); + } - ecs_flags64_t and_toggles = 0; - ecs_flags64_t not_toggles = 0; - ecs_flags64_t optional_toggles = 0; + if (flags_2nd & EcsQueryIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", ecs_query_var_name(q, op->second.var)); + return -1; + } + } - for (j = i; j < term_count; j ++) { - if (fields_done & (1llu << j)) { - continue; - } + ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); + (void)flags_src; - /* Also includes term[i], so flags get set correctly */ - term = &terms[j]; + if (!(write_state & (1ull << op->src.var))) { + /* If this is an == operator with a right-hand side that resolves to a + * single entity, the left-hand side is allowed to be undefined, as the + * instruction will be evaluated as an assignment. */ + if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { + ecs_err("uninitialized variable '%s' on left-hand side of " + "equality operator", ecs_query_var_name(q, op->src.var)); + return -1; + } + } - /* If term is not for the same src, skip */ - ecs_query_op_t next = {0}; - flecs_query_compile_term_ref(NULL, impl, &next, &term->src, - &next.src, EcsQuerySrc, EcsVarAny, ctx, false); - if (next.src.entity != cur.src.entity || - next.flags != cur.flags) - { - continue; - } + return 0; +} - /* Source matches, set flag */ - if (term->oper == EcsNot) { - not_toggles |= (1llu << j); - } else if (term->oper == EcsOptional) { - optional_toggles |= (1llu << j); - } else { - and_toggles |= (1llu << j); - } +static +int flecs_query_ensure_scope_var( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx) +{ + ecs_var_id_t var = ref->var; - fields_done |= (1llu << j); + if (query->vars[var].kind == EcsVarEntity && + !flecs_query_is_written(var, ctx->written)) + { + ecs_var_id_t table_var = query->vars[var].table_id; + if (table_var != EcsVarNone && + flecs_query_is_written(table_var, ctx->written)) + { + if (flecs_query_compile_ensure_vars( + query, op, ref, ref_kind, ctx, false, NULL)) + { + goto error; } + } + } - if (and_toggles || not_toggles) { - ecs_query_op_t op = {0}; - op.kind = EcsQueryToggle; - op.src = cur.src; - op.flags = cur.flags; + return 0; +error: + return -1; +} - if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_write(op.src.var, &op.written); - } +static +int flecs_query_ensure_scope_vars( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + ecs_term_t *term) +{ + /* If the scope uses variables as entity that have only been written as + * table, resolve them as entities before entering the scope. */ + ecs_term_t *cur = term; + while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { + /* Dummy operation to obtain variable information for term */ + ecs_query_op_t op = {0}; + flecs_query_compile_term_ref(world, query, &op, &cur->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(world, query, &op, &cur->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); - /* Encode fields: - * - first.entity is the fields that match enabled bits - * - second.entity is the fields that match disabled bits - */ - op.first.entity = and_toggles; - op.second.entity = not_toggles; - flecs_query_op_insert(&op, ctx); + if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.first, EcsQueryFirst, ctx)) + { + goto error; } + } - /* Insert separate instructions for optional terms. To make sure - * entities are returned in batches where fields are never partially - * set or unset, the result must be split up into batches that have - * the exact same toggle masks. Instead of complicating the toggle - * instruction with code to scan for blocks that have the same bits - * set, separate instructions let the query engine backtrack to get - * the right results. */ - if (optional_toggles) { - for (j = i; j < term_count; j ++) { - uint64_t field_bit = 1ull << j; - if (!(optional_toggles & field_bit)) { - continue; - } - - ecs_query_op_t op = {0}; - op.kind = EcsQueryToggleOption; - op.src = cur.src; - op.first.entity = field_bit; - op.flags = cur.flags; - flecs_query_op_insert(&op, ctx); - } + if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.second, EcsQuerySecond, ctx)) + { + goto error; } } + + cur ++; } return 0; +error: + return -1; } static -int flecs_query_insert_fixed_src_terms( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_flags64_t *compiled, +void flecs_query_compile_push( ecs_query_compile_ctx_t *ctx) { - ecs_query_t *q = &impl->pub; - int32_t i, term_count = q->term_count; - ecs_term_t *terms = q->terms; + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_begin = -1; + ctx->cur->lbl_begin = -1; +} - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; +static +void flecs_query_compile_pop( + ecs_query_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; +} - if (term->oper == EcsNot) { - /* If term has not operator and variables for first/second, we can't - * put the term first as this could prevent us from getting back - * valid results. For example: - * !$var(e), Tag($var) - * - * Here, the first term would evaluate to false (and cause the - * entire query not to match) if 'e' has any components. - * - * However, when reordering we get results: - * Tag($var), !$var(e) - * - * Now the query returns all entities with Tag, that 'e' does not - * have as component. For this reason, queries should never use - * unwritten variables in not terms- and we should also not reorder - * terms in a way that results in doing this. */ - if (flecs_term_match_multiple(term)) { - continue; - } +static +int flecs_query_compile_0_src( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + /* If the term has a 0 source, check if it's a scope open/close */ + if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { + if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { + goto error; } - - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { - continue; + if (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + flecs_query_begin_block(EcsQueryNot, ctx); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + flecs_query_compile_push(ctx); + } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { + flecs_query_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { + flecs_query_end_block(ctx, false); } + } else { + /* Noop */ + } - if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { - if (flecs_query_compile_term(world, impl, term, ctx)) { - return -1; - } + return 0; +error: + return -1; +} - *compiled |= (1llu << i); +static +ecs_flags32_t flecs_query_to_table_flags( + const ecs_query_t *q) +{ + ecs_flags32_t query_flags = q->flags; + if (!(query_flags & EcsQueryMatchDisabled) || + !(query_flags & EcsQueryMatchPrefab)) + { + ecs_flags32_t table_flags = EcsTableNotQueryable; + if (!(query_flags & EcsQueryMatchDisabled)) { + table_flags |= EcsTableIsDisabled; + } + if (!(query_flags & EcsQueryMatchPrefab)) { + table_flags |= EcsTableIsPrefab; } - } + return table_flags; + } return 0; } -int flecs_query_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_query_impl_t *query) +static +bool flecs_query_select_all( + const ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_var_id_t src_var, + ecs_query_compile_ctx_t *ctx) { - /* Compile query to operations. Only necessary for non-trivial queries, as - * trivial queries use trivial iterators that don't use query ops. */ - bool needs_plan = true; - ecs_flags32_t flags = query->pub.flags; - ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; - if ((flags & trivial_flags) == trivial_flags) { - if (query->cache) { - if (flags & EcsQueryIsCacheable) { - needs_plan = false; - } + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; + + if (term->oper == EcsNot || term->oper == EcsOptional || + term->oper == EcsNotFrom || pred_match) + { + ecs_query_op_t match_any = {0}; + match_any.kind = EcsAnd; + match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); + match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); + match_any.src = op->src; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; } else { - if (!(flags & EcsQueryMatchWildcards)) { - needs_plan = false; - } + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); } - } + match_any.written = (1ull << src_var); + match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); + flecs_query_op_insert(&match_any, ctx); + flecs_query_write_ctx(op->src.var, ctx, false); - if (!needs_plan) { - /* Initialize space for $this variable */ - query->pub.var_count = 1; - query->var_count = 1; - query->var_size = 1; - query->vars = &flecs_this_array; - query->pub.vars = &flecs_this_name_array; - query->pub.flags |= EcsQueryHasTableThisVar; - return 0; + /* Update write administration */ + return true; } + return false; +} - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - ecs_query_compile_ctx_t ctx = {0}; - ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); - ctx.ops = &stage->operations; - ctx.cur = ctx.ctrlflow; - ctx.cur->lbl_begin = -1; - ctx.cur->lbl_begin = -1; - ecs_vec_clear(ctx.ops); +#ifdef FLECS_META +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); - /* Find all variables defined in query */ - if (flecs_query_discover_vars(stage, query)) { + first_id = ECS_TERM_REF_ID(&term->first); + + /* First compile as if it's a regular term, to match the component */ + term->flags_ &= (uint16_t)~EcsTermIsMember; + + /* Replace term id with member parent (the component) */ + ecs_entity_t component = ecs_get_parent(world, first_id); + if (!component) { + ecs_err("member without parent in query"); return -1; } - /* If query contains fixed source terms, insert operation to set sources */ - int32_t i, term_count = q->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->src.id & EcsIsEntity) { - ecs_query_op_t set_fixed = {0}; - set_fixed.kind = EcsQuerySetFixed; - flecs_query_op_insert(&set_fixed, &ctx); - break; - } + if (!ecs_has(world, component, EcsComponent)) { + ecs_err("parent of member is not a component"); + return -1; + } + + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; + + term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); + term->second.id = 0; + term->id = component; + + ctx->oper = (ecs_oper_kind_t)term->oper; + if (term->oper == EcsNot && !second_wildcard) { + /* When matching a member term with not operator, we need to cover both + * the case where an entity doesn't have the component, and where it + * does have the component, but doesn't match the member. */ + term->oper = EcsOptional; } - /* If the query contains terms with fixed ids (no wildcards, variables), - * insert instruction that initializes ecs_iter_t::ids. This allows for the - * insertion of simpler instructions later on. - * If the query is entirely cacheable, ids are populated by the cache. */ - if (q->cache_kind != EcsQueryCacheAll) { - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (flecs_term_is_fixed_id(q, term) || - (term->src.id & EcsIsEntity && - !(term->src.id & ~EcsTermRefFlags))) - { - ecs_query_op_t set_ids = {0}; - set_ids.kind = EcsQuerySetIds; - flecs_query_op_insert(&set_ids, &ctx); - break; - } - } + return 0; +} + +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + ecs_entity_t component = ECS_TERM_REF_ID(&term->first); + const EcsComponent *comp = ecs_get(world, component, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Restore term values */ + term->id = term_id; + term->first.id = first_id; + term->second.id = second_id; + term->flags_ |= EcsTermIsMember; + term->oper = flecs_ito(int16_t, ctx->oper); + + first_id = ECS_TERM_REF_ID(&term->first); + const EcsMember *member = ecs_get(world, first_id, EcsMember); + ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_var_t *var = &impl->vars[op->src.var]; + const char *var_name = flecs_term_ref_var_name(&term->src); + ecs_var_id_t evar = flecs_query_find_var_id( + impl, var_name, EcsVarEntity); + + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; + + if (term->oper == EcsOptional) { + second_wildcard = true; } - ecs_flags64_t compiled = 0; + ecs_query_op_t mbr_op = *op; + mbr_op.kind = EcsQueryMemberEq; + mbr_op.first.entity = /* Encode type size and member offset */ + flecs_ito(uint32_t, member->offset) | + (flecs_ito(uint64_t, comp->size) << 32); - /* Always evaluate terms with fixed source before other terms */ - flecs_query_insert_fixed_src_terms( - world, query, &compiled, &ctx); + /* If this is a term with a Not operator, conditionally evaluate member on + * whether term was set by previous operation (see begin_member_term). */ + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + if (second_wildcard && ctx->oper == EcsNot) { + /* A !(T.value, *) term doesn't need special operations */ + return 0; + } - /* Compile cacheable terms */ - flecs_query_insert_cache_search(query, &compiled, &ctx); + /* Resolve to entity variable before entering if block, so that we + * don't have different branches of the query working with different + * versions of the same variable. */ + if (var->kind == EcsVarTable) { + flecs_query_insert_each(op->src.var, evar, ctx, cond_write); + var = &impl->vars[evar]; + } - /* Insert trivial term search if query allows for it */ - flecs_query_insert_trivial_search(query, &compiled, &ctx); + ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); + if_op->other = term->field_index; - /* If a query starts with one or more optional terms, first compile the non - * optional terms. This prevents having to insert an instruction that - * matches the query against every entity in the storage. - * Only skip optional terms at the start of the query so that any - * short-circuiting behavior isn't affected (a non-optional term can become - * optional if it uses a variable set in an optional term). */ - int32_t start_term = 0; - for (; start_term < term_count; start_term ++) { - if (terms[start_term].oper != EcsOptional) { - break; + if (ctx->oper == EcsNot) { + mbr_op.kind = EcsQueryMemberNeq; } } - do { - /* Compile remaining query terms to instructions */ - for (i = start_term; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int32_t compile = i; - - if (compiled & (1ull << i)) { - continue; /* Already compiled */ - } + if (var->kind == EcsVarTable) { + /* If MemberEq is called on table variable, store it on .other member. + * This causes MemberEq to do double duty as 'each' instruction, + * which is faster than having to go back & forth between instructions + * while finding matching values. */ + mbr_op.other = flecs_itolbl(op->src.var + 1); - if (term->oper == EcsOptional && start_term) { - /* Don't reorder past the first optional term that's not in the - * initial list of optional terms. This protects short - * circuiting branching in the query. - * A future algorithm could look at which variables are - * accessed by optional terms, and continue reordering terms - * that don't access those variables. */ - break; - } + /* Mark entity variable as written */ + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &mbr_op.written); + } - bool can_reorder = true; - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - can_reorder = false; - } + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, + &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - /* If variables have been written, but this term has no known variables, - * first try to resolve terms that have known variables. This can - * significantly reduce the search space. - * Only perform this optimization after at least one variable has been - * written to, as all terms are unknown otherwise. */ - if (can_reorder && ctx.written && - flecs_query_term_is_unknown(query, term, &ctx)) + if (second_wildcard) { + mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + mbr_op.second.entity = EcsWildcard; + } else { + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, + &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + + if (term->second.id & EcsIsVariable) { + if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, + EcsQuerySecond, ctx, cond_write, NULL)) { - int32_t term_index = flecs_query_term_next_known( - query, &ctx, i + 1, compiled); - if (term_index != -1) { - term = &q->terms[term_index]; - compile = term_index; - i --; /* Repeat current term */ - } - } - - if (flecs_query_compile_term(world, query, term, &ctx)) { - return -1; + goto error; } - compiled |= (1ull << compile); + flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); + flecs_query_write(mbr_op.second.var, &mbr_op.written); } + } - if (start_term) { - start_term = 0; /* Repeat, now also insert optional terms */ - } else { - break; - } - } while (true); + flecs_query_op_insert(&mbr_op, ctx); - ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); - if (this_id != EcsVarNone) { - /* If This variable has been written as entity, insert an operation to - * assign it to it.entities for consistency. */ - if (ctx.written & (1ull << this_id)) { - ecs_query_op_t set_this = {0}; - set_this.kind = EcsQuerySetThis; - set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); - set_this.first.var = this_id; - flecs_query_op_insert(&set_this, &ctx); - } + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + flecs_query_end_block(ctx, false); } - /* Make sure non-This variables are written as entities */ - if (query->vars) { - for (i = 0; i < query->var_count; i ++) { - ecs_query_var_t *var = &query->vars[i]; - if (var->id && var->kind == EcsVarTable && var->name) { - ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, - EcsVarEntity); - if (!flecs_query_is_written(var_id, ctx.written)) { - /* Skip anonymous variables */ - if (!flecs_query_var_is_anonymous(query, var_id)) { - flecs_query_insert_each(var->id, var_id, &ctx, false); - } - } - } - } - } + return 0; +error: + return -1; +} +#else +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + (void)world; (void)term; (void)ctx; (void)first_id; + return 0; +} - /* If query contains non-This variables as term source, build lookup array */ - if (query->src_vars) { - ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); - bool only_anonymous = true; +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; + (void)first_id; (void)second_id; (void)cond_write; + return 0; +} +#endif - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; - } +static +void flecs_query_mark_last_or_op( + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); + op_ptr->next = FlecsRuleOrMarker; +} - if (!flecs_query_var_is_anonymous(query, var_id)) { - only_anonymous = false; - break; - } else { - /* Don't fetch component data for anonymous variables. Because - * not all metadata (such as it.sources) is initialized for - * anonymous variables, and because they may only be available - * as table variables (each is not guaranteed to be inserted for - * anonymous variables) the iterator may not have sufficient - * information to resolve component data. */ - for (int32_t t = 0; t < q->term_count; t ++) { - ecs_term_t *term = &q->terms[t]; - if (term->field_index == i) { - term->inout = EcsInOutNone; - } +static +void flecs_query_set_op_kind( + ecs_query_op_t *op, + ecs_term_t *term, + bool src_is_var) +{ + /* Default instruction for And operators. If the source is fixed (like for + * singletons or terms with an entity source), use With, which like And but + * just matches against a source (vs. finding a source). */ + op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; + + /* Ignore cascade flag */ + ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); + + /* Handle *From operators */ + if (term->oper == EcsAndFrom) { + op->kind = EcsQueryAndFrom; + } else if (term->oper == EcsOrFrom) { + op->kind = EcsQueryOrFrom; + } else if (term->oper == EcsNotFrom) { + op->kind = EcsQueryNotFrom; + + /* If query is transitive, use Trav(ersal) instruction */ + } else if (term->flags_ & EcsTermTransitive) { + ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); + op->kind = EcsQueryTrav; + + /* If term queries for union pair, use union instruction */ + } else if (term->flags_ & EcsTermIsUnion) { + if (op->kind == EcsQueryAnd) { + op->kind = EcsQueryUnionEq; + if (term->oper == EcsNot) { + if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { + term->oper = EcsAnd; + op->kind = EcsQueryUnionNeq; } } + } else { + op->kind = EcsQueryUnionEqWith; } - /* Don't insert setvar instruction if all vars are anonymous */ - if (!only_anonymous) { - ecs_query_op_t set_vars = {0}; - set_vars.kind = EcsQuerySetVars; - flecs_query_op_insert(&set_vars, &ctx); - } - - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; + if ((term->src.id & trav_flags) == EcsUp) { + if (op->kind == EcsQueryUnionEq) { + op->kind = EcsQueryUnionEqUp; } - - if (query->vars[var_id].kind == EcsVarTable) { - var_id = flecs_query_find_var_id(query, query->vars[var_id].name, - EcsVarEntity); - - /* Variables used as source that aren't This must be entities */ - ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + if (op->kind == EcsQueryUnionEq) { + op->kind = EcsQueryUnionEqSelfUp; } - - query->src_vars[i] = var_id; } - } - - ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - - /* If query is empty, insert Nothing instruction */ - if (!(term_count - ctx.skipped)) { - ecs_vec_clear(ctx.ops); - ecs_query_op_t nothing = {0}; - nothing.kind = EcsQueryNothing; - flecs_query_op_insert(¬hing, &ctx); } else { - /* If query contains terms for toggleable components, insert toggle */ - if (!(q->flags & EcsQueryTableOnly)) { - flecs_query_insert_toggle(query, &ctx); + if ((term->src.id & trav_flags) == EcsUp) { + op->kind = EcsQueryUp; + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + op->kind = EcsQuerySelfUp; + } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + op->kind = EcsQueryAndAny; } - - /* Insert yield. If program reaches this operation, a result was found */ - ecs_query_op_t yield = {0}; - yield.kind = EcsQueryYield; - flecs_query_op_insert(&yield, &ctx); } +} - int32_t op_count = ecs_vec_count(ctx.ops); - if (op_count) { - query->op_count = op_count; - query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); - ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); - ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); +int flecs_query_compile_term( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_id_t term_id = term->id; + ecs_entity_t first_id = term->first.id; + ecs_entity_t second_id = term->second.id; + bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; + bool member_term = (term->flags_ & EcsTermIsMember) != 0; + if (member_term) { + flecs_query_compile_begin_member_term(world, term, ctx, first_id); } - return 0; -} - -/** - * @file query/compiler/compiler_term.c - * @brief Compile query term. - */ + ecs_query_t *q = &query->pub; + bool first_term = term == q->terms; + bool first_is_var = term->first.id & EcsIsVariable; + bool second_is_var = term->second.id & EcsIsVariable; + bool src_is_var = term->src.id & EcsIsVariable; + bool src_is_wildcard = src_is_var && + (ECS_TERM_REF_ID(&term->src) == EcsWildcard || + ECS_TERM_REF_ID(&term->src) == EcsAny); + bool src_is_lookup = false; + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool is_optional = (term->oper == EcsOptional); + bool is_or = flecs_term_is_or(q, term); + bool first_or = false, last_or = false; + bool cond_write = term->oper == EcsOptional || is_or; + ecs_query_op_t op = {0}; + if (is_or) { + first_or = first_term || (term[-1].oper != EcsOr); + last_or = term->oper != EcsOr; + } -#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || + term->oper == EcsNotFrom) + { + const ecs_type_t *type = ecs_get_type(world, term->id); + if (!type) { + /* Empty type for id in *From operation is a noop */ + ctx->skipped ++; + return 0; + } -ecs_var_id_t flecs_query_find_var_id( - const ecs_query_impl_t *query, - const char *name, - ecs_var_kind_t kind) -{ - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = type->count; + ecs_id_t *ti_ids = type->array; - if (kind == EcsVarTable) { - if (!ecs_os_strcmp(name, EcsThisName)) { - if (query->pub.flags & EcsQueryHasTableThisVar) { - return 0; - } else { - return EcsVarNone; + for (i = 0; i < count; i ++) { + ecs_id_t ti_id = ti_ids[i]; + ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); + if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + break; } } - if (!flecs_name_index_is_init(&query->tvar_index)) { - return EcsVarNone; + if (i == count) { + /* Type did not contain any ids to perform operation on */ + ctx->skipped ++; + return 0; } + } - uint64_t index = flecs_name_index_find( - &query->tvar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; - } - return flecs_utovar(index); + /* !_ (don't match anything) terms always return nothing. */ + if (term->oper == EcsNot && term->id == EcsAny) { + op.kind = EcsQueryNothing; + flecs_query_op_insert(&op, ctx); + return 0; } - if (kind == EcsVarEntity) { - if (!flecs_name_index_is_init(&query->evar_index)) { - return EcsVarNone; + if (first_or) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } else if (is_or) { + ctx->written = ctx->ctrlflow->written_or; + } + + if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { + if (flecs_query_compile_0_src(world, query, term, ctx)) { + goto error; } + return 0; + } - uint64_t index = flecs_name_index_find( - &query->evar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; + if (builtin_pred) { + ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); + if (id_noflags == EcsWildcard || id_noflags == EcsAny) { + /* Noop */ + return 0; } - return flecs_utovar(index); } - ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); + op.field_index = flecs_ito(int8_t, term->field_index); + op.term_index = flecs_ito(int8_t, term - q->terms); - /* If searching for any kind of variable, start with most specific */ - ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); - if (index != EcsVarNone) { - return index; + flecs_query_set_op_kind(&op, term, src_is_var); + + bool is_not = (term->oper == EcsNot) && !builtin_pred; + + /* Save write state at start of term so we can use it to reliably track + * variables got written by this term. */ + ecs_write_flags_t cond_write_state = ctx->cond_written; + + /* Resolve variables and entities for operation arguments */ + flecs_query_compile_term_ref(world, query, &op, &term->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarAny, ctx, true); + + bool src_written = true; + if (src_is_var) { + src_is_lookup = query->vars[op.src.var].lookup != NULL; + src_written = flecs_query_is_written(op.src.var, ctx->written); } - return flecs_query_find_var_id(query, name, EcsVarTable); -} + /* Insert each instructions for table -> entity variable if needed */ + bool first_written, second_written; + if (flecs_query_compile_ensure_vars( + query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) + { + goto error; + } -static -ecs_var_id_t flecs_query_most_specific_var( - ecs_query_impl_t *query, - const char *name, - ecs_var_kind_t kind, - ecs_query_compile_ctx_t *ctx) -{ - if (kind == EcsVarTable || kind == EcsVarEntity) { - return flecs_query_find_var_id(query, name, kind); + if (flecs_query_compile_ensure_vars( + query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) + { + goto error; } - ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); - if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { - /* If entity variable is available and written to, it contains the most - * specific result and should be used. */ - return evar; + /* Store write state of variables for first OR term in chain which will get + * restored for the other terms in the chain, so that all OR terms make the + * same assumptions about which variables were already written. */ + if (first_or) { + ctx->ctrlflow->written_or = ctx->written; + } + + /* If an optional or not term is inserted for a source that's not been + * written to yet, insert instruction that selects all entities so we have + * something to match the optional/not against. */ + if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { + src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); + } + + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { + if (query->vars[op.src.var].kind == EcsVarTable) { + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); + ctx->ctrlflow->written_or |= (1llu << op.src.var); + } } - ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); - if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { - /* If variable of any kind is requested and variable hasn't been written - * yet, write to table variable */ - return tvar; + if (flecs_query_compile_ensure_vars( + query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) + { + goto error; } - /* If table var is written, and entity var doesn't exist or is not written, - * return table var */ - if (tvar != EcsVarNone) { - return tvar; - } else { - return evar; - } -} + /* If source is Any (_) and first and/or second are unconstrained, insert an + * ids instruction instead of an And */ + if (term->flags_ & EcsTermMatchAnySrc) { + op.kind = EcsQueryIds; + /* Use up-to-date written values after potentially inserting each */ + if (!first_written || !second_written) { + if (!first_written) { + /* If first is unknown, traverse left: <- (*, t) */ + if (ECS_TERM_REF_ID(&term->first) != EcsAny) { + op.kind = EcsQueryIdsLeft; + } + } else { + /* If second is wildcard, traverse right: (r, *) -> */ + if (ECS_TERM_REF_ID(&term->second) != EcsAny) { + op.kind = EcsQueryIdsRight; + } + } + op.src.entity = 0; + src_is_var = false; + op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); + } -ecs_query_lbl_t flecs_query_op_insert( - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); - int32_t count = ecs_vec_count(ctx->ops); - *elem = *op; - if (count > 1) { - if (ctx->cur->lbl_begin == -1) { - /* Variables written by previous instruction can't be written by - * this instruction, except when this is part of an OR chain. */ - elem->written &= ~elem[-1].written; + /* If source variable is not written and we're querying just for Any, insert + * a dedicated instruction that uses the Any record in the id index. Any + * queries that are evaluated against written sources can use Wildcard + * records, which is what the AndAny instruction does. */ + } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { + /* Lookup variables ($var.child_name) are always written */ + if (!src_is_lookup) { + op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ } } - elem->next = flecs_itolbl(count); - elem->prev = flecs_itolbl(count - 2); - return flecs_itolbl(count - 1); -} + /* If this is a transitive term and both the target and source are unknown, + * find the targets for the relationship first. This clusters together + * tables for the same target, which allows for more efficient usage of the + * traversal caches. */ + if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { + if (!src_written && !second_written) { + flecs_query_insert_unconstrained_transitive( + query, &op, ctx, cond_write); + } + } -ecs_query_op_t* flecs_query_begin_block( - ecs_query_op_kind_t kind, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t op = {0}; - op.kind = flecs_ito(uint8_t, kind); - ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); - return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); -} + /* Check if this term has variables that have been conditionally written, + * like variables written by an optional term. */ + if (ctx->cond_written) { + if (!is_or || first_or) { + flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); + } + } -void flecs_query_end_block( - ecs_query_compile_ctx_t *ctx, - bool reset) -{ - ecs_query_op_t new_op = {0}; - new_op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); - - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - ops[ctx->cur->lbl_begin].next = end; + /* If term can toggle and is Not, change operator to Optional as we + * have to match entities that have the component but disabled. */ + if (toggle_term && is_not) { + is_not = false; + is_optional = true; + } - ecs_query_op_t *end_op = &ops[end]; - if (reset && ctx->cur->lbl_query != -1) { - ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; - end_op->prev = ctx->cur->lbl_begin; - end_op->src = query_op->src; - end_op->first = query_op->first; - end_op->second = query_op->second; - end_op->flags = query_op->flags; - end_op->field_index = query_op->field_index; - } else { - end_op->prev = ctx->cur->lbl_begin; - end_op->field_index = -1; + /* Handle Not, Optional, Or operators */ + if (is_not) { + flecs_query_begin_block(EcsQueryNot, ctx); + } else if (is_optional) { + flecs_query_begin_block(EcsQueryOptional, ctx); + } else if (first_or) { + flecs_query_begin_block_or(&op, term, ctx); } - ctx->cur->lbl_begin = -1; -} + /* If term has component inheritance enabled, insert instruction to walk + * down the relationship tree of the id. */ + if (term->flags_ & EcsTermIdInherited) { + flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); + } -static -void flecs_query_begin_block_cond_eval( - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - ecs_write_flags_t cond_write_state) -{ - ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; - ecs_write_flags_t cond_mask = 0; + op.match_flags = term->flags_; - if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { - first_var = op->first.var; - ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << first_var); - } - if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { - second_var = op->second.var; - ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << second_var); - } - if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { - src_var = op->src.var; - ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << src_var); + ecs_write_flags_t write_state = ctx->written; + if (first_is_var) { + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op.flags |= (EcsQueryIsVar << EcsQueryFirst); } - /* Variables set in an OR chain are marked as conditional writes. However, - * writes from previous terms in the current OR chain shouldn't be treated - * as variables that are conditionally set, so instead use the write mask - * from before the chain started. */ - if (ctx->ctrlflow->in_or) { - cond_write_state = ctx->ctrlflow->cond_written_or; + if (term->src.id & EcsSelf) { + op.flags |= EcsQueryIsSelf; } - /* If this term uses conditionally set variables, insert instruction that - * jumps over the term if the variables weren't set yet. */ - if (cond_mask & cond_write_state) { - ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); - - ecs_query_op_t jmp_op = {0}; - jmp_op.kind = EcsQueryIfVar; - - if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); - jmp_op.first.var = first_var; + /* Insert instructions for lookup variables */ + if (first_is_var) { + if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { + write_state |= (1ull << op.first.var); // lookups are resolved inline } - if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); - jmp_op.second.var = second_var; + } + if (src_is_var) { + if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { + write_state |= (1ull << op.src.var); // lookups are resolved inline } - if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - jmp_op.src.var = src_var; + } + if (second_is_var) { + if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { + write_state |= (1ull << op.second.var); // lookups are resolved inline } - - flecs_query_op_insert(&jmp_op, ctx); - } else { - ctx->cur->lbl_cond_eval = -1; } -} -static -void flecs_query_end_block_cond_eval( - ecs_query_compile_ctx_t *ctx) -{ - if (ctx->cur->lbl_cond_eval == -1) { - return; + if (builtin_pred) { + if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { + goto error; + } } - ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_query_op_t end_op = {0}; - end_op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); - - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - ops[ctx->cur->lbl_cond_eval].next = end; - - ecs_query_op_t *end_op_ptr = &ops[end]; - ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; - end_op_ptr->prev = ctx->cur->lbl_cond_eval; - end_op_ptr->src = query_op->src; - end_op_ptr->first = query_op->first; - end_op_ptr->second = query_op->second; - end_op_ptr->flags = query_op->flags; - end_op_ptr->field_index = query_op->field_index; -} + /* If we're writing the $this variable, filter out disabled/prefab entities + * unless the query explicitly matches them. + * This could've been done with regular With instructions, but since + * filtering out disabled/prefab entities is the default and this check is + * cheap to perform on table flags, it's worth special casing. */ + if (!src_written && op.src.var == 0) { + op.other = flecs_itolbl(flecs_query_to_table_flags(q)); + } -static -void flecs_query_begin_block_or( - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); - or_op->kind = EcsQueryOr; - or_op->field_index = term->field_index; + /* After evaluating a term, a used variable is always written */ + if (src_is_var) { + flecs_query_write(op.src.var, &op.written); + flecs_query_write_ctx(op.src.var, ctx, cond_write); + } + if (first_is_var) { + flecs_query_write(op.first.var, &op.written); + flecs_query_write_ctx(op.first.var, ctx, cond_write); + } + if (second_is_var) { + flecs_query_write(op.second.var, &op.written); + flecs_query_write_ctx(op.second.var, ctx, cond_write); + } + + flecs_query_op_insert(&op, ctx); - /* Set the source of the evaluate terms as source of the Or instruction. - * This lets the engine determine whether the variable has already been - * written. When the source is not yet written, an OR operation needs to - * take the union of all the terms in the OR chain. When the variable is - * known, it will return after the first matching term. - * - * In case a term in the OR expression is an equality predicate which - * compares the left hand side with a variable, the variable acts as an - * alias, so we can always assume that it's written. */ - bool add_src = true; - if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { - if (!(flecs_query_is_written(op->src.var, ctx->written))) { - add_src = false; + ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + if (is_or && !member_term) { + flecs_query_mark_last_or_op(ctx); + } + + /* Handle self-references between src and first/second variables */ + if (src_is_var) { + if (first_is_var) { + flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); + } + if (second_is_var && op.first.var != op.second.var) { + flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); } } - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (add_src) { - or_op->flags = (EcsQueryIsVar << EcsQuerySrc); - or_op->src = op->src; - ctx->cur->src_or = op->src; + /* Handle self references between first and second variables */ + if (!ecs_id_is_wildcard(first_id)) { + if (first_is_var && !first_written && (op.first.var == op.second.var)) { + flecs_query_insert_pair_eq(term->field_index, ctx); } + } - ctx->cur->src_written_or = flecs_query_is_written( - op->src.var, ctx->written); + /* Handle closing of Not, Optional and Or operators */ + if (is_not) { + flecs_query_end_block(ctx, true); + } else if (is_optional) { + flecs_query_end_block(ctx, true); } -} -static -void flecs_query_end_block_or( - ecs_query_impl_t *impl, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t op = {0}; - op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); + /* Now that the term is resolved, evaluate member of component */ + if (member_term) { + flecs_query_compile_end_member_term(world, query, &op, term, ctx, + term_id, first_id, second_id, cond_write); + if (is_or) { + flecs_query_mark_last_or_op(ctx); + } + } - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - int32_t i, prev_or = ctx->cur->lbl_begin + 1; - for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { - if (ops[i].next == FlecsRuleOrMarker) { - if (i == (end - 1)) { - ops[prev_or].prev = ctx->cur->lbl_begin; - } else { - ops[prev_or].prev = flecs_itolbl(i + 1); - } + if (last_or) { + flecs_query_end_block_or(query, ctx); + } - ops[i].next = flecs_itolbl(end); + /* Handle closing of conditional evaluation */ + if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { + if (!is_or || last_or) { + flecs_query_end_block_cond_eval(ctx); + } + } - prev_or = i + 1; + /* Ensure that term id is set after evaluating Not */ + if (term->flags_ & EcsTermIdInherited) { + if (is_not) { + ecs_query_op_t set_id = {0}; + set_id.kind = EcsQuerySetId; + set_id.first.entity = term->id; + set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); + set_id.field_index = flecs_ito(int8_t, term->field_index); + flecs_query_op_insert(&set_id, ctx); } } - ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; - bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); - first->next = flecs_itolbl(end); - ops[end].prev = ctx->cur->lbl_begin; - ops[end - 1].prev = ctx->cur->lbl_begin; + return 0; +error: + return -1; +} - ctx->ctrlflow->in_or = false; - ctx->cur->lbl_begin = -1; - if (src_is_var) { - ecs_var_id_t src_var = first->src.var; - ctx->written |= (1llu << src_var); +/** + * @file query/engine/cache.c + * @brief Cached query implementation. + */ - /* If src is a table variable, it is possible that this was resolved to - * an entity variable in all of the OR terms. If this is the case, mark - * entity variable as written as well. */ - ecs_query_var_t *var = &impl->vars[src_var]; - if (var->kind == EcsVarTable) { - const char *name = var->name; - if (!name) { - name = "this"; - } - ecs_var_id_t evar = flecs_query_find_var_id( - impl, name, EcsVarEntity); - if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { - ctx->written |= (1llu << evar); - ctx->cond_written &= ~(1llu << evar); - } - } - } - ctx->written |= ctx->cond_written; +int32_t flecs_query_cache_table_count( + ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + return cache->cache.tables.count; +} - /* Scan which variables were conditionally written in the OR chain and - * reset instructions after the OR chain. If a variable is set in part one - * of a chain but not part two, there would be nothing writing to the - * variable in part two, leaving it to the previous value. To address this - * a reset is inserted that resets the variable value on redo. */ - for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { - ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); - ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); +int32_t flecs_query_cache_empty_table_count( + ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + return cache->cache.empty_tables.count; +} - /* Skip variable if it's the source for the OR chain */ - if (src_is_var && (i == first->src.var)) { - continue; - } +int32_t flecs_query_cache_entity_count( + const ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + + int32_t result = 0; + ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; + if (!last) { + return 0; + } - if (!prev && cur) { - ecs_query_op_t reset_op = {0}; - reset_op.kind = EcsQueryReset; - reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - reset_op.src.var = flecs_itovar(i); - flecs_query_op_insert(&reset_op, ctx); - } + for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { + result += ecs_table_count(cur->table); } -} -void flecs_query_insert_each( - ecs_var_id_t tvar, - ecs_var_id_t evar, - ecs_query_compile_ctx_t *ctx, - bool cond_write) -{ - ecs_query_op_t each = {0}; - each.kind = EcsQueryEach; - each.src.var = evar; - each.first.var = tvar; - each.flags = (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &each.written); - flecs_query_op_insert(&each, ctx); + return result; } static -void flecs_query_insert_lookup( - ecs_var_id_t base_var, - ecs_var_id_t evar, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +uint64_t flecs_query_cache_get_group_id( + ecs_query_cache_t *cache, + ecs_table_t *table) { - ecs_query_op_t lookup = {0}; - lookup.kind = EcsQueryLookup; - lookup.src.var = evar; - lookup.first.var = base_var; - lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &lookup.written); - flecs_query_op_insert(&lookup, ctx); + if (cache->group_by_callback) { + return cache->group_by_callback(cache->query->world, table, + cache->group_by, cache->group_by_ctx); + } else { + return 0; + } } static -void flecs_query_insert_unconstrained_transitive( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +void flecs_query_cache_compute_group_id( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - /* Create anonymous variable to store the target ids. This will return the - * list of targets without constraining the variable of the term, which - * needs to stay variable to find all transitive relationships for a src. */ - ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - /* First, find ids to start traversal from. This fixes op.second. */ - ecs_query_op_t find_ids = {0}; - find_ids.kind = EcsQueryIdsRight; - find_ids.field_index = -1; - find_ids.first = op->first; - find_ids.second = op->second; - find_ids.flags = op->flags; - find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); - find_ids.second.var = tgt; - flecs_query_write_ctx(tgt, ctx, cond_write); - flecs_query_write(tgt, &find_ids.written); - flecs_query_op_insert(&find_ids, ctx); + if (cache->group_by_callback) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* Next, iterate all tables for the ids. This fixes op.src */ - ecs_query_op_t and_op = {0}; - and_op.kind = EcsQueryAnd; - and_op.field_index = op->field_index; - and_op.first = op->first; - and_op.second = op->second; - and_op.src = op->src; - and_op.flags = op->flags | EcsQueryIsSelf; - and_op.second.var = tgt; - flecs_query_write_ctx(and_op.src.var, ctx, cond_write); - flecs_query_write(and_op.src.var, &and_op.written); - flecs_query_op_insert(&and_op, ctx); + match->group_id = flecs_query_cache_get_group_id(cache, table); + } else { + match->group_id = 0; + } } static -void flecs_query_insert_inheritance( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +ecs_query_cache_table_list_t* flecs_query_cache_get_group( + const ecs_query_cache_t *cache, + uint64_t group_id) { - /* Anonymous variable to store the resolved component ids */ - ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + return ecs_map_get_deref( + &cache->groups, ecs_query_cache_table_list_t, group_id); +} - flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); +static +ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( + ecs_query_cache_t *cache, + uint64_t id) +{ + ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, + ecs_query_cache_table_list_t, id); - ecs_query_op_t trav_op = {0}; - trav_op.kind = EcsQueryTrav; - trav_op.field_index = -1; - trav_op.first.entity = EcsIsA; - trav_op.second.entity = ECS_TERM_REF_ID(&term->first); - trav_op.src.var = tvar; - trav_op.flags = EcsQueryIsSelf; - trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); - trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - trav_op.written |= (1ull << tvar); - if (term->first.id & EcsSelf) { - trav_op.match_flags |= EcsTermReflexive; + if (!group) { + group = ecs_map_insert_alloc_t(&cache->groups, + ecs_query_cache_table_list_t, id); + ecs_os_zeromem(group); + if (cache->on_group_create) { + group->info.ctx = cache->on_group_create( + cache->query->world, id, cache->group_by_ctx); + } } - flecs_query_op_insert(&trav_op, ctx); - flecs_query_insert_each(tvar, evar, ctx, cond_write); - ecs_query_ref_t r = { .var = evar }; - op->first = r; - op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op->flags |= (EcsQueryIsVar << EcsQueryFirst); + return group; } -void flecs_query_compile_term_ref( - ecs_world_t *world, - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_term_ref_t *term_ref, - ecs_query_ref_t *ref, - ecs_flags8_t ref_kind, - ecs_var_kind_t kind, - ecs_query_compile_ctx_t *ctx, - bool create_wildcard_vars) +static +void flecs_query_cache_remove_group( + ecs_query_cache_t *cache, + uint64_t id) { - (void)world; - - if (!ecs_term_ref_is_set(term_ref)) { - return; - } - - if (term_ref->id & EcsIsVariable) { - op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); - const char *name = flecs_term_ref_var_name(term_ref); - if (name) { - ref->var = flecs_query_most_specific_var(query, name, kind, ctx); - ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - } else if (create_wildcard_vars) { - bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); - if (is_wildcard && (kind == EcsVarAny)) { - ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - } else { - ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - } - if (is_wildcard) { - flecs_set_var_label(&query->vars[ref->var], - ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); - } - ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + if (cache->on_group_delete) { + ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, + ecs_query_cache_table_list_t, id); + if (group) { + cache->on_group_delete(cache->query->world, id, + group->info.ctx, cache->group_by_ctx); } } - if (term_ref->id & EcsIsEntity) { - op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); - ref->entity = ECS_TERM_REF_ID(term_ref); + ecs_map_remove_free(&cache->groups, id); +} + +static +uint64_t flecs_query_cache_default_group_by( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)ctx; + + ecs_id_t match; + if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { + return ecs_pair_second(world, match); } + return 0; } +/* Find the last node of the group after which this group should be inserted */ static -int flecs_query_compile_ensure_vars( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - ecs_query_compile_ctx_t *ctx, - bool cond_write, - bool *written_out) +ecs_query_cache_table_match_t* flecs_query_cache_find_group_insertion_node( + ecs_query_cache_t *cache, + uint64_t group_id) { - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - bool written = false; + /* Grouping must be enabled */ + ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); - if (flags & EcsQueryIsVar) { - ecs_var_id_t var_id = ref->var; - ecs_query_var_t *var = &query->vars[var_id]; + ecs_map_iter_t it = ecs_map_iter(&cache->groups); + ecs_query_cache_table_list_t *list, *closest_list = NULL; + uint64_t id, closest_id = 0; + + bool desc = false; - if (var->kind == EcsVarEntity && - !flecs_query_is_written(var_id, ctx->written)) - { - /* If entity variable is not yet written but a table variant exists - * that has been written, insert each operation to translate from - * entity variable to table */ - ecs_var_id_t tvar = var->table_id; - if ((tvar != EcsVarNone) && - flecs_query_is_written(tvar, ctx->written)) - { - if (var->lookup) { - if (!flecs_query_is_written(tvar, ctx->written)) { - ecs_err("dependent variable of '$%s' is not written", - var->name); - return -1; - } + if (cache->cascade_by) { + desc = (cache->query->terms[ + cache->cascade_by - 1].src.id & EcsDesc) != 0; + } - if (!flecs_query_is_written(var->base_id, ctx->written)) { - flecs_query_insert_each( - tvar, var->base_id, ctx, cond_write); - } - } else { - flecs_query_insert_each(tvar, var_id, ctx, cond_write); - } + /* Find closest smaller group id */ + while (ecs_map_next(&it)) { + id = ecs_map_key(&it); - /* Variable was written, just not as entity */ - written = true; - } else if (var->lookup) { - if (!flecs_query_is_written(var->base_id, ctx->written)) { - ecs_err("dependent variable of '$%s' is not written", - var->name); - return -1; - } + if (!desc) { + if (id >= group_id) { + continue; + } + } else { + if (id <= group_id) { + continue; } } - written |= flecs_query_is_written(var_id, ctx->written); + list = ecs_map_ptr(&it); + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } + + bool comp; + if (!desc) { + comp = ((group_id - id) < (group_id - closest_id)); + } else { + comp = ((group_id - id) > (group_id - closest_id)); + } + + if (!closest_list || comp) { + closest_id = id; + closest_list = list; + } + } + + if (closest_list) { + return closest_list->last; } else { - /* If it's not a variable, it's always written */ - written = true; + return NULL; /* Group should be first in query */ } +} - if (written_out) { - *written_out = written; +/* Initialize group with first node */ +static +void flecs_query_cache_create_group( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) +{ + uint64_t group_id = match->group_id; + + /* If query has grouping enabled & this is a new/empty group, find + * the insertion point for the group */ + ecs_query_cache_table_match_t *insert_after = + flecs_query_cache_find_group_insertion_node(cache, group_id); + + if (!insert_after) { + /* This group should appear first in the query list */ + ecs_query_cache_table_match_t *query_first = cache->list.first; + if (query_first) { + /* If this is not the first match for the query, insert before it */ + match->next = query_first; + query_first->prev = match; + cache->list.first = match; + } else { + /* If this is the first match of the query, initialize its list */ + ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.first = match; + cache->list.last = match; + } + } else { + ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This group should appear after another group */ + ecs_query_cache_table_match_t *insert_before = insert_after->next; + match->prev = insert_after; + insert_after->next = match; + match->next = insert_before; + if (insert_before) { + insert_before->prev = match; + } else { + ecs_assert(cache->list.last == insert_after, + ECS_INTERNAL_ERROR, NULL); + + /* This group should appear last in the query list */ + cache->list.last = match; + } } - - return 0; } +/* Find the list the node should be part of */ static -bool flecs_query_compile_lookup( - ecs_query_impl_t *query, - ecs_var_id_t var_id, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_query_var_t *var = &query->vars[var_id]; - if (var->lookup) { - flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); - return true; + if (cache->group_by_callback) { + return flecs_query_cache_get_group(cache, match->group_id); } else { - return false; + return &cache->list; } } +/* Find or create the list the node should be part of */ static -void flecs_query_insert_contains( - ecs_query_impl_t *query, - ecs_var_id_t src_var, - ecs_var_id_t other_var, - ecs_query_compile_ctx_t *ctx) +ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_query_op_t contains = {0}; - if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { - contains.kind = EcsQueryContain; - contains.src.var = src_var; - contains.first.var = other_var; - contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_op_insert(&contains, ctx); + if (cache->group_by_callback) { + return flecs_query_cache_ensure_group(cache, match->group_id); + } else { + return &cache->list; } } +/* Remove node from list */ static -void flecs_query_insert_pair_eq( - int32_t field_index, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t contains = {0}; - contains.kind = EcsQueryPairEq; - contains.field_index = flecs_ito(int8_t, field_index); - flecs_query_op_insert(&contains, ctx); -} - -static -int flecs_query_compile_builtin_pred( - ecs_query_t *q, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_write_flags_t write_state) +void flecs_query_cache_remove_table_node( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_entity_t id = ECS_TERM_REF_ID(&term->first); + ecs_query_cache_table_match_t *prev = match->prev; + ecs_query_cache_table_match_t *next = match->next; - ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; - ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; - ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; - - ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); - if (id == EcsPredEq) { - if (term->second.id & EcsIsName) { - op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); - } else { - op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); - } - } else if (id == EcsPredMatch) { - op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); - } + ecs_query_cache_table_list_t *list = + flecs_query_cache_get_node_list(cache, match); - if (flags_2nd & EcsQueryIsVar) { - if (!(write_state & (1ull << op->second.var))) { - ecs_err("uninitialized variable '%s' on right-hand side of " - "equality operator", ecs_query_var_name(q, op->second.var)); - return -1; - } + if (!list || !list->first) { + /* If list contains no matches, the match must be empty */ + ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + return; } - ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); - (void)flags_src; + ecs_assert(prev != NULL || cache->list.first == match, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || cache->list.last == match, + ECS_INTERNAL_ERROR, NULL); - if (!(write_state & (1ull << op->src.var))) { - /* If this is an == operator with a right-hand side that resolves to a - * single entity, the left-hand side is allowed to be undefined, as the - * instruction will be evaluated as an assignment. */ - if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { - ecs_err("uninitialized variable '%s' on left-hand side of " - "equality operator", ecs_query_var_name(q, op->src.var)); - return -1; - } + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; } - return 0; -} + ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + list->info.table_count --; -static -int flecs_query_ensure_scope_var( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - ecs_query_compile_ctx_t *ctx) -{ - ecs_var_id_t var = ref->var; + if (cache->group_by_callback) { + uint64_t group_id = match->group_id; - if (query->vars[var].kind == EcsVarEntity && - !flecs_query_is_written(var, ctx->written)) - { - ecs_var_id_t table_var = query->vars[var].table_id; - if (table_var != EcsVarNone && - flecs_query_is_written(table_var, ctx->written)) - { - if (flecs_query_compile_ensure_vars( - query, op, ref, ref_kind, ctx, false, NULL)) - { - goto error; - } + /* Make sure query.list is updated if this is the first or last group */ + if (cache->list.first == match) { + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.first = next; + prev = next; + } + if (cache->list.last == match) { + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.last = prev; + next = prev; } - } - - return 0; -error: - return -1; -} -static -int flecs_query_ensure_scope_vars( - ecs_world_t *world, - ecs_query_impl_t *query, - ecs_query_compile_ctx_t *ctx, - ecs_term_t *term) -{ - /* If the scope uses variables as entity that have only been written as - * table, resolve them as entities before entering the scope. */ - ecs_term_t *cur = term; - while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { - /* Dummy operation to obtain variable information for term */ - ecs_query_op_t op = {0}; - flecs_query_compile_term_ref(world, query, &op, &cur->first, - &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(world, query, &op, &cur->second, - &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); + ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + cache->list.info.table_count --; + list->info.match_count ++; - if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { - if (flecs_query_ensure_scope_var( - query, &op, &op.first, EcsQueryFirst, ctx)) - { - goto error; - } + /* Make sure group list only contains nodes that belong to the group */ + if (prev && prev->group_id != group_id) { + /* The previous node belonged to another group */ + prev = next; } - - if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (flecs_query_ensure_scope_var( - query, &op, &op.second, EcsQuerySecond, ctx)) - { - goto error; - } + if (next && next->group_id != group_id) { + /* The next node belonged to another group */ + next = prev; } - cur ++; + /* Do check again, in case both prev & next belonged to another group */ + if ((!prev && !next) || (prev && prev->group_id != group_id)) { + /* There are no more matches left in this group */ + flecs_query_cache_remove_group(cache, group_id); + list = NULL; + } } - return 0; -error: - return -1; -} - -static -void flecs_query_compile_push( - ecs_query_compile_ctx_t *ctx) -{ - ctx->cur = &ctx->ctrlflow[++ ctx->scope]; - ctx->cur->lbl_begin = -1; - ctx->cur->lbl_begin = -1; -} - -static -void flecs_query_compile_pop( - ecs_query_compile_ctx_t *ctx) -{ - ctx->cur = &ctx->ctrlflow[-- ctx->scope]; -} - -static -int flecs_query_compile_0_src( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) -{ - /* If the term has a 0 source, check if it's a scope open/close */ - if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { - if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { - goto error; - } - if (term->oper == EcsNot) { - ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); - flecs_query_begin_block(EcsQueryNot, ctx); - } else { - ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + if (list) { + if (list->first == match) { + list->first = next; } - flecs_query_compile_push(ctx); - } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { - flecs_query_compile_pop(ctx); - if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { - flecs_query_end_block(ctx, false); + if (list->last == match) { + list->last = prev; } - } else { - /* Noop */ } - return 0; -error: - return -1; + match->prev = NULL; + match->next = NULL; + + cache->match_count ++; } +/* Add node to list */ static -ecs_flags32_t flecs_query_to_table_flags( - const ecs_query_t *q) +void flecs_query_cache_insert_table_node( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_flags32_t query_flags = q->flags; - if (!(query_flags & EcsQueryMatchDisabled) || - !(query_flags & EcsQueryMatchPrefab)) - { - ecs_flags32_t table_flags = EcsTableNotQueryable; - if (!(query_flags & EcsQueryMatchDisabled)) { - table_flags |= EcsTableIsDisabled; - } - if (!(query_flags & EcsQueryMatchPrefab)) { - table_flags |= EcsTableIsPrefab; - } + /* Node should not be part of an existing list */ + ecs_assert(match->prev == NULL && match->next == NULL, + ECS_INTERNAL_ERROR, NULL); - return table_flags; + /* If this is the first match, activate system */ + if (!cache->list.first && cache->entity) { + ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); } - return 0; -} -static -bool flecs_query_select_all( - const ecs_query_t *q, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_var_id_t src_var, - ecs_query_compile_ctx_t *ctx) -{ - bool builtin_pred = flecs_term_is_builtin_pred(term); - bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; + flecs_query_cache_compute_group_id(cache, match); - if (term->oper == EcsNot || term->oper == EcsOptional || - term->oper == EcsNotFrom || pred_match) - { - ecs_query_op_t match_any = {0}; - match_any.kind = EcsAnd; - match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); - match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); - match_any.src = op->src; - match_any.field_index = -1; - if (!pred_match) { - match_any.first.entity = EcsAny; - } else { - /* If matching by name, instead of finding all tables, just find - * the ones with a name. */ - match_any.first.entity = ecs_id(EcsIdentifier); - match_any.second.entity = EcsName; - match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); - } - match_any.written = (1ull << src_var); - match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); - flecs_query_op_insert(&match_any, ctx); - flecs_query_write_ctx(op->src.var, ctx, false); + ecs_query_cache_table_list_t *list = + flecs_query_cache_ensure_node_list(cache, match); + if (list->last) { + ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - /* Update write administration */ - return true; - } - return false; -} + ecs_query_cache_table_match_t *last = list->last; + ecs_query_cache_table_match_t *last_next = last->next; -#ifdef FLECS_META -static -int flecs_query_compile_begin_member_term( - ecs_world_t *world, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_entity_t first_id) -{ - ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + match->prev = last; + match->next = last_next; + last->next = match; - first_id = ECS_TERM_REF_ID(&term->first); + if (last_next) { + last_next->prev = match; + } - /* First compile as if it's a regular term, to match the component */ - term->flags_ &= (uint16_t)~EcsTermIsMember; + list->last = match; - /* Replace term id with member parent (the component) */ - ecs_entity_t component = ecs_get_parent(world, first_id); - if (!component) { - ecs_err("member without parent in query"); - return -1; - } + if (cache->group_by_callback) { + /* Make sure to update query list if this is the last group */ + if (cache->list.last == last) { + cache->list.last = match; + } + } + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - if (!ecs_has(world, component, EcsComponent)) { - ecs_err("parent of member is not a component"); - return -1; + list->first = match; + list->last = match; + + if (cache->group_by_callback) { + /* Initialize group with its first node */ + flecs_query_cache_create_group(cache, match); + } } - bool second_wildcard = - (ECS_TERM_REF_ID(&term->second) == EcsWildcard || - ECS_TERM_REF_ID(&term->second) == EcsAny) && - (term->second.id & EcsIsVariable) && !term->second.name; + if (cache->group_by_callback) { + list->info.table_count ++; + list->info.match_count ++; + } - term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); - term->second.id = 0; - term->id = component; + cache->list.info.table_count ++; + cache->match_count ++; - ctx->oper = (ecs_oper_kind_t)term->oper; - if (term->oper == EcsNot && !second_wildcard) { - /* When matching a member term with not operator, we need to cover both - * the case where an entity doesn't have the component, and where it - * does have the component, but doesn't match the member. */ - term->oper = EcsOptional; - } + ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); - return 0; + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); } static -int flecs_query_compile_end_member_term( +ecs_query_cache_table_match_t* flecs_query_cache_cache_add( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_id_t term_id, - ecs_entity_t first_id, - ecs_entity_t second_id, - bool cond_write) + ecs_query_cache_table_t *elem) { - ecs_entity_t component = ECS_TERM_REF_ID(&term->first); - const EcsComponent *comp = ecs_get(world, component, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Restore term values */ - term->id = term_id; - term->first.id = first_id; - term->second.id = second_id; - term->flags_ |= EcsTermIsMember; - term->oper = flecs_ito(int16_t, ctx->oper); - - first_id = ECS_TERM_REF_ID(&term->first); - const EcsMember *member = ecs_get(world, first_id, EcsMember); - ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_var_t *var = &impl->vars[op->src.var]; - const char *var_name = flecs_term_ref_var_name(&term->src); - ecs_var_id_t evar = flecs_query_find_var_id( - impl, var_name, EcsVarEntity); - - bool second_wildcard = - (ECS_TERM_REF_ID(&term->second) == EcsWildcard || - ECS_TERM_REF_ID(&term->second) == EcsAny) && - (term->second.id & EcsIsVariable) && !term->second.name; + ecs_query_cache_table_match_t *result = + flecs_bcalloc(&world->allocators.query_table_match); - if (term->oper == EcsOptional) { - second_wildcard = true; + if (!elem->first) { + elem->first = result; + elem->last = result; + } else { + ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); + elem->last->next_match = result; + elem->last = result; } - ecs_query_op_t mbr_op = *op; - mbr_op.kind = EcsQueryMemberEq; - mbr_op.first.entity = /* Encode type size and member offset */ - flecs_ito(uint32_t, member->offset) | - (flecs_ito(uint64_t, comp->size) << 32); + return result; +} - /* If this is a term with a Not operator, conditionally evaluate member on - * whether term was set by previous operation (see begin_member_term). */ - if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { - if (second_wildcard && ctx->oper == EcsNot) { - /* A !(T.value, *) term doesn't need special operations */ - return 0; - } +/* The group by function for cascade computes the tree depth for the table type. + * This causes tables in the query cache to be ordered by depth, which ensures + * breadth-first iteration order. */ +static +uint64_t flecs_query_cache_group_by_cascade( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)id; + ecs_term_t *term = ctx; + ecs_entity_t rel = term->trav; + int32_t depth = flecs_relation_depth(world, rel, table); + return flecs_ito(uint64_t, depth); +} - /* Resolve to entity variable before entering if block, so that we - * don't have different branches of the query working with different - * versions of the same variable. */ - if (var->kind == EcsVarTable) { - flecs_query_insert_each(op->src.var, evar, ctx, cond_write); - var = &impl->vars[evar]; - } +static +ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *qt, + ecs_table_t *table) +{ + /* Add match for table. One table can have more than one match, if + * the query contains wildcards. */ + ecs_query_cache_table_match_t *qm = flecs_query_cache_cache_add( + cache->query->world, qt); + + qm->table = table; + qm->trs = flecs_balloc(&cache->allocators.trs); + + /* Insert match to iteration list if table is not empty */ + if (!table || ecs_table_count(table) != 0 || + (cache->query->flags & EcsQueryCacheYieldEmptyTables)) + { + flecs_query_cache_insert_table_node(cache, qm); + } - ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); - if_op->other = term->field_index; + return qm; +} - if (ctx->oper == EcsNot) { - mbr_op.kind = EcsQueryMemberNeq; - } - } +static +void flecs_query_cache_set_table_match( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *qm, + ecs_iter_t *it) +{ + ecs_query_t *query = cache->query; + int8_t i, field_count = query->field_count; - if (var->kind == EcsVarTable) { - /* If MemberEq is called on table variable, store it on .other member. - * This causes MemberEq to do double duty as 'each' instruction, - * which is faster than having to go back & forth between instructions - * while finding matching values. */ - mbr_op.other = flecs_itolbl(op->src.var + 1); + ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - /* Mark entity variable as written */ - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &mbr_op.written); - } + /* Reset resources in case this is an existing record */ + ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), + it->trs, ecs_table_record_t*, field_count); - flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, - &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); + /* Find out whether to store result-specific ids array or fixed array */ + ecs_id_t *ids = cache->query->ids; + for (i = 0; i < field_count; i ++) { + if (it->ids[i] != ids[i]) { + break; + } + } - if (second_wildcard) { - mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - mbr_op.second.entity = EcsWildcard; + if (i != field_count) { + if (qm->ids == ids || !qm->ids) { + qm->ids = flecs_balloc(&cache->allocators.ids); + } + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); } else { - flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, - &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); - - if (term->second.id & EcsIsVariable) { - if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, - EcsQuerySecond, ctx, cond_write, NULL)) - { - goto error; - } - - flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); - flecs_query_write(mbr_op.second.var, &mbr_op.written); + if (qm->ids != ids) { + flecs_bfree(&cache->allocators.ids, qm->ids); + qm->ids = ids; } } - flecs_query_op_insert(&mbr_op, ctx); + /* Find out whether to store result-specific sources array or fixed array */ + for (i = 0; i < field_count; i ++) { + if (it->sources[i]) { + break; + } + } - if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { - flecs_query_end_block(ctx, false); + if (i != field_count) { + if (qm->sources == cache->sources || !qm->sources) { + qm->sources = flecs_balloc(&cache->allocators.sources); + } + ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + } else { + if (qm->sources != cache->sources) { + flecs_bfree(&cache->allocators.sources, qm->sources); + qm->sources = cache->sources; + } } - return 0; -error: - return -1; -} -#else -static -int flecs_query_compile_begin_member_term( - ecs_world_t *world, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_entity_t first_id) -{ - (void)world; (void)term; (void)ctx; (void)first_id; - return 0; + qm->set_fields = it->set_fields; + qm->up_fields = it->up_fields; } static -int flecs_query_compile_end_member_term( +ecs_query_cache_table_t* flecs_query_cache_table_insert( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_id_t term_id, - ecs_entity_t first_id, - ecs_entity_t second_id, - bool cond_write) + ecs_query_cache_t *cache, + ecs_table_t *table) { - (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; - (void)first_id; (void)second_id; (void)cond_write; - return 0; -} -#endif + ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); + if (table) { + qt->table_id = table->id; + } else { + qt->table_id = 0; + } -static -void flecs_query_mark_last_or_op( - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); - op_ptr->next = FlecsRuleOrMarker; + if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { + ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); + } else { + ecs_table_cache_insert(&cache->cache, table, &qt->hdr); + } + + return qt; } +/** Populate query cache with tables */ static -void flecs_query_set_op_kind( - ecs_query_op_t *op, - ecs_term_t *term, - bool src_is_var) +void flecs_query_cache_match_tables( + ecs_world_t *world, + ecs_query_cache_t *cache) { - /* Default instruction for And operators. If the source is fixed (like for - * singletons or terms with an entity source), use With, which like And but - * just matches against a source (vs. finding a source). */ - op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; - - /* Ignore cascade flag */ - ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); - - /* Handle *From operators */ - if (term->oper == EcsAndFrom) { - op->kind = EcsQueryAndFrom; - } else if (term->oper == EcsOrFrom) { - op->kind = EcsQueryOrFrom; - } else if (term->oper == EcsNotFrom) { - op->kind = EcsQueryNotFrom; + ecs_table_t *table = NULL; + ecs_query_cache_table_t *qt = NULL; - /* If query is transitive, use Trav(ersal) instruction */ - } else if (term->flags_ & EcsTermTransitive) { - ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); - op->kind = EcsQueryTrav; + ecs_iter_t it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); + ECS_BIT_SET(it.flags, EcsIterTableOnly); - /* If term queries for union pair, use union instruction */ - } else if (term->flags_ & EcsTermIsUnion) { - if (op->kind == EcsQueryAnd) { - op->kind = EcsQueryUnionEq; - if (term->oper == EcsNot) { - if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { - term->oper = EcsAnd; - op->kind = EcsQueryUnionNeq; - } - } - } else { - op->kind = EcsQueryUnionEqWith; + while (ecs_query_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + /* New table matched, add record to cache */ + table = it.table; + qt = flecs_query_cache_table_insert(world, cache, table); } - if ((term->src.id & trav_flags) == EcsUp) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqUp; - } - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqSelfUp; - } - } - } else { - if ((term->src.id & trav_flags) == EcsUp) { - op->kind = EcsQueryUp; - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - op->kind = EcsQuerySelfUp; - } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - op->kind = EcsQueryAndAny; - } + ecs_query_cache_table_match_t *qm = + flecs_query_cache_add_table_match(cache, qt, table); + flecs_query_cache_set_table_match(cache, qm, &it); } } -int flecs_query_compile_term( +static +bool flecs_query_cache_match_table( ecs_world_t *world, - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) + ecs_query_cache_t *cache, + ecs_table_t *table) { - ecs_id_t term_id = term->id; - ecs_entity_t first_id = term->first.id; - ecs_entity_t second_id = term->second.id; - bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; - bool member_term = (term->flags_ & EcsTermIsMember) != 0; - if (member_term) { - flecs_query_compile_begin_member_term(world, term, ctx, first_id); + if (!ecs_map_is_init(&cache->cache.index)) { + return false; } - ecs_query_t *q = &query->pub; - bool first_term = term == q->terms; - bool first_is_var = term->first.id & EcsIsVariable; - bool second_is_var = term->second.id & EcsIsVariable; - bool src_is_var = term->src.id & EcsIsVariable; - bool src_is_wildcard = src_is_var && - (ECS_TERM_REF_ID(&term->src) == EcsWildcard || - ECS_TERM_REF_ID(&term->src) == EcsAny); - bool src_is_lookup = false; - bool builtin_pred = flecs_term_is_builtin_pred(term); - bool is_optional = (term->oper == EcsOptional); - bool is_or = flecs_term_is_or(q, term); - bool first_or = false, last_or = false; - bool cond_write = term->oper == EcsOptional || is_or; - ecs_query_op_t op = {0}; + ecs_query_cache_table_t *qt = NULL; + ecs_query_t *q = cache->query; - if (is_or) { - first_or = first_term || (term[-1].oper != EcsOr); - last_or = term->oper != EcsOr; - } + /* Iterate uncached query for table to check if it matches. If this is a + * wildcard query, a table can match multiple times. */ + ecs_iter_t it = flecs_query_iter(world, q); + it.flags |= EcsIterNoData; + ecs_iter_set_var_as_table(&it, 0, table); - if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || - term->oper == EcsNotFrom) - { - const ecs_type_t *type = ecs_get_type(world, term->id); - if (!type) { - /* Empty type for id in *From operation is a noop */ - ctx->skipped ++; - return 0; + while (ecs_query_next(&it)) { + ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); + if (qt == NULL) { + table = it.table; + qt = flecs_query_cache_table_insert(world, cache, table); } - int32_t i, count = type->count; - ecs_id_t *ti_ids = type->array; + ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( + cache, qt, table); + flecs_query_cache_set_table_match(cache, qm, &it); + } - for (i = 0; i < count; i ++) { - ecs_id_t ti_id = ti_ids[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - break; - } - } + return qt != NULL; +} - if (i == count) { - /* Type did not contain any ids to perform operation on */ - ctx->skipped ++; - return 0; +static +bool flecs_query_cache_has_refs( + ecs_query_cache_t *cache) +{ + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; + for (i = 0; i < count; i ++) { + if (terms[i].src.id & (EcsUp | EcsIsEntity)) { + return true; } } - /* !_ (don't match anything) terms always return nothing. */ - if (term->oper == EcsNot && term->id == EcsAny) { - op.kind = EcsQueryNothing; - flecs_query_op_insert(&op, ctx); - return 0; - } + return false; +} - if (first_or) { - ctx->ctrlflow->cond_written_or = ctx->cond_written; - ctx->ctrlflow->in_or = true; - } else if (is_or) { - ctx->written = ctx->ctrlflow->written_or; - } +static +void flecs_query_cache_for_each_component_monitor( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache, + void(*callback)( + ecs_world_t* world, + ecs_id_t id, + ecs_query_t *q)) +{ + ecs_query_t *q = &impl->pub; + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; - if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { - if (flecs_query_compile_0_src(world, query, term, ctx)) { - goto error; + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; + + if (src->id & EcsUp) { + callback(world, ecs_pair(term->trav, EcsWildcard), q); + if (term->trav != EcsIsA) { + callback(world, ecs_pair(EcsIsA, EcsWildcard), q); + } + callback(world, term->id, q); + + } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { + callback(world, term->id, q); } - return 0; } +} - if (builtin_pred) { - ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); - if (id_noflags == EcsWildcard || id_noflags == EcsAny) { - /* Noop */ - return 0; - } +static +bool flecs_query_cache_is_term_ref_supported( + ecs_term_ref_t *ref) +{ + if (!(ref->id & EcsIsVariable)) { + return true; } + if (ecs_id_is_wildcard(ref->id)) { + return true; + } + return false; +} - op.field_index = flecs_ito(int8_t, term->field_index); - op.term_index = flecs_ito(int8_t, term - q->terms); +static +int flecs_query_cache_process_signature( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; - flecs_query_set_op_kind(&op, term, src_is_var); + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *second = &term->second; - bool is_not = (term->oper == EcsNot) && !builtin_pred; + bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); + bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); + bool is_second_ok = flecs_query_cache_is_term_ref_supported(second); - /* Save write state at start of term so we can use it to reliably track - * variables got written by this term. */ - ecs_write_flags_t cond_write_state = ctx->cond_written; + (void)first; + (void)second; + (void)is_src_ok; + (void)is_first_ok; + (void)is_second_ok; - /* Resolve variables and entities for operation arguments */ - flecs_query_compile_term_ref(world, query, &op, &term->first, - &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); - flecs_query_compile_term_ref(world, query, &op, &term->second, - &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); - flecs_query_compile_term_ref(world, query, &op, &term->src, - &op.src, EcsQuerySrc, EcsVarAny, ctx, true); + /* Queries do not support named variables */ + ecs_check(is_src_ok || ecs_term_match_this(term), + ECS_UNSUPPORTED, NULL); + ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); + ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); + ecs_check(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, + "invalid usage of InOutFilter for query"); - bool src_written = true; - if (src_is_var) { - src_is_lookup = query->vars[op.src.var].lookup != NULL; - src_written = flecs_query_is_written(op.src.var, ctx->written); + if (src->id & EcsCascade) { + ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, + "query can only have one cascade term"); + cache->cascade_by = i + 1; + } } - /* Insert each instructions for table -> entity variable if needed */ - bool first_written, second_written; - if (flecs_query_compile_ensure_vars( - query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) - { - goto error; - } + impl->pub.flags |= + (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); - if (flecs_query_compile_ensure_vars( - query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) - { - goto error; - } + flecs_query_cache_for_each_component_monitor( + world, impl, cache, flecs_monitor_register); - /* Store write state of variables for first OR term in chain which will get - * restored for the other terms in the chain, so that all OR terms make the - * same assumptions about which variables were already written. */ - if (first_or) { - ctx->ctrlflow->written_or = ctx->written; - } + return 0; +error: + return -1; +} - /* If an optional or not term is inserted for a source that's not been - * written to yet, insert instruction that selects all entities so we have - * something to match the optional/not against. */ - if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { - src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); - } +/** When a table becomes empty remove it from the query list, or vice versa. */ +static +void flecs_query_cache_update_table( + ecs_query_cache_t *cache, + ecs_table_t *table, + bool empty) +{ + int32_t prev_count = flecs_query_cache_table_count(cache); + ecs_table_cache_set_empty(&cache->cache, table, empty); + int32_t cur_count = flecs_query_cache_table_count(cache); - /* A bit of special logic for OR expressions and equality predicates. If the - * left-hand of an equality operator is a table, and there are multiple - * operators in an Or expression, the Or chain should match all entities in - * the table that match the right hand sides of the operator expressions. - * For this to work, the src variable needs to be resolved as entity, as an - * Or chain would otherwise only yield the first match from a table. */ - if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { - if (query->vars[op.src.var].kind == EcsVarTable) { - flecs_query_compile_term_ref(world, query, &op, &term->src, - &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - ctx->ctrlflow->written_or |= (1llu << op.src.var); - } - } + if (prev_count != cur_count) { + ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); + ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_cache_table_match_t *cur, *next; - if (flecs_query_compile_ensure_vars( - query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) - { - goto error; - } + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; - /* If source is Any (_) and first and/or second are unconstrained, insert an - * ids instruction instead of an And */ - if (term->flags_ & EcsTermMatchAnySrc) { - op.kind = EcsQueryIds; - /* Use up-to-date written values after potentially inserting each */ - if (!first_written || !second_written) { - if (!first_written) { - /* If first is unknown, traverse left: <- (*, t) */ - if (ECS_TERM_REF_ID(&term->first) != EcsAny) { - op.kind = EcsQueryIdsLeft; - } + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_remove_table_node(cache, cur); } else { - /* If second is wildcard, traverse right: (r, *) -> */ - if (ECS_TERM_REF_ID(&term->second) != EcsAny) { - op.kind = EcsQueryIdsRight; - } + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_insert_table_node(cache, cur); } - op.src.entity = 0; - src_is_var = false; - op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ - op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); + } + } + + ecs_assert(cur_count || cache->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); +} + +/* Remove table */ +static +void flecs_query_cache_table_match_free( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *elem, + ecs_query_cache_table_match_t *first) +{ + ecs_query_cache_table_match_t *cur, *next; + ecs_world_t *world = cache->query->world; + + for (cur = first; cur != NULL; cur = next) { + flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); + + if (cur->ids != cache->query->ids) { + flecs_bfree(&cache->allocators.ids, cur->ids); } - /* If source variable is not written and we're querying just for Any, insert - * a dedicated instruction that uses the Any record in the id index. Any - * queries that are evaluated against written sources can use Wildcard - * records, which is what the AndAny instruction does. */ - } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { - /* Lookup variables ($var.child_name) are always written */ - if (!src_is_lookup) { - op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ + if (cur->sources != cache->sources) { + flecs_bfree(&cache->allocators.sources, cur->sources); } - } - /* If this is a transitive term and both the target and source are unknown, - * find the targets for the relationship first. This clusters together - * tables for the same target, which allows for more efficient usage of the - * traversal caches. */ - if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { - if (!src_written && !second_written) { - flecs_query_insert_unconstrained_transitive( - query, &op, ctx, cond_write); + if (cur->monitor) { + flecs_bfree(&cache->allocators.monitors, cur->monitor); } - } - /* Check if this term has variables that have been conditionally written, - * like variables written by an optional term. */ - if (ctx->cond_written) { - if (!is_or || first_or) { - flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); + if (!elem->hdr.empty) { + flecs_query_cache_remove_table_node(cache, cur); } - } - /* If term can toggle and is Not, change operator to Optional as we - * have to match entities that have the component but disabled. */ - if (toggle_term && is_not) { - is_not = false; - is_optional = true; - } + next = cur->next_match; - /* Handle Not, Optional, Or operators */ - if (is_not) { - flecs_query_begin_block(EcsQueryNot, ctx); - } else if (is_optional) { - flecs_query_begin_block(EcsQueryOptional, ctx); - } else if (first_or) { - flecs_query_begin_block_or(&op, term, ctx); + flecs_bfree(&world->allocators.query_table_match, cur); } +} - /* If term has component inheritance enabled, insert instruction to walk - * down the relationship tree of the id. */ - if (term->flags_ & EcsTermIdInherited) { - flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); +static +void flecs_query_cache_table_free( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *elem) +{ + flecs_query_cache_table_match_free(cache, elem, elem->first); + flecs_bfree(&cache->query->world->allocators.query_table, elem); +} + +static +void flecs_query_cache_unmatch_table( + ecs_query_cache_t *cache, + ecs_table_t *table, + ecs_query_cache_table_t *elem) +{ + if (!elem) { + elem = ecs_table_cache_get(&cache->cache, table); } + if (elem) { + ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); + flecs_query_cache_table_free(cache, elem); + } +} - op.match_flags = term->flags_; +/* Rematch system with tables after a change happened to a watched entity */ +static +void flecs_query_cache_rematch_tables( + ecs_world_t *world, + ecs_query_impl_t *impl) +{ + ecs_iter_t it; + ecs_table_t *table = NULL; + ecs_query_cache_table_t *qt = NULL; + ecs_query_cache_table_match_t *qm = NULL; + ecs_query_cache_t *cache = impl->cache; - ecs_write_flags_t write_state = ctx->written; - if (first_is_var) { - op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op.flags |= (EcsQueryIsVar << EcsQueryFirst); + if (cache->monitor_generation == world->monitor_generation) { + return; } - if (term->src.id & EcsSelf) { - op.flags |= EcsQueryIsSelf; + ecs_os_perf_trace_push("flecs.query.rematch"); + + cache->monitor_generation = world->monitor_generation; + + it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); + + world->info.rematch_count_total ++; + int32_t rematch_count = ++ cache->rematch_count; + + ecs_time_t t = {0}; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_measure(&t); } - /* Insert instructions for lookup variables */ - if (first_is_var) { - if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { - write_state |= (1ull << op.first.var); // lookups are resolved inline + while (ecs_query_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + if (qm && qm->next_match) { + flecs_query_cache_table_match_free(cache, qt, qm->next_match); + qm->next_match = NULL; + } + + table = it.table; + + qt = ecs_table_cache_get(&cache->cache, table); + if (!qt) { + qt = flecs_query_cache_table_insert(world, cache, table); + } + + ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + qt->rematch_count = rematch_count; + qm = NULL; } - } - if (src_is_var) { - if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { - write_state |= (1ull << op.src.var); // lookups are resolved inline + if (!qm) { + qm = qt->first; + } else { + qm = qm->next_match; } - } - if (second_is_var) { - if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { - write_state |= (1ull << op.second.var); // lookups are resolved inline + if (!qm) { + qm = flecs_query_cache_add_table_match(cache, qt, table); } - } - if (builtin_pred) { - if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { - goto error; + flecs_query_cache_set_table_match(cache, qm, &it); + + if (table && ecs_table_count(table) && cache->group_by_callback) { + if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { + /* Update table group */ + flecs_query_cache_remove_table_node(cache, qm); + flecs_query_cache_insert_table_node(cache, qm); + } } } - /* If we're writing the $this variable, filter out disabled/prefab entities - * unless the query explicitly matches them. - * This could've been done with regular With instructions, but since - * filtering out disabled/prefab entities is the default and this check is - * cheap to perform on table flags, it's worth special casing. */ - if (!src_written && op.src.var == 0) { - op.other = flecs_itolbl(flecs_query_to_table_flags(q)); + if (qm && qm->next_match) { + flecs_query_cache_table_match_free(cache, qt, qm->next_match); + qm->next_match = NULL; } - /* After evaluating a term, a used variable is always written */ - if (src_is_var) { - flecs_query_write(op.src.var, &op.written); - flecs_query_write_ctx(op.src.var, ctx, cond_write); - } - if (first_is_var) { - flecs_query_write(op.first.var, &op.written); - flecs_query_write_ctx(op.first.var, ctx, cond_write); - } - if (second_is_var) { - flecs_query_write(op.second.var, &op.written); - flecs_query_write_ctx(op.second.var, ctx, cond_write); + /* Iterate all tables in cache, remove ones that weren't just matched */ + ecs_table_cache_iter_t cache_it; + if (flecs_table_cache_all_iter(&cache->cache, &cache_it)) { + while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { + if (qt->rematch_count != rematch_count) { + flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); + } + } } - - flecs_query_op_insert(&op, ctx); - ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); - if (is_or && !member_term) { - flecs_query_mark_last_or_op(ctx); + if (world->flags & EcsWorldMeasureFrameTime) { + world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } - /* Handle self-references between src and first/second variables */ - if (src_is_var) { - if (first_is_var) { - flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); - } - if (second_is_var && op.first.var != op.second.var) { - flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); - } + ecs_os_perf_trace_pop("flecs.query.rematch"); +} + +/* -- Private API -- */ + +void flecs_query_cache_notify( + ecs_world_t *world, + ecs_query_t *q, + ecs_query_cache_event_t *event) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + flecs_query_cache_match_table(world, cache, event->table); + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + flecs_query_cache_unmatch_table(cache, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + flecs_query_cache_rematch_tables(world, impl); + break; } +} - /* Handle self references between first and second variables */ - if (!ecs_id_is_wildcard(first_id)) { - if (first_is_var && !first_written && (op.first.var == op.second.var)) { - flecs_query_insert_pair_eq(term->field_index, ctx); +static +int flecs_query_cache_order_by( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_entity_t order_by, + ecs_order_by_action_t order_by_callback, + ecs_sort_table_action_t action) +{ + ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_cache_t *cache = impl->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_id_is_wildcard(order_by), + ECS_INVALID_PARAMETER, NULL); + + /* Find order_by term & make sure it is queried for */ + const ecs_query_t *query = cache->query; + int32_t i, count = query->term_count; + int32_t order_by_term = -1; + + if (order_by) { + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &query->terms[i]; + + /* Only And terms are supported */ + if (term->id == order_by && term->oper == EcsAnd) { + order_by_term = i; + break; + } } - } - - /* Handle closing of Not, Optional and Or operators */ - if (is_not) { - flecs_query_end_block(ctx, true); - } else if (is_optional) { - flecs_query_end_block(ctx, true); - } - /* Now that the term is resolved, evaluate member of component */ - if (member_term) { - flecs_query_compile_end_member_term(world, query, &op, term, ctx, - term_id, first_id, second_id, cond_write); - if (is_or) { - flecs_query_mark_last_or_op(ctx); + if (order_by_term == -1) { + char *id_str = ecs_id_str(world, order_by); + ecs_err("order_by component '%s' is not queried for", id_str); + ecs_os_free(id_str); + goto error; } } - if (last_or) { - flecs_query_end_block_or(query, ctx); - } + cache->order_by = order_by; + cache->order_by_callback = order_by_callback; + cache->order_by_term = order_by_term; + cache->order_by_table_callback = action; - /* Handle closing of conditional evaluation */ - if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { - if (!is_or || last_or) { - flecs_query_end_block_cond_eval(ctx); - } - } + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); + flecs_query_cache_sort_tables(world, impl); - /* Ensure that term id is set after evaluating Not */ - if (term->flags_ & EcsTermIdInherited) { - if (is_not) { - ecs_query_op_t set_id = {0}; - set_id.kind = EcsQuerySetId; - set_id.first.entity = term->id; - set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); - set_id.field_index = flecs_ito(int8_t, term->field_index); - flecs_query_op_insert(&set_id, ctx); - } + if (!cache->table_slices.array) { + flecs_query_cache_build_sorted_tables(cache); } return 0; @@ -68677,7436 +68222,7897 @@ int flecs_query_compile_term( return -1; } -/** - * @file query/engine/cache.c - * @brief Cached query implementation. - */ +static +void flecs_query_cache_group_by( + ecs_query_cache_t *cache, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, + "query is already grouped"); + ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, + "query is already grouped"); + if (!group_by) { + /* Builtin function that groups by relationship */ + group_by = flecs_query_cache_default_group_by; + } -int32_t flecs_query_cache_table_count( - ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - return cache->cache.tables.count; -} + cache->group_by = sort_component; + cache->group_by_callback = group_by; -int32_t flecs_query_cache_empty_table_count( - ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - return cache->cache.empty_tables.count; + ecs_map_init_w_params(&cache->groups, + &cache->query->world->allocators.query_table_list); +error: + return; } -int32_t flecs_query_cache_entity_count( - const ecs_query_cache_t *cache) +static +void flecs_query_cache_on_event( + ecs_iter_t *it) { - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - - int32_t result = 0; - ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; - if (!last) { - return 0; - } - - for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { - result += ecs_table_count(cur->table); + /* Because this is the observer::run callback, checking if this is event is + * already handled is not done for us. */ + ecs_world_t *world = it->world; + ecs_observer_t *o = it->ctx; + ecs_observer_impl_t *o_impl = flecs_observer_impl(o); + if (o_impl->last_event_id) { + if (o_impl->last_event_id[0] == world->event_id) { + return; + } + o_impl->last_event_id[0] = world->event_id; } - return result; -} + ecs_query_impl_t *impl = o->ctx; + flecs_poly_assert(impl, ecs_query_t); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = it->table; + ecs_entity_t event = it->event; -static -uint64_t flecs_query_cache_get_group_id( - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - if (cache->group_by_callback) { - return cache->group_by_callback(cache->query->world, table, - cache->group_by, cache->group_by_ctx); - } else { - return 0; + if (event == EcsOnTableCreate) { + /* Creation of new table */ + if (flecs_query_cache_match_table(world, cache, table)) { + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); + } + } + return; } -} -static -void flecs_query_cache_compute_group_id( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (cache->group_by_callback) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /* The observer isn't doing the matching because the query can do it more + * efficiently by checking the table with the query cache. */ + if (ecs_table_cache_get(&cache->cache, table) == NULL) { + return; + } - match->group_id = flecs_query_cache_get_group_id(cache, table); - } else { - match->group_id = 0; + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); } -} -static -ecs_query_cache_table_list_t* flecs_query_cache_get_group( - const ecs_query_cache_t *cache, - uint64_t group_id) -{ - return ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); + if (event == EcsOnTableEmpty) { + flecs_query_cache_update_table(cache, table, true); + } else + if (event == EcsOnTableFill) { + flecs_query_cache_update_table(cache, table, false); + } else if (event == EcsOnTableDelete) { + /* Deletion of table */ + flecs_query_cache_unmatch_table(cache, table, NULL); + return; + } } static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( - ecs_query_cache_t *cache, - uint64_t id) +void flecs_query_cache_table_cache_free( + ecs_query_cache_t *cache) { - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); + ecs_table_cache_iter_t it; + ecs_query_cache_table_t *qt; - if (!group) { - group = ecs_map_insert_alloc_t(&cache->groups, - ecs_query_cache_table_list_t, id); - ecs_os_zeromem(group); - if (cache->on_group_create) { - group->info.ctx = cache->on_group_create( - cache->query->world, id, cache->group_by_ctx); + if (flecs_table_cache_all_iter(&cache->cache, &it)) { + while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { + flecs_query_cache_table_free(cache, qt); } } - return group; + ecs_table_cache_fini(&cache->cache); } static -void flecs_query_cache_remove_group( - ecs_query_cache_t *cache, - uint64_t id) +void flecs_query_cache_allocators_init( + ecs_query_cache_t *cache) { - if (cache->on_group_delete) { - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); - if (group) { - cache->on_group_delete(cache->query->world, id, - group->info.ctx, cache->group_by_ctx); - } + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_init(&cache->allocators.trs, + field_count * ECS_SIZEOF(ecs_table_record_t*)); + flecs_ballocator_init(&cache->allocators.ids, + field_count * ECS_SIZEOF(ecs_id_t)); + flecs_ballocator_init(&cache->allocators.sources, + field_count * ECS_SIZEOF(ecs_entity_t)); + flecs_ballocator_init(&cache->allocators.monitors, + (1 + field_count) * ECS_SIZEOF(int32_t)); } - - ecs_map_remove_free(&cache->groups, id); } static -uint64_t flecs_query_cache_default_group_by( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) +void flecs_query_cache_allocators_fini( + ecs_query_cache_t *cache) { - (void)ctx; - - ecs_id_t match; - if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { - return ecs_pair_second(world, match); + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_fini(&cache->allocators.trs); + flecs_ballocator_fini(&cache->allocators.ids); + flecs_ballocator_fini(&cache->allocators.sources); + flecs_ballocator_fini(&cache->allocators.monitors); } - return 0; -} - -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_cache_table_match_t* flecs_query_cache_find_group_insertion_node( - ecs_query_cache_t *cache, - uint64_t group_id) +} + +void flecs_query_cache_fini( + ecs_query_impl_t *impl) { - /* Grouping must be enabled */ - ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = impl->pub.world; + ecs_stage_t *stage = impl->stage; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_iter_t it = ecs_map_iter(&cache->groups); - ecs_query_cache_table_list_t *list, *closest_list = NULL; - uint64_t id, closest_id = 0; - - bool desc = false; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (cache->cascade_by) { - desc = (cache->query->terms[ - cache->cascade_by - 1].src.id & EcsDesc) != 0; + if (cache->observer) { + flecs_observer_fini(cache->observer); } - /* Find closest smaller group id */ - while (ecs_map_next(&it)) { - id = ecs_map_key(&it); - - if (!desc) { - if (id >= group_id) { - continue; - } - } else { - if (id <= group_id) { - continue; - } + ecs_group_delete_action_t on_delete = cache->on_group_delete; + if (on_delete) { + ecs_map_iter_t it = ecs_map_iter(&cache->groups); + while (ecs_map_next(&it)) { + ecs_query_cache_table_list_t *group = ecs_map_ptr(&it); + uint64_t group_id = ecs_map_key(&it); + on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); } + cache->on_group_delete = NULL; + } - list = ecs_map_ptr(&it); - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; + if (cache->group_by_ctx_free) { + if (cache->group_by_ctx) { + cache->group_by_ctx_free(cache->group_by_ctx); } + } - bool comp; - if (!desc) { - comp = ((group_id - id) < (group_id - closest_id)); - } else { - comp = ((group_id - id) > (group_id - closest_id)); - } + flecs_query_cache_for_each_component_monitor(world, impl, cache, + flecs_monitor_unregister); + flecs_query_cache_table_cache_free(cache); - if (!closest_list || comp) { - closest_id = id; - closest_list = list; - } - } + ecs_map_fini(&cache->groups); - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); + + if (cache->query->term_count) { + flecs_bfree(&cache->allocators.sources, cache->sources); } + + flecs_query_cache_allocators_fini(cache); + ecs_query_fini(cache->query); + + flecs_bfree(&stage->allocators.query_cache, cache); } -/* Initialize group with first node */ -static -void flecs_query_cache_create_group( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) +/* -- Public API -- */ + +ecs_query_cache_t* flecs_query_cache_init( + ecs_query_impl_t *impl, + const ecs_query_desc_t *const_desc) { - uint64_t group_id = match->group_id; + ecs_world_t *world = impl->pub.real_world; + ecs_stage_t *stage = impl->stage; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_query_desc_t was not initialized to zero"); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot create query during world fini"); - /* If query has grouping enabled & this is a new/empty group, find - * the insertion point for the group */ - ecs_query_cache_table_match_t *insert_after = - flecs_query_cache_find_group_insertion_node(cache, group_id); + /* Create private version of desc to create the uncached query that will + * populate the query cache. */ + ecs_query_desc_t desc = *const_desc; + ecs_entity_t entity = desc.entity; + desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ + desc.group_by_callback = NULL; + desc.group_by = 0; + desc.order_by_callback = NULL; + desc.order_by = 0; + desc.entity = 0; - if (!insert_after) { - /* This group should appear first in the query list */ - ecs_query_cache_table_match_t *query_first = cache->list.first; - if (query_first) { - /* If this is not the first match for the query, insert before it */ - match->next = query_first; - query_first->prev = match; - cache->list.first = match; - } else { - /* If this is the first match of the query, initialize its list */ - ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = match; - cache->list.last = match; - } - } else { - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + /* Don't pass ctx/binding_ctx to uncached query */ + desc.ctx = NULL; + desc.binding_ctx = NULL; + desc.ctx_free = NULL; + desc.binding_ctx_free = NULL; - /* This group should appear after another group */ - ecs_query_cache_table_match_t *insert_before = insert_after->next; - match->prev = insert_after; - insert_after->next = match; - match->next = insert_before; - if (insert_before) { - insert_before->prev = match; - } else { - ecs_assert(cache->list.last == insert_after, - ECS_INTERNAL_ERROR, NULL); - - /* This group should appear last in the query list */ - cache->list.last = match; - } + ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); + result->entity = entity; + impl->cache = result; + + ecs_observer_desc_t observer_desc = { .query = desc }; + observer_desc.query.flags |= EcsQueryNested; + + ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; + desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; + + /* order_by is not compatible with matching empty tables, as it causes + * a query to return table slices, not entire tables. */ + if (const_desc->order_by_callback) { + query_flags &= ~EcsQueryMatchEmptyTables; } -} -/* Find the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_get_group(cache, match->group_id); - } else { - return &cache->list; + ecs_query_t *q = result->query = ecs_query_init(world, &desc); + if (!q) { + goto error; } -} -/* Find or create the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_ensure_group(cache, match->group_id); - } else { - return &cache->list; + /* The uncached query used to populate the cache always matches empty + * tables. This flag determines whether the empty tables are stored + * separately in the cache or are treated as regular tables. This is only + * enabled if the user requested that the query matches empty tables. */ + ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, + !!(query_flags & EcsQueryMatchEmptyTables)); + + flecs_query_cache_allocators_init(result); + + /* Zero'd out sources array that's used for results that only match $this. + * This reduces the amount of memory used by the cache, and improves CPU + * cache locality during iteration when doing source checks. */ + if (result->query->term_count) { + result->sources = flecs_bcalloc(&result->allocators.sources); } -} -/* Remove node from list */ -static -void flecs_query_cache_remove_table_node( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_query_cache_table_match_t *prev = match->prev; - ecs_query_cache_table_match_t *next = match->next; + if (q->term_count) { + observer_desc.run = flecs_query_cache_on_event; + observer_desc.ctx = impl; - ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); + int32_t event_index = 0; + if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { + observer_desc.events[event_index ++] = EcsOnTableEmpty; + observer_desc.events[event_index ++] = EcsOnTableFill; + } - ecs_query_cache_table_list_t *list = - flecs_query_cache_get_node_list(cache, match); + observer_desc.events[event_index ++] = EcsOnTableCreate; + observer_desc.events[event_index ++] = EcsOnTableDelete; + observer_desc.flags_ = EcsObserverBypassQuery; - if (!list || !list->first) { - /* If list contains no matches, the match must be empty */ - ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - return; + /* ecs_query_init could have moved away resources from the terms array + * in the descriptor, so use the terms array from the query. */ + ecs_os_memcpy_n(observer_desc.query.terms, q->terms, + ecs_term_t, FLECS_TERM_COUNT_MAX); + observer_desc.query.expr = NULL; /* Already parsed */ + + result->observer = flecs_observer_init(world, entity, &observer_desc); + if (!result->observer) { + goto error; + } } - ecs_assert(prev != NULL || cache->list.first == match, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || cache->list.last == match, - ECS_INTERNAL_ERROR, NULL); + result->prev_match_count = -1; - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; + if (ecs_should_log_1()) { + char *query_expr = ecs_query_str(result->query); + ecs_dbg_1("#[green]query#[normal] [%s] created", + query_expr ? query_expr : ""); + ecs_os_free(query_expr); } - ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - list->info.table_count --; + ecs_log_push_1(); - if (cache->group_by_callback) { - uint64_t group_id = match->group_id; + if (flecs_query_cache_process_signature(world, impl, result)) { + goto error; + } - /* Make sure query.list is updated if this is the first or last group */ - if (cache->list.first == match) { - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = next; - prev = next; - } - if (cache->list.last == match) { - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.last = prev; - next = prev; - } + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, + flecs_query_cache_group_by_cascade); + result->group_by_ctx = &result->query->terms[cascade_by - 1]; + } - ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - cache->list.info.table_count --; - list->info.match_count ++; + if (const_desc->group_by_callback || const_desc->group_by) { + ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, + "cannot mix cascade and group_by"); + flecs_query_cache_group_by(result, + const_desc->group_by, const_desc->group_by_callback); + result->group_by_ctx = const_desc->group_by_ctx; + result->on_group_create = const_desc->on_group_create; + result->on_group_delete = const_desc->on_group_delete; + result->group_by_ctx_free = const_desc->group_by_ctx_free; + } - /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->group_id != group_id) { - /* The previous node belonged to another group */ - prev = next; - } - if (next && next->group_id != group_id) { - /* The next node belonged to another group */ - next = prev; - } + /* Ensure that while initially populating the query with tables, they are + * in the right empty/non-empty list. This ensures the query won't miss + * empty/non-empty events for tables that are currently out of sync, but + * change back to being in sync before processing pending events. */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_table_cache_init(world, &result->cache); + flecs_query_cache_match_tables(world, result); - /* Do check again, in case both prev & next belonged to another group */ - if ((!prev && !next) || (prev && prev->group_id != group_id)) { - /* There are no more matches left in this group */ - flecs_query_cache_remove_group(cache, group_id); - list = NULL; + if (const_desc->order_by_callback) { + if (flecs_query_cache_order_by(world, impl, + const_desc->order_by, const_desc->order_by_callback, + const_desc->order_by_table_callback)) + { + goto error; } } - if (list) { - if (list->first == match) { - list->first = next; - } - if (list->last == match) { - list->last = prev; + if (entity) { + if (!flecs_query_cache_table_count(result) && result->query->term_count){ + ecs_add_id(world, entity, EcsEmpty); } } - match->prev = NULL; - match->next = NULL; + ecs_log_pop_1(); - cache->match_count ++; + return result; +error: + return NULL; } -/* Add node to list */ -static -void flecs_query_cache_insert_table_node( +ecs_query_cache_table_t* flecs_query_cache_get_table( ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) + ecs_table_t *table) { - /* Node should not be part of an existing list */ - ecs_assert(match->prev == NULL && match->next == NULL, - ECS_INTERNAL_ERROR, NULL); - - /* If this is the first match, activate system */ - if (!cache->list.first && cache->entity) { - ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); - } - - flecs_query_cache_compute_group_id(cache, match); - - ecs_query_cache_table_list_t *list = - flecs_query_cache_ensure_node_list(cache, match); - if (list->last) { - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_query_cache_table_match_t *last = list->last; - ecs_query_cache_table_match_t *last_next = last->next; + return ecs_table_cache_get(&cache->cache, table); +} - match->prev = last; - match->next = last_next; - last->next = match; +void ecs_iter_set_group( + ecs_iter_t *it, + uint64_t group_id) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, + "cannot set group during iteration"); - if (last_next) { - last_next->prev = match; - } + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *q = flecs_query_impl(qit->query); + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(q, ecs_query_t); + ecs_query_cache_t *cache = q->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); - list->last = match; + ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( + cache, group_id); + if (!node) { + qit->node = NULL; + qit->last = NULL; + return; + } - if (cache->group_by_callback) { - /* Make sure to update query list if this is the last group */ - if (cache->list.last == last) { - cache->list.last = match; - } - } + ecs_query_cache_table_match_t *first = node->first; + if (first) { + qit->node = node->first; + qit->last = node->last; } else { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - - list->first = match; - list->last = match; + qit->node = NULL; + qit->last = NULL; + } + +error: + return; +} - if (cache->group_by_callback) { - /* Initialize group with its first node */ - flecs_query_cache_create_group(cache, match); - } +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( + flecs_query_impl(query)->cache, group_id); + if (!node) { + return NULL; } + + return &node->info; +} - if (cache->group_by_callback) { - list->info.table_count ++; - list->info.match_count ++; +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + const ecs_query_group_info_t *info = ecs_query_get_group_info( + query, group_id); + if (!info) { + return NULL; + } else { + return info->ctx; } +} - cache->list.info.table_count ++; - cache->match_count ++; +/** + * @file query/engine/cache_iter.c + * @brief Compile query term. + */ - ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); +static +void flecs_query_update_node_up_trs( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) +{ + ecs_termset_t fields = node->up_fields & node->set_fields; + if (fields) { + const ecs_query_impl_t *impl = ctx->query; + const ecs_query_t *q = &impl->pub; + ecs_query_cache_t *cache = impl->cache; + int32_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(fields & (1llu << i))) { + continue; + } + + ecs_entity_t src = node->sources[i]; + if (src) { + const ecs_table_record_t *tr = node->trs[i]; + ecs_record_t *r = flecs_entities_get(ctx->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + if (r->table != tr->hdr.table) { + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_assert(idr->id == q->ids[i], ECS_INTERNAL_ERROR, NULL); + tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); + if (cache->field_map) { + ctx->it->trs[cache->field_map[i]] = tr; + } + } + } + } + } } static -ecs_query_cache_table_match_t* flecs_query_cache_cache_add( - ecs_world_t *world, - ecs_query_cache_table_t *elem) +ecs_query_cache_table_match_t* flecs_query_cache_next( + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *result = - flecs_bcalloc(&world->allocators.query_table_match); - - if (!elem->first) { - elem->first = result; - elem->last = result; - } else { - ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); - elem->last->next_match = result; - elem->last = result; + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_cache_table_match_t *node = qit->node; + ecs_query_cache_table_match_t *prev = qit->prev; + + if (prev != qit->last) { + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ctx->vars[0].range.table = node->table; + it->group_id = node->group_id; + qit->node = node->next; + qit->prev = node; + return node; } - return result; + return NULL; } -/* The group by function for cascade computes the tree depth for the table type. - * This causes tables in the query cache to be ordered by depth, which ensures - * breadth-first iteration order. */ static -uint64_t flecs_query_cache_group_by_cascade( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) +ecs_query_cache_table_match_t* flecs_query_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - (void)id; - ecs_term_t *term = ctx; - ecs_entity_t rel = term->trav; - int32_t depth = flecs_relation_depth(world, rel, table); - return flecs_ito(uint64_t, depth); -} + ecs_iter_t *it = ctx->it; + if (!redo) { + ecs_var_t *var = &ctx->vars[0]; + ecs_table_t *table = var->range.table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); -static -ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *qt, - ecs_table_t *table) -{ - /* Add match for table. One table can have more than one match, if - * the query contains wildcards. */ - ecs_query_cache_table_match_t *qm = flecs_query_cache_cache_add( - cache->query->world, qt); - - qm->table = table; - qm->trs = flecs_balloc(&cache->allocators.trs); + ecs_query_cache_table_t *qt = flecs_query_cache_get_table( + ctx->query->cache, table); + if (!qt) { + return NULL; + } - /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0 || - (cache->query->flags & EcsQueryCacheYieldEmptyTables)) - { - flecs_query_cache_insert_table_node(cache, qm); + ecs_query_iter_t *qit = &it->priv_.iter.query; + qit->prev = NULL; + qit->node = qt->first; + qit->last = qt->last; } - return qm; + return flecs_query_cache_next(ctx); } static -void flecs_query_cache_set_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *qm, - ecs_iter_t *it) +void flecs_query_cache_init_mapped_fields( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) { - ecs_query_t *query = cache->query; - int8_t i, field_count = query->field_count; - - ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - - /* Reset resources in case this is an existing record */ - ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), - it->trs, ecs_table_record_t*, field_count); + ecs_iter_t *it = ctx->it; + const ecs_query_impl_t *impl = ctx->query; + ecs_query_cache_t *cache = impl->cache; + int32_t i, field_count = cache->query->field_count; + int8_t *field_map = cache->field_map; - /* Find out whether to store result-specific ids array or fixed array */ - ecs_id_t *ids = cache->query->ids; for (i = 0; i < field_count; i ++) { - if (it->ids[i] != ids[i]) { - break; - } - } + int8_t field_index = field_map[i]; + it->trs[field_index] = node->trs[i]; - if (i != field_count) { - if (qm->ids == ids || !qm->ids) { - qm->ids = flecs_balloc(&cache->allocators.ids); - } - ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); - } else { - if (qm->ids != ids) { - flecs_bfree(&cache->allocators.ids, qm->ids); - qm->ids = ids; - } - } + it->ids[field_index] = node->ids[i]; + it->sources[field_index] = node->sources[i]; - /* Find out whether to store result-specific sources array or fixed array */ - for (i = 0; i < field_count; i ++) { - if (it->sources[i]) { - break; - } - } + ecs_termset_t bit = (ecs_termset_t)(1u << i); + ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); - if (i != field_count) { - if (qm->sources == cache->sources || !qm->sources) { - qm->sources = flecs_balloc(&cache->allocators.sources); - } - ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); - } else { - if (qm->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, qm->sources); - qm->sources = cache->sources; - } + ECS_TERMSET_COND(it->set_fields, field_bit, node->set_fields & bit); + ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); } - - qm->set_fields = it->set_fields; - qm->up_fields = it->up_fields; } -static -ecs_query_cache_table_t* flecs_query_cache_table_insert( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Iterate cache for query that's partially cached */ +bool flecs_query_cache_search( + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); - if (table) { - qt->table_id = table->id; - } else { - qt->table_id = 0; + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + if (!node) { + return false; } - if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { - ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); - } else { - ecs_table_cache_insert(&cache->cache, table, &qt->hdr); - } + flecs_query_cache_init_mapped_fields(ctx, node); + ctx->vars[0].range.count = node->count; + ctx->vars[0].range.offset = node->offset; - return qt; + flecs_query_update_node_up_trs(ctx, node); + + return true; } -/** Populate query cache with tables */ -static -void flecs_query_cache_match_tables( - ecs_world_t *world, - ecs_query_cache_t *cache) +/* Iterate cache for query that's entirely cached */ +bool flecs_query_is_cache_search( + const ecs_query_run_ctx_t *ctx) { - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + if (!node) { + return false; + } - ecs_iter_t it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - ECS_BIT_SET(it.flags, EcsIterTableOnly); + ecs_iter_t *it = ctx->it; + it->trs = node->trs; + it->ids = node->ids; + it->sources = node->sources; + it->set_fields = node->set_fields; + it->up_fields = node->up_fields; - while (ecs_query_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - /* New table matched, add record to cache */ - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - } + flecs_query_update_node_up_trs(ctx, node); - ecs_query_cache_table_match_t *qm = - flecs_query_cache_add_table_match(cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); - } + return true; } -static -bool flecs_query_cache_match_table( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - if (!ecs_map_is_init(&cache->cache.index)) { + ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); + if (!node) { return false; } - ecs_query_cache_table_t *qt = NULL; - ecs_query_t *q = cache->query; - - /* Iterate uncached query for table to check if it matches. If this is a - * wildcard query, a table can match multiple times. */ - ecs_iter_t it = flecs_query_iter(world, q); - it.flags |= EcsIterNoData; - ecs_iter_set_var_as_table(&it, 0, table); + flecs_query_cache_init_mapped_fields(ctx, node); + flecs_query_update_node_up_trs(ctx, node); - while (ecs_query_next(&it)) { - ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); - if (qt == NULL) { - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - } + return true; +} - ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( - cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_is_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) +{ + ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); + if (!node) { + return false; } - return qt != NULL; + ecs_iter_t *it = ctx->it; + it->trs = node->trs; + it->ids = node->ids; + it->sources = node->sources; + + flecs_query_update_node_up_trs(ctx, node); + + return true; } -static -bool flecs_query_cache_has_refs( - ecs_query_cache_t *cache) -{ - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - for (i = 0; i < count; i ++) { - if (terms[i].src.id & (EcsUp | EcsIsEntity)) { - return true; - } - } +/** + * @file query/engine/cache_order_by.c + * @brief Order by implementation + */ + - return false; -} +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) static -void flecs_query_cache_for_each_component_monitor( +void flecs_query_cache_sort_table( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_cache_t *cache, - void(*callback)( - ecs_world_t* world, - ecs_id_t id, - ecs_query_t *q)) + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare, + ecs_sort_table_action_t sort) { - ecs_query_t *q = &impl->pub; - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *src = &term->src; + int32_t count = ecs_table_count(table); + if (!count) { + /* Nothing to sort */ + return; + } + + if (count < 2) { + return; + } - if (src->id & EcsUp) { - callback(world, ecs_pair(term->trav, EcsWildcard), q); - if (term->trav != EcsIsA) { - callback(world, ecs_pair(EcsIsA, EcsWildcard), q); - } - callback(world, term->id, q); + ecs_entity_t *entities = table->data.entities; + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &table->data.columns[column_index]; + ecs_type_info_t *ti = column->ti; + size = ti->size; + ptr = column->data; + } - } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { - callback(world, term->id, q); - } + if (sort) { + sort(world, table, entities, ptr, size, 0, count - 1, compare); + } else { + flecs_query_cache_sort_table_generic( + world, table, entities, ptr, size, 0, count - 1, compare); } } +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_cache_table_match_t *match; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; + static -bool flecs_query_cache_is_term_ref_supported( - ecs_term_ref_t *ref) +const void* ptr_from_helper( + sort_helper_t *helper) { - if (!(ref->id & EcsIsVariable)) { - return true; - } - if (ecs_id_is_wildcard(ref->id)) { - return true; + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; + } else { + return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } - return false; } static -int flecs_query_cache_process_signature( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_cache_t *cache) +ecs_entity_t e_from_helper( + sort_helper_t *helper) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *src = &term->src; - ecs_term_ref_t *second = &term->second; - - bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); - bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); - bool is_second_ok = flecs_query_cache_is_term_ref_supported(second); - - (void)first; - (void)second; - (void)is_src_ok; - (void)is_first_ok; - (void)is_second_ok; - - /* Queries do not support named variables */ - ecs_check(is_src_ok || ecs_term_match_this(term), - ECS_UNSUPPORTED, NULL); - ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); - ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); - ecs_check(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, - "invalid usage of InOutFilter for query"); - - if (src->id & EcsCascade) { - ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, - "query can only have one cascade term"); - cache->cascade_by = i + 1; - } + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; } - - impl->pub.flags |= - (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); - - flecs_query_cache_for_each_component_monitor( - world, impl, cache, flecs_monitor_register); - - return 0; -error: - return -1; } -/** When a table becomes empty remove it from the query list, or vice versa. */ static -void flecs_query_cache_update_table( +void flecs_query_cache_build_sorted_table_range( ecs_query_cache_t *cache, - ecs_table_t *table, - bool empty) + ecs_query_cache_table_list_t *list) { - int32_t prev_count = flecs_query_cache_table_count(cache); - ecs_table_cache_set_empty(&cache->cache, table, empty); - int32_t cur_count = flecs_query_cache_table_count(cache); + ecs_world_t *world = cache->query->world; + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, + "cannot sort query in multithreaded mode"); - if (prev_count != cur_count) { - ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_cache_table_match_t *cur, *next; + ecs_entity_t id = cache->order_by; + ecs_order_by_action_t compare = cache->order_by_callback; + int32_t table_count = list->info.table_count; + if (!table_count) { + return; + } - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; + ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_match_t); + int32_t to_sort = 0; + int32_t order_by_term = cache->order_by_term; - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); + sort_helper_t *helper = flecs_alloc_n( + &world->allocator, sort_helper_t, table_count); + ecs_query_cache_table_match_t *cur, *end = list->last->next; + for (cur = list->first; cur != end; cur = cur->next) { + ecs_table_t *table = cur->table; - flecs_query_cache_remove_table_node(cache, cur); + if (ecs_table_count(table) == 0) { + continue; + } + + if (id) { + const ecs_term_t *term = &cache->query->terms[order_by_term]; + int32_t field = term->field_index; + ecs_size_t size = cache->query->sizes[field]; + ecs_entity_t src = cur->sources[field]; + if (src == 0) { + int32_t column_index = cur->trs[field]->column; + ecs_column_t *column = &table->data.columns[column_index]; + helper[to_sort].ptr = column->data; + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_query_cache_insert_table_node(cache, cur); + if (term->src.id & EcsUp) { + ecs_entity_t base = 0; + ecs_search_relation(world, r->table, 0, id, + EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); + if (base && base != src) { /* Component could be inherited */ + r = flecs_entities_get(world, base); + } + } + + helper[to_sort].ptr = ecs_table_get_id( + world, r->table, id, ECS_RECORD_TO_ROW(r->row)); + helper[to_sort].elem_size = size; + helper[to_sort].shared = true; } + ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; } - } - ecs_assert(cur_count || cache->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); -} + helper[to_sort].match = cur; + helper[to_sort].entities = table->data.entities; + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } -/* Remove table */ -static -void flecs_query_cache_table_match_free( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *elem, - ecs_query_cache_table_match_t *first) -{ - ecs_query_cache_table_match_t *cur, *next; - ecs_world_t *world = cache->query->world; + ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); - for (cur = first; cur != NULL; cur = next) { - flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); + bool proceed; + do { + int32_t j, min = 0; + proceed = true; - if (cur->ids != cache->query->ids) { - flecs_bfree(&cache->allocators.ids, cur->ids); + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } } - if (cur->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, cur->sources); + if (!proceed) { + break; } - if (cur->monitor) { - flecs_bfree(&cache->allocators.monitors, cur->monitor); + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } + + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); + + if (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + e1 = e_from_helper(&helper[min]); + } } - if (!elem->hdr.empty) { - flecs_query_cache_remove_table_node(cache, cur); + sort_helper_t *cur_helper = &helper[min]; + if (!cur || cur->trs != cur_helper->match->trs) { + cur = ecs_vec_append_t(NULL, &cache->table_slices, + ecs_query_cache_table_match_t); + *cur = *(cur_helper->match); + cur->offset = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; } - next = cur->next_match; + cur_helper->row ++; + } while (proceed); - flecs_bfree(&world->allocators.query_table_match, cur); + /* Iterate through the vector of slices to set the prev/next ptrs. This + * can't be done while building the vector, as reallocs may occur */ + int32_t i, count = ecs_vec_count(&cache->table_slices); + ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); + for (i = 0; i < count; i ++) { + nodes[i].prev = &nodes[i - 1]; + nodes[i].next = &nodes[i + 1]; } -} -static -void flecs_query_cache_table_free( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *elem) -{ - flecs_query_cache_table_match_free(cache, elem, elem->first); - flecs_bfree(&cache->query->world->allocators.query_table, elem); + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } -static -void flecs_query_cache_unmatch_table( - ecs_query_cache_t *cache, - ecs_table_t *table, - ecs_query_cache_table_t *elem) +void flecs_query_cache_build_sorted_tables( + ecs_query_cache_t *cache) { - if (!elem) { - elem = ecs_table_cache_get(&cache->cache, table); - } - if (elem) { - ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); - flecs_query_cache_table_free(cache, elem); + ecs_vec_clear(&cache->table_slices); + + if (cache->group_by_callback) { + /* Populate sorted node list in grouping order */ + ecs_query_cache_table_match_t *cur = cache->list.first; + if (cur) { + do { + /* Find list for current group */ + uint64_t group_id = cur->group_id; + ecs_query_cache_table_list_t *list = ecs_map_get_deref( + &cache->groups, ecs_query_cache_table_list_t, group_id); + ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Sort tables in current group */ + flecs_query_cache_build_sorted_table_range(cache, list); + + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } + } else { + flecs_query_cache_build_sorted_table_range(cache, &cache->list); } } -/* Rematch system with tables after a change happened to a watched entity */ -static -void flecs_query_cache_rematch_tables( +void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl) { - ecs_iter_t it; - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; - ecs_query_cache_table_match_t *qm = NULL; ecs_query_cache_t *cache = impl->cache; - - if (cache->monitor_generation == world->monitor_generation) { + ecs_order_by_action_t compare = cache->order_by_callback; + if (!compare) { return; } - ecs_os_perf_trace_push("flecs.query.rematch"); - - cache->monitor_generation = world->monitor_generation; - - it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - - world->info.rematch_count_total ++; - int32_t rematch_count = ++ cache->rematch_count; - - ecs_time_t t = {0}; - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_measure(&t); - } - - while (ecs_query_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); - qm->next_match = NULL; - } + ecs_sort_table_action_t sort = cache->order_by_table_callback; + + ecs_entity_t order_by = cache->order_by; + int32_t order_by_term = cache->order_by_term; - table = it.table; + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ - qt = ecs_table_cache_get(&cache->cache, table); - if (!qt) { - qt = flecs_query_cache_table_insert(world, cache, table); - } + bool tables_sorted = false; - ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - qt->rematch_count = rematch_count; - qm = NULL; - } - if (!qm) { - qm = qt->first; - } else { - qm = qm->next_match; - } - if (!qm) { - qm = flecs_query_cache_add_table_match(cache, qt, table); - } + ecs_id_record_t *idr = flecs_id_record_get(world, order_by); + ecs_table_cache_iter_t it; + ecs_query_cache_table_t *qt; + flecs_table_cache_iter(&cache->cache, &it); - flecs_query_cache_set_table_match(cache, qm, &it); + while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { + ecs_table_t *table = qt->hdr.table; + bool dirty = false; - if (table && ecs_table_count(table) && cache->group_by_callback) { - if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { - /* Update table group */ - flecs_query_cache_remove_table_node(cache, qm); - flecs_query_cache_insert_table_node(cache, qm); - } + if (flecs_query_check_table_monitor(impl, qt, 0)) { + tables_sorted = true; + dirty = true; } - } - - if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); - qm->next_match = NULL; - } - /* Iterate all tables in cache, remove ones that weren't just matched */ - ecs_table_cache_iter_t cache_it; - if (flecs_table_cache_all_iter(&cache->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { - if (qt->rematch_count != rematch_count) { - flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); + int32_t column = -1; + if (order_by) { + if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { + dirty = true; } - } - } - - if (world->flags & EcsWorldMeasureFrameTime) { - world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } - - ecs_os_perf_trace_pop("flecs.query.rematch"); -} - -/* -- Private API -- */ - -void flecs_query_cache_notify( - ecs_world_t *world, - ecs_query_t *q, - ecs_query_cache_event_t *event) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_cache_t *cache = impl->cache; - - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - flecs_query_cache_match_table(world, cache, event->table); - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, event->table, NULL); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - flecs_query_cache_rematch_tables(world, impl); - break; - } -} -static -int flecs_query_cache_order_by( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_entity_t order_by, - ecs_order_by_action_t order_by_callback, - ecs_sort_table_action_t action) -{ - ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_query_cache_t *cache = impl->cache; - ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_id_is_wildcard(order_by), - ECS_INVALID_PARAMETER, NULL); + if (dirty) { + column = -1; - /* Find order_by term & make sure it is queried for */ - const ecs_query_t *query = cache->query; - int32_t i, count = query->term_count; - int32_t order_by_term = -1; + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr, table); + if (tr) { + column = tr->column; + } - if (order_by) { - for (i = 0; i < count; i ++) { - const ecs_term_t *term = &query->terms[i]; - - /* Only And terms are supported */ - if (term->id == order_by && term->oper == EcsAnd) { - order_by_term = i; - break; + if (column == -1) { + /* Component is shared, no sorting is needed */ + dirty = false; + } } } - if (order_by_term == -1) { - char *id_str = ecs_id_str(world, order_by); - ecs_err("order_by component '%s' is not queried for", id_str); - ecs_os_free(id_str); - goto error; + if (!dirty) { + continue; } - } - cache->order_by = order_by; - cache->order_by_callback = order_by_callback; - cache->order_by_term = order_by_term; - cache->order_by_table_callback = action; - - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - flecs_query_cache_sort_tables(world, impl); + /* Something has changed, sort the table. Prefers using + * flecs_query_cache_sort_table when available */ + flecs_query_cache_sort_table(world, table, column, compare, sort); + tables_sorted = true; + } - if (!cache->table_slices.array) { + if (tables_sorted || cache->match_count != cache->prev_match_count) { flecs_query_cache_build_sorted_tables(cache); + cache->match_count ++; /* Increase version if tables changed */ } - - return 0; -error: - return -1; } -static -void flecs_query_cache_group_by( - ecs_query_cache_t *cache, - ecs_entity_t sort_component, - ecs_group_by_action_t group_by) -{ - ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, - "query is already grouped"); - ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, - "query is already grouped"); +/** + * @file query/engine/change_detection.c + * @brief Compile query term. + */ - if (!group_by) { - /* Builtin function that groups by relationship */ - group_by = flecs_query_cache_default_group_by; - } - cache->group_by = sort_component; - cache->group_by_callback = group_by; +typedef struct { + ecs_table_t *table; + int32_t column; +} flecs_table_column_t; - ecs_map_init_w_params(&cache->groups, - &cache->query->world->allocators.query_table_list); -error: - return; +static +void flecs_query_get_column_for_field( + const ecs_query_t *q, + ecs_query_cache_table_match_t *match, + int32_t field, + flecs_table_column_t *out) +{ + ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); + (void)q; + + const ecs_table_record_t *tr = match->trs[field]; + ecs_table_t *table = tr->hdr.table; + int32_t column = tr->column; + + out->table = table; + out->column = column; } +/* Get match monitor. Monitors are used to keep track of whether components + * matched by the query in a table have changed. */ static -void flecs_query_cache_on_event( - ecs_iter_t *it) +bool flecs_query_get_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match) { - /* Because this is the observer::run callback, checking if this is event is - * already handled is not done for us. */ - ecs_world_t *world = it->world; - ecs_observer_t *o = it->ctx; - ecs_observer_impl_t *o_impl = flecs_observer_impl(o); - if (o_impl->last_event_id) { - if (o_impl->last_event_id[0] == world->event_id) { - return; - } - o_impl->last_event_id[0] = world->event_id; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + if (match->monitor) { + return false; } - ecs_query_impl_t *impl = o->ctx; - flecs_poly_assert(impl, ecs_query_t); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = it->table; - ecs_entity_t event = it->event; + int32_t *monitor = flecs_balloc(&cache->allocators.monitors); + monitor[0] = 0; - if (event == EcsOnTableCreate) { - /* Creation of new table */ - if (flecs_query_cache_match_table(world, cache, table)) { - if (ecs_should_log_3()) { - char *table_str = ecs_table_str(world, table); - ecs_dbg_3("query cache event: %s for [%s]", - ecs_get_name(world, event), - table_str); - ecs_os_free(table_str); + /* Mark terms that don't need to be monitored. This saves time when reading + * and/or updating the monitor. */ + const ecs_query_t *q = cache->query; + int32_t i, field = -1, term_count = q->term_count; + flecs_table_column_t tc; + + for (i = 0; i < term_count; i ++) { + if (field == q->terms[i].field_index) { + if (monitor[field + 1] != -1) { + continue; } } - return; - } - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + field = q->terms[i].field_index; + monitor[field + 1] = -1; - /* The observer isn't doing the matching because the query can do it more - * efficiently by checking the table with the query cache. */ - if (ecs_table_cache_get(&cache->cache, table) == NULL) { - return; - } + /* If term isn't read, don't monitor */ + if (q->terms[i].inout != EcsIn && + q->terms[i].inout != EcsInOut && + q->terms[i].inout != EcsInOutDefault) { + continue; + } - if (ecs_should_log_3()) { - char *table_str = ecs_table_str(world, table); - ecs_dbg_3("query cache event: %s for [%s]", - ecs_get_name(world, event), - table_str); - ecs_os_free(table_str); - } + /* Don't track fields that aren't set */ + if (!(match->set_fields & (1llu << field))) { + continue; + } - if (event == EcsOnTableEmpty) { - flecs_query_cache_update_table(cache, table, true); - } else - if (event == EcsOnTableFill) { - flecs_query_cache_update_table(cache, table, false); - } else if (event == EcsOnTableDelete) { - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, table, NULL); - return; + flecs_query_get_column_for_field(q, match, field, &tc); + if (tc.column == -1) { + continue; /* Don't track terms that aren't stored */ + } + + monitor[field + 1] = 0; } -} -static -void flecs_query_cache_table_cache_free( - ecs_query_cache_t *cache) -{ - ecs_table_cache_iter_t it; - ecs_query_cache_table_t *qt; + match->monitor = monitor; - if (flecs_table_cache_all_iter(&cache->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - flecs_query_cache_table_free(cache, qt); - } - } + impl->pub.flags |= EcsQueryHasMonitor; - ecs_table_cache_fini(&cache->cache); + return true; } +/* Get monitor for fixed query terms. Fixed terms are handled separately as they + * don't require a query cache, and fixed terms aren't stored in the cache. */ static -void flecs_query_cache_allocators_init( - ecs_query_cache_t *cache) +bool flecs_query_get_fixed_monitor( + ecs_query_impl_t *impl, + bool check) { - int32_t field_count = cache->query->field_count; - if (field_count) { - flecs_ballocator_init(&cache->allocators.trs, - field_count * ECS_SIZEOF(ecs_table_record_t*)); - flecs_ballocator_init(&cache->allocators.ids, - field_count * ECS_SIZEOF(ecs_id_t)); - flecs_ballocator_init(&cache->allocators.sources, - field_count * ECS_SIZEOF(ecs_entity_t)); - flecs_ballocator_init(&cache->allocators.monitors, - (1 + field_count) * ECS_SIZEOF(int32_t)); - } -} + ecs_query_t *q = &impl->pub; + ecs_world_t *world = q->world; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; -static -void flecs_query_cache_allocators_fini( - ecs_query_cache_t *cache) -{ - int32_t field_count = cache->query->field_count; - if (field_count) { - flecs_ballocator_fini(&cache->allocators.trs); - flecs_ballocator_fini(&cache->allocators.ids); - flecs_ballocator_fini(&cache->allocators.sources); - flecs_ballocator_fini(&cache->allocators.monitors); + if (!impl->monitor) { + impl->monitor = flecs_alloc_n(&impl->stage->allocator, + int32_t, q->field_count); + check = false; /* If the monitor is new, initialize it with dirty state */ } -} -void flecs_query_cache_fini( - ecs_query_impl_t *impl) -{ - ecs_world_t *world = impl->pub.world; - ecs_stage_t *stage = impl->stage; - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int16_t field_index = term->field_index; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { + continue; /* If term doesn't read data there's nothing to track */ + } - if (cache->observer) { - flecs_observer_fini(cache->observer); - } + if (!(term->src.id & EcsIsEntity)) { + continue; /* Not a term with a fixed source */ + } - ecs_group_delete_action_t on_delete = cache->on_group_delete; - if (on_delete) { - ecs_map_iter_t it = ecs_map_iter(&cache->groups); - while (ecs_map_next(&it)) { - ecs_query_cache_table_list_t *group = ecs_map_ptr(&it); - uint64_t group_id = ecs_map_key(&it); - on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); + ecs_entity_t src = ECS_TERM_REF_ID(&term->src); + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !r->table) { + continue; /* Entity is empty, nothing to track */ } - cache->on_group_delete = NULL; - } - if (cache->group_by_ctx_free) { - if (cache->group_by_ctx) { - cache->group_by_ctx_free(cache->group_by_ctx); + ecs_id_record_t *idr = flecs_id_record_get(world, term->id); + if (!idr) { + continue; /* If id doesn't exist, entity can't have it */ } - } - flecs_query_cache_for_each_component_monitor(world, impl, cache, - flecs_monitor_unregister); - flecs_query_cache_table_cache_free(cache); + ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); + if (!tr) { + continue; /* Entity doesn't have the component */ + } - ecs_map_fini(&cache->groups); + /* Copy/check column dirty state from table */ + int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - - if (cache->query->term_count) { - flecs_bfree(&cache->allocators.sources, cache->sources); + if (!check) { + impl->monitor[field_index] = dirty_state[tr->column + 1]; + } else { + if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { + return true; + } + } } - flecs_query_cache_allocators_fini(cache); - ecs_query_fini(cache->query); - - flecs_bfree(&stage->allocators.query_cache, cache); + return !check; } -/* -- Public API -- */ - -ecs_query_cache_t* flecs_query_cache_init( - ecs_query_impl_t *impl, - const ecs_query_desc_t *const_desc) +bool flecs_query_update_fixed_monitor( + ecs_query_impl_t *impl) { - ecs_world_t *world = impl->pub.real_world; - ecs_stage_t *stage = impl->stage; - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_query_desc_t was not initialized to zero"); - ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, - "cannot create query during world fini"); - - /* Create private version of desc to create the uncached query that will - * populate the query cache. */ - ecs_query_desc_t desc = *const_desc; - ecs_entity_t entity = desc.entity; - desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ - desc.group_by_callback = NULL; - desc.group_by = 0; - desc.order_by_callback = NULL; - desc.order_by = 0; - desc.entity = 0; - - /* Don't pass ctx/binding_ctx to uncached query */ - desc.ctx = NULL; - desc.binding_ctx = NULL; - desc.ctx_free = NULL; - desc.binding_ctx_free = NULL; + return flecs_query_get_fixed_monitor(impl, false); +} - ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); - result->entity = entity; - impl->cache = result; +bool flecs_query_check_fixed_monitor( + ecs_query_impl_t *impl) +{ + return flecs_query_get_fixed_monitor(impl, true); +} - ecs_observer_desc_t observer_desc = { .query = desc }; - observer_desc.query.flags |= EcsQueryNested; - ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; - desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; +/* Check if single match term has changed */ +static +bool flecs_query_check_match_monitor_term( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match, + int32_t field) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - /* order_by is not compatible with matching empty tables, as it causes - * a query to return table slices, not entire tables. */ - if (const_desc->order_by_callback) { - query_flags &= ~EcsQueryMatchEmptyTables; + if (flecs_query_get_match_monitor(impl, match)) { + return true; } - ecs_query_t *q = result->query = ecs_query_init(world, &desc); - if (!q) { - goto error; + int32_t *monitor = match->monitor; + int32_t state = monitor[field]; + if (state == -1) { + return false; } - /* The uncached query used to populate the cache always matches empty - * tables. This flag determines whether the empty tables are stored - * separately in the cache or are treated as regular tables. This is only - * enabled if the user requested that the query matches empty tables. */ - ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, - !!(query_flags & EcsQueryMatchEmptyTables)); - - flecs_query_cache_allocators_init(result); - - /* Zero'd out sources array that's used for results that only match $this. - * This reduces the amount of memory used by the cache, and improves CPU - * cache locality during iteration when doing source checks. */ - if (result->query->term_count) { - result->sources = flecs_bcalloc(&result->allocators.sources); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (!field) { + return monitor[0] != dirty_state[0]; + } + } else if (!field) { + return false; } - if (q->term_count) { - observer_desc.run = flecs_query_cache_on_event; - observer_desc.ctx = impl; + flecs_table_column_t cur; + flecs_query_get_column_for_field( + &impl->pub, match, field - 1, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - int32_t event_index = 0; - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - observer_desc.events[event_index ++] = EcsOnTableEmpty; - observer_desc.events[event_index ++] = EcsOnTableFill; - } + return monitor[field] != flecs_table_get_dirty_state( + cache->query->world, cur.table)[cur.column + 1]; +} - observer_desc.events[event_index ++] = EcsOnTableCreate; - observer_desc.events[event_index ++] = EcsOnTableDelete; - observer_desc.flags_ = EcsObserverBypassQuery; +static +bool flecs_query_check_cache_monitor( + ecs_query_impl_t *impl) +{ + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - /* ecs_query_init could have moved away resources from the terms array - * in the descriptor, so use the terms array from the query. */ - ecs_os_memcpy_n(observer_desc.query.terms, q->terms, - ecs_term_t, FLECS_TERM_COUNT_MAX); - observer_desc.query.expr = NULL; /* Already parsed */ + /* If the match count changed, tables got matched/unmatched for the + * cache, so return that the query has changed. */ + if (cache->match_count != cache->prev_match_count) { + return true; + } - result->observer = flecs_observer_init(world, entity, &observer_desc); - if (!result->observer) { - goto error; + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cache->cache, &it)) { + ecs_query_cache_table_t *qt; + while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { + if (flecs_query_check_table_monitor(impl, qt, -1)) { + return true; + } } } - result->prev_match_count = -1; + return false; +} - if (ecs_should_log_1()) { - char *query_expr = ecs_query_str(result->query); - ecs_dbg_1("#[green]query#[normal] [%s] created", - query_expr ? query_expr : ""); - ecs_os_free(query_expr); +static +void flecs_query_init_query_monitors( + ecs_query_impl_t *impl) +{ + /* Change monitor for cache */ + ecs_query_cache_t *cache = impl->cache; + if (cache) { + ecs_query_cache_table_match_t *cur = cache->list.first; + + /* Ensure each match has a monitor */ + for (; cur != NULL; cur = cur->next) { + ecs_query_cache_table_match_t *match = + (ecs_query_cache_table_match_t*)cur; + flecs_query_get_match_monitor(impl, match); + } } +} - ecs_log_push_1(); +static +bool flecs_query_check_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match, + const ecs_iter_t *it) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_query_cache_process_signature(world, impl, result)) { - goto error; + if (flecs_query_get_match_monitor(impl, match)) { + return true; } - /* Group before matching so we won't have to move tables around later */ - int32_t cascade_by = result->cascade_by; - if (cascade_by) { - flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, - flecs_query_cache_group_by_cascade); - result->group_by_ctx = &result->query->terms[cascade_by - 1]; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = NULL; + if (table) { + dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (monitor[0] != dirty_state[0]) { + return true; + } } - if (const_desc->group_by_callback || const_desc->group_by) { - ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, - "cannot mix cascade and group_by"); - flecs_query_cache_group_by(result, - const_desc->group_by, const_desc->group_by_callback); - result->group_by_ctx = const_desc->group_by_ctx; - result->on_group_create = const_desc->on_group_create; - result->on_group_delete = const_desc->on_group_delete; - result->group_by_ctx_free = const_desc->group_by_ctx_free; - } + const ecs_query_t *query = cache->query; + ecs_world_t *world = query->world; + int32_t i, field_count = query->field_count; + ecs_entity_t *sources = match->sources; + const ecs_table_record_t **trs = it ? it->trs : match->trs; + ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; + + ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); - /* Ensure that while initially populating the query with tables, they are - * in the right empty/non-empty list. This ensures the query won't miss - * empty/non-empty events for tables that are currently out of sync, but - * change back to being in sync before processing pending events. */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - ecs_table_cache_init(world, &result->cache); - flecs_query_cache_match_tables(world, result); + for (i = 0; i < field_count; i ++) { + int32_t mon = monitor[i + 1]; + if (mon == -1) { + continue; + } - if (const_desc->order_by_callback) { - if (flecs_query_cache_order_by(world, impl, - const_desc->order_by, const_desc->order_by_callback, - const_desc->order_by_table_callback)) - { - goto error; + if (!(set_fields & (1llu << i))) { + continue; } - } - if (entity) { - if (!flecs_query_cache_table_count(result) && result->query->term_count){ - ecs_add_id(world, entity, EcsEmpty); + int32_t column = trs[i]->column; + ecs_entity_t src = sources[i]; + if (!src) { + if (column >= 0) { + /* owned component */ + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (mon != dirty_state[column + 1]) { + return true; + } + continue; + } else if (column == -1) { + continue; /* owned but not a component */ + } } - } - ecs_log_pop_1(); + /* Component from non-this source */ + ecs_entity_t fixed_src = match->sources[i]; + ecs_table_t *src_table = ecs_get_table(world, fixed_src); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } - return result; -error: - return NULL; + return false; } -ecs_query_cache_table_t* flecs_query_cache_get_table( - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Check if any term for matched table has changed */ +bool flecs_query_check_table_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_t *table, + int32_t field) { - return ecs_table_cache_get(&cache->cache, table); + ecs_query_cache_table_match_t *cur, *end = table->last->next; + + for (cur = table->first; cur != end; cur = cur->next) { + ecs_query_cache_table_match_t *match = + (ecs_query_cache_table_match_t*)cur; + if (field == -1) { + if (flecs_query_check_match_monitor(impl, match, NULL)) { + return true; + } + } else { + if (flecs_query_check_match_monitor_term(impl, match, field)) { + return true; + } + } + } + + return false; } -void ecs_iter_set_group( - ecs_iter_t *it, - uint64_t group_id) +void flecs_query_mark_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, - "cannot set group during iteration"); - - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *q = flecs_query_impl(qit->query); - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_poly_assert(q, ecs_query_t); - ecs_query_cache_t *cache = q->cache; - ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_t *q = &impl->pub; - ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( - cache, group_id); - if (!node) { - qit->node = NULL; - qit->last = NULL; + /* Evaluate all writeable non-fixed fields, set fields */ + ecs_termset_t write_fields = + (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); + if (!write_fields || (it->flags & EcsIterNoData)) { return; } - ecs_query_cache_table_match_t *first = node->first; - if (first) { - qit->node = node->first; - qit->last = node->last; - } else { - qit->node = NULL; - qit->last = NULL; - } - -error: - return; -} + ecs_world_t *world = q->world; + int16_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << i); + if (!(write_fields & field_bit)) { + continue; /* If term doesn't write data there's nothing to track */ + } -const ecs_query_group_info_t* ecs_query_get_group_info( - const ecs_query_t *query, - uint64_t group_id) -{ - flecs_poly_assert(query, ecs_query_t); - ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( - flecs_query_impl(query)->cache, group_id); - if (!node) { - return NULL; + ecs_entity_t src = it->sources[i]; + ecs_table_t *table; + if (!src) { + table = it->table; + } else { + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !(table = r->table)) { + continue; + } + + if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { + /* Shared fields that aren't marked explicitly as out/inout + * default to readonly */ + continue; + } + } + + int32_t type_index = it->trs[i]->index; + ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; + } + + ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[type_index]; + dirty_state[column + 1] ++; } - - return &node->info; } -void* ecs_query_get_group_ctx( - const ecs_query_t *query, - uint64_t group_id) +void flecs_query_mark_fixed_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - flecs_poly_assert(query, ecs_query_t); - const ecs_query_group_info_t *info = ecs_query_get_group_info( - query, group_id); - if (!info) { - return NULL; - } else { - return info->ctx; + /* This function marks fields dirty for terms with fixed sources. */ + ecs_query_t *q = &impl->pub; + ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; + if (!fixed_write_fields) { + return; } -} - -/** - * @file query/engine/cache_iter.c - * @brief Compile query term. - */ + ecs_world_t *world = q->world; + int32_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { + continue; /* If term doesn't write data there's nothing to track */ + } -static -void flecs_query_update_node_up_trs( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) -{ - ecs_termset_t fields = node->up_fields & node->set_fields; - if (fields) { - const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; - ecs_query_cache_t *cache = impl->cache; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fields & (1llu << i))) { - continue; - } + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* If the field is optional, it's possible that it didn't match */ + continue; + } - ecs_entity_t src = node->sources[i]; - if (src) { - const ecs_table_record_t *tr = node->trs[i]; - ecs_record_t *r = flecs_entities_get(ctx->world, src); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - if (r->table != tr->hdr.table) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->id == q->ids[i], ECS_INTERNAL_ERROR, NULL); - tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); - if (cache->field_map) { - ctx->it->trs[cache->field_map[i]] = tr; - } - } - } + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; } + + ecs_assert(it->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[it->trs[i]->column]; + dirty_state[column + 1] ++; } } -static -ecs_query_cache_table_match_t* flecs_query_cache_next( - const ecs_query_run_ctx_t *ctx) +/* Synchronize match monitor with table dirty state */ +void flecs_query_sync_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match) { - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_cache_table_match_t *node = qit->node; - ecs_query_cache_table_match_t *prev = qit->prev; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (prev != qit->last) { - ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - ctx->vars[0].range.table = node->table; - it->group_id = node->group_id; - qit->node = node->next; - qit->prev = node; - return node; + if (!match->monitor) { + if (impl->pub.flags & EcsQueryHasMonitor) { + flecs_query_get_match_monitor(impl, match); + } else { + return; + } } - return NULL; -} + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + } -static -ecs_query_cache_table_match_t* flecs_query_test( - const ecs_query_run_ctx_t *ctx, - bool redo) -{ - ecs_iter_t *it = ctx->it; - if (!redo) { - ecs_var_t *var = &ctx->vars[0]; - ecs_table_t *table = var->range.table; - ecs_assert(table != NULL, ECS_INVALID_OPERATION, - "the variable set on the iterator is missing a table"); + ecs_query_t *q = cache->query; + { + flecs_table_column_t tc; + int32_t t, term_count = q->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = q->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } - ecs_query_cache_table_t *qt = flecs_query_cache_get_table( - ctx->query->cache, table); - if (!qt) { - return NULL; - } + flecs_query_get_column_for_field(q, match, field, &tc); - ecs_query_iter_t *qit = &it->priv_.iter.query; - qit->prev = NULL; - qit->node = qt->first; - qit->last = qt->last; + monitor[field + 1] = flecs_table_get_dirty_state( + q->world, tc.table)[tc.column + 1]; + } } - return flecs_query_cache_next(ctx); + cache->prev_match_count = cache->match_count; } -static -void flecs_query_cache_init_mapped_fields( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) +bool ecs_query_changed( + ecs_query_t *q) { - ecs_iter_t *it = ctx->it; - const ecs_query_impl_t *impl = ctx->query; - ecs_query_cache_t *cache = impl->cache; - int32_t i, field_count = cache->query->field_count; - int8_t *field_map = cache->field_map; - - for (i = 0; i < field_count; i ++) { - int8_t field_index = field_map[i]; - it->trs[field_index] = node->trs[i]; - - it->ids[field_index] = node->ids[i]; - it->sources[field_index] = node->sources[i]; + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_termset_t bit = (ecs_termset_t)(1u << i); - ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); + ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, + "change detection is only supported on cached queries"); - ECS_TERMSET_COND(it->set_fields, field_bit, node->set_fields & bit); - ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); + /* If query reads terms with fixed sources, check those first as that's + * cheaper than checking entries in the cache. */ + if (impl->monitor) { + if (flecs_query_check_fixed_monitor(impl)) { + return true; + } } -} -/* Iterate cache for query that's partially cached */ -bool flecs_query_cache_search( - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); - if (!node) { - return false; - } + /* Check cache for changes. We can't detect changes for terms that are not + * cached/cacheable and don't have a fixed source, since that requires + * storing state per result, which doesn't happen for uncached queries. */ + if (impl->cache) { + /* If we're checking the cache, make sure that tables are in the correct + * empty/non-empty lists. */ + flecs_process_pending_tables(q->world); - flecs_query_cache_init_mapped_fields(ctx, node); - ctx->vars[0].range.count = node->count; - ctx->vars[0].range.offset = node->offset; + if (!(impl->pub.flags & EcsQueryHasMonitor)) { + flecs_query_init_query_monitors(impl); + } - flecs_query_update_node_up_trs(ctx, node); + /* Check cache entries for changes */ + return flecs_query_check_cache_monitor(impl); + } - return true; + return false; } -/* Iterate cache for query that's entirely cached */ -bool flecs_query_is_cache_search( - const ecs_query_run_ctx_t *ctx) +bool ecs_iter_changed( + ecs_iter_t *it) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - it->set_fields = node->set_fields; - it->up_fields = node->up_fields; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); - flecs_query_update_node_up_trs(ctx, node); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = flecs_query_impl(qit->query); + ecs_query_t *q = &impl->pub; - return true; -} + /* First check for changes for terms with fixed sources, if query has any */ + if (q->read_fields & q->fixed_fields) { + /* Detecting changes for uncached terms is costly, so only do it once + * per iteration. */ + if (!(it->flags & EcsIterFixedInChangeComputed)) { + it->flags |= EcsIterFixedInChangeComputed; + ECS_BIT_COND(it->flags, EcsIterFixedInChanged, + flecs_query_check_fixed_monitor(impl)); + } -/* Test if query that is entirely cached matches constrained $this */ -bool flecs_query_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo) -{ - ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); - if (!node) { - return false; + if (it->flags & EcsIterFixedInChanged) { + return true; + } } - flecs_query_cache_init_mapped_fields(ctx, node); - flecs_query_update_node_up_trs(ctx, node); + /* If query has a cache, check for changes in current matched result */ + if (impl->cache) { + ecs_query_cache_table_match_t *qm = + (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; + ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_query_check_match_monitor(impl, qm, it); + } - return true; +error: + return false; } -/* Test if query that is entirely cached matches constrained $this */ -bool flecs_query_is_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo) +void ecs_iter_skip( + ecs_iter_t *it) { - ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - - flecs_query_update_node_up_trs(ctx, node); - - return true; + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + it->flags |= EcsIterSkip; } /** - * @file query/engine/cache_order_by.c - * @brief Order by implementation + * @file query/engine/eval.c + * @brief Query engine implementation. */ -ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) +// #define FLECS_QUERY_TRACE + +#ifdef FLECS_QUERY_TRACE +static int flecs_query_trace_indent = 0; +#endif static -void flecs_query_cache_sort_table( - ecs_world_t *world, - ecs_table_t *table, - int32_t column_index, - ecs_order_by_action_t compare, - ecs_sort_table_action_t sort) +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_select_w_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_id_t id, + ecs_flags32_t filter_mask) { - int32_t count = ecs_table_count(table); - if (!count) { - /* Nothing to sort */ - return; - } - - if (count < 2) { - return; - } + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + ecs_table_record_t *tr; + ecs_table_t *table; - ecs_entity_t *entities = table->data.entities; - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_column_t *column = &table->data.columns[column_index]; - ecs_type_info_t *ti = column->ti; - size = ti->size; - ptr = column->data; - } + if (!redo) { + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } - if (sort) { - sort(world, table, entities, ptr, size, 0, count - 1, compare); - } else { - flecs_query_cache_sort_table_generic( - world, table, entities, ptr, size, 0, count - 1, compare); + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } } -} -/* Helper struct for building sorted table ranges */ -typedef struct sort_helper_t { - ecs_query_cache_table_match_t *match; - ecs_entity_t *entities; - const void *ptr; - int32_t row; - int32_t elem_size; - int32_t count; - bool shared; -} sort_helper_t; +repeat: + if (!redo || !op_ctx->remaining) { + tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } -static -const void* ptr_from_helper( - sort_helper_t *helper) -{ - ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); - if (helper->shared) { - return helper->ptr; + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); + table = tr->hdr.table; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } else { - return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); + tr = (ecs_table_record_t*)op_ctx->it.cur; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + table = tr->hdr.table; + op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + op_ctx->remaining --; + } + + if (flecs_query_table_filter(table, op->other, filter_mask)) { + goto repeat; } + + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; } -static -ecs_entity_t e_from_helper( - sort_helper_t *helper) +bool flecs_query_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; + ecs_id_t id = 0; + if (!redo) { + id = flecs_query_op_get_id(op, ctx); } + return flecs_query_select_w_id(op, redo, ctx, id, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } -static -void flecs_query_cache_build_sorted_table_range( - ecs_query_cache_t *cache, - ecs_query_cache_table_list_t *list) +bool flecs_query_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_world_t *world = cache->query->world; - ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, - "cannot sort query in multithreaded mode"); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + ecs_table_record_t *tr; - ecs_entity_t id = cache->order_by; - ecs_order_by_action_t compare = cache->order_by_callback; - int32_t table_count = list->info.table_count; - if (!table_count) { - return; + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + if (!table) { + return false; } - ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_match_t); - int32_t to_sort = 0; - int32_t order_by_term = cache->order_by_term; - - sort_helper_t *helper = flecs_alloc_n( - &world->allocator, sort_helper_t, table_count); - ecs_query_cache_table_match_t *cur, *end = list->last->next; - for (cur = list->first; cur != end; cur = cur->next) { - ecs_table_t *table = cur->table; - - if (ecs_table_count(table) == 0) { - continue; + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } } - if (id) { - const ecs_term_t *term = &cache->query->terms[order_by_term]; - int32_t field = term->field_index; - ecs_size_t size = cache->query->sizes[field]; - ecs_entity_t src = cur->sources[field]; - if (src == 0) { - int32_t column_index = cur->trs[field]->column; - ecs_column_t *column = &table->data.columns[column_index]; - helper[to_sort].ptr = column->data; - helper[to_sort].elem_size = size; - helper[to_sort].shared = false; - } else { - ecs_record_t *r = flecs_entities_get(world, src); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (term->src.id & EcsUp) { - ecs_entity_t base = 0; - ecs_search_relation(world, r->table, 0, id, - EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); - if (base && base != src) { /* Component could be inherited */ - r = flecs_entities_get(world, base); - } - } + tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - helper[to_sort].ptr = ecs_table_get_id( - world, r->table, id, ECS_RECORD_TO_ROW(r->row)); - helper[to_sort].elem_size = size; - helper[to_sort].shared = true; - } - ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); - } else { - helper[to_sort].ptr = NULL; - helper[to_sort].elem_size = 0; - helper[to_sort].shared = false; + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count); + op_ctx->it.cur = &tr->hdr; + } else { + ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); + if (--op_ctx->remaining <= 0) { + return false; } - helper[to_sort].match = cur; - helper[to_sort].entities = table->data.entities; - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; + op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); - - bool proceed; - do { - int32_t j, min = 0; - proceed = true; - - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; - } - } - - if (!proceed) { - break; - } + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; +} - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } +static +bool flecs_query_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_with(op, redo, ctx); + } else { + return flecs_query_select(op, redo, ctx); + } +} - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); +bool flecs_query_select_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_filter) +{ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); + if (!redo) { + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; } } - sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->trs != cur_helper->match->trs) { - cur = ecs_vec_append_t(NULL, &cache->table_slices, - ecs_query_cache_table_match_t); - *cur = *(cur_helper->match); - cur->offset = cur_helper->row; - cur->count = 1; + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } } else { - cur->count ++; + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } } - - cur_helper->row ++; - } while (proceed); - - /* Iterate through the vector of slices to set the prev/next ptrs. This - * can't be done while building the vector, as reallocs may occur */ - int32_t i, count = ecs_vec_count(&cache->table_slices); - ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); - for (i = 0; i < count; i ++) { - nodes[i].prev = &nodes[i - 1]; - nodes[i].next = &nodes[i + 1]; } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; - - flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); -} - -void flecs_query_cache_build_sorted_tables( - ecs_query_cache_t *cache) -{ - ecs_vec_clear(&cache->table_slices); - - if (cache->group_by_callback) { - /* Populate sorted node list in grouping order */ - ecs_query_cache_table_match_t *cur = cache->list.first; - if (cur) { - do { - /* Find list for current group */ - uint64_t group_id = cur->group_id; - ecs_query_cache_table_list_t *list = ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); - ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Sort tables in current group */ - flecs_query_cache_build_sorted_table_range(cache, list); +repeat: {} + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - flecs_query_cache_build_sorted_table_range(cache, &cache->list); + ecs_table_t *table = tr->hdr.table; + if (flecs_query_table_filter(table, op->other, table_filter)) { + goto repeat; } + + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + flecs_query_it_set_tr(it, field, tr); + return true; } -void flecs_query_cache_sort_tables( - ecs_world_t *world, - ecs_query_impl_t *impl) +bool flecs_query_with_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_t *cache = impl->cache; - ecs_order_by_action_t compare = cache->order_by_callback; - if (!compare) { - return; + if (redo) { + return false; } - ecs_sort_table_action_t sort = cache->order_by_table_callback; - - ecs_entity_t order_by = cache->order_by; - int32_t order_by_term = cache->order_by_term; - - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ - - bool tables_sorted = false; - - ecs_id_record_t *idr = flecs_id_record_get(world, order_by); - ecs_table_cache_iter_t it; - ecs_query_cache_table_t *qt; - flecs_table_cache_iter(&cache->cache, &it); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + if (!table) { + return false; + } - if (flecs_query_check_table_monitor(impl, qt, 0)) { - tables_sorted = true; - dirty = true; + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; } + } - int32_t column = -1; - if (order_by) { - if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { - dirty = true; - } + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - if (dirty) { - column = -1; + flecs_query_it_set_tr(it, field, tr); + return true; +} - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); - if (tr) { - column = tr->column; - } +static +bool flecs_query_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_up_with(op, redo, ctx); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); + } +} - if (column == -1) { - /* Component is shared, no sorting is needed */ - dirty = false; - } - } - } +static +bool flecs_query_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_self_up_with(op, redo, ctx, false); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); + } +} - if (!dirty) { - continue; +static +bool flecs_query_and_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t match_flags = op->match_flags; + if (redo) { + if (match_flags & EcsTermMatchAnySrc) { + return false; } + } - /* Something has changed, sort the table. Prefers using - * flecs_query_cache_sort_table when available */ - flecs_query_cache_sort_table(world, table, column, compare, sort); - tables_sorted = true; + uint64_t written = ctx->written[ctx->op_index]; + int32_t remaining = 1; + bool result; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + result = flecs_query_with(op, redo, ctx); + } else { + result = flecs_query_select(op, redo, ctx); + remaining = 0; } - if (tables_sorted || cache->match_count != cache->prev_match_count) { - flecs_query_cache_build_sorted_tables(cache); - cache->match_count ++; /* Increase version if tables changed */ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + + if (match_flags & EcsTermMatchAny && op_ctx->remaining) { + op_ctx->remaining = flecs_ito(int16_t, remaining); } -} -/** - * @file query/engine/change_detection.c - * @brief Compile query term. - */ + int32_t field = op->field_index; + if (field != -1) { + ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); + } + ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; -typedef struct { - ecs_table_t *table; - int32_t column; -} flecs_table_column_t; + return result; +} static -void flecs_query_get_column_for_field( - const ecs_query_t *q, - ecs_query_cache_table_match_t *match, - int32_t field, - flecs_table_column_t *out) +bool flecs_query_only_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); - (void)q; - - const ecs_table_record_t *tr = match->trs[field]; - ecs_table_t *table = tr->hdr.table; - int32_t column = tr->column; + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_and_any(op, redo, ctx); + } else { + return flecs_query_select_w_id(op, redo, ctx, EcsAny, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } +} - out->table = table; - out->column = column; +static +bool flecs_query_triv( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + ecs_flags64_t termset = op->src.entity; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + flecs_query_set_iter_this(ctx->it, ctx); + return flecs_query_trivial_test(ctx, redo, termset); + } else { + return flecs_query_trivial_search(ctx, op_ctx, redo, termset); + } } -/* Get match monitor. Monitors are used to keep track of whether components - * matched by the query in a table have changed. */ static -bool flecs_query_get_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) +bool flecs_query_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (match->monitor) { - return false; + (void)op; + (void)redo; + + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_cache_test(ctx, redo); + } else { + return flecs_query_cache_search(ctx); } +} - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = flecs_balloc(&cache->allocators.monitors); - monitor[0] = 0; +static +bool flecs_query_is_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + (void)op; - /* Mark terms that don't need to be monitored. This saves time when reading - * and/or updating the monitor. */ - const ecs_query_t *q = cache->query; - int32_t i, field = -1, term_count = q->term_count; - flecs_table_column_t tc; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_is_cache_test(ctx, redo); + } else { + return flecs_query_is_cache_search(ctx); + } +} - for (i = 0; i < term_count; i ++) { - if (field == q->terms[i].field_index) { - if (monitor[field + 1] != -1) { - continue; - } +static +int32_t flecs_query_next_inheritable_id( + ecs_world_t *world, + ecs_type_t *type, + int32_t index) +{ + int32_t i; + for (i = index; i < type->count; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); + if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + return i; } + } + return -1; +} - field = q->terms[i].field_index; - monitor[field + 1] = -1; +static +bool flecs_query_x_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_oper_kind_t oper) +{ + ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); + ecs_world_t *world = ctx->world; + ecs_type_t *type; + int32_t i; - /* If term isn't read, don't monitor */ - if (q->terms[i].inout != EcsIn && - q->terms[i].inout != EcsInOut && - q->terms[i].inout != EcsInOutDefault) { - continue; + if (!redo) { + /* Find entity that acts as the template from which we match the ids */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, id); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* Nothing to match */ + return false; } - /* Don't track fields that aren't set */ - if (!(match->set_fields & (1llu << field))) { - continue; - } + /* Find first id to test against. Skip ids with DontInherit flag. */ + type = op_ctx->type = &table->type; + op_ctx->first_id_index = flecs_query_next_inheritable_id( + world, type, 0); + op_ctx->cur_id_index = op_ctx->first_id_index; - flecs_query_get_column_for_field(q, match, field, &tc); - if (tc.column == -1) { - continue; /* Don't track terms that aren't stored */ + if (op_ctx->cur_id_index == -1) { + return false; /* No ids to filter on */ } - - monitor[field + 1] = 0; + } else { + type = op_ctx->type; } - match->monitor = monitor; + ecs_id_t *ids = type->array; - impl->pub.flags |= EcsQueryHasMonitor; + /* Check if source is variable, and if it's already written */ + bool src_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + src_written = written & (1ull << op->src.var); + } - return true; -} + do { + int32_t id_index = op_ctx->cur_id_index; -/* Get monitor for fixed query terms. Fixed terms are handled separately as they - * don't require a query cache, and fixed terms aren't stored in the cache. */ -static -bool flecs_query_get_fixed_monitor( - ecs_query_impl_t *impl, - bool check) -{ - ecs_query_t *q = &impl->pub; - ecs_world_t *world = q->world; - ecs_term_t *terms = q->terms; - int32_t i, term_count = q->term_count; + /* If source is not yet written, find tables with first id */ + if (!src_written) { + ecs_entity_t first_id = ids[id_index]; - if (!impl->monitor) { - impl->monitor = flecs_alloc_n(&impl->stage->allocator, - int32_t, q->field_count); - check = false; /* If the monitor is new, initialize it with dirty state */ - } + if (!flecs_query_select_w_id(op, redo, ctx, + first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + if (oper == EcsOrFrom) { + id_index = flecs_query_next_inheritable_id( + world, type, id_index + 1); + if (id_index != -1) { + op_ctx->cur_id_index = id_index; + redo = false; + continue; + } + } - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int16_t field_index = term->field_index; + return false; + } - if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { - continue; /* If term doesn't read data there's nothing to track */ + id_index ++; /* First id got matched */ + } else if (redo && src_written) { + return false; } - if (!(term->src.id & EcsIsEntity)) { - continue; /* Not a term with a fixed source */ + ecs_table_t *src_table = flecs_query_get_table( + op, &op->src, EcsQuerySrc, ctx); + if (!src_table) { + continue; } + + redo = true; - ecs_entity_t src = ECS_TERM_REF_ID(&term->src); - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + if (!src_written && oper == EcsOrFrom) { + /* Eliminate duplicate matches from tables that have multiple + * components from the type list */ + if (op_ctx->cur_id_index != op_ctx->first_id_index) { + for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); + if (!idr) { + continue; + } - ecs_record_t *r = flecs_entities_get(world, src); - if (!r || !r->table) { - continue; /* Entity is empty, nothing to track */ + if (idr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + + if (flecs_id_record_get_table(idr, src_table) != NULL) { + /* Already matched */ + break; + } + } + if (i != op_ctx->cur_id_index) { + continue; + } + } + return true; } - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (!idr) { - continue; /* If id doesn't exist, entity can't have it */ - } + if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { + for (i = id_index; i < type->count; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); + if (!idr) { + if (oper == EcsAndFrom) { + return false; + } else { + continue; + } + } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); - if (!tr) { - continue; /* Entity doesn't have the component */ - } + if (idr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } - /* Copy/check column dirty state from table */ - int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_id_record_get_table(idr, src_table) == NULL) { + if (oper == EcsAndFrom) { + break; /* Must have all ids */ + } + } else { + if (oper == EcsNotFrom) { + break; /* Must have none of the ids */ + } else if (oper == EcsOrFrom) { + return true; /* Single match is enough */ + } + } + } - if (!check) { - impl->monitor[field_index] = dirty_state[tr->column + 1]; - } else { - if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { - return true; + if (i == type->count) { + if (oper == EcsAndFrom || oper == EcsNotFrom) { + break; /* All ids matched */ + } } } - } + } while (true); - return !check; + return true; } -bool flecs_query_update_fixed_monitor( - ecs_query_impl_t *impl) +static +bool flecs_query_and_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - return flecs_query_get_fixed_monitor(impl, false); + return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } -bool flecs_query_check_fixed_monitor( - ecs_query_impl_t *impl) +static +bool flecs_query_not_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - return flecs_query_get_fixed_monitor(impl, true); + return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } - -/* Check if single match term has changed */ static -bool flecs_query_check_match_monitor_term( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - int32_t field) +bool flecs_query_or_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } + return flecs_query_x_from(op, redo, ctx, EcsOrFrom); +} - int32_t *monitor = match->monitor; - int32_t state = monitor[field]; - if (state == -1) { +static +bool flecs_query_ids( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (redo) { return false; } - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = match->table; - if (table) { - int32_t *dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (!field) { - return monitor[0] != dirty_state[0]; + ecs_id_record_t *cur; + ecs_id_t id = flecs_query_op_get_id(op, ctx); + + { + cur = flecs_id_record_get(ctx->world, id); + if (!cur || !cur->cache.tables.count) { + return false; } - } else if (!field) { - return false; } - flecs_table_column_t cur; - flecs_query_get_column_for_field( - &impl->pub, match, field - 1, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - - return monitor[field] != flecs_table_get_dirty_state( - cache->query->world, cur.table)[cur.column + 1]; + flecs_query_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->trs[op->field_index] = NULL; /* Mark field as set */ + } + + return true; } static -bool flecs_query_check_cache_monitor( - ecs_query_impl_t *impl) +bool flecs_query_idsright( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; - /* If the match count changed, tables got matched/unmatched for the - * cache, so return that the query has changed. */ - if (cache->match_count != cache->prev_match_count) { - return true; - } + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); + return true; + } - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&cache->cache, &it)) { - ecs_query_cache_table_t *qt; - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - if (flecs_query_check_table_monitor(impl, qt, -1)) { - return true; - } + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; } } - return false; -} + do { + cur = op_ctx->cur = op_ctx->cur->first.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ -static -void flecs_query_init_query_monitors( - ecs_query_impl_t *impl) -{ - /* Change monitor for cache */ - ecs_query_cache_t *cache = impl->cache; - if (cache) { - ecs_query_cache_table_match_t *cur = cache->list.first; + if (!cur) { + return false; + } - /* Ensure each match has a monitor */ - for (; cur != NULL; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - flecs_query_get_match_monitor(impl, match); - } + flecs_query_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } + + return true; } static -bool flecs_query_check_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - const ecs_iter_t *it) +bool flecs_query_idsleft( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = NULL; - if (table) { - dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (monitor[0] != dirty_state[0]) { + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); return true; } - } - - const ecs_query_t *query = cache->query; - ecs_world_t *world = query->world; - int32_t i, field_count = query->field_count; - ecs_entity_t *sources = match->sources; - const ecs_table_record_t **trs = it ? it->trs : match->trs; - ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; - - ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < field_count; i ++) { - int32_t mon = monitor[i + 1]; - if (mon == -1) { - continue; + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; } - - if (!(set_fields & (1llu << i))) { - continue; + } else { + if (!op_ctx->cur) { + return false; } + } - int32_t column = trs[i]->column; - ecs_entity_t src = sources[i]; - if (!src) { - if (column >= 0) { - /* owned component */ - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (mon != dirty_state[column + 1]) { - return true; - } - continue; - } else if (column == -1) { - continue; /* owned but not a component */ - } - } + do { + cur = op_ctx->cur = op_ctx->cur->second.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - /* Component from non-this source */ - ecs_entity_t fixed_src = match->sources[i]; - ecs_table_t *src_table = ecs_get_table(world, fixed_src); - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *src_dirty_state = flecs_table_get_dirty_state( - world, src_table); - if (mon != src_dirty_state[column + 1]) { - return true; - } + if (!cur) { + return false; } - return false; -} - -/* Check if any term for matched table has changed */ -bool flecs_query_check_table_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_t *table, - int32_t field) -{ - ecs_query_cache_table_match_t *cur, *end = table->last->next; + flecs_query_set_vars(op, cur->id, ctx); - for (cur = table->first; cur != end; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - if (field == -1) { - if (flecs_query_check_match_monitor(impl, match, NULL)) { - return true; - } - } else { - if (flecs_query_check_match_monitor_term(impl, match, field)) { - return true; - } - } + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } - return false; + return true; } -void flecs_query_mark_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) +static +bool flecs_query_each( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_t *q = &impl->pub; + ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); + int32_t row; - /* Evaluate all writeable non-fixed fields, set fields */ - ecs_termset_t write_fields = - (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); - if (!write_fields || (it->flags & EcsIterNoData)) { - return; + ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); + ecs_table_t *table = range.table; + if (!table) { + return false; } - ecs_world_t *world = q->world; - int16_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - ecs_termset_t field_bit = (ecs_termset_t)(1u << i); - if (!(write_fields & field_bit)) { - continue; /* If term doesn't write data there's nothing to track */ + if (!redo) { + if (!ecs_table_count(table)) { + return false; } - - ecs_entity_t src = it->sources[i]; - ecs_table_t *table; - if (!src) { - table = it->table; + row = op_ctx->row = range.offset; + } else { + int32_t end = range.count; + if (end) { + end += range.offset; } else { - ecs_record_t *r = flecs_entities_get(world, src); - if (!r || !(table = r->table)) { - continue; - } - - if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { - /* Shared fields that aren't marked explicitly as out/inout - * default to readonly */ - continue; - } - } - - int32_t type_index = it->trs[i]->index; - ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); - - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *dirty_state = table->dirty_state; - if (!dirty_state) { - continue; + end = ecs_table_count(table); + } + row = ++ op_ctx->row; + if (op_ctx->row >= end) { + return false; } - - ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); - int32_t column = table->column_map[type_index]; - dirty_state[column + 1] ++; } + + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); + + return true; } -void flecs_query_mark_fixed_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) +static +bool flecs_query_store( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - /* This function marks fields dirty for terms with fixed sources. */ - ecs_query_t *q = &impl->pub; - ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; - if (!fixed_write_fields) { - return; + if (!redo) { + flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); + return true; + } else { + return false; } +} - ecs_world_t *world = q->world; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { - continue; /* If term doesn't write data there's nothing to track */ - } +static +bool flecs_query_reset( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (!redo) { + return true; + } else { + flecs_query_var_reset(op->src.var, ctx); + return false; + } +} - ecs_entity_t src = it->sources[i]; - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = flecs_entities_get(world, src); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* If the field is optional, it's possible that it didn't match */ - continue; - } +static +bool flecs_query_lookup( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } - int32_t *dirty_state = table->dirty_state; - if (!dirty_state) { - continue; - } + const ecs_query_impl_t *query = ctx->query; + ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); + ecs_query_var_t *var = &query->vars[op->src.var]; - ecs_assert(it->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t column = table->column_map[it->trs[i]->column]; - dirty_state[column + 1] ++; + ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, + NULL, NULL, false); + if (!result) { + flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); + return false; } + + flecs_query_var_set_entity(op, op->src.var, result, ctx); + + return true; } -/* Synchronize match monitor with table dirty state */ -void flecs_query_sync_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) +static +bool flecs_query_setvars( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + (void)op; - if (!match->monitor) { - if (impl->pub.flags & EcsQueryHasMonitor) { - flecs_query_get_match_monitor(impl, match); - } else { - return; - } - } + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_var_id_t *src_vars = query->src_vars; + ecs_iter_t *it = ctx->it; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - if (table) { - int32_t *dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + if (redo) { + return false; } - ecs_query_t *q = cache->query; - { - flecs_table_column_t tc; - int32_t t, term_count = q->term_count; - for (t = 0; t < term_count; t ++) { - int32_t field = q->terms[t].field_index; - if (monitor[field + 1] == -1) { - continue; - } - - flecs_query_get_column_for_field(q, match, field, &tc); + int32_t i; + ecs_flags32_t up_fields = it->up_fields; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = src_vars[i]; + if (!var_id) { + continue; + } - monitor[field + 1] = flecs_table_get_dirty_state( - q->world, tc.table)[tc.column + 1]; + if (up_fields & (1u << i)) { + continue; } + + it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } - cache->prev_match_count = cache->match_count; + return true; } -bool ecs_query_changed( - ecs_query_t *q) +static +bool flecs_query_setthis( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); + ecs_var_t *vars = ctx->vars; + ecs_var_t *this_var = &vars[op->first.var]; - ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, - "change detection is only supported on cached queries"); + if (!redo) { + /* Save values so we can restore them later */ + op_ctx->range = vars[0].range; - /* If query reads terms with fixed sources, check those first as that's - * cheaper than checking entries in the cache. */ - if (impl->monitor) { - if (flecs_query_check_fixed_monitor(impl)) { - return true; - } + /* Constrain This table variable to a single entity from the table */ + vars[0].range = flecs_range_from_entity(this_var->entity, ctx); + vars[0].entity = this_var->entity; + return true; + } else { + /* Restore previous values, so that instructions that are operating on + * the table variable use all the entities in the table. */ + vars[0].range = op_ctx->range; + vars[0].entity = 0; + return false; } +} - /* Check cache for changes. We can't detect changes for terms that are not - * cached/cacheable and don't have a fixed source, since that requires - * storing state per result, which doesn't happen for uncached queries. */ - if (impl->cache) { - /* If we're checking the cache, make sure that tables are in the correct - * empty/non-empty lists. */ - flecs_process_pending_tables(q->world); +static +bool flecs_query_setfixed( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - if (!(impl->pub.flags & EcsQueryHasMonitor)) { - flecs_query_init_query_monitors(impl); - } + if (redo) { + return false; + } - /* Check cache entries for changes */ - return flecs_query_check_cache_monitor(impl); + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + const ecs_term_ref_t *src = &term->src; + if (src->id & EcsIsEntity) { + it->sources[term->field_index] = ECS_TERM_REF_ID(src); + } } - return false; + return true; } -bool ecs_iter_changed( - ecs_iter_t *it) +bool flecs_query_setids( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); - ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); - - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = flecs_query_impl(qit->query); - ecs_query_t *q = &impl->pub; - - /* First check for changes for terms with fixed sources, if query has any */ - if (q->read_fields & q->fixed_fields) { - /* Detecting changes for uncached terms is costly, so only do it once - * per iteration. */ - if (!(it->flags & EcsIterFixedInChangeComputed)) { - it->flags |= EcsIterFixedInChangeComputed; - ECS_BIT_COND(it->flags, EcsIterFixedInChanged, - flecs_query_check_fixed_monitor(impl)); - } + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - if (it->flags & EcsIterFixedInChanged) { - return true; - } + if (redo) { + return false; } - /* If query has a cache, check for changes in current matched result */ - if (impl->cache) { - ecs_query_cache_table_match_t *qm = - (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; - ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_query_check_match_monitor(impl, qm, it); + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + it->ids[term->field_index] = term->id; } -error: - return false; + return true; } -void ecs_iter_skip( - ecs_iter_t *it) +static +bool flecs_query_setid( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); - it->flags |= EcsIterSkip; + if (redo) { + return false; + } + + ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); + ctx->it->ids[op->field_index] = op->first.entity; + return true; } -/** - * @file query/engine/eval.c - * @brief Query engine implementation. - */ +/* Check if entity is stored in table */ +static +bool flecs_query_contain( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + ecs_var_id_t src_id = op->src.var; + ecs_var_id_t first_id = op->first.var; -// #define FLECS_QUERY_TRACE + ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); -#ifdef FLECS_QUERY_TRACE -static int flecs_query_trace_indent = 0; -#endif + ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); + return table == ecs_get_table(ctx->world, e); +} +/* Check if first and second id of pair from last operation are the same */ static -bool flecs_query_dispatch( +bool flecs_query_pair_eq( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx); + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } -bool flecs_query_select_w_id( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_id_t id, - ecs_flags32_t filter_mask) + ecs_iter_t *it = ctx->it; + ecs_id_t id = it->ids[op->field_index]; + return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); +} + +static +void flecs_query_reset_after_block( + const ecs_query_op_t *start_op, + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx, + bool result) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; - ecs_table_t *table; + ecs_query_lbl_t op_index = start_op->next; + const ecs_query_op_t *op = &ctx->qit->ops[op_index]; - if (!redo) { - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + int32_t field = op->field_index; + if (field == -1) { + goto done; + } - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } + /* Set/unset field */ + ecs_iter_t *it = ctx->it; + if (result) { + ECS_TERMSET_SET(it->set_fields, 1u << field); + return; } -repeat: - if (!redo || !op_ctx->remaining) { - tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } + /* Reset state after a field was not matched */ + ctx->written[op_index] = ctx->written[ctx->op_index]; + ctx->op_index = op_index; + ECS_TERMSET_CLEAR(it->set_fields, 1u << field); - op_ctx->column = flecs_ito(int16_t, tr->index); - op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); - table = tr->hdr.table; - flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); - } else { - tr = (ecs_table_record_t*)op_ctx->it.cur; - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - table = tr->hdr.table; - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); - op_ctx->remaining --; - } + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - if (flecs_query_table_filter(table, op->other, filter_mask)) { - goto repeat; + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; } - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; -} + it->trs[field] = NULL; -bool flecs_query_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_id_t id = 0; - if (!redo) { - id = flecs_query_op_get_id(op, ctx); + /* Reset variables */ + if (flags_1st & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ + flecs_query_var_reset(op->first.var, ctx); + } } - return flecs_query_select_w_id(op, redo, ctx, id, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + if (flags_2nd & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ + flecs_query_var_reset(op->second.var, ctx); + } + } + + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { + it->sources[field] = op->src.entity; + } + +done: + op_ctx->op_index = op_index; } -bool flecs_query_with( - const ecs_query_op_t *op, +static +bool flecs_query_run_block( bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { + if (!redo) { + op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); + } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { return false; } - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; - tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; + bool result = flecs_query_run_until( + redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); - op_ctx->column = flecs_ito(int16_t, tr->index); - op_ctx->remaining = flecs_ito(int16_t, tr->count); - op_ctx->it.cur = &tr->hdr; - } else { - ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); - if (--op_ctx->remaining <= 0) { - return false; - } + op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); + return result; +} - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); - ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); - } +static +ecs_query_lbl_t flecs_query_last_op_for_or_cond( + const ecs_query_op_t *ops, + ecs_query_lbl_t cur, + ecs_query_lbl_t last) +{ + const ecs_query_op_t *cur_op, *last_op = &ops[last]; - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; + do { + cur_op = &ops[cur]; + cur ++; + } while (cur_op->next != last && cur_op != last_op); + + return cur; } static -bool flecs_query_and( - const ecs_query_op_t *op, +bool flecs_query_run_until_for_select_or( bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) { - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_with(op, redo, ctx); - } else { - return flecs_query_select(op, redo, ctx); + ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( + ops, cur, flecs_itolbl(last)); + if (redo) { + /* If redoing, start from the last instruction of the last executed + * sequence */ + cur = flecs_itolbl(last_for_cur - 1); } + + flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); +#ifdef FLECS_QUERY_TRACE + printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", + ctx->op_index == last ? "true" : "false"); +#endif + return ctx->op_index == last; } -bool flecs_query_select_id( +static +bool flecs_query_select_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_flags32_t table_filter) + ecs_query_run_ctx_t *ctx) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; - int8_t field = op->field_index; - ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); if (!redo) { - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + op_ctx->op_index = first; + } - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; + const ecs_query_op_t *ops = qit->ops; + const ecs_query_op_t *first_op = &ops[first - 1]; + ecs_query_lbl_t last = first_op->next; + const ecs_query_op_t *last_op = &ops[last]; + const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; + bool result = false; + + do { + ecs_query_lbl_t cur = op_ctx->op_index; + ctx->op_index = cur; + ctx->written[cur] = op->written; + + result = flecs_query_run_until_for_select_or( + redo, ctx, ops, flecs_itolbl(first - 1), cur, last); + + if (result) { + if (first == cur) { + break; } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; + + /* If a previous operation in the OR chain returned a result for the + * same matched source, skip it so we don't yield for each matching + * element in the chain. */ + + /* Copy written status so that the variables we just wrote will show + * up as written for the test. This ensures that the instructions + * match against the result we already found, vs. starting a new + * search (the difference between select & with). */ + ecs_query_lbl_t prev = first; + bool dup_found = false; + + /* While terms of an OR chain always operate on the same source, it + * is possible that a table variable is resolved to an entity + * variable. When checking for duplicates, copy the entity variable + * to the table, to ensure we're only testing the found entity. */ + const ecs_query_op_t *prev_op = &ops[cur - 1]; + ecs_var_t old_table_var; + ecs_os_memset_t(&old_table_var, 0, ecs_var_t); + bool restore_table_var = false; + + if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (first_op->src.var != prev_op->src.var) { + restore_table_var = true; + old_table_var = ctx->vars[first_op->src.var]; + ctx->vars[first_op->src.var] = + ctx->vars[prev_op->src.var]; + } } - } - } -repeat: {} - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } + int16_t field_index = op->field_index; + ecs_id_t prev_id = it->ids[field_index]; + const ecs_table_record_t *prev_tr = it->trs[field_index]; - ecs_table_t *table = tr->hdr.table; - if (flecs_query_table_filter(table, op->other, table_filter)) { - goto repeat; - } + do { + ctx->written[prev] = ctx->written[last]; - flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); - flecs_query_it_set_tr(it, field, tr); - return true; -} + flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), + prev, cur); -bool flecs_query_with_id( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } + if (ctx->op_index == last) { + /* Duplicate match was found, find next result */ + redo = true; + dup_found = true; + break; + } - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_iter_t *it = ctx->it; - int8_t field = op->field_index; - ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + break; + } while (true); - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { - return false; - } + /* Restore table variable to full range for next result */ + if (restore_table_var) { + ctx->vars[first_op->src.var] = old_table_var; + } - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; + if (dup_found) { + continue; + } + + /* Restore id in case op set it */ + it->ids[field_index] = prev_id; + it->trs[field_index] = prev_tr; + break; } - } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + /* No result was found, go to next operation in chain */ + op_ctx->op_index = flecs_query_last_op_for_or_cond( + ops, op_ctx->op_index, last); + cur_op = &qit->ops[op_ctx->op_index]; - flecs_query_it_set_tr(it, field, tr); - return true; -} + redo = false; + } while (cur_op != last_op); -static -bool flecs_query_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_up_with(op, redo, ctx); - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); - } + return result; } static -bool flecs_query_self_up( +bool flecs_query_with_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_self_up_with(op, redo, ctx, false); - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + bool result = flecs_query_run_block(redo, ctx, op_ctx); + if (result) { + /* If a match was found, no need to keep searching for this source */ + op_ctx->op_index = op->next; } + + return result; } static -bool flecs_query_and_any( +bool flecs_query_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - ecs_flags16_t match_flags = op->match_flags; - if (redo) { - if (match_flags & EcsTermMatchAnySrc) { - return false; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + if (!(written & (1ull << op->src.var))) { + return flecs_query_select_or(op, redo, ctx); } } - uint64_t written = ctx->written[ctx->op_index]; - int32_t remaining = 1; - bool result; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - result = flecs_query_with(op, redo, ctx); - } else { - result = flecs_query_select(op, redo, ctx); - remaining = 0; - } - - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - - if (match_flags & EcsTermMatchAny && op_ctx->remaining) { - op_ctx->remaining = flecs_ito(int16_t, remaining); - } - - int32_t field = op->field_index; - if (field != -1) { - ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); - } - - ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; - - return result; + return flecs_query_with_or(op, redo, ctx); } static -bool flecs_query_only_any( +bool flecs_query_run_block_w_reset( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_and_any(op, redo, ctx); - } else { - return flecs_query_select_w_id(op, redo, ctx, EcsAny, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + bool result = flecs_query_run_block(redo, ctx, op_ctx); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; } static -bool flecs_query_triv( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); - ecs_flags64_t termset = op->src.entity; - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - flecs_query_set_iter_this(ctx->it, ctx); - return flecs_query_trivial_test(ctx, redo, termset); - } else { - return flecs_query_trivial_search(ctx, op_ctx, redo, termset); +bool flecs_query_not( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; } + + return !flecs_query_run_block_w_reset(op, redo, ctx); } static -bool flecs_query_cache( +bool flecs_query_optional( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - (void)redo; - - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - return flecs_query_cache_test(ctx, redo); + ecs_query_run_ctx_t *ctx) +{ + bool result = flecs_query_run_block_w_reset(op, redo, ctx); + if (!redo) { + return true; /* Return at least once */ } else { - return flecs_query_cache_search(ctx); + return result; } } static -bool flecs_query_is_cache( +bool flecs_query_eval_if( const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind) { - (void)op; - - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - return flecs_query_is_cache_test(ctx, redo); - } else { - return flecs_query_is_cache_search(ctx); + bool result = true; + if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { + result = ctx->vars[ref->var].entity != EcsWildcard; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; } + return true; } static -int32_t flecs_query_next_inheritable_id( - ecs_world_t *world, - ecs_type_t *type, - int32_t index) +bool flecs_query_if_var( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - int32_t i; - for (i = index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - return i; + if (!redo) { + if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || + !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || + !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) + { + return true; } } - return -1; + + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + return flecs_query_run_block(redo, ctx, op_ctx); } static -bool flecs_query_x_from( +bool flecs_query_if_set( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_oper_kind_t oper) + ecs_query_run_ctx_t *ctx) { - ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); - ecs_world_t *world = ctx->world; - ecs_type_t *type; - int32_t i; + ecs_iter_t *it = ctx->it; + int8_t field_index = flecs_ito(int8_t, op->other); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); if (!redo) { - /* Find entity that acts as the template from which we match the ids */ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = flecs_entities_get(world, id); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* Nothing to match */ - return false; - } - - /* Find first id to test against. Skip ids with DontInherit flag. */ - type = op_ctx->type = &table->type; - op_ctx->first_id_index = flecs_query_next_inheritable_id( - world, type, 0); - op_ctx->cur_id_index = op_ctx->first_id_index; - - if (op_ctx->cur_id_index == -1) { - return false; /* No ids to filter on */ - } - } else { - type = op_ctx->type; + op_ctx->is_set = ecs_field_is_set(it, field_index); } - ecs_id_t *ids = type->array; - - /* Check if source is variable, and if it's already written */ - bool src_written = true; - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - uint64_t written = ctx->written[ctx->op_index]; - src_written = written & (1ull << op->src.var); + if (!op_ctx->is_set) { + return !redo; } - do { - int32_t id_index = op_ctx->cur_id_index; - - /* If source is not yet written, find tables with first id */ - if (!src_written) { - ecs_entity_t first_id = ids[id_index]; - - if (!flecs_query_select_w_id(op, redo, ctx, - first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - if (oper == EcsOrFrom) { - id_index = flecs_query_next_inheritable_id( - world, type, id_index + 1); - if (id_index != -1) { - op_ctx->cur_id_index = id_index; - redo = false; - continue; - } - } + return flecs_query_run_block(redo, ctx, op_ctx); +} - return false; - } +static +bool flecs_query_end( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; (void)ctx; + return !redo; +} - id_index ++; /* First id got matched */ - } else if (redo && src_written) { - return false; - } +static +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + switch(op->kind) { + case EcsQueryAnd: return flecs_query_and(op, redo, ctx); + case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); + case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); + case EcsQueryCache: return flecs_query_cache(op, redo, ctx); + case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); + case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); + case EcsQueryUp: return flecs_query_up(op, redo, ctx); + case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); + case EcsQueryWith: return flecs_query_with(op, redo, ctx); + case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); + case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); + case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); + case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); + case EcsQueryIds: return flecs_query_ids(op, redo, ctx); + case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); + case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); + case EcsQueryEach: return flecs_query_each(op, redo, ctx); + case EcsQueryStore: return flecs_query_store(op, redo, ctx); + case EcsQueryReset: return flecs_query_reset(op, redo, ctx); + case EcsQueryOr: return flecs_query_or(op, redo, ctx); + case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); + case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); + case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); + case EcsQueryEnd: return flecs_query_end(op, redo, ctx); + case EcsQueryNot: return flecs_query_not(op, redo, ctx); + case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); + case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); + case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); + case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); + case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); + case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); + case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); + case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); + case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); + case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); + case EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); + case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); + case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); + case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); + case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(op, redo, ctx); + case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); + case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); + case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); + case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); + case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); + case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); + case EcsQueryContain: return flecs_query_contain(op, redo, ctx); + case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); + case EcsQueryYield: return false; + case EcsQueryNothing: return false; + } + return false; +} - ecs_table_t *src_table = flecs_query_get_table( - op, &op->src, EcsQuerySrc, ctx); - if (!src_table) { - continue; - } - - redo = true; +bool flecs_query_run_until( + bool redo, + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) +{ + ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); - if (!src_written && oper == EcsOrFrom) { - /* Eliminate duplicate matches from tables that have multiple - * components from the type list */ - if (op_ctx->cur_id_index != op_ctx->first_id_index) { - for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - continue; - } + ctx->op_index = cur; + const ecs_query_op_t *op = &ops[ctx->op_index]; + const ecs_query_op_t *last_op = &ops[last]; + ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - - if (flecs_id_record_get_table(idr, src_table) != NULL) { - /* Already matched */ - break; - } - } - if (i != op_ctx->cur_id_index) { - continue; - } - } - return true; - } +#ifdef FLECS_QUERY_TRACE + printf("%*sblock:\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent ++; +#endif - if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { - for (i = id_index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - if (oper == EcsAndFrom) { - return false; - } else { - continue; - } - } + do { + #ifdef FLECS_DEBUG + ctx->qit->profile[ctx->op_index].count[redo] ++; + #endif - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } +#ifdef FLECS_QUERY_TRACE + printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", + ctx->op_index, flecs_query_op_str(op->kind)); +#endif - if (flecs_id_record_get_table(idr, src_table) == NULL) { - if (oper == EcsAndFrom) { - break; /* Must have all ids */ - } - } else { - if (oper == EcsNotFrom) { - break; /* Must have none of the ids */ - } else if (oper == EcsOrFrom) { - return true; /* Single match is enough */ - } - } - } + bool result = flecs_query_dispatch(op, redo, ctx); + cur = (&op->prev)[result]; + redo = cur < ctx->op_index; - if (i == type->count) { - if (oper == EcsAndFrom || oper == EcsNotFrom) { - break; /* All ids matched */ - } - } + if (!redo) { + ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; } - } while (true); + + ctx->op_index = cur; + op = &ops[ctx->op_index]; + + if (cur <= first) { +#ifdef FLECS_QUERY_TRACE + printf("%*sfalse\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif + return false; + } + } while (op < last_op); + +#ifdef FLECS_QUERY_TRACE + printf("%*strue\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif return true; } -static -bool flecs_query_and_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsAndFrom); -} +/** + * @file query/engine/eval_iter.c + * @brief Query iterator. + */ -static -bool flecs_query_not_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsNotFrom); -} static -bool flecs_query_or_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_run_ctx_init( + ecs_iter_t *it, + ecs_query_run_ctx_t *ctx) { - return flecs_query_x_from(op, redo, ctx, EcsOrFrom); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); + ctx->world = it->real_world; + ctx->query = impl; + ctx->it = it; + ctx->vars = qit->vars; + ctx->query_vars = qit->query_vars; + ctx->written = qit->written; + ctx->op_ctx = qit->op_ctx; + ctx->qit = qit; } -static -bool flecs_query_ids( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_constrain( + ecs_iter_t *it) { - if (redo) { - return false; - } + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur; - ecs_id_t id = flecs_query_op_get_id(op, ctx); + const ecs_query_impl_t *query = ctx.query; + const ecs_query_t *q = &query->pub; + ecs_flags64_t it_written = it->constrained_vars; + ctx.written[0] = it_written; + if (it_written && ctx.query->src_vars) { + /* If variables were constrained, check if there are any table + * variables that have a constrained entity variable. */ + ecs_var_t *vars = ctx.vars; + int32_t i, count = q->field_count; + for (i = 0; i < count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + ecs_query_var_t *var = &query->vars[var_id]; - { - cur = flecs_id_record_get(ctx->world, id); - if (!cur || !cur->cache.tables.count) { - return false; + if (!(it_written & (1ull << var_id)) || + (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) + { + continue; + } + + /* Initialize table variable with constrained entity variable */ + ecs_var_t *tvar = &vars[var->table_id]; + tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); + ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } } - flecs_query_set_vars(op, cur->id, ctx); + /* This function can be called multiple times when setting variables, so + * reset flags before setting them. */ + it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| + EcsIterTrivialSearch); - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - it->trs[op->field_index] = NULL; /* Mark field as set */ - } + /* Figure out whether this query can utilize specialized iterator modes for + * improved performance. */ + ecs_flags32_t flags = q->flags; + ecs_query_cache_t *cache = query->cache; + if (flags & EcsQueryIsTrivial) { + if ((flags & EcsQueryMatchOnlySelf)) { + if (it_written) { + /* When we're testing against an entity or table, set the $this + * variable in advance since it won't change later on. This + * initializes it.count, it.entities and it.table. */ + flecs_query_set_iter_this(it, &ctx); - return true; + if (!cache) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialTest; + } + } else if (flags & EcsQueryIsCacheable) { + it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; + } + } else { + if (!cache) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialSearch; + } + } else if (flags & EcsQueryIsCacheable) { + if (!cache->order_by_callback) { + it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; + } + } + } + + /* If we're using a specialized iterator mode, make sure to + * initialize static component ids. Usually this is the first + * instruction of a query plan, but because we're not running the + * query plan when using a specialized iterator mode, manually call + * the operation on iterator init. */ + flecs_query_setids(NULL, false, &ctx); + } + } } -static -bool flecs_query_idsright( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +bool ecs_query_next( + ecs_iter_t *it) { - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!ecs_id_is_wildcard(id)) { - /* If id is not a wildcard, we can directly return it. This can - * happen if a variable was constrained by an iterator. */ - op_ctx->cur = NULL; - flecs_query_set_vars(op, id, ctx); - return true; + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + const ecs_query_op_t *ops = qit->ops; + + bool redo = it->flags & EcsIterIsValid; + if (redo) { + /* Change detection */ + if (!(it->flags & EcsIterSkip)) { + /* Mark table columns that are written to dirty */ + flecs_query_mark_fields_dirty(impl, it); + if (qit->prev) { + if (ctx.query->pub.flags & EcsQueryHasMonitor) { + /* If this query uses change detection, synchronize the + * monitor for the iterated table with the query */ + flecs_query_sync_match_monitor(impl, qit->prev); + } + } } + } - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; + it->flags &= ~(EcsIterSkip); + it->flags |= EcsIterIsValid; + it->frame_offset += it->count; + + /* Specialized iterator modes. When a query doesn't use any advanced + * features, it can call specialized iterator functions directly instead of + * going through the dispatcher of the query engine. + * The iterator mode is set during iterator initialization. Besides being + * determined by the query, there are different modes for searching and + * testing, where searching returns all matches for a query, whereas testing + * tests a single table or table range against the query. */ + + if (it->flags & EcsIterTrivialCached) { + /* Cached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + if (flecs_query_is_cache_search(&ctx)) { + goto trivial_search_yield; + } + } else if (it->flags & EcsIterTrivialTest) { + if (flecs_query_is_cache_test(&ctx, redo)) { + goto yield; + } } } else { - if (!op_ctx->cur) { - return false; + /* Uncached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { + goto yield; + } + } else if (it->flags & EcsIterTrivialTest) { + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (flecs_query_trivial_test(&ctx, redo, mask)) { + goto yield; + } + } else { + /* Default iterator mode. This enters the query VM dispatch loop. */ + if (flecs_query_run_until( + redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) + { + ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, + ECS_INTERNAL_ERROR, NULL); + flecs_query_set_iter_this(it, &ctx); + ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); + qit->op = flecs_itolbl(ctx.op_index - 1); + goto yield; + } } } - do { - cur = op_ctx->cur = op_ctx->cur->first.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - - if (!cur) { - return false; + /* Done iterating */ + flecs_query_mark_fixed_fields_dirty(impl, it); + if (ctx.query->monitor) { + flecs_query_update_fixed_monitor( + ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } - flecs_query_set_vars(op, cur->id, ctx); + ecs_iter_fini(it); + return false; - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); - } +trivial_search_yield: + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = ecs_table_entities(it->table); +yield: return true; } static -bool flecs_query_idsleft( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_fini_ctx( + ecs_iter_t *it, + ecs_query_iter_t *qit) { - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; - - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!ecs_id_is_wildcard(id)) { - /* If id is not a wildcard, we can directly return it. This can - * happen if a variable was constrained by an iterator. */ - op_ctx->cur = NULL; - flecs_query_set_vars(op, id, ctx); - return true; - } + const ecs_query_impl_t *query = flecs_query_impl(qit->query); + int32_t i, count = query->op_count; + ecs_query_op_t *ops = query->ops; + ecs_query_op_ctx_t *ctx = qit->op_ctx; + ecs_allocator_t *a = flecs_query_get_allocator(it); - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; + for (i = 0; i < count; i ++) { + ecs_query_op_t *op = &ops[i]; + switch(op->kind) { + case EcsQueryTrav: + flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); + break; + case EcsQueryUp: + case EcsQuerySelfUp: + case EcsQueryUnionEqUp: + case EcsQueryUnionEqSelfUp: { + ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; + if (cache->dir == EcsTravDown) { + flecs_query_down_cache_fini(a, cache); + } else { + flecs_query_up_cache_fini(cache); + } + break; } - } else { - if (!op_ctx->cur) { - return false; + default: + break; } } +} - do { - cur = op_ctx->cur = op_ctx->cur->second.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ +static +void flecs_query_iter_fini( + ecs_iter_t *it) +{ + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(qit->query, ecs_query_t); + int32_t op_count = flecs_query_impl(qit->query)->op_count; + int32_t var_count = flecs_query_impl(qit->query)->var_count; - if (!cur) { - return false; +#ifdef FLECS_DEBUG + if (it->flags & EcsIterProfile) { + char *str = ecs_query_plan_w_profile(qit->query, it); + printf("%s\n", str); + ecs_os_free(str); } - flecs_query_set_vars(op, cur->id, ctx); + flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); +#endif - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); - } + flecs_query_iter_fini_ctx(it, qit); + flecs_iter_free_n(qit->vars, ecs_var_t, var_count); + flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); + flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); - return true; + qit->vars = NULL; + qit->written = NULL; + qit->op_ctx = NULL; + qit->query = NULL; } -static -bool flecs_query_each( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_iter_t flecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q) { - ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); - int32_t row; + ecs_iter_t it = {0}; + ecs_query_iter_t *qit = &it.priv_.iter.query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); - ecs_table_t *table = range.table; - if (!table) { - return false; - } + int32_t i, var_count = impl->var_count; + int32_t op_count = impl->op_count ? impl->op_count : 1; + it.world = ECS_CONST_CAST(ecs_world_t*, world); - if (!redo) { - if (!ecs_table_count(table)) { - return false; - } - row = op_ctx->row = range.offset; - } else { - int32_t end = range.count; - if (end) { - end += range.offset; - } else { - end = ecs_table_count(table); - } - row = ++ op_ctx->row; - if (op_ctx->row >= end) { - return false; - } + /* If world passed to iterator is the real world, but query was created from + * a stage, stage takes precedence. */ + if (flecs_poly_is(it.world, ecs_world_t) && + flecs_poly_is(q->world, ecs_stage_t)) + { + it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } - ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - const ecs_entity_t *entities = ecs_table_entities(table); - flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); + it.real_world = q->real_world; + ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || + it.world != it.real_world, ECS_INVALID_PARAMETER, + "create iterator for stage when world is in multithreaded mode"); - return true; -} + it.query = q; + it.system = q->entity; + it.next = ecs_query_next; + it.fini = flecs_query_iter_fini; + it.field_count = q->field_count; + it.sizes = q->sizes; + it.set_fields = q->set_fields; + it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; + it.row_fields = q->row_fields; + it.up_fields = 0; + flecs_query_apply_iter_flags(&it, q); -static -bool flecs_query_store( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); - return true; - } else { - return false; - } -} + flecs_iter_init(it.world, &it, + flecs_iter_cache_ids | + flecs_iter_cache_trs | + flecs_iter_cache_sources | + flecs_iter_cache_ptrs); -static -bool flecs_query_reset( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - return true; - } else { - flecs_query_var_reset(op->src.var, ctx); - return false; + qit->query = q; + qit->query_vars = impl->vars; + qit->ops = impl->ops; + + ecs_query_cache_t *cache = impl->cache; + if (cache) { + qit->node = cache->list.first; + qit->last = cache->list.last; + + if (cache->order_by_callback && cache->list.info.table_count) { + flecs_query_cache_sort_tables(it.real_world, impl); + qit->node = ecs_vec_first(&cache->table_slices); + qit->last = ecs_vec_last_t( + &cache->table_slices, ecs_query_cache_table_match_t); + } + + cache->prev_match_count = cache->match_count; } -} -static -bool flecs_query_lookup( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; + if (var_count) { + qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } - const ecs_query_impl_t *query = ctx->query; - ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); - ecs_query_var_t *var = &query->vars[op->src.var]; + if (op_count) { + qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); + qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); + } - ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, - NULL, NULL, false); - if (!result) { - flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); - return false; +#ifdef FLECS_DEBUG + qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); +#endif + + for (i = 1; i < var_count; i ++) { + qit->vars[i].entity = EcsWildcard; } - flecs_query_var_set_entity(op, op->src.var, result, ctx); + it.variables = qit->vars; + it.variable_count = impl->pub.var_count; + it.variable_names = impl->pub.vars; - return true; + /* Set flags for unconstrained query iteration. Can be reinitialized when + * variables are constrained on iterator. */ + flecs_query_iter_constrain(&it); +error: + return it; } -static -bool flecs_query_setvars( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q) { - (void)op; - - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_var_id_t *src_vars = query->src_vars; - ecs_iter_t *it = ctx->it; - - if (redo) { - return false; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { + ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); } - int32_t i; - ecs_flags32_t up_fields = it->up_fields; - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = src_vars[i]; - if (!var_id) { - continue; - } + /* Ok, only for stats */ + ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); - if (up_fields & (1u << i)) { - continue; + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + if (cache) { + /* If monitors changed, do query rematching */ + ecs_flags32_t flags = q->flags; + if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(q->world); } - - it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } - return true; + return flecs_query_iter(world, q); } +/** + * @file query/engine/eval_member.c + * @brief Component member evaluation. + */ + + static -bool flecs_query_setthis( +bool flecs_query_member_cmp( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + bool neq) { - ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); - ecs_var_t *vars = ctx->vars; - ecs_var_t *this_var = &vars[op->first.var]; - - if (!redo) { - /* Save values so we can restore them later */ - op_ctx->range = vars[0].range; - - /* Constrain This table variable to a single entity from the table */ - vars[0].range = flecs_range_from_entity(this_var->entity, ctx); - vars[0].entity = this_var->entity; - return true; + ecs_table_range_t range; + if (op->other) { + ecs_var_id_t table_var = flecs_itovar(op->other - 1); + range = flecs_query_var_get_range(table_var, ctx); } else { - /* Restore previous values, so that instructions that are operating on - * the table variable use all the entities in the table. */ - vars[0].range = op_ctx->range; - vars[0].entity = 0; + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + } + + ecs_table_t *table = range.table; + if (!table) { return false; } -} -static -bool flecs_query_setfixed( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; + ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - if (redo) { - return false; + if (!range.count) { + range.count = ecs_table_count(range.table); } - int32_t i; - for (i = 0; i < q->term_count; i ++) { - const ecs_term_t *term = &q->terms[i]; - const ecs_term_ref_t *src = &term->src; - if (src->id & EcsIsEntity) { - it->sources[term->field_index] = ECS_TERM_REF_ID(src); + int32_t row, end = range.count; + if (end) { + end += range.offset; + } else { + end = ecs_table_count(range.table); + } + + void *data; + if (!redo) { + row = op_ctx->each.row = range.offset; + + /* Get data ptr starting from offset 0 so we can use row to index */ + range.offset = 0; + + /* Populate data field so we have the array we can compare the member + * value against. */ + data = op_ctx->data = + ecs_table_get_column(range.table, it->trs[field_index]->column, 0); + + it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; + } else { + row = ++ op_ctx->each.row; + if (op_ctx->each.row >= end) { + return false; } + + data = op_ctx->data; } - return true; -} + int32_t offset = (int32_t)op->first.entity; + int32_t size = (int32_t)(op->first.entity >> 32); + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_entity_t e = 0; + ecs_entity_t *val; -bool flecs_query_setids( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_iter_t *it = ctx->it; + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + + bool second_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { + uint64_t written = ctx->written[ctx->op_index]; + second_written = written & (1ull << op->second.var); + } + + if (second_written) { + ecs_flags16_t second_flags = flecs_query_ref_flags( + op->flags, EcsQuerySecond); + ecs_entity_t second = flecs_get_ref_entity( + &op->second, second_flags, ctx); + + do { + e = entities[row]; + + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + if (val[0] == second || second == EcsWildcard) { + if (!neq) { + goto match; + } + } else { + if (neq) { + goto match; + } + } + + row ++; + } while (row < end); - if (redo) { return false; + } else { + e = entities[row]; + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } - int32_t i; - for (i = 0; i < q->term_count; i ++) { - const ecs_term_t *term = &q->terms[i]; - it->ids[term->field_index] = term->id; +match: + if (op->other) { + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + flecs_query_var_set_entity(op, op->src.var, e, ctx); } + ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); + it->ids[field_index] = ecs_pair(mbr, val[0]); + + op_ctx->each.row = row; + return true; } -static -bool flecs_query_setid( +bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); - ctx->it->ids[op->field_index] = op->first.entity; - return true; + return flecs_query_member_cmp(op, redo, ctx, false); } -/* Check if entity is stored in table */ -static -bool flecs_query_contain( +bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_var_id_t src_id = op->src.var; - ecs_var_id_t first_id = op->first.var; + return flecs_query_member_cmp(op, redo, ctx, true); +} - ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); +/** + * @file query/engine/eval_pred.c + * @brief Equality predicate evaluation. + */ - ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); - return table == ecs_get_table(ctx->world, e); -} -/* Check if first and second id of pair from last operation are the same */ static -bool flecs_query_pair_eq( +const char* flecs_query_name_arg( const ecs_query_op_t *op, - bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_iter_t *it = ctx->it; - ecs_id_t id = it->ids[op->field_index]; - return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); + int8_t term_index = op->term_index; + const ecs_term_t *term = &ctx->query->pub.terms[term_index]; + return term->second.name; } static -void flecs_query_reset_after_block( - const ecs_query_op_t *start_op, - ecs_query_run_ctx_t *ctx, - ecs_query_ctrl_ctx_t *op_ctx, - bool result) +bool flecs_query_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) { - ecs_query_lbl_t op_index = start_op->next; - const ecs_query_op_t *op = &ctx->qit->ops[op_index]; - - int32_t field = op->field_index; - if (field == -1) { - goto done; - } - - /* Set/unset field */ - ecs_iter_t *it = ctx->it; - if (result) { - ECS_TERMSET_SET(it->set_fields, 1u << field); - return; - } - - /* Reset state after a field was not matched */ - ctx->written[op_index] = ctx->written[ctx->op_index]; - ctx->op_index = op_index; - ECS_TERMSET_CLEAR(it->set_fields, 1u << field); - - /* Ignore variables written by Not operation */ - uint64_t *written = ctx->written; - uint64_t written_cur = written[op->prev + 1]; - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; + if (l->table != r->table) { + return false; } - it->trs[field] = NULL; - - /* Reset variables */ - if (flags_1st & EcsQueryIsVar) { - if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ - flecs_query_var_reset(op->first.var, ctx); + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; } - } - if (flags_2nd & EcsQueryIsVar) { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ - flecs_query_var_reset(op->second.var, ctx); + if (r_end > l_end) { + return false; } + } else { + /* Entire table is matched */ } - /* If term has entity src, set it because no other instruction might */ - if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { - it->sources[field] = op->src.entity; - } - -done: - op_ctx->op_index = op_index; + return true; } static -bool flecs_query_run_block( +bool flecs_query_pred_eq_w_range( + const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, - ecs_query_ctrl_ctx_t *op_ctx) + ecs_table_range_t r) { - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - - if (!redo) { - op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); - } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { + if (redo) { return false; } - ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t src_var = op->src.var; + if (!(written & (1ull << src_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = src_var; + ctx->vars[l].range = r; + if (r.count == 1) { + ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; + } + return true; + } else { + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; - bool result = flecs_query_run_until( - redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); + if (!flecs_query_compare_range(&l, &r)) { + return false; + } - op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); - return result; + ctx->vars[src_var].range.offset = r.offset; + ctx->vars[src_var].range.count = r.count; + return true; + } } -static -ecs_query_lbl_t flecs_query_last_op_for_or_cond( - const ecs_query_op_t *ops, - ecs_query_lbl_t cur, - ecs_query_lbl_t last) +bool flecs_query_pred_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - const ecs_query_op_t *cur_op, *last_op = &ops[last]; - - do { - cur_op = &ops[cur]; - cur ++; - } while (cur_op->next != last && cur_op != last_op); + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); - return cur; + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -static -bool flecs_query_run_until_for_select_or( +bool flecs_query_pred_eq_name( + const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - const ecs_query_op_t *ops, - ecs_query_lbl_t first, - ecs_query_lbl_t cur, - int32_t last) + ecs_query_run_ctx_t *ctx) { - ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( - ops, cur, flecs_itolbl(last)); - if (redo) { - /* If redoing, start from the last instruction of the last executed - * sequence */ - cur = flecs_itolbl(last_for_cur - 1); + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; } - flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); -#ifdef FLECS_QUERY_TRACE - printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", - ctx->op_index == last ? "true" : "false"); -#endif - return ctx->op_index == last; + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -static -bool flecs_query_select_or( +bool flecs_query_pred_neq_w_range( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_table_range_t r) { - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t src_var = op->src.var; + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); - if (!redo) { - op_ctx->op_index = first; + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; } - const ecs_query_op_t *ops = qit->ops; - const ecs_query_op_t *first_op = &ops[first - 1]; - ecs_query_lbl_t last = first_op->next; - const ecs_query_op_t *last_op = &ops[last]; - const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; - bool result = false; + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } - do { - ecs_query_lbl_t cur = op_ctx->op_index; - ctx->op_index = cur; - ctx->written[cur] = op->written; + l_offset = l.offset; + l_count = l.count; - result = flecs_query_run_until_for_select_or( - redo, ctx, ops, flecs_itolbl(first - 1), cur, last); + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } - if (result) { - if (first == cur) { - break; - } + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[src_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } - /* If a previous operation in the OR chain returned a result for the - * same matched source, skip it so we don't yield for each matching - * element in the chain. */ + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; - /* Copy written status so that the variables we just wrote will show - * up as written for the test. This ensures that the instructions - * match against the result we already found, vs. starting a new - * search (the difference between select & with). */ - ecs_query_lbl_t prev = first; - bool dup_found = false; + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; + return false; + } +} - /* While terms of an OR chain always operate on the same source, it - * is possible that a table variable is resolved to an entity - * variable. When checking for duplicates, copy the entity variable - * to the table, to ensure we're only testing the found entity. */ - const ecs_query_op_t *prev_op = &ops[cur - 1]; - ecs_var_t old_table_var; - ecs_os_memset_t(&old_table_var, 0, ecs_var_t); - bool restore_table_var = false; +static +bool flecs_query_pred_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + bool is_neq) +{ + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; - if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (first_op->src.var != prev_op->src.var) { - restore_table_var = true; - old_table_var = ctx->vars[first_op->src.var]; - ctx->vars[first_op->src.var] = - ctx->vars[prev_op->src.var]; - } - } + ecs_var_id_t src_var = op->src.var; + const char *match = flecs_query_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + if (!l.table) { + return false; + } - int16_t field_index = op->field_index; - ecs_id_t prev_id = it->ids[field_index]; - const ecs_table_record_t *prev_tr = it->trs[field_index]; + if (!l.count) { + l.count = ecs_table_count(l.table); + } - do { - ctx->written[prev] = ctx->written[last]; + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_type_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->column_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } - flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), - prev, cur); + l = op_ctx->range; + } - if (ctx->op_index == last) { - /* Duplicate match was found, find next result */ - redo = true; - dup_found = true; - break; - } + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + if (!result) { + if (offset != -1) { break; - } while (true); - - /* Restore table variable to full range for next result */ - if (restore_table_var) { - ctx->vars[first_op->src.var] = old_table_var; } - - if (dup_found) { - continue; + } else { + if (offset == -1) { + offset = op_ctx->index; } - - /* Restore id in case op set it */ - it->ids[field_index] = prev_id; - it->trs[field_index] = prev_tr; - break; } + } - /* No result was found, go to next operation in chain */ - op_ctx->op_index = flecs_query_last_op_for_or_cond( - ops, op_ctx->op_index, last); - cur_op = &qit->ops[op_ctx->op_index]; - - redo = false; - } while (cur_op != last_op); + if (offset == -1) { + ctx->vars[src_var].range = op_ctx->range; + return false; + } - return result; + ctx->vars[src_var].range.offset = offset; + ctx->vars[src_var].range.count = (op_ctx->index - offset); + return true; } -static -bool flecs_query_with_or( +bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - - bool result = flecs_query_run_block(redo, ctx, op_ctx); - if (result) { - /* If a match was found, no need to keep searching for this source */ - op_ctx->op_index = op->next; - } - - return result; + return flecs_query_pred_match(op, redo, ctx, false); } -static -bool flecs_query_or( +bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - uint64_t written = ctx->written[ctx->op_index]; - if (!(written & (1ull << op->src.var))) { - return flecs_query_select_or(op, redo, ctx); - } - } - - return flecs_query_with_or(op, redo, ctx); + return flecs_query_pred_match(op, redo, ctx, true); } -static -bool flecs_query_run_block_w_reset( +bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); - bool result = flecs_query_run_block(redo, ctx, op_ctx); - flecs_query_reset_after_block(op, ctx, op_ctx, result); - return result; + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); } -static -bool flecs_query_not( +bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; } - return !flecs_query_run_block_w_reset(op, redo, ctx); + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); } -static -bool flecs_query_optional( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - bool result = flecs_query_run_block_w_reset(op, redo, ctx); - if (!redo) { - return true; /* Return at least once */ - } else { - return result; - } -} +/** + * @file query/engine/eval_toggle.c + * @brief Bitset toggle evaluation. + */ + + +typedef struct { + ecs_flags64_t mask; + bool has_bitset; +} flecs_query_row_mask_t; static -bool flecs_query_eval_if( - const ecs_query_op_t *op, - ecs_query_run_ctx_t *ctx, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind) +flecs_query_row_mask_t flecs_query_get_row_mask( + ecs_iter_t *it, + ecs_table_t *table, + int32_t block_index, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields, + ecs_query_toggle_ctx_t *op_ctx) { - bool result = true; - if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { - result = ctx->vars[ref->var].entity != EcsWildcard; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - flecs_query_reset_after_block(op, ctx, op_ctx, result); - return result; - } - return true; -} + ecs_flags64_t mask = UINT64_MAX; + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = and_fields | not_fields; + bool has_bitset = false; + + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } + + if (not_fields & field_bit) { + it->set_fields &= (ecs_termset_t)~field_bit; + } else if (and_fields & field_bit) { + ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ecs_id_t id = it->ids[i]; + ecs_bitset_t *bs = flecs_table_get_toggle(table, id); + if (!bs) { + if (not_fields & field_bit) { + if (op_ctx->prev_set_fields & field_bit) { + has_bitset = false; + break; + } + } + continue; + } -static -bool flecs_query_if_var( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || - !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || - !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) - { - return true; + ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); + ecs_flags64_t block = bs->data[block_index]; + + if (not_fields & field_bit) { + block = ~block; } + mask &= block; + has_bitset = true; } - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - return flecs_query_run_block(redo, ctx, op_ctx); + return (flecs_query_row_mask_t){ mask, has_bitset }; } static -bool flecs_query_if_set( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +bool flecs_query_toggle_for_up( + ecs_iter_t *it, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - ecs_iter_t *it = ctx->it; - int8_t field_index = flecs_ito(int8_t, op->other); + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - if (!redo) { - op_ctx->is_set = ecs_field_is_set(it, field_index); - } + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } - if (!op_ctx->is_set) { - return !redo; + bool match = false; + if ((it->set_fields & field_bit)) { + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + match = ecs_is_enabled_id(it->world, src, it->ids[i]); + } + + if (field_bit & not_fields) { + match = !match; + } + + if (!match) { + return false; + } } - return flecs_query_run_block(redo, ctx, op_ctx); + return true; } static -bool flecs_query_end( +bool flecs_query_toggle_cmp( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - (void)op; (void)ctx; - return !redo; -} + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + ecs_table_t *table = range.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -static -bool flecs_query_dispatch( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - switch(op->kind) { - case EcsQueryAnd: return flecs_query_and(op, redo, ctx); - case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); - case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); - case EcsQueryCache: return flecs_query_cache(op, redo, ctx); - case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); - case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); - case EcsQueryUp: return flecs_query_up(op, redo, ctx); - case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); - case EcsQueryWith: return flecs_query_with(op, redo, ctx); - case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); - case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); - case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); - case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); - case EcsQueryIds: return flecs_query_ids(op, redo, ctx); - case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); - case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); - case EcsQueryEach: return flecs_query_each(op, redo, ctx); - case EcsQueryStore: return flecs_query_store(op, redo, ctx); - case EcsQueryReset: return flecs_query_reset(op, redo, ctx); - case EcsQueryOr: return flecs_query_or(op, redo, ctx); - case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); - case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); - case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); - case EcsQueryEnd: return flecs_query_end(op, redo, ctx); - case EcsQueryNot: return flecs_query_not(op, redo, ctx); - case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); - case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); - case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); - case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); - case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); - case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); - case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); - case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); - case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); - case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); - case EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); - case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); - case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); - case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); - case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(op, redo, ctx); - case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); - case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); - case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); - case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); - case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); - case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); - case EcsQueryContain: return flecs_query_contain(op, redo, ctx); - case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); - case EcsQueryYield: return false; - case EcsQueryNothing: return false; + if ((and_fields & op_ctx->prev_set_fields) != and_fields) { + /* If not all fields matching and toggles are set, table can't match */ + return false; } - return false; -} -bool flecs_query_run_until( - bool redo, - ecs_query_run_ctx_t *ctx, - const ecs_query_op_t *ops, - ecs_query_lbl_t first, - ecs_query_lbl_t cur, - int32_t last) -{ - ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); + ecs_flags32_t up_fields = it->up_fields; + if (!redo) { + if (up_fields & (and_fields|not_fields)) { + /* If there are toggle fields that were matched with query + * traversal, evaluate those separately. */ + if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { + return false; + } - ctx->op_index = cur; - const ecs_query_op_t *op = &ops[ctx->op_index]; - const ecs_query_op_t *last_op = &ops[last]; - ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); + it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); + } + } -#ifdef FLECS_QUERY_TRACE - printf("%*sblock:\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent ++; -#endif + /* Shared fields are evaluated, can be ignored from now on */ + // and_fields &= ~up_fields; + not_fields &= ~up_fields; - do { - #ifdef FLECS_DEBUG - ctx->qit->profile[ctx->op_index].count[redo] ++; - #endif + if (!(table->flags & EcsTableHasToggle)) { + if (not_fields) { + /* If any of the toggle fields with a not operator are for fields + * that are set, without a bitset those fields can't match. */ + return false; + } else { + /* If table doesn't have toggles but query matched toggleable + * components, all entities match. */ + if (!redo) { + return true; + } else { + return false; + } + } + } -#ifdef FLECS_QUERY_TRACE - printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", - ctx->op_index, flecs_query_op_str(op->kind)); -#endif + if (table && !range.count) { + range.count = ecs_table_count(table); + if (!range.count) { + return false; + } + } - bool result = flecs_query_dispatch(op, redo, ctx); - cur = (&op->prev)[result]; - redo = cur < ctx->op_index; + int32_t i, j; + int32_t first, last, block_index, cur; + uint64_t block = 0; + if (!redo) { + op_ctx->range = range; + cur = op_ctx->cur = range.offset; + block_index = op_ctx->block_index = -1; + first = range.offset; + last = range.offset + range.count; + } else { + if (!op_ctx->has_bitset) { + goto done; + } - if (!redo) { - ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; + last = op_ctx->range.offset + op_ctx->range.count; + cur = op_ctx->cur; + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); + if (cur == last) { + goto done; } - ctx->op_index = cur; - op = &ops[ctx->op_index]; + first = cur; + block_index = op_ctx->block_index; + block = op_ctx->block; + } - if (cur <= first) { -#ifdef FLECS_QUERY_TRACE - printf("%*sfalse\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent --; -#endif - return false; - } - } while (op < last_op); - -#ifdef FLECS_QUERY_TRACE - printf("%*strue\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent --; -#endif + /* If end of last iteration is start of new block, compute new block */ + int32_t new_block_index = cur / 64, row = first; + if (new_block_index != block_index) { +compute_block: + block_index = op_ctx->block_index = new_block_index; - return true; -} + flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( + it, table, block_index, and_fields, not_fields, op_ctx); -/** - * @file query/engine/eval_iter.c - * @brief Query iterator. - */ + /* If table doesn't have bitset columns, all columns match */ + if (!(op_ctx->has_bitset = row_mask.has_bitset)) { + if (!not_fields) { + return true; + } else { + goto done; + } + } + /* No enabled bits */ + block = row_mask.mask; + if (!block) { +next_block: + new_block_index ++; + cur = new_block_index * 64; + if (cur >= last) { + /* No more rows */ + goto done; + } -static -void flecs_query_iter_run_ctx_init( - ecs_iter_t *it, - ecs_query_run_ctx_t *ctx) -{ - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); - ctx->world = it->real_world; - ctx->query = impl; - ctx->it = it; - ctx->vars = qit->vars; - ctx->query_vars = qit->query_vars; - ctx->written = qit->written; - ctx->op_ctx = qit->op_ctx; - ctx->qit = qit; -} + op_ctx->cur = cur; + goto compute_block; + } -void flecs_query_iter_constrain( - ecs_iter_t *it) -{ - ecs_query_run_ctx_t ctx; - flecs_query_iter_run_ctx_init(it, &ctx); - ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); + op_ctx->block = block; + } - const ecs_query_impl_t *query = ctx.query; - const ecs_query_t *q = &query->pub; - ecs_flags64_t it_written = it->constrained_vars; - ctx.written[0] = it_written; - if (it_written && ctx.query->src_vars) { - /* If variables were constrained, check if there are any table - * variables that have a constrained entity variable. */ - ecs_var_t *vars = ctx.vars; - int32_t i, count = q->field_count; - for (i = 0; i < count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - ecs_query_var_t *var = &query->vars[var_id]; + /* Find first enabled bit (TODO: use faster bitmagic) */ + int32_t first_bit = cur - (block_index * 64); + int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); + ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); - if (!(it_written & (1ull << var_id)) || - (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) - { - continue; + for (i = first_bit; i < last_bit; i ++) { + uint64_t bit = (1ull << i); + bool cond = 0 != (block & bit); + if (cond) { + /* Find last enabled bit */ + for (j = i; j < last_bit; j ++) { + bit = (1ull << j); + cond = !(block & bit); + if (cond) { + break; + } } - /* Initialize table variable with constrained entity variable */ - ecs_var_t *tvar = &vars[var->table_id]; - tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); - ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ + row = i + (block_index * 64); + cur = j + (block_index * 64); + break; } } - /* This function can be called multiple times when setting variables, so - * reset flags before setting them. */ - it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| - EcsIterTrivialSearch); + if (i == last_bit) { + goto next_block; + } - /* Figure out whether this query can utilize specialized iterator modes for - * improved performance. */ - ecs_flags32_t flags = q->flags; - ecs_query_cache_t *cache = query->cache; - if (flags & EcsQueryIsTrivial) { - if ((flags & EcsQueryMatchOnlySelf)) { - if (it_written) { - /* When we're testing against an entity or table, set the $this - * variable in advance since it won't change later on. This - * initializes it.count, it.entities and it.table. */ - flecs_query_set_iter_this(it, &ctx); + ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); - if (!cache) { - if (!(flags & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialTest; - } - } else if (flags & EcsQueryIsCacheable) { - it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; - } - } else { - if (!cache) { - if (!(flags & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialSearch; - } - } else if (flags & EcsQueryIsCacheable) { - if (!cache->order_by_callback) { - it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; - } - } - } + if (!(cur - row)) { + goto done; + } - /* If we're using a specialized iterator mode, make sure to - * initialize static component ids. Usually this is the first - * instruction of a query plan, but because we're not running the - * query plan when using a specialized iterator mode, manually call - * the operation on iterator init. */ - flecs_query_setids(NULL, false, &ctx); - } + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); } -} + op_ctx->cur = cur; -bool ecs_query_next( - ecs_iter_t *it) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + return true; - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); - ecs_query_run_ctx_t ctx; - flecs_query_iter_run_ctx_init(it, &ctx); - const ecs_query_op_t *ops = qit->ops; +done: + /* Restore range & set fields */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, + table, op_ctx->range.offset, op_ctx->range.count, ctx); + } - bool redo = it->flags & EcsIterIsValid; - if (redo) { - /* Change detection */ - if (!(it->flags & EcsIterSkip)) { - /* Mark table columns that are written to dirty */ - flecs_query_mark_fields_dirty(impl, it); - if (qit->prev) { - if (ctx.query->pub.flags & EcsQueryHasMonitor) { - /* If this query uses change detection, synchronize the - * monitor for the iterated table with the query */ - flecs_query_sync_match_monitor(impl, qit->prev); - } - } - } + it->set_fields = op_ctx->prev_set_fields; + return false; +} + +bool flecs_query_toggle( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; } - it->flags &= ~(EcsIterSkip); - it->flags |= EcsIterIsValid; - it->frame_offset += it->count; + ecs_flags64_t and_fields = op->first.entity; + ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; - /* Specialized iterator modes. When a query doesn't use any advanced - * features, it can call specialized iterator functions directly instead of - * going through the dispatcher of the query engine. - * The iterator mode is set during iterator initialization. Besides being - * determined by the query, there are different modes for searching and - * testing, where searching returns all matches for a query, whereas testing - * tests a single table or table range against the query. */ + return flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); +} - if (it->flags & EcsIterTrivialCached) { - /* Cached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - if (flecs_query_is_cache_search(&ctx)) { - goto trivial_search_yield; - } - } else if (it->flags & EcsIterTrivialTest) { - if (flecs_query_is_cache_test(&ctx, redo)) { - goto yield; - } - } +bool flecs_query_toggle_option( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; + op_ctx->optional_not = false; + op_ctx->has_bitset = false; + } + +repeat: {} + ecs_flags64_t and_fields = 0, not_fields = 0; + if (op_ctx->optional_not) { + not_fields = op->first.entity & op_ctx->prev_set_fields; } else { - /* Uncached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; - if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { - goto yield; - } - } else if (it->flags & EcsIterTrivialTest) { - int32_t fields = ctx.query->pub.term_count; - ecs_flags64_t mask = (2llu << (fields - 1)) - 1; - if (flecs_query_trivial_test(&ctx, redo, mask)) { - goto yield; - } - } else { - /* Default iterator mode. This enters the query VM dispatch loop. */ - if (flecs_query_run_until( - redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) - { - ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, - ECS_INTERNAL_ERROR, NULL); - flecs_query_set_iter_this(it, &ctx); - ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); - qit->op = flecs_itolbl(ctx.op_index - 1); - goto yield; - } - } + and_fields = op->first.entity; } - /* Done iterating */ - flecs_query_mark_fixed_fields_dirty(impl, it); - if (ctx.query->monitor) { - flecs_query_update_fixed_monitor( - ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); + bool result = flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); + if (!result) { + if (!op_ctx->optional_not) { + /* Run the not-branch of optional fields */ + op_ctx->optional_not = true; + it->set_fields = op_ctx->prev_set_fields; + redo = false; + goto repeat; + } } - ecs_iter_fini(it); - return false; + return result; +} -trivial_search_yield: - it->table = ctx.vars[0].range.table; - it->count = ecs_table_count(it->table); - it->entities = ecs_table_entities(it->table); -yield: - return true; -} +/** + * @file query/engine/eval_trav.c + * @brief Transitive/reflexive relationship traversal. + */ + static -void flecs_query_iter_fini_ctx( - ecs_iter_t *it, - ecs_query_iter_t *qit) +bool flecs_query_trav_fixed_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav, + ecs_entity_t second) { - const ecs_query_impl_t *query = flecs_query_impl(qit->query); - int32_t i, count = query->op_count; - ecs_query_op_t *ops = query->ops; - ecs_query_op_ctx_t *ctx = qit->op_ctx; - ecs_allocator_t *a = flecs_query_get_allocator(it); + ecs_table_t *table = range->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t count = range->count; + if (!count) { + count = ecs_table_count(table); + } - for (i = 0; i < count; i ++) { - ecs_query_op_t *op = &ops[i]; - switch(op->kind) { - case EcsQueryTrav: - flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); - break; - case EcsQueryUp: - case EcsQuerySelfUp: - case EcsQueryUnionEqUp: - case EcsQueryUnionEqSelfUp: { - ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; - if (cache->dir == EcsTravDown) { - flecs_query_down_cache_fini(a, cache); - } else { - flecs_query_up_cache_fini(cache); - } - break; - } - default: + int32_t i = range->offset, end = i + count; + for (; i < end; i ++) { + if (entities[i] == second) { + /* Even though table doesn't have the specific relationship + * pair, the relationship is reflexive and the target entity + * is stored in the table. */ break; } } + if (i == end) { + /* Table didn't contain target entity */ + return false; + } + if (count > 1) { + /* If the range contains more than one entity, set the range to + * return only the entity matched by the reflexive property. */ + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[op->src.var]; + ecs_table_range_t *var_range = &var->range; + var_range->offset = i; + var_range->count = 1; + var->entity = entities[i]; + } + + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; } static -void flecs_query_iter_fini( - ecs_iter_t *it) +bool flecs_query_trav_unknown_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t trav, + ecs_entity_t second) { - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_poly_assert(qit->query, ecs_query_t); - int32_t op_count = flecs_query_impl(qit->query)->op_count; - int32_t var_count = flecs_query_impl(qit->query)->var_count; + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_id_t src_var = op->src.var; + flecs_query_var_set_entity(op, src_var, second, ctx); + flecs_query_var_get_table(src_var, ctx); -#ifdef FLECS_DEBUG - if (it->flags & EcsIterProfile) { - char *str = ecs_query_plan_w_profile(qit->query, it); - printf("%s\n", str); - ecs_os_free(str); + ecs_table_t *table = ctx->vars[src_var].range.table; + if (table) { + if (flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return false; + } } - flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); -#endif - - flecs_query_iter_fini_ctx(it, qit); - flecs_iter_free_n(qit->vars, ecs_var_t, var_count); - flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); - flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); - - qit->vars = NULL; - qit->written = NULL; - qit->op_ctx = NULL; - qit->query = NULL; + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; } -ecs_iter_t flecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *q) +static +bool flecs_query_trav_fixed_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t it = {0}; - ecs_query_iter_t *qit = &it.priv_.iter.query; - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); + if (redo) { + return false; /* If everything's fixed, can only have a single result */ + } - int32_t i, var_count = impl->var_count; - int32_t op_count = impl->op_count ? impl->op_count : 1; - it.world = ECS_CONST_CAST(ecs_world_t*, world); + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; - /* If world passed to iterator is the real world, but query was created from - * a stage, stage takes precedence. */ - if (flecs_poly_is(it.world, ecs_world_t) && - flecs_poly_is(q->world, ecs_stage_t)) - { - it.world = ECS_CONST_CAST(ecs_world_t*, q->world); + /* Check if table has transitive relationship by traversing upwards */ + ecs_table_record_t *tr = NULL; + ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); + + if (!tr) { + if (op->match_flags & EcsTermReflexive) { + return flecs_query_trav_fixed_src_reflexive(op, ctx, + &range, trav, second); + } else { + return false; + } } - it.real_world = q->real_world; - ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || - it.world != it.real_world, ECS_INVALID_PARAMETER, - "create iterator for stage when world is in multithreaded mode"); + flecs_query_set_trav_match(op, tr, trav, second, ctx); + return true; +} - it.query = q; - it.system = q->entity; - it.next = ecs_query_next; - it.fini = flecs_query_iter_fini; - it.field_count = q->field_count; - it.sizes = q->sizes; - it.set_fields = q->set_fields; - it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; - it.row_fields = q->row_fields; - it.up_fields = 0; - flecs_query_apply_iter_flags(&it, q); +static +bool flecs_query_trav_unknown_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - flecs_iter_init(it.world, &it, - flecs_iter_cache_ids | - flecs_iter_cache_trs | - flecs_iter_cache_sources | - flecs_iter_cache_ptrs); + if (!redo) { + ecs_record_t *r_second = flecs_entities_get(ctx->world, second); + bool traversable = r_second && r_second->row & EcsEntityIsTraversable; + bool reflexive = op->match_flags & EcsTermReflexive; + if (!traversable && !reflexive) { + trav_ctx->cache.id = 0; - qit->query = q; - qit->query_vars = impl->vars; - qit->ops = impl->ops; + /* If there's no record for the entity, it can't have a subtree so + * forward operation to a regular select. */ + return flecs_query_select(op, redo, ctx); + } - ecs_query_cache_t *cache = impl->cache; - if (cache) { - qit->node = cache->list.first; - qit->last = cache->list.last; + /* Entity is traversable, which means it could have a subtree */ + flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); + trav_ctx->index = 0; - if (cache->order_by_callback && cache->list.info.table_count) { - flecs_query_cache_sort_tables(it.real_world, impl); - qit->node = ecs_vec_first(&cache->table_slices); - qit->last = ecs_vec_last_t( - &cache->table_slices, ecs_query_cache_table_match_t); + if (op->match_flags & EcsTermReflexive) { + trav_ctx->index = -1; + if(flecs_query_trav_unknown_src_reflexive( + op, ctx, trav, second)) + { + /* It's possible that we couldn't return the entity required for + * reflexive matching, like when it's a prefab or disabled. */ + return true; + } + } + } else { + if (!trav_ctx->cache.id) { + /* No traversal cache, which means this is a regular select */ + return flecs_query_select(op, redo, ctx); } - - cache->prev_match_count = cache->match_count; } - if (var_count) { - qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); + if (trav_ctx->index == -1) { + redo = false; /* First result after handling reflexive relationship */ + trav_ctx->index = 0; } - if (op_count) { - qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); - qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); + /* Forward to select */ + int32_t count = ecs_vec_count(&trav_ctx->cache.entities); + ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); + for (; trav_ctx->index < count; trav_ctx->index ++) { + ecs_trav_elem_t *el = &elems[trav_ctx->index]; + trav_ctx->and.idr = el->idr; /* prevents lookup by select */ + if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return true; + } + + redo = false; } -#ifdef FLECS_DEBUG - qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); -#endif + return false; +} - for (i = 1; i < var_count; i ++) { - qit->vars[i].entity = EcsWildcard; +static +bool flecs_query_trav_yield_reflexive_src( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav) +{ + ecs_var_t *vars = ctx->vars; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + int32_t offset = trav_ctx->offset, count = trav_ctx->count; + bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); + + if (trav_ctx->index >= (offset + count)) { + /* Restore previous offset, count */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + vars[src_var].range.offset = offset; + vars[src_var].range.count = count; + vars[src_var].entity = 0; + } + return false; } - it.variables = qit->vars; - it.variable_count = impl->pub.var_count; - it.variable_names = impl->pub.vars; + ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; + flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - /* Set flags for unconstrained query iteration. Can be reinitialized when - * variables are constrained on iterator. */ - flecs_query_iter_constrain(&it); -error: - return it; + /* Hijack existing variable to return one result at a time */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + ecs_table_t *table = vars[src_var].range.table; + ecs_assert(!table || table == ecs_get_table(ctx->world, entity), + ECS_INTERNAL_ERROR, NULL); + (void)table; + vars[src_var].entity = entity; + vars[src_var].range = flecs_range_from_entity(entity, ctx); + } + + return true; } -ecs_iter_t ecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *q) +static +bool flecs_query_trav_fixed_src_up_unknown_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); - } + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - /* Ok, only for stats */ - ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); + if (!redo) { + flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); + trav_ctx->index = 0; + if (op->match_flags & EcsTermReflexive) { + trav_ctx->yield_reflexive = true; + trav_ctx->index = range.offset; + trav_ctx->offset = range.offset; + trav_ctx->count = range.count ? range.count : ecs_table_count(table); + } + } else { + trav_ctx->index ++; + } - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_cache_t *cache = impl->cache; - if (cache) { - /* If monitors changed, do query rematching */ - ecs_flags32_t flags = q->flags; - if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { - flecs_eval_component_monitors(q->world); + if (trav_ctx->yield_reflexive) { + if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { + return true; } + trav_ctx->yield_reflexive = false; + trav_ctx->index = 0; } - return flecs_query_iter(world, q); + if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { + return false; + } + + ecs_trav_elem_t *el = ecs_vec_get_t( + &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); + flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); + return true; +} + +bool flecs_query_trav( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + + if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + /* This can't happen, src or second should have been resolved */ + ecs_abort(ECS_INTERNAL_ERROR, + "invalid instruction sequence: unconstrained traversal"); + } else { + return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); + } + } else { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); + } else { + return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); + } + } } /** - * @file query/engine/eval_member.c - * @brief Component member evaluation. + * @file query/engine/eval_union.c + * @brief Union relationship evaluation. */ static -bool flecs_query_member_cmp( +bool flecs_query_union_with_wildcard( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, bool neq) { + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; + ecs_table_range_t range; - if (op->other) { - ecs_var_id_t table_var = flecs_itovar(op->other - 1); - range = flecs_query_var_get_range(table_var, ctx); - } else { + ecs_table_t *table; + if (!redo) { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + table = range.table; + if (!range.count) { + range.count = ecs_table_count(table); + } + + op_ctx->range = range; + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { + return neq; + } + + if (neq) { + if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { + /* If table has (R, Union) none match !(R, _) */ + return false; + } else { + /* If table doesn't have (R, Union) all match !(R, _) */ + return true; + } + } + + op_ctx->row = 0; + } else { + if (neq) { + /* !(R, _) terms only can have a single result */ + return false; + } + + range = op_ctx->range; + table = range.table; + op_ctx->row ++; } - ecs_table_t *table = range.table; - if (!table) { +next_row: + if (op_ctx->row >= range.count) { + /* Restore range */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + op_ctx->range.offset, op_ctx->range.count, ctx); + } return false; } - ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - if (!range.count) { - range.count = ecs_table_count(range.table); + ecs_entity_t e = ecs_table_entities(range.table) + [range.offset + op_ctx->row]; + ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + if (!tgt) { + op_ctx->row ++; + goto next_row; } - int32_t row, end = range.count; - if (end) { - end += range.offset; - } else { - end = ecs_table_count(range.table); + it->ids[field_index] = ecs_pair(rel, tgt); + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + range.offset + op_ctx->row, 1, ctx); } + flecs_query_set_vars(op, it->ids[field_index], ctx); - void *data; - if (!redo) { - row = op_ctx->each.row = range.offset; + return true; +} - /* Get data ptr starting from offset 0 so we can use row to index */ - range.offset = 0; +static +bool flecs_query_union_with_tgt( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, + ecs_entity_t tgt, + bool neq) +{ + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - /* Populate data field so we have the array we can compare the member - * value against. */ - data = op_ctx->data = - ecs_table_get_column(range.table, it->trs[field_index]->column, 0); + ecs_table_range_t range; + ecs_table_t *table; + if (!redo) { + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + table = range.table; + if (!range.count) { + range.count = ecs_table_count(table); + } - it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; - } else { - row = ++ op_ctx->each.row; - if (op_ctx->each.row >= end) { + op_ctx->range = range; + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { return false; } - data = op_ctx->data; + op_ctx->row = 0; + } else { + range = op_ctx->range; + table = range.table; + op_ctx->row ++; } - int32_t offset = (int32_t)op->first.entity; - int32_t size = (int32_t)(op->first.entity >> 32); - const ecs_entity_t *entities = ecs_table_entities(table); - ecs_entity_t e = 0; - ecs_entity_t *val; - - ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - - bool second_written = true; - if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { - uint64_t written = ctx->written[ctx->op_index]; - second_written = written & (1ull << op->second.var); +next_row: + if (op_ctx->row >= range.count) { + /* Restore range */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + op_ctx->range.offset, op_ctx->range.count, ctx); + } + return false; } - if (second_written) { - ecs_flags16_t second_flags = flecs_query_ref_flags( - op->flags, EcsQuerySecond); - ecs_entity_t second = flecs_get_ref_entity( - &op->second, second_flags, ctx); - - do { - e = entities[row]; - - val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); - if (val[0] == second || second == EcsWildcard) { - if (!neq) { - goto match; - } - } else { - if (neq) { - goto match; - } - } - - row ++; - } while (row < end); - - return false; - } else { - e = entities[row]; - val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); - flecs_query_var_set_entity(op, op->second.var, val[0], ctx); + ecs_entity_t e = ecs_table_entities(range.table) + [range.offset + op_ctx->row]; + ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + bool match = e_tgt == tgt; + if (neq) { + match = !match; } -match: - if (op->other) { - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - flecs_query_var_set_entity(op, op->src.var, e, ctx); + if (!match) { + op_ctx->row ++; + goto next_row; } - ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); - it->ids[field_index] = ecs_pair(mbr, val[0]); + it->ids[field_index] = ecs_pair(rel, tgt); + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + range.offset + op_ctx->row, 1, ctx); + } - op_ctx->each.row = row; + flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } -bool flecs_query_member_eq( +bool flecs_query_union_with( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + bool neq) { - return flecs_query_member_cmp(op, redo, ctx, false); -} + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_pair_second(ctx->world, id); -bool flecs_query_member_neq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_member_cmp(op, redo, ctx, true); + if (tgt == EcsWildcard) { + return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); + } else { + return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); + } } -/** - * @file query/engine/eval_pred.c - * @brief Equality predicate evaluation. - */ - - static -const char* flecs_query_name_arg( +bool flecs_query_union_select_tgt( const ecs_query_op_t *op, - ecs_query_run_ctx_t *ctx) -{ - int8_t term_index = op->term_index; - const ecs_term_t *term = &ctx->query->pub.terms[term_index]; - return term->second.name; -} - -static -bool flecs_query_compare_range( - const ecs_table_range_t *l, - const ecs_table_range_t *r) + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, + ecs_entity_t tgt) { - if (l->table != r->table) { - return false; - } + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - if (l->count) { - int32_t l_end = l->offset + l->count; - int32_t r_end = r->offset + r->count; - if (r->offset < l->offset) { - return false; - } - if (r_end > l_end) { + if (!redo) { + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { return false; } + + op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); } else { - /* Entire table is matched */ + op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + } + + if (!op_ctx->cur) { + return false; } + ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + flecs_query_set_vars(op, it->ids[field_index], ctx); + + it->ids[field_index] = ecs_pair(rel, tgt); + return true; } static -bool flecs_query_pred_eq_w_range( +bool flecs_query_union_select_wildcard( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - ecs_table_range_t r) + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel) { - if (redo) { - return false; - } + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - uint64_t written = ctx->written[ctx->op_index]; - ecs_var_id_t src_var = op->src.var; - if (!(written & (1ull << src_var))) { - /* left = unknown, right = known. Assign right-hand value to left */ - ecs_var_id_t l = src_var; - ctx->vars[l].range = r; - if (r.count == 1) { - ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; + if (!redo) { + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { + return false; } - return true; - } else { - ecs_table_range_t l = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - if (!flecs_query_compare_range(&l, &r)) { + op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); + op_ctx->tgt = 0; + } + +next_tgt: + if (!op_ctx->tgt) { + if (!ecs_map_next(&op_ctx->tgt_iter)) { return false; } - ctx->vars[src_var].range.offset = r.offset; - ctx->vars[src_var].range.count = r.count; - return true; + op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); + op_ctx->cur = 0; + it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); + } + + if (!op_ctx->cur) { + op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); + } else { + op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + } + + if (!op_ctx->cur) { + op_ctx->tgt = 0; + goto next_tgt; } + + ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + flecs_query_set_vars(op, it->ids[field_index], ctx); + + return true; } -bool flecs_query_pred_eq( +bool flecs_query_union_select( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; (void)written; - ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized eq operand"); + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - ecs_table_range_t r = flecs_query_get_range( - op, &op->second, EcsQuerySecond, ctx); - return flecs_query_pred_eq_w_range(op, redo, ctx, r); + if (tgt == EcsWildcard) { + return flecs_query_union_select_wildcard(op, redo, ctx, rel); + } else { + return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); + } +} + +bool flecs_query_union( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_union_with(op, redo, ctx, false); + } else { + return flecs_query_union_select(op, redo, ctx); + } } -bool flecs_query_pred_eq_name( +bool flecs_query_union_neq( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx) { - const char *name = flecs_query_name_arg(op, ctx); - ecs_entity_t e = ecs_lookup(ctx->world, name); - if (!e) { - /* Entity doesn't exist */ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_union_with(op, redo, ctx, true); + } else { return false; } - - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -bool flecs_query_pred_neq_w_range( +static +void flecs_query_union_set_shared( const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx, - ecs_table_range_t r) + const ecs_query_run_ctx_t *ctx) { - ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); - ecs_var_id_t src_var = op->src.var; - ecs_table_range_t l = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_id_record_t *idr = op_ctx->idr_with; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - /* If tables don't match, neq always returns once */ - if (l.table != r.table) { - return true && !redo; - } + ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); + idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t l_offset; - int32_t l_count; - if (!redo) { - /* Make sure we're working with the correct table count */ - if (!l.count && l.table) { - l.count = ecs_table_count(l.table); - } + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; + ecs_entity_t src = it->sources[field_index]; + ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); + + it->ids[field_index] = ecs_pair(rel, tgt); +} - l_offset = l.offset; - l_count = l.count; +bool flecs_query_union_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!redo) { + if (!flecs_query_up_with(op, redo, ctx)) { + return false; + } - /* Cache old value */ - op_ctx->range = l; + flecs_query_union_set_shared(op, ctx); + return true; + } else { + return false; + } } else { - l_offset = op_ctx->range.offset; - l_count = op_ctx->range.count; + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); } +} - /* If the table matches, a Neq returns twice: once for the slice before the - * excluded slice, once for the slice after the excluded slice. If the right - * hand range starts & overlaps with the left hand range, there is only - * one slice. */ - ecs_var_t *var = &ctx->vars[src_var]; - if (!redo && r.offset > l_offset) { - int32_t end = r.offset; - if (end > l_count) { - end = l_count; +bool flecs_query_union_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (redo) { + goto next_for_union; } - /* Return first slice */ - var->range.table = l.table; - var->range.offset = l_offset; - var->range.count = end - l_offset; - op_ctx->redo = false; - return true; - } else if (!op_ctx->redo) { - int32_t l_end = op_ctx->range.offset + l_count; - int32_t r_end = r.offset + r.count; - - if (l_end <= r_end) { - /* If end of existing range falls inside the excluded range, there's - * nothing more to return */ - var->range = l; +next_for_self_up_with: + if (!flecs_query_self_up_with(op, redo, ctx, false)) { return false; } - /* Return second slice */ - var->range.table = l.table; - var->range.offset = r_end; - var->range.count = l_end - r_end; + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; + if (it->sources[field_index]) { + flecs_query_union_set_shared(op, ctx); + return true; + } + +next_for_union: + if (!flecs_query_union_with(op, redo, ctx, false)) { + goto next_for_self_up_with; + } - /* Flag so we know we're done the next redo */ - op_ctx->redo = true; return true; } else { - /* Restore previous value */ - var->range = l; - return false; + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); } } +/** + * @file query/engine/eval.c + * @brief Query engine implementation. + */ + + +/* Find tables with requested component that has traversable entities. */ static -bool flecs_query_pred_match( +bool flecs_query_up_select_table( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - bool is_neq) + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) { - ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); - uint64_t written = ctx->written[ctx->op_index]; - ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized match operand"); - (void)written; - - ecs_var_id_t src_var = op->src.var; - const char *match = flecs_query_name_arg(op, ctx); - ecs_table_range_t l; - if (!redo) { - l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - if (!l.table) { - return false; - } + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_iter_t *it = ctx->it; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + ecs_table_range_t range; - if (!l.count) { - l.count = ecs_table_count(l.table); + do { + bool result; + if (kind == FlecsQueryUpSelectId) { + result = flecs_query_select_id(op, redo, ctx, 0); + } else if (kind == FlecsQueryUpSelectDefault) { + result = flecs_query_select_w_id(op, redo, ctx, + op_ctx->with, 0); + } else if (kind == FlecsQueryUpSelectUnion) { + result = flecs_query_union_select(op, redo, ctx); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - op_ctx->range = l; - op_ctx->index = l.offset; - op_ctx->name_col = flecs_ito(int16_t, - ecs_table_get_type_index(ctx->world, l.table, - ecs_pair(ecs_id(EcsIdentifier), EcsName))); - if (op_ctx->name_col == -1) { - return is_neq; - } - op_ctx->name_col = flecs_ito(int16_t, - l.table->column_map[op_ctx->name_col]); - ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); - } else { - if (op_ctx->name_col == -1) { - /* Table has no name */ + if (!result) { + /* No remaining tables with component found. */ return false; } - l = op_ctx->range; + redo = true; + + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep searching until we find a table that has the requested component, + * with traversable entities */ + } while (!self && range.table->_->traversable_count == 0); + + if (!range.count) { + range.count = ecs_table_count(range.table); } - const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; - int32_t count = l.offset + l.count, offset = -1; - for (; op_ctx->index < count; op_ctx->index ++) { - const char *name = names[op_ctx->index].value; - bool result = strstr(name, match); - if (is_neq) { - result = !result; - } + op_ctx->table = range.table; + op_ctx->row = range.offset; + op_ctx->end = range.offset + range.count; + op_ctx->matched = it->ids[op->field_index]; - if (!result) { - if (offset != -1) { + return true; +} + +/* Find next traversable entity in table. */ +static +ecs_trav_down_t* flecs_query_up_find_next_traversable( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_world_t *world = ctx->world; + ecs_iter_t *it = ctx->it; + const ecs_query_t *q = &ctx->query->pub; + ecs_table_t *table = op_ctx->table; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + + if (table->_->traversable_count == 0) { + /* No traversable entities in table */ + op_ctx->table = NULL; + return NULL; + } else { + int32_t row; + ecs_entity_t entity = 0; + const ecs_entity_t *entities = ecs_table_entities(table); + + for (row = op_ctx->row; row < op_ctx->end; row ++) { + entity = entities[row]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (record->row & EcsEntityIsTraversable) { + /* Found traversable entity */ + it->sources[op->field_index] = entity; break; } - } else { - if (offset == -1) { - offset = op_ctx->index; - } } - } - if (offset == -1) { - ctx->vars[src_var].range = op_ctx->range; - return false; - } + if (row == op_ctx->end) { + /* No traversable entities remaining in table */ + op_ctx->table = NULL; + return NULL; + } - ctx->vars[src_var].range.offset = offset; - ctx->vars[src_var].range.count = (op_ctx->index - offset); - return true; -} + op_ctx->row = row; -bool flecs_query_pred_eq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, false); -} + /* Get down cache entry for traversable entity */ + bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; + op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, + op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); + op_ctx->cache_elem = -1; + } -bool flecs_query_pred_neq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, true); + return op_ctx->down; } -bool flecs_query_pred_neq( +/* Select all tables that can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) { - uint64_t written = ctx->written[ctx->op_index]; (void)written; - ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized neq operand"); + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_iter_t *it = ctx->it; + bool redo_select = redo; + const ecs_query_t *q = &ctx->query->pub; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_table_range_t r = flecs_query_get_range( - op, &op->second, EcsQuerySecond, ctx); - return flecs_query_pred_neq_w_range(op, redo, ctx, r); -} + op_ctx->trav = q->terms[op->term_index].trav; -bool flecs_query_pred_neq_name( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - const char *name = flecs_query_name_arg(op, ctx); - ecs_entity_t e = ecs_lookup(ctx->world, name); - if (!e) { - /* Entity doesn't exist */ - return true && !redo; + /* Reuse id record from previous iteration if possible*/ + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); } - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_neq_w_range(op, redo, ctx, r); -} + /* If id record is not found, or if it doesn't have any tables, revert to + * iterating owned components (no traversal) */ + if (!op_ctx->idr_trav || + !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + { + if (!self) { + /* If operation does not match owned components, return false */ + return false; + } else if (kind == FlecsQueryUpSelectId) { + return flecs_query_select_id(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } else if (kind == FlecsQueryUpSelectDefault) { + return flecs_query_select(op, redo, ctx); + } else if (kind == FlecsQueryUpSelectUnion) { + return flecs_query_union_select(op, redo, ctx); + } else { + /* Invalid select kind */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + } -/** - * @file query/engine/eval_toggle.c - * @brief Bitset toggle evaluation. - */ + if (!redo) { + /* Get component id to match */ + op_ctx->with = flecs_query_op_get_id(op, ctx); + /* Get id record for component to match */ + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + if (!op_ctx->idr_with) { + /* If id record does not exist, there can't be any results */ + return false; + } -typedef struct { - ecs_flags64_t mask; - bool has_bitset; -} flecs_query_row_mask_t; + op_ctx->down = NULL; + op_ctx->cache_elem = 0; + } -static -flecs_query_row_mask_t flecs_query_get_row_mask( - ecs_iter_t *it, - ecs_table_t *table, - int32_t block_index, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields, - ecs_query_toggle_ctx_t *op_ctx) -{ - ecs_flags64_t mask = UINT64_MAX; - int32_t i, field_count = it->field_count; - ecs_flags64_t fields = and_fields | not_fields; - bool has_bitset = false; + /* Get last used entry from down traversal cache. Cache entries in the down + * traversal cache contain a list of tables that can reach the requested + * component through the traversal relationship, for a traversable entity + * which acts as the key for the cache. */ + ecs_trav_down_t *down = op_ctx->down; - for (i = 0; i < field_count; i ++) { - uint64_t field_bit = 1llu << i; - if (!(fields & field_bit)) { - continue; - } +next_down_entry: + /* Get (next) entry in down traversal cache */ + while (!down) { + ecs_table_t *table = op_ctx->table; - if (not_fields & field_bit) { - it->set_fields &= (ecs_termset_t)~field_bit; - } else if (and_fields & field_bit) { - ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } + /* Get (next) table with traversable entities that have the + * requested component. We'll traverse downwards from the + * traversable entities in the table to find all entities that can + * reach the component through the traversal relationship. */ + if (!table) { + /* Reset source, in case we have to return a component matched + * by the entity in the found table. */ + it->sources[op->field_index] = 0; - ecs_id_t id = it->ids[i]; - ecs_bitset_t *bs = flecs_table_get_toggle(table, id); - if (!bs) { - if (not_fields & field_bit) { - if (op_ctx->prev_set_fields & field_bit) { - has_bitset = false; - break; - } + if (!flecs_query_up_select_table( + op, redo_select, ctx, trav_kind, kind)) + { + return false; } - continue; - } - - ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); - ecs_flags64_t block = bs->data[block_index]; - - if (not_fields & field_bit) { - block = ~block; - } - mask &= block; - has_bitset = true; - } - return (flecs_query_row_mask_t){ mask, has_bitset }; -} + table = op_ctx->table; -static -bool flecs_query_toggle_for_up( - ecs_iter_t *it, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields) -{ - int32_t i, field_count = it->field_count; - ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; + /* If 'self' is true, we're evaluating a term with self|up. This + * means that before traversing downwards, we should also return + * the current table as result. */ + if (self) { + if (!flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + flecs_reset_source_set_flag(it, op->field_index); + op_ctx->row --; + return true; + } + } - for (i = 0; i < field_count; i ++) { - uint64_t field_bit = 1llu << i; - if (!(fields & field_bit)) { - continue; + redo_select = true; + } else { + /* Evaluate next entity in table */ + op_ctx->row ++; } - bool match = false; - if ((it->set_fields & field_bit)) { - ecs_entity_t src = it->sources[i]; - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); - match = ecs_is_enabled_id(it->world, src, it->ids[i]); + /* Get down cache entry for next traversable entity in table */ + down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); + if (!down) { + goto next_down_entry; } + } - if (field_bit & not_fields) { - match = !match; - } +next_down_elem: + /* Get next element (table) in cache entry */ + if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { + /* No more elements in cache entry, find next.*/ + down = NULL; + goto next_down_entry; + } - if (!match) { - return false; - } + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); + flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); + flecs_query_set_vars(op, op_ctx->matched, ctx); + + if (flecs_query_table_filter(elem->table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + /* Go to next table if table contains prefabs, disabled entities or + * entities that are not queryable. */ + goto next_down_elem; } + flecs_set_source_set_flag(it, op->field_index); + return true; } -static -bool flecs_query_toggle_cmp( +/* Check if a table can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields) + const ecs_query_run_ctx_t *ctx) { + const ecs_query_t *q = &ctx->query->pub; + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - ecs_table_range_t range = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - ecs_table_t *table = range.table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if ((and_fields & op_ctx->prev_set_fields) != and_fields) { - /* If not all fields matching and toggles are set, table can't match */ + op_ctx->trav = q->terms[op->term_index].trav; + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); + } + + if (!op_ctx->idr_trav || + !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + { + /* If there are no tables with traversable relationship, there are no + * matches. */ return false; } - ecs_flags32_t up_fields = it->up_fields; if (!redo) { - if (up_fields & (and_fields|not_fields)) { - /* If there are toggle fields that were matched with query - * traversal, evaluate those separately. */ - if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { - return false; - } + op_ctx->trav = q->terms[op->term_index].trav; + op_ctx->with = flecs_query_op_get_id(op, ctx); + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); + /* If id record for component doesn't exist, there are no matches */ + if (!op_ctx->idr_with) { + return false; } - } - - /* Shared fields are evaluated, can be ignored from now on */ - // and_fields &= ~up_fields; - not_fields &= ~up_fields; - if (!(table->flags & EcsTableHasToggle)) { - if (not_fields) { - /* If any of the toggle fields with a not operator are for fields - * that are set, without a bitset those fields can't match. */ + /* Get the range (table) that is currently being evaluated. In most + * cases the range will cover the entire table, but in some cases it + * can only cover a subset of the entities in the table. */ + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.table) { return false; - } else { - /* If table doesn't have toggles but query matched toggleable - * components, all entities match. */ - if (!redo) { - return true; - } else { - return false; - } } - } - if (table && !range.count) { - range.count = ecs_table_count(table); - if (!range.count) { + /* Get entry from up traversal cache. The up traversal cache contains + * the entity on which the component was found, with additional metadata + * on where it is stored. */ + ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &op_ctx->cache, + range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, + op_ctx->idr_trav); + + if (!up) { + /* Component is not reachable from table */ return false; } + + it->sources[op->field_index] = flecs_entities_get_alive( + ctx->world, up->src); + it->trs[op->field_index] = up->tr; + it->ids[op->field_index] = up->id; + flecs_query_set_vars(op, up->id, ctx); + flecs_set_source_set_flag(it, op->field_index); + return true; + } else { + /* The table either can or can't reach the component, nothing to do for + * a second evaluation of this operation.*/ + return false; } +} - int32_t i, j; - int32_t first, last, block_index, cur; - uint64_t block = 0; +/* Check if a table can reach the target component through the traversal + * relationship, or if the table has the target component itself. */ +bool flecs_query_self_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool id_only) +{ if (!redo) { - op_ctx->range = range; - cur = op_ctx->cur = range.offset; - block_index = op_ctx->block_index = -1; - first = range.offset; - last = range.offset + range.count; - } else { - if (!op_ctx->has_bitset) { - goto done; - } + bool result; - last = op_ctx->range.offset + op_ctx->range.count; - cur = op_ctx->cur; - ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - if (cur == last) { - goto done; + if (id_only) { + /* Simple id, no wildcards */ + result = flecs_query_with_id(op, redo, ctx); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + op_ctx->remaining = 1; + } else { + result = flecs_query_with(op, redo, ctx); } - first = cur; - block_index = op_ctx->block_index; - block = op_ctx->block; - } - - /* If end of last iteration is start of new block, compute new block */ - int32_t new_block_index = cur / 64, row = first; - if (new_block_index != block_index) { -compute_block: - block_index = op_ctx->block_index = new_block_index; - - flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( - it, table, block_index, and_fields, not_fields, op_ctx); + flecs_reset_source_set_flag(ctx->it, op->field_index); - /* If table doesn't have bitset columns, all columns match */ - if (!(op_ctx->has_bitset = row_mask.has_bitset)) { - if (!not_fields) { - return true; - } else { - goto done; + if (result) { + /* Table has component, no need to traverse*/ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + op_ctx->trav = 0; + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { + /* Matching self, so set sources to 0 */ + ecs_iter_t *it = ctx->it; + it->sources[op->field_index] = 0; } + return true; } - /* No enabled bits */ - block = row_mask.mask; - if (!block) { -next_block: - new_block_index ++; - cur = new_block_index * 64; - if (cur >= last) { - /* No more rows */ - goto done; - } - - op_ctx->cur = cur; - goto compute_block; + /* Table doesn't have component, traverse relationship */ + return flecs_query_up_with(op, redo, ctx); + } else { + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + if (op_ctx->trav == 0) { + /* If matching components without traversing, make sure to still + * match remaining components that match the id (wildcard). */ + return flecs_query_with(op, redo, ctx); } - - op_ctx->block = block; } - /* Find first enabled bit (TODO: use faster bitmagic) */ - int32_t first_bit = cur - (block_index * 64); - int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); - ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); + return false; +} - for (i = first_bit; i < last_bit; i ++) { - uint64_t bit = (1ull << i); - bool cond = 0 != (block & bit); - if (cond) { - /* Find last enabled bit */ - for (j = i; j < last_bit; j ++) { - bit = (1ull << j); - cond = !(block & bit); - if (cond) { - break; - } - } +/** + * @file query/engine/eval_utils.c + * @brief Query engine evaluation utilities. + */ - row = i + (block_index * 64); - cur = j + (block_index * 64); - break; + +void flecs_query_set_iter_this( + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx) +{ + const ecs_var_t *var = &ctx->vars[0]; + const ecs_table_range_t *range = &var->range; + ecs_table_t *table = range->table; + int32_t count = range->count; + if (table) { + if (!count) { + count = ecs_table_count(table); + } + it->table = table; + it->offset = range->offset; + it->count = count; + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities += it->offset; } + } else if (count == 1) { + it->count = 1; + it->entities = &ctx->vars[0].entity; } +} - if (i == last_bit) { - goto next_block; - } +ecs_query_op_ctx_t* flecs_op_ctx_( + const ecs_query_run_ctx_t *ctx) +{ + return &ctx->op_ctx[ctx->op_index]; +} - ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + +void flecs_reset_source_set_flag( + ecs_iter_t *it, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); +} + +void flecs_set_source_set_flag( + ecs_iter_t *it, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_SET(it->up_fields, 1u << field_index); +} - if (!(cur - row)) { - goto done; +ecs_table_range_t flecs_range_from_entity( + ecs_entity_t e, + const ecs_query_run_ctx_t *ctx) +{ + ecs_record_t *r = flecs_entities_get(ctx->world, e); + if (!r) { + return (ecs_table_range_t){ 0 }; } + return (ecs_table_range_t){ + .table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1 + }; +} - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); +ecs_table_range_t flecs_query_var_get_range( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return var->range; } - op_ctx->cur = cur; - return true; - -done: - /* Restore range & set fields */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, - table, op_ctx->range.offset, op_ctx->range.count, ctx); + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range; } - it->set_fields = op_ctx->prev_set_fields; - return false; + return (ecs_table_range_t){ 0 }; } -bool flecs_query_toggle( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +ecs_table_t* flecs_query_var_get_table( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - if (!redo) { - op_ctx->prev_set_fields = it->set_fields; + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return table; } - ecs_flags64_t and_fields = op->first.entity; - ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range.table; + } - return flecs_query_toggle_cmp( - op, redo, ctx, and_fields, not_fields); + return NULL; } -bool flecs_query_toggle_option( +ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - if (!redo) { - op_ctx->prev_set_fields = it->set_fields; - op_ctx->optional_not = false; - op_ctx->has_bitset = false; - } - -repeat: {} - ecs_flags64_t and_fields = 0, not_fields = 0; - if (op_ctx->optional_not) { - not_fields = op->first.entity & op_ctx->prev_set_fields; + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + return ecs_get_table(ctx->world, ref->entity); } else { - and_fields = op->first.entity; - } - - bool result = flecs_query_toggle_cmp( - op, redo, ctx, and_fields, not_fields); - if (!result) { - if (!op_ctx->optional_not) { - /* Run the not-branch of optional fields */ - op_ctx->optional_not = true; - it->set_fields = op_ctx->prev_set_fields; - redo = false; - goto repeat; - } + return flecs_query_var_get_table(ref->var, ctx); } - - return result; } - -/** - * @file query/engine/eval_trav.c - * @brief Transitive/reflexive relationship traversal. - */ - - -static -bool flecs_query_trav_fixed_src_reflexive( +ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_table_range_t *range, - ecs_entity_t trav, - ecs_entity_t second) + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) { - ecs_table_t *table = range->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t count = range->count; - if (!count) { - count = ecs_table_count(table); - } - - int32_t i = range->offset, end = i + count; - for (; i < end; i ++) { - if (entities[i] == second) { - /* Even though table doesn't have the specific relationship - * pair, the relationship is reflexive and the target entity - * is stored in the table. */ - break; + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ref->entity, ctx); + } else { + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(var->entity, ctx); } } - if (i == end) { - /* Table didn't contain target entity */ - return false; - } - if (count > 1) { - /* If the range contains more than one entity, set the range to - * return only the entity matched by the reflexive property. */ - ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[op->src.var]; - ecs_table_range_t *var_range = &var->range; - var_range->offset = i; - var_range->count = 1; - var->entity = entities[i]; - } - - flecs_query_set_trav_match(op, NULL, trav, second, ctx); - return true; + return (ecs_table_range_t){0}; } -static -bool flecs_query_trav_unknown_src_reflexive( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t trav, - ecs_entity_t second) +ecs_entity_t flecs_query_var_get_entity( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_var_id_t src_var = op->src.var; - flecs_query_var_set_entity(op, src_var, second, ctx); - flecs_query_var_get_table(src_var, ctx); - - ecs_table_t *table = ctx->vars[src_var].range.table; - if (table) { - if (flecs_query_table_filter(table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - return false; - } + ecs_var_t *var = &ctx->vars[var_id]; + ecs_entity_t entity = var->entity; + if (entity) { + return entity; } - flecs_query_set_trav_match(op, NULL, trav, second, ctx); - return true; + ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = var->range.table; + const ecs_entity_t *entities = ecs_table_entities(table); + var->entity = entities[var->range.offset]; + return var->entity; } -static -bool flecs_query_trav_fixed_src_up_fixed_second( - const ecs_query_op_t *op, - bool redo, +void flecs_query_var_reset( + ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; /* If everything's fixed, can only have a single result */ - } + ctx->vars[var_id].entity = EcsWildcard; + ctx->vars[var_id].range.table = NULL; +} - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); - ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); - ecs_table_t *table = range.table; +void flecs_query_var_set_range( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx) +{ + (void)op; + ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; +} - /* Check if table has transitive relationship by traversing upwards */ - ecs_table_record_t *tr = NULL; - ecs_search_relation(ctx->world, table, 0, - ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); +void flecs_query_var_narrow_range( + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx) +{ + ecs_var_t *var = &ctx->vars[var_id]; + + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; - if (!tr) { - if (op->match_flags & EcsTermReflexive) { - return flecs_query_trav_fixed_src_reflexive(op, ctx, - &range, trav, second); - } else { - return false; - } + ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); + if (ctx->query_vars[var_id].kind != EcsVarTable) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + var->entity = ecs_table_entities(table)[offset]; } - - flecs_query_set_trav_match(op, tr, trav, second, ctx); - return true; } -static -bool flecs_query_trav_unknown_src_up_fixed_second( +void flecs_query_var_set_entity( const ecs_query_op_t *op, - bool redo, + ecs_var_id_t var_id, + ecs_entity_t entity, const ecs_query_run_ctx_t *ctx) { - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - - if (!redo) { - ecs_record_t *r_second = flecs_entities_get(ctx->world, second); - bool traversable = r_second && r_second->row & EcsEntityIsTraversable; - bool reflexive = op->match_flags & EcsTermReflexive; - if (!traversable && !reflexive) { - trav_ctx->cache.id = 0; - - /* If there's no record for the entity, it can't have a subtree so - * forward operation to a regular select. */ - return flecs_query_select(op, redo, ctx); - } + (void)op; + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->range.table = NULL; + var->entity = entity; +} - /* Entity is traversable, which means it could have a subtree */ - flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); - trav_ctx->index = 0; +void flecs_query_set_vars( + const ecs_query_op_t *op, + ecs_id_t id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - if (op->match_flags & EcsTermReflexive) { - trav_ctx->index = -1; - if(flecs_query_trav_unknown_src_reflexive( - op, ctx, trav, second)) - { - /* It's possible that we couldn't return the entity required for - * reflexive matching, like when it's a prefab or disabled. */ - return true; + if (flags_1st & EcsQueryIsVar) { + ecs_var_id_t var = op->first.var; + if (op->written & (1ull << var)) { + if (ECS_IS_PAIR(id)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + } else { + flecs_query_var_set_entity(op, var, id, ctx); } } - } else { - if (!trav_ctx->cache.id) { - /* No traversal cache, which means this is a regular select */ - return flecs_query_select(op, redo, ctx); - } } - if (trav_ctx->index == -1) { - redo = false; /* First result after handling reflexive relationship */ - trav_ctx->index = 0; + if (flags_2nd & EcsQueryIsVar) { + ecs_var_id_t var = op->second.var; + if (op->written & (1ull << var)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + } } +} - /* Forward to select */ - int32_t count = ecs_vec_count(&trav_ctx->cache.entities); - ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); - for (; trav_ctx->index < count; trav_ctx->index ++) { - ecs_trav_elem_t *el = &elems[trav_ctx->index]; - trav_ctx->and.idr = el->idr; /* prevents lookup by select */ - if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - return true; - } - - redo = false; +ecs_table_range_t flecs_get_ref_range( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) +{ + if (flag & EcsQueryIsEntity) { + return flecs_range_from_entity(ref->entity, ctx); + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_range(ref->var, ctx); } + return (ecs_table_range_t){0}; +} - return false; +ecs_entity_t flecs_get_ref_entity( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) +{ + if (flag & EcsQueryIsEntity) { + return ref->entity; + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_entity(ref->var, ctx); + } + return 0; } -static -bool flecs_query_trav_yield_reflexive_src( +ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_table_range_t *range, - ecs_entity_t trav) + uint64_t written, + const ecs_query_run_ctx_t *ctx) { - ecs_var_t *vars = ctx->vars; - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - int32_t offset = trav_ctx->offset, count = trav_ctx->count; - bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t first = 0, second = 0; - if (trav_ctx->index >= (offset + count)) { - /* Restore previous offset, count */ - if (src_is_var) { - ecs_var_id_t src_var = op->src.var; - vars[src_var].range.offset = offset; - vars[src_var].range.count = count; - vars[src_var].entity = 0; + if (flags_1st) { + if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { + first = flecs_get_ref_entity(&op->first, flags_1st, ctx); + } else if (flags_1st & EcsQueryIsVar) { + first = EcsWildcard; } - return false; } - - ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; - flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - - /* Hijack existing variable to return one result at a time */ - if (src_is_var) { - ecs_var_id_t src_var = op->src.var; - ecs_table_t *table = vars[src_var].range.table; - ecs_assert(!table || table == ecs_get_table(ctx->world, entity), - ECS_INTERNAL_ERROR, NULL); - (void)table; - vars[src_var].entity = entity; - vars[src_var].range = flecs_range_from_entity(entity, ctx); + if (flags_2nd) { + if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); + } else if (flags_2nd & EcsQueryIsVar) { + second = EcsWildcard; + } } - return true; + if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { + return ecs_pair(first, second); + } else { + return flecs_entities_get_alive(ctx->world, first); + } } -static -bool flecs_query_trav_fixed_src_up_unknown_second( +ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, - bool redo, const ecs_query_run_ctx_t *ctx) { - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); - ecs_table_t *table = range.table; - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + uint64_t written = ctx->written[ctx->op_index]; + return flecs_query_op_get_id_w_written(op, written, ctx); +} - if (!redo) { - flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); - trav_ctx->index = 0; - if (op->match_flags & EcsTermReflexive) { - trav_ctx->yield_reflexive = true; - trav_ctx->index = range.offset; - trav_ctx->offset = range.offset; - trav_ctx->count = range.count ? range.count : ecs_table_count(table); - } +int16_t flecs_query_next_column( + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { + column = column + 1; } else { - trav_ctx->index ++; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(NULL, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); } + return flecs_ito(int16_t, column); +} - if (trav_ctx->yield_reflexive) { - if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { - return true; - } - trav_ctx->yield_reflexive = false; - trav_ctx->index = 0; - } +void flecs_query_it_set_tr( + ecs_iter_t *it, + int32_t field_index, + const ecs_table_record_t *tr) +{ + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + it->trs[field_index] = tr; +} - if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { - return false; - } +ecs_id_t flecs_query_it_set_id( + ecs_iter_t *it, + ecs_table_t *table, + int32_t field_index, + int32_t column) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + return it->ids[field_index] = table->type.array[column]; +} - ecs_trav_elem_t *el = ecs_vec_get_t( - &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); - flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); - return true; +void flecs_query_set_match( + const ecs_query_op_t *op, + ecs_table_t *table, + int32_t column, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t field_index = op->field_index; + if (field_index == -1) { + return; + } + + ecs_iter_t *it = ctx->it; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[column]; + flecs_query_it_set_tr(it, field_index, tr); + ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); + flecs_query_set_vars(op, matched, ctx); } -bool flecs_query_trav( +void flecs_query_set_trav_match( const ecs_query_op_t *op, - bool redo, + const ecs_table_record_t *tr, + ecs_entity_t trav, + ecs_entity_t second, const ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - - if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - /* This can't happen, src or second should have been resolved */ - ecs_abort(ECS_INTERNAL_ERROR, - "invalid instruction sequence: unconstrained traversal"); - } else { - return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); - } - } else { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); - } else { - return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); - } + int32_t field_index = op->field_index; + if (field_index == -1) { + return; } + + ecs_iter_t *it = ctx->it; + ecs_id_t matched = ecs_pair(trav, second); + it->ids[op->field_index] = matched; + flecs_query_it_set_tr(it, op->field_index, tr); + flecs_query_set_vars(op, matched, ctx); +} + +bool flecs_query_table_filter( + ecs_table_t *table, + ecs_query_lbl_t other, + ecs_flags32_t filter_mask) +{ + uint32_t filter = flecs_ito(uint32_t, other); + return (table->flags & filter_mask & filter) != 0; } /** - * @file query/engine/eval_union.c - * @brief Union relationship evaluation. + * @file query/engine/trav_cache.c + * @brief Cache that stores the result of graph traversal. */ static -bool flecs_query_union_with_wildcard( - const ecs_query_op_t *op, - bool redo, +void flecs_query_build_down_cache( + ecs_world_t *world, + ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - bool neq) + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); + if (!idr) { + return; + } - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); + ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + elem->entity = entity; + elem->idr = idr; + + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + int32_t i, count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (r->row & EcsEntityIsTraversable) { + flecs_query_build_down_cache( + world, a, ctx, cache, trav, entities[i]); + } + } } + } +} - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return neq; +static +void flecs_query_build_up_cache( + ecs_world_t *world, + ecs_allocator_t *a, + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table, + const ecs_table_record_t *tr, + int32_t root_column) +{ + ecs_id_t *ids = table->type.array; + int32_t i = tr->index, end = i + tr->count; + bool is_root = root_column == -1; + + for (; i < end; i ++) { + ecs_entity_t second = ecs_pair_second(world, ids[i]); + if (is_root) { + root_column = i; } - if (neq) { - if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { - /* If table has (R, Union) none match !(R, _) */ - return false; - } else { - /* If table doesn't have (R, Union) all match !(R, _) */ - return true; + ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + + el->entity = second; + el->tr = &table->_->records[i]; + el->idr = NULL; + + ecs_record_t *r = flecs_entities_get_any(world, second); + if (r->table) { + ecs_table_record_t *r_tr = flecs_id_record_get_table( + cache->idr, r->table); + if (!r_tr) { + return; } + flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, + r_tr, root_column); } + } +} - op_ctx->row = 0; - } else { - if (neq) { - /* !(R, _) terms only can have a single result */ - return false; - } +void flecs_query_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache) +{ + ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); +} - range = op_ctx->range; - table = range.table; - op_ctx->row ++; +void flecs_query_get_trav_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) +{ + if (cache->id != ecs_pair(trav, entity) || cache->up) { + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); + cache->id = ecs_pair(trav, entity); + cache->up = false; } +} -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); +void flecs_query_get_trav_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + + ecs_id_record_t *idr = cache->idr; + if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { + idr = cache->idr = flecs_id_record_get(world, + ecs_pair(trav, EcsWildcard)); + if (!idr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; } - return false; } - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - if (!tgt) { - op_ctx->row ++; - goto next_row; + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; } - it->ids[field_index] = ecs_pair(rel, tgt); + ecs_id_t id = table->type.array[tr->index]; - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); + if (cache->id != id || !cache->up) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); + cache->id = id; + cache->up = true; + } +} + +/** + * @file query/engine/trav_down_cache.c + * @brief Compile query term. + */ + + +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self, + bool empty); + +static +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self, + bool empty); + +static +ecs_trav_down_t* flecs_trav_down_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t entity) +{ + ecs_trav_down_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_down_t, entity); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); + ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); } - flecs_query_set_vars(op, it->ids[field_index], ctx); - return true; + return trav[0]; } static -bool flecs_query_union_with_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt, - bool neq) +ecs_trav_down_t* flecs_trav_table_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); - } + if (!table->_->traversable_count) { + return dst; + } - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t entity = entities[i]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (!record) { + continue; } - op_ctx->row = 0; - } else { - range = op_ctx->range; - table = range.table; - op_ctx->row ++; - } + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, entity)); + if (!idr_trav) { + continue; + } -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); + flecs_trav_entity_down(world, a, cache, dst, + trav, idr_trav, idr_with, self, empty); } - return false; } - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - bool match = e_tgt == tgt; - if (neq) { - match = !match; - } + return dst; +} - if (!match) { - op_ctx->row ++; - goto next_row; +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self, + bool empty) +{ + if (trav == EcsIsA || !world->idr_isa_wildcard) { + return; } - it->ids[field_index] = ecs_pair(rel, tgt); - - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); + ecs_id_record_t *idr_isa = flecs_id_record_get( + world, ecs_pair(EcsIsA, entity)); + if (!idr_isa) { + return; } - flecs_query_set_vars(op, it->ids[field_index], ctx); + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr_isa->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } - return true; -} + if (ecs_table_has_id(world, table, idr_with->id)) { + /* Table owns component */ + continue; + } -bool flecs_query_union_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool neq) -{ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *record = flecs_entities_get(world, e); + if (!record) { + continue; + } - if (tgt == EcsWildcard) { - return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); - } else { - return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, e)); + if (idr_trav) { + flecs_trav_entity_down(world, a, cache, dst, trav, + idr_trav, idr_with, self, empty); + } + + flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, + idr_with, self, empty); + } + } + } } } static -bool flecs_query_union_select_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt) +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } + int32_t first = ecs_vec_count(&dst->elems); - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); + ecs_table_cache_iter_t it; + bool result; + if (empty) { + result = flecs_table_cache_all_iter(&idr_trav->cache, &it); } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + result = flecs_table_cache_iter(&idr_trav->cache, &it); } - if (!op_ctx->cur) { - return false; - } + if (result) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + bool leaf = false; - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); + if (flecs_id_record_get_table(idr_with, table) != NULL) { + if (self) { + continue; + } + leaf = true; + } - it->ids[field_index] = ecs_pair(rel, tgt); + /* If record is not the first instance of (trav, *), don't add it + * to the cache. */ + int32_t index = tr->index; + if (index) { + ecs_id_t id = table->type.array[index - 1]; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { + int32_t col = ecs_search_relation(world, table, 0, + idr_with->id, trav, EcsUp, NULL, NULL, &tr); + ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); + + if (col != index) { + /* First relationship through which the id is + * reachable is not the current one, so skip. */ + continue; + } + } + } + + ecs_trav_down_elem_t *elem = ecs_vec_append_t( + a, &dst->elems, ecs_trav_down_elem_t); + elem->table = table; + elem->leaf = leaf; + } + } + + /* Breadth first walk */ + int32_t t, last = ecs_vec_count(&dst->elems); + for (t = first; t < last; t ++) { + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &dst->elems, ecs_trav_down_elem_t, t); + if (!elem->leaf) { + flecs_trav_table_down(world, a, cache, dst, trav, + elem->table, idr_with, self, empty); + } + } - return true; + return dst; } -static -bool flecs_query_union_select_wildcard( - const ecs_query_op_t *op, - bool redo, +ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel) + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t e, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_world_t *world = ctx->it->real_world; + ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravDown; - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); - op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); - op_ctx->tgt = 0; + ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); + if (result->ready) { + return result; } -next_tgt: - if (!op_ctx->tgt) { - if (!ecs_map_next(&op_ctx->tgt_iter)) { - return false; + ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); + if (!idr_trav) { + if (trav != EcsIsA) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, idr_with, self, empty); } - - op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); - op_ctx->cur = 0; - it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); + result->ready = true; + return result; } - if (!op_ctx->cur) { - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); - } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); - } + ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); - if (!op_ctx->cur) { - op_ctx->tgt = 0; - goto next_tgt; + /* Cover IsA -> trav paths. If a parent inherits a component, then children + * of that parent should find the component through up traversal. */ + if (idr_with->flags & EcsIdOnInstantiateInherit) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, idr_with, self, empty); } - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); + flecs_trav_entity_down( + world, a, cache, result, trav, idr_trav, idr_with, self, empty); + result->ready = true; - return true; + return result; } -bool flecs_query_union_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - - if (tgt == EcsWildcard) { - return flecs_query_union_select_wildcard(op, redo, ctx, rel); - } else { - return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); + ecs_map_iter_t it = ecs_map_iter(&cache->src); + while (ecs_map_next(&it)) { + ecs_trav_down_t *t = ecs_map_ptr(&it); + ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); } + ecs_map_fini(&cache->src); } -bool flecs_query_union( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +/** + * @file query/engine/trav_up_cache.c + * @brief Compile query term. + */ + + +static +ecs_trav_up_t* flecs_trav_up_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + uint64_t table_id) { - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_union_with(op, redo, ctx, false); - } else { - return flecs_query_union_select(op, redo, ctx); + ecs_trav_up_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_up_t, table_id); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); } + + return trav[0]; } -bool flecs_query_union_neq( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_union_with(op, redo, ctx, true); - } else { - return false; +static +int32_t flecs_trav_type_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + ecs_type_t *type) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); + if (tr) { + up->id = type->array[tr->index]; + up->tr = tr; + return tr->index; } + + return -1; } static -void flecs_query_union_set_shared( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx) +int32_t flecs_trav_type_offset_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + int32_t offset, + ecs_id_t with, + ecs_type_t *type) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_id_record_t *idr = op_ctx->idr_with; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); - idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + while (offset < type->count) { + ecs_id_t type_id = type->array[offset ++]; + if (ecs_id_match(type_id, with)) { + up->id = type_id; + up->tr = &table->_->records[offset - 1]; + return offset - 1; + } + } - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - ecs_entity_t src = it->sources[field_index]; - ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); - - it->ids[field_index] = ecs_pair(rel, tgt); + return -1; } -bool flecs_query_union_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +static +ecs_trav_up_t* flecs_trav_table_up( + const ecs_query_run_ctx_t *ctx, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + const ecs_world_t *world, + ecs_entity_t src, + ecs_id_t with, + ecs_id_t rel, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (!redo) { - if (!flecs_query_up_with(op, redo, ctx)) { - return false; + ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); + if (up->ready) { + return up; + } + + ecs_record_t *src_record = flecs_entities_get_any(world, src); + ecs_table_t *table = src_record->table; + if (!table) { + goto not_found; + } + + ecs_type_t type = table->type; + if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { + up->src = src; + goto found; + } + + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = idr_trav == world->idr_isa_wildcard; + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + goto not_found; + } + + if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { + goto not_found; + } + } + + ecs_trav_up_t up_pair = {0}; + int32_t r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; } - flecs_query_union_set_shared(op, ctx); - return true; - } else { - return false; + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } + + if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { + idr_trav = world->idr_isa_wildcard; + r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } } - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); } + +not_found: + up->tr = NULL; +found: + up->ready = true; + return up; } -bool flecs_query_union_self_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_trav_up_t* flecs_query_get_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + ecs_id_t with, + ecs_entity_t trav, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (redo) { - goto next_for_union; - } + if (cache->with && cache->with != with) { + flecs_query_up_cache_fini(cache); + } -next_for_self_up_with: - if (!flecs_query_self_up_with(op, redo, ctx, false)) { - return false; - } + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - if (it->sources[field_index]) { - flecs_query_union_set_shared(op, ctx); - return true; - } + ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravUp; + cache->with = with; -next_for_union: - if (!flecs_query_union_with(op, redo, ctx, false)) { - goto next_for_self_up_with; - } + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); + if (!tr) { + return NULL; /* Table doesn't have the relationship */ + } - return true; - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_id_t id = table->type.array[i]; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, + with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (result->src != 0) { + return result; + } } + + return NULL; +} + +void flecs_query_up_cache_fini( + ecs_trav_up_cache_t *cache) +{ + ecs_map_fini(&cache->src); } /** - * @file query/engine/eval.c - * @brief Query engine implementation. + * @file query/engine/trivial_iter.c + * @brief Iterator for trivial queries. */ -/* Find tables with requested component that has traversable entities. */ static -bool flecs_query_up_select_table( - const ecs_query_op_t *op, - bool redo, +bool flecs_query_trivial_search_init( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind, - ecs_query_up_select_kind_t kind) + ecs_query_trivial_ctx_t *op_ctx, + const ecs_query_t *query, + bool redo, + ecs_flags64_t term_set) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_iter_t *it = ctx->it; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_table_range_t range; - - do { - bool result; - if (kind == FlecsQueryUpSelectId) { - result = flecs_query_select_id(op, redo, ctx, 0); - } else if (kind == FlecsQueryUpSelectDefault) { - result = flecs_query_select_w_id(op, redo, ctx, - op_ctx->with, 0); - } else if (kind == FlecsQueryUpSelectUnion) { - result = flecs_query_union_select(op, redo, ctx); - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); + if (!redo) { + /* Find first trivial term*/ + int32_t t = 0; + if (term_set) { + for (; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } } - if (!result) { - /* No remaining tables with component found. */ + ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); + op_ctx->start_from = t; + + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); + if (!idr) { return false; } - redo = true; - - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + if (query->flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } - /* Keep searching until we find a table that has the requested component, - * with traversable entities */ - } while (!self && range.table->_->traversable_count == 0); + /* Find next term to evaluate once */ + + for (t = t + 1; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } - if (!range.count) { - range.count = ecs_table_count(range.table); + op_ctx->first_to_eval = t; } - op_ctx->table = range.table; - op_ctx->row = range.offset; - op_ctx->end = range.offset + range.count; - op_ctx->matched = it->ids[op->field_index]; - return true; } -/* Find next traversable entity in table. */ -static -ecs_trav_down_t* flecs_query_up_find_next_traversable( - const ecs_query_op_t *op, +bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind) + ecs_query_trivial_ctx_t *op_ctx, + bool redo, + ecs_flags64_t term_set) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_world_t *world = ctx->world; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; - const ecs_query_t *q = &ctx->query->pub; - ecs_table_t *table = op_ctx->table; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; + int32_t t, term_count = query->pub.term_count; - if (table->_->traversable_count == 0) { - /* No traversable entities in table */ - op_ctx->table = NULL; - return NULL; - } else { - int32_t row; - ecs_entity_t entity = 0; - const ecs_entity_t *entities = ecs_table_entities(table); + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { + return false; + } - for (row = op_ctx->row; row < op_ctx->end; row ++) { - entity = entities[row]; - ecs_record_t *record = flecs_entities_get(world, entity); - if (record->row & EcsEntityIsTraversable) { - /* Found traversable entity */ - it->sources[op->field_index] = entity; - break; - } + do { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; } - if (row == op_ctx->end) { - /* No traversable entities remaining in table */ - op_ctx->table = NULL; - return NULL; + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + continue; } - op_ctx->row = row; + for (t = op_ctx->first_to_eval; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; + } - /* Get down cache entry for traversable entity */ - bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; - op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, - op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); - op_ctx->cache_elem = -1; - } + const ecs_term_t *term = &terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); + if (!idr) { + break; + } - return op_ctx->down; + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + idr, table); + if (!tr_with) { + break; + } + + it->trs[term->field_index] = tr_with; + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->trs[op_ctx->start_from] = tr; + break; + } + } while (true); + + return true; } -/* Select all tables that can reach the target component through the traversal - * relationship. */ -bool flecs_query_up_select( - const ecs_query_op_t *op, - bool redo, +bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind, - ecs_query_up_select_kind_t kind) + ecs_query_trivial_ctx_t *op_ctx, + bool redo) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_id_t *ids = q->ids; ecs_iter_t *it = ctx->it; - bool redo_select = redo; - const ecs_query_t *q = &ctx->query->pub; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - - op_ctx->trav = q->terms[op->term_index].trav; + int32_t t, term_count = query->pub.term_count; - /* Reuse id record from previous iteration if possible*/ - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { + return false; } - /* If id record is not found, or if it doesn't have any tables, revert to - * iterating owned components (no traversal) */ - if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) +next: { - if (!self) { - /* If operation does not match owned components, return false */ + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { return false; - } else if (kind == FlecsQueryUpSelectId) { - return flecs_query_select_id(op, redo, ctx, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } else if (kind == FlecsQueryUpSelectDefault) { - return flecs_query_select(op, redo, ctx); - } else if (kind == FlecsQueryUpSelectUnion) { - return flecs_query_union_select(op, redo, ctx); - } else { - /* Invalid select kind */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); } - } - if (!redo) { - /* Get component id to match */ - op_ctx->with = flecs_query_op_get_id(op, ctx); + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + goto next; + } - /* Get id record for component to match */ - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - if (!op_ctx->idr_with) { - /* If id record does not exist, there can't be any results */ - return false; + for (t = 1; t < term_count; t ++) { + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); + if (!idr) { + return false; + } + + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + idr, table); + if (!tr_with) { + goto next; + } + + it->trs[t] = tr_with; } - op_ctx->down = NULL; - op_ctx->cache_elem = 0; + it->table = table; + it->count = ecs_table_count(table); + it->entities = ecs_table_entities(table); + it->trs[0] = tr; } - /* Get last used entry from down traversal cache. Cache entries in the down - * traversal cache contain a list of tables that can reach the requested - * component through the traversal relationship, for a traversable entity - * which acts as the key for the cache. */ - ecs_trav_down_t *down = op_ctx->down; + return true; +} -next_down_entry: - /* Get (next) entry in down traversal cache */ - while (!down) { - ecs_table_t *table = op_ctx->table; +bool flecs_query_trivial_test( + const ecs_query_run_ctx_t *ctx, + bool redo, + ecs_flags64_t term_set) +{ + if (redo) { + return false; + } else { + const ecs_query_impl_t *impl = ctx->query; + const ecs_query_t *q = &impl->pub; + const ecs_term_t *terms = q->terms; + ecs_iter_t *it = ctx->it; + int32_t t, term_count = impl->pub.term_count; - /* Get (next) table with traversable entities that have the - * requested component. We'll traverse downwards from the - * traversable entities in the table to find all entities that can - * reach the component through the traversal relationship. */ - if (!table) { - /* Reset source, in case we have to return a component matched - * by the entity in the found table. */ - it->sources[op->field_index] = 0; + ecs_table_t *table = it->table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); - if (!flecs_query_up_select_table( - op, redo_select, ctx, trav_kind, kind)) - { - return false; + for (t = 0; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; } - table = op_ctx->table; + const ecs_term_t *term = &terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); + if (!idr) { + return false; + } - /* If 'self' is true, we're evaluating a term with self|up. This - * means that before traversing downwards, we should also return - * the current table as result. */ - if (self) { - if (!flecs_query_table_filter(table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - flecs_reset_source_set_flag(it, op->field_index); - op_ctx->row --; - return true; - } + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; } - redo_select = true; - } else { - /* Evaluate next entity in table */ - op_ctx->row ++; + it->trs[term->field_index] = tr; } - /* Get down cache entry for next traversable entity in table */ - down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); - if (!down) { - goto next_down_entry; + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities = &it->entities[it->offset]; } - } -next_down_elem: - /* Get next element (table) in cache entry */ - if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { - /* No more elements in cache entry, find next.*/ - down = NULL; - goto next_down_entry; + return true; } +} - ecs_trav_down_elem_t *elem = ecs_vec_get_t( - &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); - flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); - flecs_query_set_vars(op, op_ctx->matched, ctx); +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - if (flecs_query_table_filter(elem->table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - /* Go to next table if table contains prefabs, disabled entities or - * entities that are not queryable. */ - goto next_down_elem; - } - flecs_set_source_set_flag(it, op->field_index); +#ifdef FLECS_SCRIPT + +#define flecs_expr_ast_new(parser, T, kind)\ + (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) + +static +void* flecs_expr_ast_new_( + ecs_script_parser_t *parser, + ecs_size_t size, + ecs_expr_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_expr_node_t *result = flecs_calloc(a, size); + result->kind = kind; + result->pos = parser->pos; + return result; +} + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.bool_ = value; + result->ptr = &result->storage.bool_; + result->node.type = ecs_id(ecs_bool_t); + return result; +} + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.i64 = value; + result->ptr = &result->storage.i64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.u64 = value; + result->ptr = &result->storage.u64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.f64 = value; + result->ptr = &result->storage.f64; + result->node.type = ecs_id(ecs_f64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.string = value; + result->ptr = &result->storage.string; + result->node.type = ecs_id(ecs_string_t); + return result; +} + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_identifier_t *result = flecs_expr_ast_new( + parser, ecs_expr_identifier_t, EcsExprIdentifier); + result->value = value; + return result; +} - return true; +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_variable_t *result = flecs_expr_ast_new( + parser, ecs_expr_variable_t, EcsExprVariable); + result->value = value; + return result; } -/* Check if a table can reach the target component through the traversal - * relationship. */ -bool flecs_query_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser) { - const ecs_query_t *q = &ctx->query->pub; - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_iter_t *it = ctx->it; + ecs_expr_unary_t *result = flecs_expr_ast_new( + parser, ecs_expr_unary_t, EcsExprUnary); + return result; +} - op_ctx->trav = q->terms[op->term_index].trav; - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); - } +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprBinary); + return result; +} - if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) - { - /* If there are no tables with traversable relationship, there are no - * matches. */ - return false; - } +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser) +{ + ecs_expr_member_t *result = flecs_expr_ast_new( + parser, ecs_expr_member_t, EcsExprMember); + return result; +} - if (!redo) { - op_ctx->trav = q->terms[op->term_index].trav; - op_ctx->with = flecs_query_op_get_id(op, ctx); - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser) +{ + ecs_expr_element_t *result = flecs_expr_ast_new( + parser, ecs_expr_element_t, EcsExprElement); + return result; +} - /* If id record for component doesn't exist, there are no matches */ - if (!op_ctx->idr_with) { - return false; - } +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *expr, + ecs_entity_t type) +{ + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; + ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); + result->node.kind = EcsExprCast; + result->node.pos = expr->pos; + result->node.type = type; + result->expr = expr; + return result; +} - /* Get the range (table) that is currently being evaluated. In most - * cases the range will cover the entire table, but in some cases it - * can only cover a subset of the entities in the table. */ - ecs_table_range_t range = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - if (!range.table) { - return false; - } +#endif - /* Get entry from up traversal cache. The up traversal cache contains - * the entity on which the component was found, with additional metadata - * on where it is stored. */ - ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &op_ctx->cache, - range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, - op_ctx->idr_trav); +/** + * @file addons/script/expr/parser.c + * @brief Script expression parser. + */ - if (!up) { - /* Component is not reachable from table */ - return false; - } - it->sources[op->field_index] = flecs_entities_get_alive( - ctx->world, up->src); - it->trs[op->field_index] = up->tr; - it->ids[op->field_index] = up->id; - flecs_query_set_vars(op, up->id, ctx); - flecs_set_source_set_flag(it, op->field_index); - return true; - } else { - /* The table either can or can't reach the component, nothing to do for - * a second evaluation of this operation.*/ - return false; - } -} +#ifdef FLECS_SCRIPT -/* Check if a table can reach the target component through the traversal - * relationship, or if the table has the target component itself. */ -bool flecs_query_self_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool id_only) -{ - if (!redo) { - bool result; +/* From https://en.cppreference.com/w/c/language/operator_precedence */ - if (id_only) { - /* Simple id, no wildcards */ - result = flecs_query_with_id(op, redo, ctx); - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - op_ctx->remaining = 1; - } else { - result = flecs_query_with(op, redo, ctx); - } +static int flecs_expr_precedence[] = { + [EcsTokParenOpen] = 1, + [EcsTokMember] = 1, + [EcsTokBracketOpen] = 1, + [EcsTokNot] = 2, + [EcsTokMul] = 3, + [EcsTokDiv] = 3, + [EcsTokMod] = 3, + [EcsTokAdd] = 4, + [EcsTokSub] = 4, + [EcsTokShiftLeft] = 5, + [EcsTokShiftRight] = 5, + [EcsTokGt] = 6, + [EcsTokGtEq] = 6, + [EcsTokLt] = 6, + [EcsTokLtEq] = 6, + [EcsTokEq] = 7, + [EcsTokNeq] = 7, + [EcsTokBitwiseAnd] = 8, + [EcsTokBitwiseOr] = 10, + [EcsTokAnd] = 11, + [EcsTokOr] = 12, +}; - flecs_reset_source_set_flag(ctx->it, op->field_index); +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); - if (result) { - /* Table has component, no need to traverse*/ - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - op_ctx->trav = 0; - if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { - /* Matching self, so set sources to 0 */ - ecs_iter_t *it = ctx->it; - it->sources[op->field_index] = 0; - } - return true; - } +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); - /* Table doesn't have component, traverse relationship */ - return flecs_query_up_with(op, redo, ctx); - } else { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - if (op_ctx->trav == 0) { - /* If matching components without traversing, make sure to still - * match remaining components that match the id (wildcard). */ - return flecs_query_with(op, redo, ctx); - } +static +bool flecs_has_precedence( + ecs_script_token_kind_t first, + ecs_script_token_kind_t second) +{ + if (!flecs_expr_precedence[first]) { + return false; } - - return false; + return flecs_expr_precedence[first] < flecs_expr_precedence[second]; } -/** - * @file query/engine/eval_utils.c - * @brief Query engine evaluation utilities. - */ +static +const char* flecs_script_parse_rhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_expr_node_t *left, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + const char *last_pos = pos; + TokenFramePush(); -void flecs_query_set_iter_this( - ecs_iter_t *it, - const ecs_query_run_ctx_t *ctx) -{ - const ecs_var_t *var = &ctx->vars[0]; - const ecs_table_range_t *range = &var->range; - ecs_table_t *table = range->table; - int32_t count = range->count; - if (table) { - if (!count) { - count = ecs_table_count(table); - } - it->table = table; - it->offset = range->offset; - it->count = count; - it->entities = ecs_table_entities(table); - if (it->entities) { - it->entities += it->offset; - } - } else if (count == 1) { - it->count = 1; - it->entities = &ctx->vars[0].entity; - } -} + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; -ecs_query_op_ctx_t* flecs_op_ctx_( - const ecs_query_run_ctx_t *ctx) -{ - return &ctx->op_ctx[ctx->op_index]; -} + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } -#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + /* Consume lookahead token */ + pos = lookahead; -void flecs_reset_source_set_flag( - ecs_iter_t *it, - int32_t field_index) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); -} + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; -void flecs_set_source_set_flag( - ecs_iter_t *it, - int32_t field_index) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_SET(it->up_fields, 1u << field_index); -} + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } -ecs_table_range_t flecs_range_from_entity( - ecs_entity_t e, - const ecs_query_run_ctx_t *ctx) -{ - ecs_record_t *r = flecs_entities_get(ctx->world, e); - if (!r) { - return (ecs_table_range_t){ 0 }; - } - return (ecs_table_range_t){ - .table = r->table, - .offset = ECS_RECORD_TO_ROW(r->row), - .count = 1 - }; -} + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); -ecs_table_range_t flecs_query_var_get_range( - int32_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - ecs_table_t *table = var->range.table; - if (table) { - return var->range; - } + break; + } - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range; - } + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; - return (ecs_table_range_t){ 0 }; -} + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); -ecs_table_t* flecs_query_var_get_table( - int32_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_var_t *var = &ctx->vars[var_id]; - ecs_table_t *table = var->range.table; - if (table) { - return table; - } + break; + } - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range.table; - } + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; - return NULL; -} + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; + } + }; -ecs_table_t* flecs_query_get_table( - const ecs_query_op_t *op, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - if (flags & EcsQueryIsEntity) { - return ecs_get_table(ctx->world, ref->entity); - } else { - return flecs_query_var_get_table(ref->var, ctx); - } -} + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; -ecs_table_range_t flecs_query_get_range( - const ecs_query_op_t *op, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - if (flags & EcsQueryIsEntity) { - ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); - return flecs_range_from_entity(ref->entity, ctx); - } else { - ecs_var_t *var = &ctx->vars[ref->var]; - if (var->range.table) { - return ctx->vars[ref->var].range; - } else if (var->entity) { - return flecs_range_from_entity(var->entity, ctx); + break; } - } - return (ecs_table_range_t){0}; -} + ) -ecs_entity_t flecs_query_var_get_entity( - ecs_var_id_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - ecs_entity_t entity = var->entity; - if (entity) { - return entity; + if (pos[0] && (pos != last_pos)) { + pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); } - ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = var->range.table; - const ecs_entity_t *entities = ecs_table_entities(table); - var->entity = entities[var->range.offset]; - return var->entity; -} + TokenFramePop(); -void flecs_query_var_reset( - ecs_var_id_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ctx->vars[var_id].entity = EcsWildcard; - ctx->vars[var_id].range.table = NULL; + return pos; +error: + return NULL; } -void flecs_query_var_set_range( - const ecs_query_op_t *op, - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - const ecs_query_run_ctx_t *ctx) +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) { - (void)op; - ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_query_is_written(var_id, op->written), - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - var->entity = 0; - var->range = (ecs_table_range_t){ - .table = table, - .offset = offset, - .count = count - }; -} - -void flecs_query_var_narrow_range( - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - const ecs_query_run_ctx_t *ctx) -{ - ecs_var_t *var = &ctx->vars[var_id]; - - var->entity = 0; - var->range = (ecs_table_range_t){ - .table = table, - .offset = offset, - .count = count - }; - - ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - if (ctx->query_vars[var_id].kind != EcsVarTable) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - var->entity = ecs_table_entities(table)[offset]; - } -} + TokenFramePush(); -void flecs_query_var_set_entity( - const ecs_query_op_t *op, - ecs_var_id_t var_id, - ecs_entity_t entity, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_query_is_written(var_id, op->written), - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - var->range.table = NULL; - var->entity = entity; -} + Parse( + case EcsTokNumber: { + const char *expr = Token(0); + if (strchr(expr, '.') || strchr(expr, 'e')) { + *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); + } else if (expr[0] == '-') { + *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + } + break; + } -void flecs_query_set_vars( - const ecs_query_op_t *op, - ecs_id_t id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + case EcsTokString: { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + break; + } - if (flags_1st & EcsQueryIsVar) { - ecs_var_id_t var = op->first.var; - if (op->written & (1ull << var)) { - if (ECS_IS_PAIR(id)) { - flecs_query_var_set_entity( - op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + case EcsTokIdentifier: { + const char *expr = Token(0); + if (expr[0] == '$') { + *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); + } else if (!ecs_os_strcmp(expr, "true")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); + } else if (!ecs_os_strcmp(expr, "false")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { - flecs_query_var_set_entity(op, var, id, ctx); + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); } + break; } - } - if (flags_2nd & EcsQueryIsVar) { - ecs_var_id_t var = op->second.var; - if (op->written & (1ull << var)) { - flecs_query_var_set_entity( - op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + case EcsTokParenOpen: { + pos = flecs_script_parse_expr(parser, pos, 0, out); + Parse_1(EcsTokParenClose, { + break; + }) + break; } - } + + case EcsTokNot: { + ecs_expr_unary_t *unary = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); + unary->operator = EcsTokNot; + *out = (ecs_expr_node_t*)unary; + break; + } + ) + + TokenFramePop(); + + /* Parse right-hand side of expression if there is one */ + return flecs_script_parse_rhs( + parser, pos, tokenizer, *out, left_oper, out); +error: + return NULL; } -ecs_table_range_t flecs_get_ref_range( - const ecs_query_ref_t *ref, - ecs_flags16_t flag, - const ecs_query_run_ctx_t *ctx) +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) { - if (flag & EcsQueryIsEntity) { - return flecs_range_from_entity(ref->entity, ctx); - } else if (flag & EcsQueryIsVar) { - return flecs_query_var_get_range(ref->var, ctx); - } - return (ecs_table_range_t){0}; + ParserBegin; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); + + EndOfRule; + + ParserEnd; } -ecs_entity_t flecs_get_ref_entity( - const ecs_query_ref_t *ref, - ecs_flags16_t flag, - const ecs_query_run_ctx_t *ctx) +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr) { - if (flag & EcsQueryIsEntity) { - return ref->entity; - } else if (flag & EcsQueryIsVar) { - return flecs_query_var_get_entity(ref->var, ctx); + if (!script) { + script = flecs_script_new(world); } - return 0; + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + flecs_script_expr_visit_type(script, out); + flecs_script_expr_visit_fold(script, &out); + + return out; +error: + return NULL; } -ecs_id_t flecs_query_op_get_id_w_written( - const ecs_query_op_t *op, - uint64_t written, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_entity_t first = 0, second = 0; +#endif - if (flags_1st) { - if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { - first = flecs_get_ref_entity(&op->first, flags_1st, ctx); - } else if (flags_1st & EcsQueryIsVar) { - first = EcsWildcard; - } +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + + +#ifdef FLECS_SCRIPT + +#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - if (flags_2nd) { - if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); - } else if (flags_2nd & EcsQueryIsVar) { - second = EcsWildcard; - } + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { - return ecs_pair(first, second); - } else { - return flecs_entities_get_alive(ctx->world, first); +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } -} -ecs_id_t flecs_query_op_get_id( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - return flecs_query_op_get_id_w_written(op, written, ctx); -} +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } -int16_t flecs_query_next_column( - ecs_table_t *table, - ecs_id_t id, - int32_t column) -{ - if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { - column = column + 1; - } else { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - column = ecs_search_offset(NULL, table, column + 1, id, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - return flecs_ito(int16_t, column); -} -void flecs_query_it_set_tr( - ecs_iter_t *it, - int32_t field_index, - const ecs_table_record_t *tr) -{ - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - it->trs[field_index] = tr; -} +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } -ecs_id_t flecs_query_it_set_id( - ecs_iter_t *it, - ecs_table_t *table, - int32_t field_index, - int32_t column) +int flecs_expr_unary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - return it->ids[field_index] = table->type.array[column]; -} + ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; -void flecs_query_set_match( - const ecs_query_op_t *op, - ecs_table_t *table, - int32_t column, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t field_index = op->field_index; - if (field_index == -1) { - return; + if (node->operator != EcsTokNot) { + flecs_expr_visit_error(script, node, + "operator invalid for unary expression"); + goto error; } - ecs_iter_t *it = ctx->it; - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = &table->_->records[column]; - flecs_query_it_set_tr(it, field_index, tr); - ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); - flecs_query_set_vars(op, matched, ctx); -} + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } -void flecs_query_set_trav_match( - const ecs_query_op_t *op, - const ecs_table_record_t *tr, - ecs_entity_t trav, - ecs_entity_t second, - const ecs_query_run_ctx_t *ctx) -{ - int32_t field_index = op->field_index; - if (field_index == -1) { - return; + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; } - ecs_iter_t *it = ctx->it; - ecs_id_t matched = ecs_pair(trav, second); - it->ids[op->field_index] = matched; - flecs_query_it_set_tr(it, op->field_index, tr); - flecs_query_set_vars(op, matched, ctx); -} + if (node->expr->type != ecs_id(ecs_bool_t)) { + char *type_str = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "! operator cannot be applied to value of type '%s' (must be bool)"); + ecs_os_free(type_str); + goto error; + } -bool flecs_query_table_filter( - ecs_table_t *table, - ecs_query_lbl_t other, - ecs_flags32_t filter_mask) -{ - uint32_t filter = flecs_ito(uint32_t, other); - return (table->flags & filter_mask & filter) != 0; -} + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); -/** - * @file query/engine/trav_cache.c - * @brief Cache that stores the result of graph traversal. - */ + *node_ptr = (ecs_expr_node_t*)result; + return 0; +error: + return -1; +} -static -void flecs_query_build_down_cache( - ecs_world_t *world, - ecs_allocator_t *a, - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); - if (!idr) { - return; + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left)) { + goto error; } - ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - elem->entity = entity; - elem->idr = idr; + if (flecs_script_expr_visit_fold(script, &node->right)) { + goto error; + } - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = tr->hdr.table; - if (!table->_->traversable_count) { - continue; - } + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } - int32_t i, count = ecs_table_count(table); - const ecs_entity_t *entities = ecs_table_entities(table); - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - if (r->row & EcsEntityIsTraversable) { - flecs_query_build_down_cache( - world, a, ctx, cache, trav, entities[i]); - } - } - } + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + + switch(node->operator) { + case EcsTokAdd: + ECS_BINARY_OP(node->left, node->right, result, +); + break; + case EcsTokSub: + ECS_BINARY_OP(node->left, node->right, result, -); + break; + case EcsTokMul: + ECS_BINARY_OP(node->left, node->right, result, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(node->left, node->right, result, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(node->left, node->right, result, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(node->left, node->right, result, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(node->left, node->right, result, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); + break; + case EcsTokShiftLeft: + ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + break; + default: + flecs_expr_visit_error(script, node->left, "unsupported operator"); + goto error; } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; } -static -void flecs_query_build_up_cache( - ecs_world_t *world, - ecs_allocator_t *a, - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_table_t *table, - const ecs_table_record_t *tr, - int32_t root_column) +int flecs_expr_cast_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_id_t *ids = table->type.array; - int32_t i = tr->index, end = i + tr->count; - bool is_root = root_column == -1; - - for (; i < end; i ++) { - ecs_entity_t second = ecs_pair_second(world, ids[i]); - if (is_root) { - root_column = i; - } + ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - - el->entity = second; - el->tr = &table->_->records[i]; - el->idr = NULL; + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } - ecs_record_t *r = flecs_entities_get_any(world, second); - if (r->table) { - ecs_table_record_t *r_tr = flecs_id_record_get_table( - cache->idr, r->table); - if (!r_tr) { - return; - } - flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, - r_tr, root_column); - } + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; } -} -void flecs_query_trav_cache_fini( - ecs_allocator_t *a, - ecs_trav_cache_t *cache) -{ - ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); -} + ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + + /* Reuse existing node to hold casted value */ + *node_ptr = (ecs_expr_node_t*)expr; + + ecs_entity_t dst_type = node->node.type; + ecs_entity_t src_type = expr->node.type; -void flecs_query_get_trav_down_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) -{ - if (cache->id != ecs_pair(trav, entity) || cache->up) { - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); - cache->id = ecs_pair(trav, entity); - cache->up = false; + if (dst_type == src_type) { + /* No cast necessary if types are equal */ + return 0; } + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + ecs_value_t value = { + .type = src_type, + .ptr = expr->ptr + }; + + ecs_meta_set_value(&cur, &value); + + expr->node.type = dst_type; + + return 0; +error: + return -1; } -void flecs_query_get_trav_up_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_table_t *table) +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; - ecs_id_record_t *idr = cache->idr; - if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { - idr = cache->idr = flecs_id_record_get(world, - ecs_pair(trav, EcsWildcard)); - if (!idr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_fold(script, node_ptr)) { + goto error; } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + if (flecs_expr_cast_visit_fold(script, node_ptr)) { + goto error; + } + break; } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; - } - - ecs_id_t id = table->type.array[tr->index]; - - if (cache->id != id || !cache->up) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); - cache->id = id; - cache->up = true; - } + return 0; +error: + return -1; } +#endif + /** - * @file query/engine/trav_down_cache.c - * @brief Compile query term. + * @file addons/script/expr_to_str.c + * @brief Script expression AST to string visitor. */ -static -void flecs_trav_entity_down_isa( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_entity_t entity, - ecs_id_record_t *idr_with, - bool self, - bool empty); +#ifdef FLECS_SCRIPT -static -ecs_trav_down_t* flecs_trav_entity_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, - bool self, - bool empty); +typedef struct ecs_expr_str_visitor_t { + const ecs_world_t *world; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_expr_str_visitor_t; -static -ecs_trav_down_t* flecs_trav_down_ensure( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_entity_t entity) +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node); + +int flecs_expr_value_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_val_t *node) { - ecs_trav_down_t **trav = ecs_map_ensure_ref( - &cache->src, ecs_trav_down_t, entity); - if (!trav[0]) { - trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); - ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); + return ecs_ptr_to_str_buf( + v->world, node->node.type, node->ptr, v->buf); +} + +int flecs_expr_unary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_unary_t *node) +{ + switch(node->operator) { + case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; + default: + ecs_err("invalid operator for unary expression"); + return -1; + }; + + if (flecs_expr_node_to_str(v, node->expr)) { + goto error; } - return trav[0]; + return 0; +error: + return -1; } -static -ecs_trav_down_t* flecs_trav_table_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - const ecs_table_t *table, - ecs_id_record_t *idr_with, - bool self, - bool empty) +int flecs_expr_binary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_binary_t *node) { - ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(v->buf, "("); - if (!table->_->traversable_count) { - return dst; + if (flecs_expr_node_to_str(v, node->left)) { + goto error; } - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(v->buf, " "); - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_entity_t entity = entities[i]; - ecs_record_t *record = flecs_entities_get(world, entity); - if (!record) { - continue; - } + switch(node->operator) { + case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; + case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; + case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; + case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; + case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; + case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; + case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; + case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; + case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; + case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; + case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; + case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; + case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; + case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; + case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; + case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; + case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; + default: + ecs_err("invalid operator for binary expression"); + return -1; + }; - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, entity)); - if (!idr_trav) { - continue; - } + ecs_strbuf_appendlit(v->buf, " "); - flecs_trav_entity_down(world, a, cache, dst, - trav, idr_trav, idr_with, self, empty); - } + if (flecs_expr_node_to_str(v, node->right)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_identifier_t *node) +{ + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_variable_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_variable_t *node) +{ + ecs_strbuf_appendlit(v->buf, "$"); + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_member_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_member_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; } - return dst; + ecs_strbuf_appendlit(v->buf, "."); + ecs_strbuf_appendstr(v->buf, node->member_name); + return 0; } -static -void flecs_trav_entity_down_isa( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_entity_t entity, - ecs_id_record_t *idr_with, - bool self, - bool empty) +int flecs_expr_element_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_element_t *node) { - if (trav == EcsIsA || !world->idr_isa_wildcard) { - return; + if (flecs_expr_node_to_str(v, node->left)) { + return -1; } - ecs_id_record_t *idr_isa = flecs_id_record_get( - world, ecs_pair(EcsIsA, entity)); - if (!idr_isa) { - return; + ecs_strbuf_appendlit(v->buf, "["); + if (flecs_expr_node_to_str(v, node->index)) { + return -1; } + ecs_strbuf_appendlit(v->buf, "]"); + return 0; +} - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr_isa->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (!table->_->traversable_count) { - continue; - } - - if (ecs_table_has_id(world, table, idr_with->id)) { - /* Table owns component */ - continue; - } +int flecs_expr_cast_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_cast_t *node) +{ + return flecs_expr_node_to_str(v, node->expr); +} - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_entity_t e = entities[i]; - ecs_record_t *record = flecs_entities_get(world, e); - if (!record) { - continue; - } +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, e)); - if (idr_trav) { - flecs_trav_entity_down(world, a, cache, dst, trav, - idr_trav, idr_with, self, empty); - } + if (node->type) { + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + } - flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, - idr_with, self, empty); - } - } + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + goto error; + } + break; + case EcsExprUnary: + if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + goto error; } + break; } -} -static -ecs_trav_down_t* flecs_trav_entity_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, - bool self, - bool empty) -{ - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } - int32_t first = ecs_vec_count(&dst->elems); + return 0; +error: + return -1; +} - ecs_table_cache_iter_t it; - bool result; - if (empty) { - result = flecs_table_cache_all_iter(&idr_trav->cache, &it); - } else { - result = flecs_table_cache_iter(&idr_trav->cache, &it); +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + if (flecs_expr_node_to_str(&v, expr)) { + ecs_strbuf_reset(&buf); + return NULL; } - if (result) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = tr->hdr.table; - bool leaf = false; + return ecs_strbuf_get(&buf); +} - if (flecs_id_record_get_table(idr_with, table) != NULL) { - if (self) { - continue; - } - leaf = true; - } +#endif - /* If record is not the first instance of (trav, *), don't add it - * to the cache. */ - int32_t index = tr->index; - if (index) { - ecs_id_t id = table->type.array[index - 1]; - if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { - int32_t col = ecs_search_relation(world, table, 0, - idr_with->id, trav, EcsUp, NULL, NULL, &tr); - ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - if (col != index) { - /* First relationship through which the id is - * reachable is not the current one, so skip. */ - continue; - } - } - } - ecs_trav_down_elem_t *elem = ecs_vec_append_t( - a, &dst->elems, ecs_trav_down_elem_t); - elem->table = table; - elem->leaf = leaf; - } +#ifdef FLECS_SCRIPT + +static +bool flecs_expr_operator_is_equality( + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return true; + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } +error: + return false; +} + +static +bool flecs_expr_is_type_number( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} - /* Breadth first walk */ - int32_t t, last = ecs_vec_count(&dst->elems); - for (t = first; t < last; t ++) { - ecs_trav_down_elem_t *elem = ecs_vec_get_t( - &dst->elems, ecs_trav_down_elem_t, t); - if (!elem->leaf) { - flecs_trav_table_down(world, a, cache, dst, trav, - elem->table, idr_with, self, empty); - } +static +ecs_entity_t flecs_expr_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } - - return dst; +error: + return 0; } -ecs_trav_down_t* flecs_query_get_down_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t e, - ecs_id_record_t *idr_with, - bool self, - bool empty) +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_expr_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) { - ecs_world_t *world = ctx->it->real_world; - ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); - cache->dir = EcsTravDown; - - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_map_init_if(&cache->src, a); - - ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); - if (result->ready) { - return result; + if (type == ecs_id(ecs_u64_t)) { + return promote_to; } - - ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); - if (!idr_trav) { - if (trav != EcsIsA) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); - } - result->ready = true; - return result; + if (promote_to == ecs_id(ecs_u64_t)) { + return type; } - - ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); - - /* Cover IsA -> trav paths. If a parent inherits a component, then children - * of that parent should find the component through up traversal. */ - if (idr_with->flags & EcsIdOnInstantiateInherit) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); + if (type == ecs_id(ecs_f64_t)) { + return type; } - - flecs_trav_entity_down( - world, a, cache, result, trav, idr_trav, idr_with, self, empty); - result->ready = true; - - return result; + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); } -void flecs_query_down_cache_fini( - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache) +static +bool flecs_expr_oper_valid_for_type( + ecs_entity_t type, + ecs_script_token_kind_t op) { - ecs_map_iter_t it = ecs_map_iter(&cache->src); - while (ecs_map_next(&it)) { - ecs_trav_down_t *t = ecs_map_ptr(&it); - ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); + switch(op) { + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + return flecs_expr_is_type_number(type); + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + return type == ecs_id(ecs_u64_t) || + type == ecs_id(ecs_u32_t) || + type == ecs_id(ecs_u16_t) || + type == ecs_id(ecs_u8_t); + case EcsTokEq: + case EcsTokNeq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return flecs_expr_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - ecs_map_fini(&cache->src); } -/** - * @file query/engine/trav_up_cache.c - * @brief Compile query term. - */ - - static -ecs_trav_up_t* flecs_trav_up_ensure( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - uint64_t table_id) +int flecs_expr_type_for_oper( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) { - ecs_trav_up_t **trav = ecs_map_ensure_ref( - &cache->src, ecs_trav_up_t, table_id); - if (!trav[0]) { - trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); + ecs_world_t *world = script->world; + ecs_expr_node_t *left = node->left, *right = node->right; + + switch(node->operator) { + case EcsTokDiv: + /* Result type of a division is always a float */ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + return 0; + case EcsTokAnd: + case EcsTokOr: + /* Result type of a condition operator is always a bool */ + *operand_type = ecs_id(ecs_bool_t); + *result_type = ecs_id(ecs_bool_t); + return 0; + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + *result_type = ecs_id(ecs_bool_t); + break; + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } - return trav[0]; -} + const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + char *lname = ecs_get_path(world, left->type); + char *rname = ecs_get_path(world, right->type); + flecs_expr_visit_error(script, node, + "invalid non-primitive type in binary expression (%s, %s)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } -static -int32_t flecs_trav_type_search( - ecs_trav_up_t *up, - const ecs_table_t *table, - ecs_id_record_t *idr_with, - ecs_type_t *type) -{ - ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); - if (tr) { - up->id = type->array[tr->index]; - up->tr = tr; - return tr->index; + ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + if (ltype == rtype) { + *operand_type = ltype; + goto done; } - return -1; -} + if (flecs_expr_operator_is_equality(node->operator)) { + char *lname = ecs_id_str(world, ltype); + char *rname = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "mismatching types in equality expression (%s vs %s)", + lname, rname); + ecs_os_free(rname); + ecs_os_free(lname); + return 0; + } -static -int32_t flecs_trav_type_offset_search( - ecs_trav_up_t *up, - const ecs_table_t *table, - int32_t offset, - ecs_id_t with, - ecs_type_t *type) -{ - ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); + if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { + flecs_expr_visit_error(script, node, + "incompatible types in binary expression"); + return 0; + } - while (offset < type->count) { - ecs_id_t type_id = type->array[offset ++]; - if (ecs_id_match(type_id, with)) { - up->id = type_id; - up->tr = &table->_->records[offset - 1]; - return offset - 1; - } + *operand_type = flecs_expr_promote_type(ltype, rtype); + +done: + if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + /* Result of subtracting two unsigned ints can be negative */ + *operand_type = ecs_id(ecs_i64_t); + } + + if (!*result_type) { + *result_type = *operand_type; } + return 0; +error: return -1; } static -ecs_trav_up_t* flecs_trav_table_up( - const ecs_query_run_ctx_t *ctx, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - const ecs_world_t *world, - ecs_entity_t src, - ecs_id_t with, - ecs_id_t rel, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) +int flecs_expr_unary_visit_type( + ecs_script_t *script, + ecs_expr_unary_t *node) { - ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); - if (up->ready) { - return up; + if (flecs_script_expr_visit_type(script, node->expr)) { + goto error; } - ecs_record_t *src_record = flecs_entities_get_any(world, src); - ecs_table_t *table = src_record->table; - if (!table) { - goto not_found; - } + /* The only supported unary expression is not (!) which returns a bool */ + node->node.type = ecs_id(ecs_bool_t); - ecs_type_t type = table->type; - if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { - up->src = src; - goto found; + if (node->expr->type != ecs_id(ecs_bool_t)) { + node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); } - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && rel) { - bool is_a = idr_trav == world->idr_isa_wildcard; - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - goto not_found; - } - - if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { - goto not_found; - } - } - - ecs_trav_up_t up_pair = {0}; - int32_t r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); - - while (r_column != -1) { - ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, - world, tgt, with, rel, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } - - r_column = flecs_trav_type_offset_search( - &up_pair, table, r_column + 1, rel, &type); - } - - if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { - idr_trav = world->idr_isa_wildcard; - r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); + return 0; +error: + return -1; +} - while (r_column != -1) { - ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); +static +int flecs_expr_binary_visit_type( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + /* Operands must be of this type or casted to it */ + ecs_entity_t operand_type = 0; - ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, - world, tgt, with, rel, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } + /* Resulting type of binary expression */ + ecs_entity_t result_type = 0; - r_column = flecs_trav_type_offset_search( - &up_pair, table, r_column + 1, rel, &type); - } - } + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; } -not_found: - up->tr = NULL; -found: - up->ready = true; - return up; -} - -ecs_trav_up_t* flecs_query_get_up_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_table_t *table, - ecs_id_t with, - ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) -{ - if (cache->with && cache->with != with) { - flecs_query_up_cache_fini(cache); + if (flecs_script_expr_visit_type(script, node->right)) { + goto error; } - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_map_init_if(&cache->src, a); + if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + goto error; + } - ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); - cache->dir = EcsTravUp; - cache->with = with; + if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + flecs_expr_visit_error(script, node, "invalid operator for type"); + goto error; + } - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); - if (!tr) { - return NULL; /* Table doesn't have the relationship */ + if (operand_type != node->left->type) { + node->left = (ecs_expr_node_t*)flecs_expr_cast( + script, node->left, operand_type); } - int32_t i = tr->index, end = i + tr->count; - for (; i < end; i ++) { - ecs_id_t id = table->type.array[i]; - ecs_entity_t tgt = ECS_PAIR_SECOND(id); - ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, - with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - if (result->src != 0) { - return result; - } + if (operand_type != node->right->type) { + node->right = (ecs_expr_node_t*)flecs_expr_cast( + script, node->right, operand_type); } - return NULL; -} + node->node.type = result_type; -void flecs_query_up_cache_fini( - ecs_trav_up_cache_t *cache) -{ - ecs_map_fini(&cache->src); + return 0; +error: + return -1; } -/** - * @file query/engine/trivial_iter.c - * @brief Iterator for trivial queries. - */ - - static -bool flecs_query_trivial_search_init( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - const ecs_query_t *query, - bool redo, - ecs_flags64_t term_set) +int flecs_expr_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node) { - if (!redo) { - /* Find first trivial term*/ - int32_t t = 0; - if (term_set) { - for (; t < query->term_count; t ++) { - if (term_set & (1llu << t)) { - break; - } - } - } - - ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); - op_ctx->start_from = t; - - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); - if (!idr) { - return false; - } - - if (query->flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } - - /* Find next term to evaluate once */ - - for (t = t + 1; t < query->term_count; t ++) { - if (term_set & (1llu << t)) { - break; - } - } - - op_ctx->first_to_eval = t; + node->node.type = ecs_id(ecs_entity_t); + node->id = ecs_lookup(script->world, node->value); + if (!node->id) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; } - return true; + return 0; +error: + return -1; } -bool flecs_query_trivial_search( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - bool redo, - ecs_flags64_t term_set) -{ - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - const ecs_term_t *terms = q->terms; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = query->pub.term_count; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { - return false; - } - - do { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } +static +int flecs_expr_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + return 0; +} - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - continue; - } +static +int flecs_expr_member_visit_type( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } - for (t = op_ctx->first_to_eval; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); - if (!idr) { - break; - } + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); - if (!tr_with) { - break; - } + if (type->kind != EcsStructType) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on non-struct type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } - it->trs[term->field_index] = tr_with; - } + ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); + ecs_meta_push(&cur); /* { */ + int prev_log = ecs_log_set_level(-4); + if (ecs_meta_dotmember(&cur, node->member_name)) { + ecs_log_set_level(prev_log); + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "unresolved member '%s' for type '%s'", + node->member_name, type_str); + ecs_os_free(type_str); + goto error; + } + ecs_log_set_level(prev_log); - if (t == term_count) { - ctx->vars[0].range.table = table; - ctx->vars[0].range.count = 0; - ctx->vars[0].range.offset = 0; - it->trs[op_ctx->start_from] = tr; - break; - } - } while (true); + node->node.type = ecs_meta_get_type(&cur); + ecs_meta_pop(&cur); /* } */ - return true; + return 0; +error: + return -1; } -bool flecs_query_is_trivial_search( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - bool redo) +static +int flecs_expr_element_visit_type( + ecs_script_t *script, + ecs_expr_element_t *node) { - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - const ecs_id_t *ids = q->ids; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = query->pub.term_count; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { - return false; + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; } -next: - { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } - - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - goto next; - } + if (flecs_script_expr_visit_type(script, node->index)) { + goto error; + } - for (t = 1; t < term_count; t ++) { - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); - if (!idr) { - return false; - } + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot use [] on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); - if (!tr_with) { - goto next; - } + bool is_entity_type = false; - it->trs[t] = tr_with; + if (type->kind == EcsPrimitiveType) { + const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); + if (ptype->kind == EcsEntity) { + is_entity_type = true; } + } - it->table = table; - it->count = ecs_table_count(table); - it->entities = ecs_table_entities(table); - it->trs[0] = tr; + if (is_entity_type) { + if (node->index->kind == EcsExprIdentifier) { + node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + } else { + flecs_expr_visit_error(script, node, + "invalid component expression"); + goto error; + } + } else if (type->kind == EcsArrayType) { + const EcsArray *type_array = ecs_get(world, left_type, EcsArray); + ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_array->type; + } else if (type->kind == EcsVectorType) { + const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); + ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_vector->type; + } else { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); + goto error; } - return true; + return 0; +error: + return -1; } -bool flecs_query_trivial_test( - const ecs_query_run_ctx_t *ctx, - bool redo, - ecs_flags64_t term_set) +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node) { - if (redo) { - return false; - } else { - const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; - const ecs_term_t *terms = q->terms; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = impl->pub.term_count; - - ecs_table_t *table = it->table; - ecs_assert(table != NULL, ECS_INVALID_OPERATION, - "the variable set on the iterator is missing a table"); - - for (t = 0; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } - - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); - if (!idr) { - return false; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - it->trs[term->field_index] = tr; + switch(node->kind) { + case EcsExprValue: + /* Value types are assigned by the AST */ + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + goto error; } - - it->entities = ecs_table_entities(table); - if (it->entities) { - it->entities = &it->entities[it->offset]; + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + goto error; } - - return true; + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + break; } + + return 0; +error: + return -1; } +#endif + diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr/ast.c similarity index 99% rename from src/addons/script/expr_ast.c rename to src/addons/script/expr/ast.c index 51680cbd6c..aba0b1b389 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr/ast.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" #define flecs_expr_ast_new(parser, T, kind)\ (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr/ast.h similarity index 100% rename from src/addons/script/expr_ast.h rename to src/addons/script/expr/ast.h diff --git a/src/addons/script/new_expr.c b/src/addons/script/expr/parser.c similarity index 98% rename from src/addons/script/new_expr.c rename to src/addons/script/expr/parser.c index 571080828f..e1bbb4525f 100644 --- a/src/addons/script/new_expr.c +++ b/src/addons/script/expr/parser.c @@ -1,13 +1,13 @@ /** - * @file addons/script/expr.c + * @file addons/script/expr/parser.c * @brief Script expression parser. */ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" -#include "parser.h" +#include "../script.h" +#include "../parser.h" /* From https://en.cppreference.com/w/c/language/operator_precedence */ diff --git a/src/addons/script/expr_visit.h b/src/addons/script/expr/visit.h similarity index 100% rename from src/addons/script/expr_visit.h rename to src/addons/script/expr/visit.h diff --git a/src/addons/script/expr_visit_fold.c b/src/addons/script/expr/visit_fold.c similarity index 97% rename from src/addons/script/expr_visit_fold.c rename to src/addons/script/expr/visit_fold.c index f98b0be7de..9f416455bc 100644 --- a/src/addons/script/expr_visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" #define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) @@ -103,7 +103,7 @@ int flecs_expr_unary_visit_fold( return 0; } - if (node->node.type != ecs_id(ecs_bool_t)) { + if (node->expr->type != ecs_id(ecs_bool_t)) { char *type_str = ecs_get_path(script->world, node->node.type); flecs_expr_visit_error(script, node, "! operator cannot be applied to value of type '%s' (must be bool)"); @@ -117,7 +117,9 @@ int flecs_expr_unary_visit_fold( result->node.pos = node->node.pos; result->node.type = ecs_id(ecs_bool_t); result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); + *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); + + *node_ptr = (ecs_expr_node_t*)result; return 0; error: diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr/visit_to_str.c similarity index 99% rename from src/addons/script/expr_to_str.c rename to src/addons/script/expr/visit_to_str.c index a91fa616a6..1f16c79f47 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" typedef struct ecs_expr_str_visitor_t { const ecs_world_t *world; diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr/visit_type.c similarity index 98% rename from src/addons/script/expr_visit_type.c rename to src/addons/script/expr/visit_type.c index cf610593f6..4352915a7c 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" static bool flecs_expr_operator_is_equality( @@ -249,6 +249,10 @@ int flecs_expr_unary_visit_type( /* The only supported unary expression is not (!) which returns a bool */ node->node.type = ecs_id(ecs_bool_t); + if (node->expr->type != ecs_id(ecs_bool_t)) { + node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); + } + return 0; error: return -1; diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 8053440020..a9b6416e9b 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -45,8 +45,8 @@ struct ecs_script_parser_t { }; #include "ast.h" -#include "expr_ast.h" -#include "expr_visit.h" +#include "expr/ast.h" +#include "expr/visit.h" #include "visit.h" #include "visit_eval.h" From d09a17698be9445335ef8c60a0a48c5a2fc60a68 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 25 Nov 2024 12:23:28 -0800 Subject: [PATCH 08/83] Fix folding issues --- src/addons/script/expr/parser.c | 71 ++++++++++++++++++++++++++- src/addons/script/expr/visit_fold.c | 10 +++- src/addons/script/expr/visit_to_str.c | 23 +-------- src/addons/script/expr/visit_type.c | 32 +++++++++--- src/addons/script/tokenizer.c | 54 ++++++++++++++++++++ src/addons/script/tokenizer.h | 3 ++ 6 files changed, 159 insertions(+), 34 deletions(-) diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index e1bbb4525f..fd38bad883 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -284,12 +284,79 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } - flecs_script_expr_visit_type(script, out); - flecs_script_expr_visit_fold(script, &out); + if (flecs_script_expr_visit_type(script, out)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &out)) { + goto error; + } return out; error: return NULL; } +FLECS_API +const char* _ecs_script_expr_run( + ecs_world_t *world, + const char *expr, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_script_t *script = flecs_script_new(world); + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + if (flecs_script_expr_visit_type(script, out)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &out)) { + goto error; + } + + if (!value->type) { + value->type = out->type; + } + + if (value->type && !value->ptr) { + value->ptr = ecs_value_new(world, value->type); + } + + ecs_assert(value->type != 0 && value->ptr != NULL, ECS_INVALID_OPERATION, + "failed to allocate storage for expression result"); + + if (out->kind == EcsExprValue) { + if (value->type == out->type) { + ecs_value_copy(world, value->type, value->ptr, + ((ecs_expr_val_t*)out)->ptr); + } + } else { + ecs_abort(ECS_UNSUPPORTED, "can't evaluate dynamic expressions yet"); + } + + return result; +error: + return NULL; +} + #endif diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 9f416455bc..d3553f722a 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -192,11 +192,17 @@ int flecs_expr_binary_visit_fold( case EcsTokOr: ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); break; + case EcsTokBitwiseAnd: + ECS_BINARY_INT_OP(node->left, node->right, result, &); + break; + case EcsTokBitwiseOr: + ECS_BINARY_INT_OP(node->left, node->right, result, |); + break; case EcsTokShiftLeft: - ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + ECS_BINARY_INT_OP(node->left, node->right, result, <<); break; case EcsTokShiftRight: - ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + ECS_BINARY_INT_OP(node->left, node->right, result, >>); break; default: flecs_expr_visit_error(script, node->left, "unsupported operator"); diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 1f16c79f47..5d54b5a05e 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -59,28 +59,7 @@ int flecs_expr_binary_to_str( ecs_strbuf_appendlit(v->buf, " "); - switch(node->operator) { - case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; - case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; - case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; - case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; - case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; - case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; - case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; - case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; - case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; - case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; - case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; - case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; - case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; - case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; - case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; - case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; - case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; - default: - ecs_err("invalid operator for binary expression"); - return -1; - }; + ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); ecs_strbuf_appendlit(v->buf, " "); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 4352915a7c..38907e7d30 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -38,6 +38,21 @@ bool flecs_expr_operator_is_equality( return false; } +static +bool flecs_expr_is_type_integer( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return false; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + static bool flecs_expr_is_type_number( ecs_entity_t type) @@ -118,10 +133,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokBitwiseOr: case EcsTokShiftLeft: case EcsTokShiftRight: - return type == ecs_id(ecs_u64_t) || - type == ecs_id(ecs_u32_t) || - type == ecs_id(ecs_u16_t) || - type == ecs_id(ecs_u8_t); + return flecs_expr_is_type_integer(type); case EcsTokEq: case EcsTokNeq: case EcsTokAnd: @@ -211,13 +223,13 @@ int flecs_expr_type_for_oper( lname, rname); ecs_os_free(rname); ecs_os_free(lname); - return 0; + goto error; } if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { flecs_expr_visit_error(script, node, "incompatible types in binary expression"); - return 0; + goto error; } *operand_type = flecs_expr_promote_type(ltype, rtype); @@ -250,7 +262,8 @@ int flecs_expr_unary_visit_type( node->node.type = ecs_id(ecs_bool_t); if (node->expr->type != ecs_id(ecs_bool_t)) { - node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); + node->expr = (ecs_expr_node_t*)flecs_expr_cast( + script, node->expr, ecs_id(ecs_bool_t)); } return 0; @@ -282,7 +295,10 @@ int flecs_expr_binary_visit_type( } if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { - flecs_expr_visit_error(script, node, "invalid operator for type"); + char *type_str = ecs_get_path(script->world, result_type); + flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", + flecs_script_token_str(node->operator), type_str); + ecs_os_free(type_str); goto error; } diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 7627620b87..99d8770002 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -88,6 +88,60 @@ const char* flecs_script_token_kind_str( } } +const char* flecs_script_token_str( + ecs_script_token_kind_t kind) +{ + switch(kind) { + case EcsTokUnknown: return "unknown token"; + case EcsTokColon: return ":"; + case EcsTokScopeOpen: return "{"; + case EcsTokScopeClose: return "}"; + case EcsTokParenOpen: return "("; + case EcsTokParenClose: return ")"; + case EcsTokBracketOpen: return "["; + case EcsTokBracketClose: return "]"; + case EcsTokAnnotation: return "@"; + case EcsTokComma: return ","; + case EcsTokSemiColon: return ";"; + case EcsTokAssign: return "="; + case EcsTokAdd: return "+"; + case EcsTokSub: return "-"; + case EcsTokMul: return "*"; + case EcsTokDiv: return "/"; + case EcsTokMod: return "%%"; + case EcsTokBitwiseOr: return "|"; + case EcsTokBitwiseAnd: return "&"; + case EcsTokNot: return "!"; + case EcsTokOptional: return "?"; + case EcsTokEq: return "=="; + case EcsTokNeq: return "!="; + case EcsTokGt: return ">"; + case EcsTokGtEq: return ">="; + case EcsTokLt: return "<"; + case EcsTokLtEq: return "<="; + case EcsTokAnd: return "&&"; + case EcsTokOr: return "||"; + case EcsTokMatch: return "~="; + case EcsTokShiftLeft: return "<<"; + case EcsTokShiftRight: return ">>"; + case EcsTokKeywordWith: return "with"; + case EcsTokKeywordUsing: return "using"; + case EcsTokKeywordTemplate: return "template"; + case EcsTokKeywordProp: return "prop"; + case EcsTokKeywordConst: return "const"; + case EcsTokKeywordIf: return "if"; + case EcsTokKeywordElse: return "else"; + case EcsTokKeywordModule: return "module"; + case EcsTokIdentifier: return "identifier"; + case EcsTokString: return "string"; + case EcsTokNumber: return "number"; + case EcsTokNewline: return "newline"; + case EcsTokEnd: return "end of script"; + default: + return ""; + } +} + const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos) diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index 960f9cd6a9..11f801bf67 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -86,6 +86,9 @@ const char* flecs_script_until( const char* flecs_script_token_kind_str( ecs_script_token_kind_t kind); +const char* flecs_script_token_str( + ecs_script_token_kind_t kind); + const char* flecs_script_token( ecs_script_parser_t *parser, const char *ptr, From eb884409ebee96c1f2bffa7cd857e1204174900b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 25 Nov 2024 18:27:32 -0800 Subject: [PATCH 09/83] Fix issues with operator precedence --- distr/flecs.c | 418 ++++++++++++++++++-------- src/addons/script/expr.c | 2 +- src/addons/script/expr/parser.c | 185 +++++++----- src/addons/script/expr/visit_to_str.c | 23 +- src/addons/script/tokenizer.c | 19 +- test/script/src/Expr.c | 88 +++--- 6 files changed, 459 insertions(+), 276 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 584c072168..8f0fa8db55 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4616,6 +4616,9 @@ const char* flecs_script_until( const char* flecs_script_token_kind_str( ecs_script_token_kind_t kind); +const char* flecs_script_token_str( + ecs_script_token_kind_t kind); + const char* flecs_script_token( ecs_script_parser_t *parser, const char *ptr, @@ -56422,7 +56425,7 @@ const char* flecs_script_expr_run( return NULL; } -const char* ecs_script_expr_run( +const char* _ecs_script_expr_run( ecs_world_t *world, const char *ptr, ecs_value_t *value, @@ -59782,6 +59785,60 @@ const char* flecs_script_token_kind_str( } } +const char* flecs_script_token_str( + ecs_script_token_kind_t kind) +{ + switch(kind) { + case EcsTokUnknown: return "unknown token"; + case EcsTokColon: return ":"; + case EcsTokScopeOpen: return "{"; + case EcsTokScopeClose: return "}"; + case EcsTokParenOpen: return "("; + case EcsTokParenClose: return ")"; + case EcsTokBracketOpen: return "["; + case EcsTokBracketClose: return "]"; + case EcsTokAnnotation: return "@"; + case EcsTokComma: return ","; + case EcsTokSemiColon: return ";"; + case EcsTokAssign: return "="; + case EcsTokAdd: return "+"; + case EcsTokSub: return "-"; + case EcsTokMul: return "*"; + case EcsTokDiv: return "/"; + case EcsTokMod: return "%%"; + case EcsTokBitwiseOr: return "|"; + case EcsTokBitwiseAnd: return "&"; + case EcsTokNot: return "!"; + case EcsTokOptional: return "?"; + case EcsTokEq: return "=="; + case EcsTokNeq: return "!="; + case EcsTokGt: return ">"; + case EcsTokGtEq: return ">="; + case EcsTokLt: return "<"; + case EcsTokLtEq: return "<="; + case EcsTokAnd: return "&&"; + case EcsTokOr: return "||"; + case EcsTokMatch: return "~="; + case EcsTokShiftLeft: return "<<"; + case EcsTokShiftRight: return ">>"; + case EcsTokKeywordWith: return "with"; + case EcsTokKeywordUsing: return "using"; + case EcsTokKeywordTemplate: return "template"; + case EcsTokKeywordProp: return "prop"; + case EcsTokKeywordConst: return "const"; + case EcsTokKeywordIf: return "if"; + case EcsTokKeywordElse: return "else"; + case EcsTokKeywordModule: return "module"; + case EcsTokIdentifier: return "identifier"; + case EcsTokString: return "string"; + case EcsTokNumber: return "number"; + case EcsTokNewline: return "newline"; + case EcsTokEnd: return "end of script"; + default: + return ""; + } +} + const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos) @@ -59911,9 +59968,9 @@ const char* flecs_script_identifier( // Number token static static bool flecs_script_is_number( - char c) + const char *c) { - return isdigit(c) || (c == '-'); + return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); } static @@ -59928,8 +59985,15 @@ const char* flecs_script_number( bool dot_parsed = false; bool e_parsed = false; - ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; + + if (pos[0] == '-') { + outpos[0] = pos[0]; + pos ++; + outpos ++; + } + do { char c = pos[0]; bool valid_number = false; @@ -60208,6 +60272,9 @@ const char* flecs_script_token( } return pos; + } else if (flecs_script_is_number(pos)) { + return flecs_script_number(parser, pos, out); + Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) @@ -60256,9 +60323,6 @@ const char* flecs_script_token( } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); - } else if (flecs_script_is_number(pos[0])) { - return flecs_script_number(parser, pos, out); - } else if (flecs_script_is_identifier(pos[0])) { return flecs_script_identifier(parser, pos, out); } @@ -74853,7 +74917,7 @@ bool flecs_has_precedence( if (!flecs_expr_precedence[first]) { return false; } - return flecs_expr_precedence[first] < flecs_expr_precedence[second]; + return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } static @@ -74867,98 +74931,98 @@ const char* flecs_script_parse_rhs( { const char *last_pos = pos; - TokenFramePush(); - - LookAhead( - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBracketOpen: - case EcsTokMember: - { - ecs_script_token_kind_t oper = lookahead_token.kind; - - /* Only consume more tokens if operator has precedence */ - if (flecs_has_precedence(left_oper, oper)) { - break; - } + do { + TokenFramePush(); - /* Consume lookahead token */ - pos = lookahead; + last_pos = pos; - switch(oper) { - case EcsTokBracketOpen: { - ecs_expr_element_t *result = flecs_expr_element(parser); - result->left = *out; + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; - pos = flecs_script_parse_lhs( - parser, pos, tokenizer, 0, &result->index); - if (!pos) { - goto error; + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; } - Parse_1(']', { - *out = (ecs_expr_node_t*)result; - break; - }); + /* Consume lookahead token */ + pos = lookahead; - break; - } + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; + + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } - case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); - Parse_1(EcsTokIdentifier, { - result->member_name = Token(1); - *out = (ecs_expr_node_t*)result; break; - }); + } - break; - } + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; - default: { - ecs_expr_binary_t *result = flecs_expr_binary(parser); - result->left = *out; - result->operator = oper; + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); - pos = flecs_script_parse_lhs(parser, pos, tokenizer, - result->operator, &result->right); - if (!pos) { - goto error; + break; } - *out = (ecs_expr_node_t*)result; - break; - } - }; - /* Ensures lookahead tokens in token buffer don't get overwritten */ - parser->token_keep = parser->token_cur; + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; - break; - } - ) + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; + } + }; - if (pos[0] && (pos != last_pos)) { - pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); - } + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; - TokenFramePop(); + break; + } + ) + + TokenFramePop(); + } while (pos != last_pos); return pos; error: @@ -75022,10 +75086,19 @@ const char* flecs_script_parse_lhs( *out = (ecs_expr_node_t*)unary; break; } + + case EcsTokSub: { + ecs_expr_binary_t *binary = flecs_expr_binary(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &binary->right); + binary->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); + binary->operator = EcsTokMul; + *out = (ecs_expr_node_t*)binary; + break; + } ) TokenFramePop(); - + /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs( parser, pos, tokenizer, *out, left_oper, out); @@ -75079,14 +75152,99 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } - flecs_script_expr_visit_type(script, out); - flecs_script_expr_visit_fold(script, &out); + if (flecs_script_expr_visit_type(script, out)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &out)) { + goto error; + } return out; error: return NULL; } +FLECS_API +const char* ecs_script_expr_run( + ecs_world_t *world, + const char *expr, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_script_t *script = flecs_script_new(world); + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + if (flecs_script_expr_visit_type(script, out)) { + goto error; + } + + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + if (flecs_script_expr_visit_fold(script, &out)) { + goto error; + } + + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + if (!value->type) { + value->type = out->type; + } + + if (value->type && !value->ptr) { + value->ptr = ecs_value_new(world, value->type); + } + + ecs_assert(value->type != 0 && value->ptr != NULL, ECS_INVALID_OPERATION, + "failed to allocate storage for expression result"); + + if (out->kind == EcsExprValue) { + if (value->type == out->type) { + // Output value is same as expression, copy value + ecs_value_copy(world, value->type, value->ptr, + ((ecs_expr_val_t*)out)->ptr); + } else { + // Cast value to desired output type + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, value->type, value->ptr); + + ecs_value_t expr_result = { + .type = out->type, + .ptr = ((ecs_expr_val_t*)out)->ptr + }; + + if (ecs_meta_set_value(&cur, &expr_result)) { + goto error; + } + } + } else { + ecs_abort(ECS_UNSUPPORTED, "can't evaluate dynamic expressions yet"); + } + + return result; +error: + return NULL; +} + #endif /** @@ -75281,11 +75439,17 @@ int flecs_expr_binary_visit_fold( case EcsTokOr: ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); break; + case EcsTokBitwiseAnd: + ECS_BINARY_INT_OP(node->left, node->right, result, &); + break; + case EcsTokBitwiseOr: + ECS_BINARY_INT_OP(node->left, node->right, result, |); + break; case EcsTokShiftLeft: - ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + ECS_BINARY_INT_OP(node->left, node->right, result, <<); break; case EcsTokShiftRight: - ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + ECS_BINARY_INT_OP(node->left, node->right, result, >>); break; default: flecs_expr_visit_error(script, node->left, "unsupported operator"); @@ -75417,12 +75581,7 @@ int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) { - switch(node->operator) { - case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; - default: - ecs_err("invalid operator for unary expression"); - return -1; - }; + ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); if (flecs_expr_node_to_str(v, node->expr)) { goto error; @@ -75445,28 +75604,7 @@ int flecs_expr_binary_to_str( ecs_strbuf_appendlit(v->buf, " "); - switch(node->operator) { - case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; - case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; - case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; - case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; - case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; - case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; - case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; - case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; - case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; - case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; - case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; - case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; - case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; - case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; - case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; - case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; - case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; - default: - ecs_err("invalid operator for binary expression"); - return -1; - }; + ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); ecs_strbuf_appendlit(v->buf, " "); @@ -75540,11 +75678,11 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } + // if (node->type) { + // ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + // ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + // ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + // } switch(node->kind) { case EcsExprValue: @@ -75591,9 +75729,9 @@ int flecs_expr_node_to_str( break; } - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } + // if (node->type) { + // ecs_strbuf_append(v->buf, ")"); + // } return 0; error: @@ -75655,6 +75793,21 @@ bool flecs_expr_operator_is_equality( return false; } +static +bool flecs_expr_is_type_integer( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return false; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + static bool flecs_expr_is_type_number( ecs_entity_t type) @@ -75735,10 +75888,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokBitwiseOr: case EcsTokShiftLeft: case EcsTokShiftRight: - return type == ecs_id(ecs_u64_t) || - type == ecs_id(ecs_u32_t) || - type == ecs_id(ecs_u16_t) || - type == ecs_id(ecs_u8_t); + return flecs_expr_is_type_integer(type); case EcsTokEq: case EcsTokNeq: case EcsTokAnd: @@ -75828,13 +75978,13 @@ int flecs_expr_type_for_oper( lname, rname); ecs_os_free(rname); ecs_os_free(lname); - return 0; + goto error; } if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { flecs_expr_visit_error(script, node, "incompatible types in binary expression"); - return 0; + goto error; } *operand_type = flecs_expr_promote_type(ltype, rtype); @@ -75867,7 +76017,8 @@ int flecs_expr_unary_visit_type( node->node.type = ecs_id(ecs_bool_t); if (node->expr->type != ecs_id(ecs_bool_t)) { - node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); + node->expr = (ecs_expr_node_t*)flecs_expr_cast( + script, node->expr, ecs_id(ecs_bool_t)); } return 0; @@ -75899,7 +76050,10 @@ int flecs_expr_binary_visit_type( } if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { - flecs_expr_visit_error(script, node, "invalid operator for type"); + char *type_str = ecs_get_path(script->world, result_type); + flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", + flecs_script_token_str(node->operator), type_str); + ecs_os_free(type_str); goto error; } diff --git a/src/addons/script/expr.c b/src/addons/script/expr.c index 424f29a2d7..c8b6d6f9c3 100644 --- a/src/addons/script/expr.c +++ b/src/addons/script/expr.c @@ -1715,7 +1715,7 @@ const char* flecs_script_expr_run( return NULL; } -const char* ecs_script_expr_run( +const char* _ecs_script_expr_run( ecs_world_t *world, const char *ptr, ecs_value_t *value, diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index fd38bad883..eb2fbe86fc 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -58,7 +58,7 @@ bool flecs_has_precedence( if (!flecs_expr_precedence[first]) { return false; } - return flecs_expr_precedence[first] < flecs_expr_precedence[second]; + return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } static @@ -72,98 +72,98 @@ const char* flecs_script_parse_rhs( { const char *last_pos = pos; - TokenFramePush(); + do { + TokenFramePush(); + + last_pos = pos; + + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; + + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } - LookAhead( - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBracketOpen: - case EcsTokMember: - { - ecs_script_token_kind_t oper = lookahead_token.kind; - - /* Only consume more tokens if operator has precedence */ - if (flecs_has_precedence(left_oper, oper)) { - break; - } + /* Consume lookahead token */ + pos = lookahead; - /* Consume lookahead token */ - pos = lookahead; + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; - switch(oper) { - case EcsTokBracketOpen: { - ecs_expr_element_t *result = flecs_expr_element(parser); - result->left = *out; + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } - pos = flecs_script_parse_lhs( - parser, pos, tokenizer, 0, &result->index); - if (!pos) { - goto error; - } + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); - Parse_1(']', { - *out = (ecs_expr_node_t*)result; break; - }); + } - break; - } + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; - case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); - Parse_1(EcsTokIdentifier, { - result->member_name = Token(1); - *out = (ecs_expr_node_t*)result; break; - }); - - break; - } + } - default: { - ecs_expr_binary_t *result = flecs_expr_binary(parser); - result->left = *out; - result->operator = oper; + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; - pos = flecs_script_parse_lhs(parser, pos, tokenizer, - result->operator, &result->right); - if (!pos) { - goto error; + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; } - *out = (ecs_expr_node_t*)result; - break; - } - }; - - /* Ensures lookahead tokens in token buffer don't get overwritten */ - parser->token_keep = parser->token_cur; + }; - break; - } - ) + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; - if (pos[0] && (pos != last_pos)) { - pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); - } + break; + } + ) - TokenFramePop(); + TokenFramePop(); + } while (pos != last_pos); return pos; error: @@ -227,10 +227,19 @@ const char* flecs_script_parse_lhs( *out = (ecs_expr_node_t*)unary; break; } + + case EcsTokSub: { + ecs_expr_binary_t *binary = flecs_expr_binary(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &binary->right); + binary->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); + binary->operator = EcsTokMul; + *out = (ecs_expr_node_t*)binary; + break; + } ) TokenFramePop(); - + /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs( parser, pos, tokenizer, *out, left_oper, out); @@ -298,7 +307,7 @@ ecs_expr_node_t* ecs_script_parse_expr( } FLECS_API -const char* _ecs_script_expr_run( +const char* ecs_script_expr_run( ecs_world_t *world, const char *expr, ecs_value_t *value, @@ -330,10 +339,14 @@ const char* _ecs_script_expr_run( goto error; } + // printf("%s\n", ecs_script_expr_to_str(world, out)); + if (flecs_script_expr_visit_fold(script, &out)) { goto error; } + // printf("%s\n", ecs_script_expr_to_str(world, out)); + if (!value->type) { value->type = out->type; } @@ -347,8 +360,22 @@ const char* _ecs_script_expr_run( if (out->kind == EcsExprValue) { if (value->type == out->type) { + // Output value is same as expression, copy value ecs_value_copy(world, value->type, value->ptr, ((ecs_expr_val_t*)out)->ptr); + } else { + // Cast value to desired output type + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, value->type, value->ptr); + + ecs_value_t expr_result = { + .type = out->type, + .ptr = ((ecs_expr_val_t*)out)->ptr + }; + + if (ecs_meta_set_value(&cur, &expr_result)) { + goto error; + } } } else { ecs_abort(ECS_UNSUPPORTED, "can't evaluate dynamic expressions yet"); diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 5d54b5a05e..79942fc7c2 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -31,12 +31,7 @@ int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) { - switch(node->operator) { - case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; - default: - ecs_err("invalid operator for unary expression"); - return -1; - }; + ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); if (flecs_expr_node_to_str(v, node->expr)) { goto error; @@ -133,11 +128,11 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } + // if (node->type) { + // ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + // ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + // ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + // } switch(node->kind) { case EcsExprValue: @@ -184,9 +179,9 @@ int flecs_expr_node_to_str( break; } - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } + // if (node->type) { + // ecs_strbuf_append(v->buf, ")"); + // } return 0; error: diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 99d8770002..985ba7caf0 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -271,9 +271,9 @@ const char* flecs_script_identifier( // Number token static static bool flecs_script_is_number( - char c) + const char *c) { - return isdigit(c) || (c == '-'); + return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); } static @@ -288,8 +288,15 @@ const char* flecs_script_number( bool dot_parsed = false; bool e_parsed = false; - ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; + + if (pos[0] == '-') { + outpos[0] = pos[0]; + pos ++; + outpos ++; + } + do { char c = pos[0]; bool valid_number = false; @@ -568,6 +575,9 @@ const char* flecs_script_token( } return pos; + } else if (flecs_script_is_number(pos)) { + return flecs_script_number(parser, pos, out); + Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) @@ -616,9 +626,6 @@ const char* flecs_script_token( } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); - } else if (flecs_script_is_number(pos[0])) { - return flecs_script_number(parser, pos, out); - } else if (flecs_script_is_identifier(pos[0])) { return flecs_script_identifier(parser, pos, out); } diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index a3bb9f2d37..5e6e7da4a5 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -5,7 +5,7 @@ void Expr_add_2_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); ecs_value_free(world, v.type, v.ptr); @@ -18,12 +18,12 @@ void Expr_add_2_int_literals_twice(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); ecs_value_free(world, v.type, v.ptr); @@ -49,7 +49,7 @@ void Expr_mul_2_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "20 * 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_u64_t*)v.ptr, 20 * 10); ecs_value_free(world, v.type, v.ptr); @@ -75,7 +75,7 @@ void Expr_add_3_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); ecs_value_free(world, v.type, v.ptr); @@ -88,12 +88,12 @@ void Expr_add_3_int_literals_twice(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); ecs_value_free(world, v.type, v.ptr); @@ -119,9 +119,9 @@ void Expr_mul_3_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "2 * 5 * 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, 2 * 5 * 10); + test_uint(*(ecs_i64_t*)v.ptr, 2 * 5 * 10); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -207,7 +207,7 @@ void Expr_add_mul_3_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 20 * 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 * 2); ecs_value_free(world, v.type, v.ptr); @@ -285,9 +285,9 @@ void Expr_mul_add_mul_add_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "2 * 4 + 6 * 8 + 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, 2 * 4 + 6 * 8 + 10); + test_uint(*(ecs_i64_t*)v.ptr, 2 * 4 + 6 * 8 + 10); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -480,9 +480,9 @@ void Expr_mul_lparen_add_add_rparen_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 * (20 + 30)", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, 10 * (20 + 30)); + test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30)); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -493,9 +493,9 @@ void Expr_mul_lparen_add_add_add_rparen_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 * (20 + 30 + 40)", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, 10 * (20 + 30 + 40)); + test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30 + 40)); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -506,9 +506,9 @@ void Expr_mul_lparen_add_add_rparen_add_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 * (20 + 30) + 40", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, 10 * (20 + 30) + 40); + test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30) + 40); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -519,9 +519,9 @@ void Expr_lparen_add_add_rparen_mul_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "(20 + 30) * 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, (20 + 30) * 10); + test_uint(*(ecs_i64_t*)v.ptr, (20 + 30) * 10); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -532,9 +532,9 @@ void Expr_lparen_add_add_add_rparen_mul_int_literals(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "(20 + 30 + 40) * 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, (20 + 30 + 40) * 10); + test_uint(*(ecs_i64_t*)v.ptr, (20 + 30 + 40) * 10); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -545,9 +545,9 @@ void Expr_double_paren_add_add(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "((20 + 30))", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, ((20 + 30))); + test_uint(*(ecs_i64_t*)v.ptr, ((20 + 30))); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -558,9 +558,9 @@ void Expr_double_paren_literal(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "((20))", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, ((20))); + test_uint(*(ecs_i64_t*)v.ptr, ((20))); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -571,9 +571,9 @@ void Expr_lparen_add_add_rparen_mul_lparen_add_add_rparen(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "(10 + 20) * (20 + 30)", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(ecs_u64_t*)v.ptr, (10 + 20) * (20 + 30)); + test_uint(*(ecs_i64_t*)v.ptr, (10 + 20) * (20 + 30)); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1692,9 +1692,9 @@ void Expr_shift_left_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "1 << 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 1 << 2); + test_uint(*(ecs_i64_t*)v.ptr, 1 << 2); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1705,9 +1705,9 @@ void Expr_shift_right_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "4 >> 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 4 >> 2); + test_uint(*(ecs_i64_t*)v.ptr, 4 >> 2); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1718,9 +1718,9 @@ void Expr_shift_left_int_add_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "1 << 2 + 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 1 << (2 + 10)); + test_uint(*(ecs_i64_t*)v.ptr, 1 << (2 + 10)); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1731,9 +1731,9 @@ void Expr_shift_left_int_mul_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "1 << 2 * 10", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 1 << 2 * 10); + test_uint(*(ecs_i64_t*)v.ptr, 1 << 2 * 10); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1744,9 +1744,9 @@ void Expr_add_int_shift_left_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 1 << 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, (10 + 1) << 2); + test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << 2); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1757,9 +1757,9 @@ void Expr_mul_int_shift_left_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 * 1 << 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 10 * 1 << 2); + test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1770,9 +1770,9 @@ void Expr_add_int_shift_left_int_add_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 + 1 << 2 + 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, (10 + 1) << (2 + 2)); + test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << (2 + 2)); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); @@ -1783,9 +1783,9 @@ void Expr_mul_int_shift_left_int_mul_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10 * 1 << 2 * 2", &v, NULL) != NULL); - test_assert(v.type == ecs_id(ecs_u64_t)); + test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); - test_uint(*(uint64_t*)v.ptr, 10 * 1 << 2 * 2); + test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2 * 2); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); From 98452a521f87730c1fdc507d905e72dc7220cb3c Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 26 Nov 2024 14:22:18 -0800 Subject: [PATCH 10/83] Implement initializer expressions --- distr/flecs.c | 755 +++++++++++++++++++++++--- distr/flecs.h | 1 + include/flecs/addons/script.h | 1 + src/addons/meta/cursor.c | 3 +- src/addons/script/expr/ast.c | 22 + src/addons/script/expr/ast.h | 22 +- src/addons/script/expr/parser.c | 228 +++++++- src/addons/script/expr/visit.h | 6 +- src/addons/script/expr/visit_fold.c | 197 ++++++- src/addons/script/expr/visit_to_str.c | 63 ++- src/addons/script/expr/visit_type.c | 204 +++++-- src/addons/script/parser.h | 8 +- src/addons/script/serialize.c | 2 + test/script/project.json | 7 + test/script/src/Deserialize.c | 4 +- test/script/src/Expr.c | 266 +++++++++ test/script/src/main.c | 37 +- 17 files changed, 1670 insertions(+), 156 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 8f0fa8db55..a6063f30eb 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4867,6 +4867,7 @@ ecs_script_if_t* flecs_script_insert_if( typedef enum ecs_expr_node_kind_t { EcsExprValue, + EcsExprInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, @@ -4899,13 +4900,25 @@ typedef struct ecs_expr_val_t { double f32; double f64; const char *string; + ecs_entity_t entity; } storage; } ecs_expr_val_t; +typedef struct ecs_expr_initializer_element_t { + const char *member; + ecs_expr_node_t *value; + uintptr_t offset; +} ecs_expr_initializer_element_t; + +typedef struct ecs_expr_initializer_t { + ecs_expr_node_t node; + ecs_vec_t elements; + bool is_collection; +} ecs_expr_initializer_t; + typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; - ecs_entity_t id; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { @@ -4966,6 +4979,13 @@ ecs_expr_val_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); +ecs_expr_val_t* flecs_expr_entity( + ecs_script_parser_t *parser, + ecs_entity_t value); + +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_script_parser_t *parser); + ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value); @@ -5008,11 +5028,13 @@ ecs_expr_cast_t* flecs_expr_cast( int flecs_script_expr_visit_type( ecs_script_t *script, - ecs_expr_node_t *node); + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc); int flecs_script_expr_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node); + ecs_expr_node_t **node, + const ecs_script_expr_run_desc_t *desc); #endif @@ -47861,7 +47883,8 @@ int ecs_meta_next( } if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); + ecs_err("out of collection bounds (%d elements vs. size %d)", + scope->elem_cur + 1, get_elem_count(scope)); return -1; } return 0; @@ -56854,7 +56877,9 @@ char* ecs_script_string_interpolate( __VA_ARGS__\ }\ )\ - pos = old_ptr;\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ ) #define LookAhead_3(tok1, tok2, tok3, ...)\ @@ -56866,7 +56891,9 @@ char* ecs_script_string_interpolate( __VA_ARGS__\ }\ )\ - pos = old_ptr;\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ ) /* Open scope */ @@ -59028,6 +59055,8 @@ int flecs_expr_ser_type_op( ecs_strbuf_t *str, bool is_expr) { + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + switch(op->kind) { case EcsOpPush: case EcsOpPop: @@ -74792,6 +74821,28 @@ ecs_expr_val_t* flecs_expr_string( return result; } +ecs_expr_val_t* flecs_expr_entity( + ecs_script_parser_t *parser, + ecs_entity_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.entity = value; + result->ptr = &result->storage.entity; + result->node.type = ecs_id(ecs_entity_t); + return result; +} + +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_script_parser_t *parser) +{ + ecs_expr_initializer_t *result = flecs_expr_ast_new( + parser, ecs_expr_initializer_t, EcsExprInitializer); + ecs_vec_init_t(&parser->script->allocator, &result->elements, + ecs_expr_initializer_element_t, 0); + return result; +} + ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value) @@ -74920,6 +74971,124 @@ bool flecs_has_precedence( return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } +static +ecs_entity_t flecs_script_default_lookup( + const ecs_world_t *world, + const char *name, + void *ctx) +{ + (void)ctx; + return ecs_lookup(world, name); +} + +static +const char* flecs_script_parse_initializer( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_initializer_t *node) +{ + bool first = true; + + ecs_allocator_t *a = &parser->script->allocator; + + do { + ParserBegin; + + if (first) { + /* End of initializer */ + LookAhead_1('}', { + pos = lookahead; + EndOfRule; + }) + + first = false; + } + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + { + /* Parse member name */ + LookAhead_2(EcsTokIdentifier, ':', { + elem->member = Token(0); + LookAhead_Keep(); + pos = lookahead; + break; + }) + } + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } + + { + /* Parse next element or end of initializer*/ + LookAhead( + case ',': { + pos = lookahead; + break; + } + case '}': { + EndOfRule; + } + ) + } + } while (true); + + ParserEnd; +} + +static +const char* flecs_script_parse_collection_initializer( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_initializer_t *node) +{ + bool first = true; + + ecs_allocator_t *a = &parser->script->allocator; + + do { + ParserBegin; + + if (first) { + /* End of initializer */ + LookAhead_1(']', { + pos = lookahead; + EndOfRule; + }) + + first = false; + } + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } + + { + /* Parse next element or end of initializer*/ + LookAhead( + case ',': { + pos = lookahead; + break; + } + case ']': { + EndOfRule; + } + ) + } + } while (true); + + ParserEnd; +} + static const char* flecs_script_parse_rhs( ecs_script_parser_t *parser, @@ -75071,34 +75240,82 @@ const char* flecs_script_parse_lhs( break; } - case EcsTokParenOpen: { + case EcsTokNot: { + ecs_expr_unary_t *node = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); + if (!pos) { + goto error; + } + + node->operator = EcsTokNot; + *out = (ecs_expr_node_t*)node; + break; + } + + case EcsTokSub: { + ecs_expr_binary_t *node = flecs_expr_binary(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &node->right); + if (!pos) { + goto error; + } + + node->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); + node->operator = EcsTokMul; + *out = (ecs_expr_node_t*)node; + break; + } + + case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); - Parse_1(EcsTokParenClose, { + if (!pos) { + goto error; + } + + Parse_1(')', { break; }) break; } - case EcsTokNot: { - ecs_expr_unary_t *unary = flecs_expr_unary(parser); - pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); - unary->operator = EcsTokNot; - *out = (ecs_expr_node_t*)unary; + case '{': { + ecs_expr_initializer_t *node = flecs_expr_initializer(parser); + pos = flecs_script_parse_initializer(parser, pos, node); + if (!pos) { + goto error; + } + + Parse_1('}', { + break; + }) + + *out = (ecs_expr_node_t*)node; break; } - case EcsTokSub: { - ecs_expr_binary_t *binary = flecs_expr_binary(parser); - pos = flecs_script_parse_expr(parser, pos, 0, &binary->right); - binary->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); - binary->operator = EcsTokMul; - *out = (ecs_expr_node_t*)binary; + case '[': { + ecs_expr_initializer_t *node = flecs_expr_initializer(parser); + node->is_collection = true; + + pos = flecs_script_parse_collection_initializer(parser, pos, node); + if (!pos) { + goto error; + } + + Parse_1(']', { + break; + }) + + *out = (ecs_expr_node_t*)node; break; } ) TokenFramePop(); + if (!pos[0]) { + return pos; + } + /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs( parser, pos, tokenizer, *out, left_oper, out); @@ -75152,11 +75369,11 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } - if (flecs_script_expr_visit_type(script, out)) { + if (flecs_script_expr_visit_type(script, out, NULL)) { goto error; } - if (flecs_script_expr_visit_fold(script, &out)) { + if (flecs_script_expr_visit_fold(script, &out, NULL)) { goto error; } @@ -75172,6 +75389,22 @@ const char* ecs_script_expr_run( ecs_value_t *value, const ecs_script_expr_run_desc_t *desc) { + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.type) { + priv_desc.type = value->type; + } else if (desc && (value->type != desc->type)) { + ecs_throw("type of value parameter does not match desc->type", + ECS_INVALID_PARAMETER, NULL); + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + ecs_script_t *script = flecs_script_new(world); ecs_script_parser_t parser = { @@ -75194,20 +75427,26 @@ const char* ecs_script_expr_run( goto error; } - if (flecs_script_expr_visit_type(script, out)) { + if (flecs_script_expr_visit_type(script, out, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (flecs_script_expr_visit_fold(script, &out)) { + if (flecs_script_expr_visit_fold(script, &out, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_expr_to_str(world, out)); if (!value->type) { - value->type = out->type; + if (priv_desc.type) { + /* If explicit out type is provided, use that */ + value->type = priv_desc.type; + } else { + /* Otherwise use resolved expression type */ + value->type = out->type; + } } if (value->type && !value->ptr) { @@ -75219,11 +75458,11 @@ const char* ecs_script_expr_run( if (out->kind == EcsExprValue) { if (value->type == out->type) { - // Output value is same as expression, copy value + /* Output value is same as expression, copy value */ ecs_value_copy(world, value->type, value->ptr, ((ecs_expr_val_t*)out)->ptr); } else { - // Cast value to desired output type + /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, value->type, value->ptr); @@ -75331,7 +75570,8 @@ const char* ecs_script_expr_run( int flecs_expr_unary_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; @@ -75341,7 +75581,7 @@ int flecs_expr_unary_visit_fold( goto error; } - if (flecs_script_expr_visit_fold(script, &node->expr)) { + if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -75375,15 +75615,16 @@ int flecs_expr_unary_visit_fold( int flecs_expr_binary_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left)) { + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->right)) { + if (flecs_script_expr_visit_fold(script, &node->right, desc)) { goto error; } @@ -75399,6 +75640,18 @@ int flecs_expr_binary_visit_fold( result->node.pos = node->node.pos; result->node.type = node->node.type; + /* Handle bitmask separately since it's not done by switch */ + if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { + ecs_assert(node->left->type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->right->type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; + ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; + goto done; + } + switch(node->operator) { case EcsTokAdd: ECS_BINARY_OP(node->left, node->right, result, +); @@ -75456,8 +75709,8 @@ int flecs_expr_binary_visit_fold( goto error; } +done: *node_ptr = (ecs_expr_node_t*)result; - return 0; error: return -1; @@ -75465,11 +75718,12 @@ int flecs_expr_binary_visit_fold( int flecs_expr_cast_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->expr)) { + if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -75506,9 +75760,159 @@ int flecs_expr_cast_visit_fold( return -1; } +int flecs_expr_initializer_pre_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + bool *can_fold) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + /* If this is a nested initializer, don't fold it but instead fold its + * values. Because nested initializers are flattened, this ensures that + * we'll be using the correct member offsets later. */ + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_pre_fold( + script, (ecs_expr_initializer_t*)elem->value, desc, can_fold)) + { + goto error; + } + continue; + } + + if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + goto error; + } + + if (elem->value->kind != EcsExprValue) { + *can_fold = false; + } + } + + return 0; +error: + return -1; +} + +int flecs_expr_initializer_post_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + void *value) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_post_fold( + script, (ecs_expr_initializer_t*)elem->value, value)) + { + goto error; + } + continue; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem_value->node.type; + + if (ecs_value_copy(script->world, type, + ECS_OFFSET(value, elem->offset), elem_value->ptr)) + { + goto error; + } + } + + return 0; +error: + return -1; +} + +int flecs_expr_initializer_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + bool can_fold = true; + + ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { + goto error; + } + + /* If all elements of initializer fold to literals, initializer itself can + * be folded into a literal. */ + if (can_fold) { + void *value = ecs_value_new(script->world, node->node.type); + ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + + if (flecs_expr_initializer_post_fold(script, node, value)) { + goto error; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + result->ptr = value; + *node_ptr = (ecs_expr_node_t*)result; + } + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; + ecs_entity_t type = node->node.type; + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = type; + + if (type == ecs_id(ecs_entity_t)) { + result->storage.entity = desc->lookup_action( + script->world, node->value, desc->lookup_ctx); + if (!result->storage.entity) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + result->ptr = &result->storage.entity; + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, type, &result->storage.u64); + if (ecs_meta_set_string(&cur, node->value)) { + goto error; + } + result->ptr = &result->storage.u64; + } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; @@ -75516,17 +75920,25 @@ int flecs_script_expr_visit_fold( switch(node->kind) { case EcsExprValue: break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprUnary: - if (flecs_expr_unary_visit_fold(script, node_ptr)) { + if (flecs_expr_unary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_visit_fold(script, node_ptr)) { + if (flecs_expr_binary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprVariable: break; @@ -75537,7 +75949,7 @@ int flecs_script_expr_visit_fold( case EcsExprElement: break; case EcsExprCast: - if (flecs_expr_cast_visit_fold(script, node_ptr)) { + if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { goto error; } break; @@ -75592,6 +76004,37 @@ int flecs_expr_unary_to_str( return -1; } +int flecs_expr_initializer_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_initializer_t *node) +{ + ecs_strbuf_appendlit(v->buf, "{"); + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + ecs_strbuf_appendstr(v->buf, ", "); + } + + ecs_expr_initializer_element_t *elem = &elems[i]; + if (elem->member) { + ecs_strbuf_appendstr(v->buf, elem->member); + ecs_strbuf_appendlit(v->buf, ":"); + } + + if (flecs_expr_node_to_str(v, elem->value)) { + goto error; + } + } + + ecs_strbuf_appendlit(v->buf, "}"); + + return 0; +error: + return -1; +} + int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -75623,6 +76066,7 @@ int flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) { + ecs_strbuf_appendlit(v->buf, "@"); ecs_strbuf_appendstr(v->buf, node->value); return 0; } @@ -75678,11 +76122,18 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - // if (node->type) { - // ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - // ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - // ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - // } + if (node->type) { + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + const char *name = ecs_get_name(v->world, node->type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, node->type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + } switch(node->kind) { case EcsExprValue: @@ -75690,6 +76141,11 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprInitializer: + if (flecs_expr_initializer_to_str(v, (ecs_expr_initializer_t*)node)) { + goto error; + } + break; case EcsExprUnary: if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { goto error; @@ -75727,11 +76183,14 @@ int flecs_expr_node_to_str( goto error; } break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); + break; } - // if (node->type) { - // ecs_strbuf_append(v->buf, ")"); - // } + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } return 0; error: @@ -75763,6 +76222,13 @@ char* ecs_script_expr_to_str( #ifdef FLECS_SCRIPT +static +int flecs_script_expr_visit_type_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc); + static bool flecs_expr_operator_is_equality( ecs_script_token_kind_t op) @@ -75875,6 +76341,7 @@ ecs_entity_t flecs_expr_promote_type( static bool flecs_expr_oper_valid_for_type( + ecs_world_t *world, ecs_entity_t type, ecs_script_token_kind_t op) { @@ -75886,6 +76353,11 @@ bool flecs_expr_oper_valid_for_type( return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: + if (ecs_get(world, type, EcsBitmask) != NULL) { + return true; + } + + /* fall through */ case EcsTokShiftLeft: case EcsTokShiftRight: return flecs_expr_is_type_integer(type); @@ -75953,6 +76425,14 @@ int flecs_expr_type_for_oper( const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { + /* Only primitives and bitmask constants are allowed */ + if (left->type == right->type) { + if (ecs_get(world, left->type, EcsBitmask) != NULL) { + *operand_type = left->type; + goto done; + } + } + char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, @@ -75960,7 +76440,7 @@ int flecs_expr_type_for_oper( lname, rname); ecs_os_free(lname); ecs_os_free(rname); - return 0; + goto error; } ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); @@ -76004,12 +76484,86 @@ int flecs_expr_type_for_oper( return -1; } +static +int flecs_expr_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) +{ + if (!cur) { + flecs_expr_visit_error(script, node, "missing type for initializer"); + goto error; + } + + ecs_entity_t type = ecs_meta_get_type(cur); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_push(cur); /* { */ + + if (ecs_meta_is_collection(cur) != node->is_collection) { + char *type_str = ecs_get_path(script->world, type); + if (node->is_collection) { + flecs_expr_visit_error(script, node, + "invalid collection literal for non-collection type '%s'" + " (expected '[]')", type_str); + } else { + flecs_expr_visit_error(script, node, + "invalid object literal for collection type '%s' (expected {})", + type_str); + } + + ecs_os_free(type_str); + goto error; + } + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + if (ecs_meta_next(cur)) { /* , */ + goto error; + } + } + + ecs_expr_initializer_element_t *elem = &elems[i]; + if (elem->member) { + if (ecs_meta_dotmember(cur, elem->member)) { /* x: */ + flecs_expr_visit_error(script, node, "cannot resolve member"); + goto error; + } + } + + ecs_entity_t elem_type = ecs_meta_get_type(cur); + if (flecs_script_expr_visit_type_priv(script, elem->value, cur, desc)) { + goto error; + } + + if (elem->value->type != elem_type) { + elem->value = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, elem_type); + } + + elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + } + + node->node.type = type; + + ecs_meta_pop(cur); /* } */ + + return 0; +error: + return -1; +} + static int flecs_expr_unary_visit_type( ecs_script_t *script, - ecs_expr_unary_t *node) + ecs_expr_unary_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->expr)) { + if (flecs_script_expr_visit_type_priv(script, node->expr, cur, desc)) { goto error; } @@ -76029,7 +76583,9 @@ int flecs_expr_unary_visit_type( static int flecs_expr_binary_visit_type( ecs_script_t *script, - ecs_expr_binary_t *node) + ecs_expr_binary_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { /* Operands must be of this type or casted to it */ ecs_entity_t operand_type = 0; @@ -76037,11 +76593,11 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } - if (flecs_script_expr_visit_type(script, node->right)) { + if (flecs_script_expr_visit_type_priv(script, node->right, cur, desc)) { goto error; } @@ -76049,7 +76605,7 @@ int flecs_expr_binary_visit_type( goto error; } - if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + if (!flecs_expr_oper_valid_for_type(script->world, result_type, node->operator)) { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_script_token_str(node->operator), type_str); @@ -76077,25 +76633,24 @@ int flecs_expr_binary_visit_type( static int flecs_expr_identifier_visit_type( ecs_script_t *script, - ecs_expr_identifier_t *node) + ecs_expr_identifier_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { - node->node.type = ecs_id(ecs_entity_t); - node->id = ecs_lookup(script->world, node->value); - if (!node->id) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; + if (cur) { + node->node.type = ecs_meta_get_type(cur); + } else { + node->node.type = ecs_id(ecs_entity_t); } return 0; -error: - return -1; } static int flecs_expr_variable_visit_type( ecs_script_t *script, - ecs_expr_variable_t *node) + ecs_expr_variable_t *node, + const ecs_script_expr_run_desc_t *desc) { node->node.type = ecs_id(ecs_entity_t); return 0; @@ -76104,9 +76659,10 @@ int flecs_expr_variable_visit_type( static int flecs_expr_member_visit_type( ecs_script_t *script, - ecs_expr_member_t *node) + ecs_expr_member_t *node, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { goto error; } @@ -76157,13 +76713,14 @@ int flecs_expr_member_visit_type( static int flecs_expr_element_visit_type( ecs_script_t *script, - ecs_expr_element_t *node) + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { goto error; } - if (flecs_script_expr_visit_type(script, node->index)) { + if (flecs_script_expr_visit_type_priv(script, node->index, NULL, desc)) { goto error; } @@ -76190,7 +76747,15 @@ int flecs_expr_element_visit_type( if (is_entity_type) { if (node->index->kind == EcsExprIdentifier) { - node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->index; + node->node.type = desc->lookup_action( + script->world, ident->value, desc->lookup_ctx); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unresolved component identifier '%s'", + ident->value); + goto error; + } } else { flecs_expr_visit_error(script, node, "invalid component expression"); @@ -76217,9 +76782,12 @@ int flecs_expr_element_visit_type( return -1; } -int flecs_script_expr_visit_type( +static +int flecs_script_expr_visit_type_priv( ecs_script_t *script, - ecs_expr_node_t *node) + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); @@ -76227,35 +76795,54 @@ int flecs_script_expr_visit_type( case EcsExprValue: /* Value types are assigned by the AST */ break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprUnary: - if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + if (flecs_expr_unary_visit_type( + script, (ecs_expr_unary_t*)node, cur, desc)) + { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + if (flecs_expr_binary_visit_type( + script, (ecs_expr_binary_t*)node, cur, desc)) + { goto error; } break; case EcsExprIdentifier: - if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_visit_type( + script, (ecs_expr_identifier_t*)node, cur, desc)) + { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_visit_type( + script, (ecs_expr_variable_t*)node, desc)) + { goto error; } break; case EcsExprFunction: break; case EcsExprMember: - if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_visit_type( + script, (ecs_expr_member_t*)node, desc)) + { goto error; } break; case EcsExprElement: - if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_visit_type( + script, (ecs_expr_element_t*)node, desc)) + { goto error; } break; @@ -76268,5 +76855,19 @@ int flecs_script_expr_visit_type( return -1; } +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc) +{ + if (desc->type) { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, desc->type, NULL); + return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + } else { + return flecs_script_expr_visit_type_priv(script, node, NULL, desc); + } +} + #endif diff --git a/distr/flecs.h b/distr/flecs.h index 338a5dc67b..5f9877abe5 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14569,6 +14569,7 @@ typedef struct ecs_script_expr_run_desc_t { void *ctx); void *lookup_ctx; ecs_script_vars_t *vars; + ecs_entity_t type; } ecs_script_expr_run_desc_t; /** Parse standalone expression into value. diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index c31bc7e3d6..eee55e87bd 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -390,6 +390,7 @@ typedef struct ecs_script_expr_run_desc_t { void *ctx); void *lookup_ctx; ecs_script_vars_t *vars; + ecs_entity_t type; } ecs_script_expr_run_desc_t; /** Parse standalone expression into value. diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 17d6439dce..0bc67a321b 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -240,7 +240,8 @@ int ecs_meta_next( } if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); + ecs_err("out of collection bounds (%d elements vs. size %d)", + scope->elem_cur + 1, get_elem_count(scope)); return -1; } return 0; diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index aba0b1b389..bb86e6a468 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -85,6 +85,28 @@ ecs_expr_val_t* flecs_expr_string( return result; } +ecs_expr_val_t* flecs_expr_entity( + ecs_script_parser_t *parser, + ecs_entity_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.entity = value; + result->ptr = &result->storage.entity; + result->node.type = ecs_id(ecs_entity_t); + return result; +} + +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_script_parser_t *parser) +{ + ecs_expr_initializer_t *result = flecs_expr_ast_new( + parser, ecs_expr_initializer_t, EcsExprInitializer); + ecs_vec_init_t(&parser->script->allocator, &result->elements, + ecs_expr_initializer_element_t, 0); + return result; +} + ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value) diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index de99896231..12a0e2f2fb 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -8,6 +8,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprValue, + EcsExprInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, @@ -40,13 +41,25 @@ typedef struct ecs_expr_val_t { double f32; double f64; const char *string; + ecs_entity_t entity; } storage; } ecs_expr_val_t; +typedef struct ecs_expr_initializer_element_t { + const char *member; + ecs_expr_node_t *value; + uintptr_t offset; +} ecs_expr_initializer_element_t; + +typedef struct ecs_expr_initializer_t { + ecs_expr_node_t node; + ecs_vec_t elements; + bool is_collection; +} ecs_expr_initializer_t; + typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; - ecs_entity_t id; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { @@ -107,6 +120,13 @@ ecs_expr_val_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); +ecs_expr_val_t* flecs_expr_entity( + ecs_script_parser_t *parser, + ecs_entity_t value); + +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_script_parser_t *parser); + ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value); diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index eb2fbe86fc..3f389bc21e 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -61,6 +61,124 @@ bool flecs_has_precedence( return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } +static +ecs_entity_t flecs_script_default_lookup( + const ecs_world_t *world, + const char *name, + void *ctx) +{ + (void)ctx; + return ecs_lookup(world, name); +} + +static +const char* flecs_script_parse_initializer( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_initializer_t *node) +{ + bool first = true; + + ecs_allocator_t *a = &parser->script->allocator; + + do { + ParserBegin; + + if (first) { + /* End of initializer */ + LookAhead_1('}', { + pos = lookahead; + EndOfRule; + }) + + first = false; + } + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + { + /* Parse member name */ + LookAhead_2(EcsTokIdentifier, ':', { + elem->member = Token(0); + LookAhead_Keep(); + pos = lookahead; + break; + }) + } + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } + + { + /* Parse next element or end of initializer*/ + LookAhead( + case ',': { + pos = lookahead; + break; + } + case '}': { + EndOfRule; + } + ) + } + } while (true); + + ParserEnd; +} + +static +const char* flecs_script_parse_collection_initializer( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_initializer_t *node) +{ + bool first = true; + + ecs_allocator_t *a = &parser->script->allocator; + + do { + ParserBegin; + + if (first) { + /* End of initializer */ + LookAhead_1(']', { + pos = lookahead; + EndOfRule; + }) + + first = false; + } + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } + + { + /* Parse next element or end of initializer*/ + LookAhead( + case ',': { + pos = lookahead; + break; + } + case ']': { + EndOfRule; + } + ) + } + } while (true); + + ParserEnd; +} + static const char* flecs_script_parse_rhs( ecs_script_parser_t *parser, @@ -212,34 +330,82 @@ const char* flecs_script_parse_lhs( break; } - case EcsTokParenOpen: { + case EcsTokNot: { + ecs_expr_unary_t *node = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); + if (!pos) { + goto error; + } + + node->operator = EcsTokNot; + *out = (ecs_expr_node_t*)node; + break; + } + + case EcsTokSub: { + ecs_expr_binary_t *node = flecs_expr_binary(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &node->right); + if (!pos) { + goto error; + } + + node->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); + node->operator = EcsTokMul; + *out = (ecs_expr_node_t*)node; + break; + } + + case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); - Parse_1(EcsTokParenClose, { + if (!pos) { + goto error; + } + + Parse_1(')', { break; }) break; } - case EcsTokNot: { - ecs_expr_unary_t *unary = flecs_expr_unary(parser); - pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); - unary->operator = EcsTokNot; - *out = (ecs_expr_node_t*)unary; + case '{': { + ecs_expr_initializer_t *node = flecs_expr_initializer(parser); + pos = flecs_script_parse_initializer(parser, pos, node); + if (!pos) { + goto error; + } + + Parse_1('}', { + break; + }) + + *out = (ecs_expr_node_t*)node; break; } - case EcsTokSub: { - ecs_expr_binary_t *binary = flecs_expr_binary(parser); - pos = flecs_script_parse_expr(parser, pos, 0, &binary->right); - binary->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); - binary->operator = EcsTokMul; - *out = (ecs_expr_node_t*)binary; + case '[': { + ecs_expr_initializer_t *node = flecs_expr_initializer(parser); + node->is_collection = true; + + pos = flecs_script_parse_collection_initializer(parser, pos, node); + if (!pos) { + goto error; + } + + Parse_1(']', { + break; + }) + + *out = (ecs_expr_node_t*)node; break; } ) TokenFramePop(); + if (!pos[0]) { + return pos; + } + /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs( parser, pos, tokenizer, *out, left_oper, out); @@ -293,11 +459,11 @@ ecs_expr_node_t* ecs_script_parse_expr( goto error; } - if (flecs_script_expr_visit_type(script, out)) { + if (flecs_script_expr_visit_type(script, out, NULL)) { goto error; } - if (flecs_script_expr_visit_fold(script, &out)) { + if (flecs_script_expr_visit_fold(script, &out, NULL)) { goto error; } @@ -313,6 +479,22 @@ const char* ecs_script_expr_run( ecs_value_t *value, const ecs_script_expr_run_desc_t *desc) { + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.type) { + priv_desc.type = value->type; + } else if (desc && (value->type != desc->type)) { + ecs_throw("type of value parameter does not match desc->type", + ECS_INVALID_PARAMETER, NULL); + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + ecs_script_t *script = flecs_script_new(world); ecs_script_parser_t parser = { @@ -335,20 +517,26 @@ const char* ecs_script_expr_run( goto error; } - if (flecs_script_expr_visit_type(script, out)) { + if (flecs_script_expr_visit_type(script, out, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (flecs_script_expr_visit_fold(script, &out)) { + if (flecs_script_expr_visit_fold(script, &out, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_expr_to_str(world, out)); if (!value->type) { - value->type = out->type; + if (priv_desc.type) { + /* If explicit out type is provided, use that */ + value->type = priv_desc.type; + } else { + /* Otherwise use resolved expression type */ + value->type = out->type; + } } if (value->type && !value->ptr) { @@ -360,11 +548,11 @@ const char* ecs_script_expr_run( if (out->kind == EcsExprValue) { if (value->type == out->type) { - // Output value is same as expression, copy value + /* Output value is same as expression, copy value */ ecs_value_copy(world, value->type, value->ptr, ((ecs_expr_val_t*)out)->ptr); } else { - // Cast value to desired output type + /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, value->type, value->ptr); diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index 889a3bc868..96e963b5fa 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -13,10 +13,12 @@ int flecs_script_expr_visit_type( ecs_script_t *script, - ecs_expr_node_t *node); + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc); int flecs_script_expr_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node); + ecs_expr_node_t **node, + const ecs_script_expr_run_desc_t *desc); #endif diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index d3553f722a..728b81e670 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -84,7 +84,8 @@ int flecs_expr_unary_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; @@ -94,7 +95,7 @@ int flecs_expr_unary_visit_fold( goto error; } - if (flecs_script_expr_visit_fold(script, &node->expr)) { + if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -128,15 +129,16 @@ int flecs_expr_unary_visit_fold( int flecs_expr_binary_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left)) { + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->right)) { + if (flecs_script_expr_visit_fold(script, &node->right, desc)) { goto error; } @@ -152,6 +154,18 @@ int flecs_expr_binary_visit_fold( result->node.pos = node->node.pos; result->node.type = node->node.type; + /* Handle bitmask separately since it's not done by switch */ + if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { + ecs_assert(node->left->type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->right->type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; + ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; + goto done; + } + switch(node->operator) { case EcsTokAdd: ECS_BINARY_OP(node->left, node->right, result, +); @@ -209,8 +223,8 @@ int flecs_expr_binary_visit_fold( goto error; } +done: *node_ptr = (ecs_expr_node_t*)result; - return 0; error: return -1; @@ -218,11 +232,12 @@ int flecs_expr_binary_visit_fold( int flecs_expr_cast_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->expr)) { + if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -259,9 +274,159 @@ int flecs_expr_cast_visit_fold( return -1; } +int flecs_expr_initializer_pre_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + bool *can_fold) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + /* If this is a nested initializer, don't fold it but instead fold its + * values. Because nested initializers are flattened, this ensures that + * we'll be using the correct member offsets later. */ + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_pre_fold( + script, (ecs_expr_initializer_t*)elem->value, desc, can_fold)) + { + goto error; + } + continue; + } + + if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + goto error; + } + + if (elem->value->kind != EcsExprValue) { + *can_fold = false; + } + } + + return 0; +error: + return -1; +} + +int flecs_expr_initializer_post_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + void *value) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_post_fold( + script, (ecs_expr_initializer_t*)elem->value, value)) + { + goto error; + } + continue; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem_value->node.type; + + if (ecs_value_copy(script->world, type, + ECS_OFFSET(value, elem->offset), elem_value->ptr)) + { + goto error; + } + } + + return 0; +error: + return -1; +} + +int flecs_expr_initializer_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + bool can_fold = true; + + ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { + goto error; + } + + /* If all elements of initializer fold to literals, initializer itself can + * be folded into a literal. */ + if (can_fold) { + void *value = ecs_value_new(script->world, node->node.type); + ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + + if (flecs_expr_initializer_post_fold(script, node, value)) { + goto error; + } + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + result->ptr = value; + *node_ptr = (ecs_expr_node_t*)result; + } + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; + ecs_entity_t type = node->node.type; + + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = type; + + if (type == ecs_id(ecs_entity_t)) { + result->storage.entity = desc->lookup_action( + script->world, node->value, desc->lookup_ctx); + if (!result->storage.entity) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + result->ptr = &result->storage.entity; + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, type, &result->storage.u64); + if (ecs_meta_set_string(&cur, node->value)) { + goto error; + } + result->ptr = &result->storage.u64; + } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_fold( ecs_script_t *script, - ecs_expr_node_t **node_ptr) + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; @@ -269,17 +434,25 @@ int flecs_script_expr_visit_fold( switch(node->kind) { case EcsExprValue: break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprUnary: - if (flecs_expr_unary_visit_fold(script, node_ptr)) { + if (flecs_expr_unary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_visit_fold(script, node_ptr)) { + if (flecs_expr_binary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprVariable: break; @@ -290,7 +463,7 @@ int flecs_script_expr_visit_fold( case EcsExprElement: break; case EcsExprCast: - if (flecs_expr_cast_visit_fold(script, node_ptr)) { + if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { goto error; } break; diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 79942fc7c2..2fd4bcbd58 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -42,6 +42,37 @@ int flecs_expr_unary_to_str( return -1; } +int flecs_expr_initializer_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_initializer_t *node) +{ + ecs_strbuf_appendlit(v->buf, "{"); + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + ecs_strbuf_appendstr(v->buf, ", "); + } + + ecs_expr_initializer_element_t *elem = &elems[i]; + if (elem->member) { + ecs_strbuf_appendstr(v->buf, elem->member); + ecs_strbuf_appendlit(v->buf, ":"); + } + + if (flecs_expr_node_to_str(v, elem->value)) { + goto error; + } + } + + ecs_strbuf_appendlit(v->buf, "}"); + + return 0; +error: + return -1; +} + int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -73,6 +104,7 @@ int flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) { + ecs_strbuf_appendlit(v->buf, "@"); ecs_strbuf_appendstr(v->buf, node->value); return 0; } @@ -128,11 +160,18 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - // if (node->type) { - // ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - // ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - // ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - // } + if (node->type) { + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + const char *name = ecs_get_name(v->world, node->type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, node->type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + } switch(node->kind) { case EcsExprValue: @@ -140,6 +179,11 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprInitializer: + if (flecs_expr_initializer_to_str(v, (ecs_expr_initializer_t*)node)) { + goto error; + } + break; case EcsExprUnary: if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { goto error; @@ -177,11 +221,14 @@ int flecs_expr_node_to_str( goto error; } break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); + break; } - // if (node->type) { - // ecs_strbuf_append(v->buf, ")"); - // } + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } return 0; error: diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 38907e7d30..7f28730ad9 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -8,6 +8,13 @@ #ifdef FLECS_SCRIPT #include "../script.h" +static +int flecs_script_expr_visit_type_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc); + static bool flecs_expr_operator_is_equality( ecs_script_token_kind_t op) @@ -120,6 +127,7 @@ ecs_entity_t flecs_expr_promote_type( static bool flecs_expr_oper_valid_for_type( + ecs_world_t *world, ecs_entity_t type, ecs_script_token_kind_t op) { @@ -131,6 +139,11 @@ bool flecs_expr_oper_valid_for_type( return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: + if (ecs_get(world, type, EcsBitmask) != NULL) { + return true; + } + + /* fall through */ case EcsTokShiftLeft: case EcsTokShiftRight: return flecs_expr_is_type_integer(type); @@ -198,6 +211,14 @@ int flecs_expr_type_for_oper( const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { + /* Only primitives and bitmask constants are allowed */ + if (left->type == right->type) { + if (ecs_get(world, left->type, EcsBitmask) != NULL) { + *operand_type = left->type; + goto done; + } + } + char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, @@ -205,7 +226,7 @@ int flecs_expr_type_for_oper( lname, rname); ecs_os_free(lname); ecs_os_free(rname); - return 0; + goto error; } ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); @@ -249,12 +270,86 @@ int flecs_expr_type_for_oper( return -1; } +static +int flecs_expr_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) +{ + if (!cur) { + flecs_expr_visit_error(script, node, "missing type for initializer"); + goto error; + } + + ecs_entity_t type = ecs_meta_get_type(cur); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_push(cur); /* { */ + + if (ecs_meta_is_collection(cur) != node->is_collection) { + char *type_str = ecs_get_path(script->world, type); + if (node->is_collection) { + flecs_expr_visit_error(script, node, + "invalid collection literal for non-collection type '%s'" + " (expected '[]')", type_str); + } else { + flecs_expr_visit_error(script, node, + "invalid object literal for collection type '%s' (expected {})", + type_str); + } + + ecs_os_free(type_str); + goto error; + } + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + if (ecs_meta_next(cur)) { /* , */ + goto error; + } + } + + ecs_expr_initializer_element_t *elem = &elems[i]; + if (elem->member) { + if (ecs_meta_dotmember(cur, elem->member)) { /* x: */ + flecs_expr_visit_error(script, node, "cannot resolve member"); + goto error; + } + } + + ecs_entity_t elem_type = ecs_meta_get_type(cur); + if (flecs_script_expr_visit_type_priv(script, elem->value, cur, desc)) { + goto error; + } + + if (elem->value->type != elem_type) { + elem->value = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, elem_type); + } + + elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + } + + node->node.type = type; + + ecs_meta_pop(cur); /* } */ + + return 0; +error: + return -1; +} + static int flecs_expr_unary_visit_type( ecs_script_t *script, - ecs_expr_unary_t *node) + ecs_expr_unary_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->expr)) { + if (flecs_script_expr_visit_type_priv(script, node->expr, cur, desc)) { goto error; } @@ -274,7 +369,9 @@ int flecs_expr_unary_visit_type( static int flecs_expr_binary_visit_type( ecs_script_t *script, - ecs_expr_binary_t *node) + ecs_expr_binary_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { /* Operands must be of this type or casted to it */ ecs_entity_t operand_type = 0; @@ -282,11 +379,11 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } - if (flecs_script_expr_visit_type(script, node->right)) { + if (flecs_script_expr_visit_type_priv(script, node->right, cur, desc)) { goto error; } @@ -294,7 +391,7 @@ int flecs_expr_binary_visit_type( goto error; } - if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + if (!flecs_expr_oper_valid_for_type(script->world, result_type, node->operator)) { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_script_token_str(node->operator), type_str); @@ -322,25 +419,24 @@ int flecs_expr_binary_visit_type( static int flecs_expr_identifier_visit_type( ecs_script_t *script, - ecs_expr_identifier_t *node) + ecs_expr_identifier_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { - node->node.type = ecs_id(ecs_entity_t); - node->id = ecs_lookup(script->world, node->value); - if (!node->id) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; + if (cur) { + node->node.type = ecs_meta_get_type(cur); + } else { + node->node.type = ecs_id(ecs_entity_t); } return 0; -error: - return -1; } static int flecs_expr_variable_visit_type( ecs_script_t *script, - ecs_expr_variable_t *node) + ecs_expr_variable_t *node, + const ecs_script_expr_run_desc_t *desc) { node->node.type = ecs_id(ecs_entity_t); return 0; @@ -349,9 +445,10 @@ int flecs_expr_variable_visit_type( static int flecs_expr_member_visit_type( ecs_script_t *script, - ecs_expr_member_t *node) + ecs_expr_member_t *node, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { goto error; } @@ -402,13 +499,14 @@ int flecs_expr_member_visit_type( static int flecs_expr_element_visit_type( ecs_script_t *script, - ecs_expr_element_t *node) + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type(script, node->left)) { + if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { goto error; } - if (flecs_script_expr_visit_type(script, node->index)) { + if (flecs_script_expr_visit_type_priv(script, node->index, NULL, desc)) { goto error; } @@ -435,7 +533,15 @@ int flecs_expr_element_visit_type( if (is_entity_type) { if (node->index->kind == EcsExprIdentifier) { - node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->index; + node->node.type = desc->lookup_action( + script->world, ident->value, desc->lookup_ctx); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unresolved component identifier '%s'", + ident->value); + goto error; + } } else { flecs_expr_visit_error(script, node, "invalid component expression"); @@ -462,9 +568,12 @@ int flecs_expr_element_visit_type( return -1; } -int flecs_script_expr_visit_type( +static +int flecs_script_expr_visit_type_priv( ecs_script_t *script, - ecs_expr_node_t *node) + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); @@ -472,35 +581,54 @@ int flecs_script_expr_visit_type( case EcsExprValue: /* Value types are assigned by the AST */ break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprUnary: - if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + if (flecs_expr_unary_visit_type( + script, (ecs_expr_unary_t*)node, cur, desc)) + { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + if (flecs_expr_binary_visit_type( + script, (ecs_expr_binary_t*)node, cur, desc)) + { goto error; } break; case EcsExprIdentifier: - if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_visit_type( + script, (ecs_expr_identifier_t*)node, cur, desc)) + { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_visit_type( + script, (ecs_expr_variable_t*)node, desc)) + { goto error; } break; case EcsExprFunction: break; case EcsExprMember: - if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_visit_type( + script, (ecs_expr_member_t*)node, desc)) + { goto error; } break; case EcsExprElement: - if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_visit_type( + script, (ecs_expr_element_t*)node, desc)) + { goto error; } break; @@ -513,4 +641,18 @@ int flecs_script_expr_visit_type( return -1; } +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc) +{ + if (desc->type) { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, desc->type, NULL); + return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + } else { + return flecs_script_expr_visit_type_priv(script, node, NULL, desc); + } +} + #endif diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index 277102a665..07a6facf9b 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -194,7 +194,9 @@ __VA_ARGS__\ }\ )\ - pos = old_ptr;\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ ) #define LookAhead_3(tok1, tok2, tok3, ...)\ @@ -206,7 +208,9 @@ __VA_ARGS__\ }\ )\ - pos = old_ptr;\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ ) /* Open scope */ diff --git a/src/addons/script/serialize.c b/src/addons/script/serialize.c index ca04c803cb..fa427f1c9e 100644 --- a/src/addons/script/serialize.c +++ b/src/addons/script/serialize.c @@ -217,6 +217,8 @@ int flecs_expr_ser_type_op( ecs_strbuf_t *str, bool is_expr) { + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + switch(op->kind) { case EcsOpPush: case EcsOpPop: diff --git a/test/script/project.json b/test/script/project.json index 3c653ec9f7..9d86890573 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -419,6 +419,13 @@ "double_paren_literal", "lparen_add_add_rparen_mul_lparen_add_add_rparen", "float_result_add_2_int_literals", + "struct_result_implicit_members", + "struct_result_explicit_members", + "struct_result_explicit_members_reverse", + "struct_result_nested_implicit_members", + "struct_result_nested_explicit_members", + "struct_result_nested_explicit_members_reverse", + "struct_result_nested_explicit_dotmembers", "struct_result_add_2_int_literals", "struct_result_add_2_2_fields_int_literals", "struct_result_add_3_int_literals", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index a0e1d99ede..e3e089bf58 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -463,6 +463,7 @@ void Deserialize_enum(void) { ecs_world_t *world = ecs_init(); ecs_entity_t t = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "Color" }), .constants = { {"Red"}, {"Blue"}, {"Green"} } @@ -510,6 +511,7 @@ void Deserialize_bitmask(void) { ecs_world_t *world = ecs_init(); ecs_entity_t t = ecs_bitmask_init(world, &(ecs_bitmask_desc_t){ + .entity = ecs_entity(world, { .name = "Toppings" }), .constants = { {"Lettuce"}, {"Bacon"}, {"Tomato"}, {"Cheese"}, {"BLT", Bacon | Lettuce | Tomato} } @@ -1501,7 +1503,7 @@ void Deserialize_discover_type_int(void) { ecs_value_t v = {0}; test_assert(ecs_script_expr_run(world, "10", &v, NULL) != NULL); - test_uint(v.type, ecs_id(ecs_u64_t)); + test_uint(v.type, ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(uint64_t*)v.ptr, 10); ecs_value_free(world, v.type, v.ptr); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 5e6e7da4a5..0afb78ce20 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -595,6 +595,272 @@ void Expr_float_result_add_2_int_literals(void) { ecs_fini(world); } +void Expr_struct_result_implicit_members(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Position v = {0}; + const char *ptr = ecs_script_expr_run(world, "{5 + 5, 10 + 10}", &(ecs_value_t){ + .type = t, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.x, 10); + test_uint(v.y, 20); + + ecs_fini(world); +} + +void Expr_struct_result_explicit_members(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Position v = {0}; + const char *ptr = ecs_script_expr_run(world, "{x: 5 + 5, y: 10 + 10}", &(ecs_value_t){ + .type = t, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.x, 10); + test_uint(v.y, 20); + + ecs_fini(world); +} + + +void Expr_struct_result_explicit_members_reverse(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Position v = {0}; + const char *ptr = ecs_script_expr_run(world, "{y: 5 + 5, x: 10 + 10}", &(ecs_value_t){ + .type = t, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.y, 10); + test_uint(v.x, 20); + + ecs_fini(world); +} + +void Expr_struct_result_nested_implicit_members(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t line = ecs_struct(world, { + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line v = {0}; + const char *ptr = ecs_script_expr_run(world, "{{5 + 5, 10 + 10}, {10 + 20, 20 + 20}}", + &(ecs_value_t){ + .type = line, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.start.x, 10); + test_uint(v.start.y, 20); + test_uint(v.stop.x, 30); + test_uint(v.stop.y, 40); + + ecs_fini(world); +} + +void Expr_struct_result_nested_explicit_members(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Point" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t line = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Line" }), + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line v = {0}; + const char *ptr = ecs_script_expr_run(world, + "{start: {x: 5 + 5, y: 10 + 10}, stop: {x: 10 + 20, y: 20 + 20}}", + &(ecs_value_t){ + .type = line, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.start.x, 10); + test_uint(v.start.y, 20); + test_uint(v.stop.x, 30); + test_uint(v.stop.y, 40); + + ecs_fini(world); +} + +void Expr_struct_result_nested_explicit_members_reverse(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Point" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t line = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Line" }), + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line v = {0}; + const char *ptr = ecs_script_expr_run(world, + "{stop: {x: 5 + 5, y: 10 + 10}, start: {x: 10 + 20, y: 20 + 20}}", + &(ecs_value_t){ + .type = line, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.stop.x, 10); + test_uint(v.stop.y, 20); + test_uint(v.start.x, 30); + test_uint(v.start.y, 40); + + ecs_fini(world); +} + +void Expr_struct_result_nested_explicit_dotmembers(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Point" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t line = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Line" }), + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line v = {0}; + const char *ptr = ecs_script_expr_run(world, + "{stop.x: 5 + 5, start.x: 10 + 10, stop.y: 10 + 20, start.y: 20 + 20}", + &(ecs_value_t){ + .type = line, .ptr = &v + }, NULL); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_uint(v.stop.x, 10); + test_uint(v.stop.y, 30); + test_uint(v.start.x, 20); + test_uint(v.start.y, 40); + + ecs_fini(world); +} + void Expr_struct_result_add_2_int_literals(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 26bcf83476..ac4664980c 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -409,6 +409,13 @@ void Expr_double_paren_add_add(void); void Expr_double_paren_literal(void); void Expr_lparen_add_add_rparen_mul_lparen_add_add_rparen(void); void Expr_float_result_add_2_int_literals(void); +void Expr_struct_result_implicit_members(void); +void Expr_struct_result_explicit_members(void); +void Expr_struct_result_explicit_members_reverse(void); +void Expr_struct_result_nested_implicit_members(void); +void Expr_struct_result_nested_explicit_members(void); +void Expr_struct_result_nested_explicit_members_reverse(void); +void Expr_struct_result_nested_explicit_dotmembers(void); void Expr_struct_result_add_2_int_literals(void); void Expr_struct_result_add_2_2_fields_int_literals(void); void Expr_struct_result_add_3_int_literals(void); @@ -2229,6 +2236,34 @@ bake_test_case Expr_testcases[] = { "float_result_add_2_int_literals", Expr_float_result_add_2_int_literals }, + { + "struct_result_implicit_members", + Expr_struct_result_implicit_members + }, + { + "struct_result_explicit_members", + Expr_struct_result_explicit_members + }, + { + "struct_result_explicit_members_reverse", + Expr_struct_result_explicit_members_reverse + }, + { + "struct_result_nested_implicit_members", + Expr_struct_result_nested_implicit_members + }, + { + "struct_result_nested_explicit_members", + Expr_struct_result_nested_explicit_members + }, + { + "struct_result_nested_explicit_members_reverse", + Expr_struct_result_nested_explicit_members_reverse + }, + { + "struct_result_nested_explicit_dotmembers", + Expr_struct_result_nested_explicit_dotmembers + }, { "struct_result_add_2_int_literals", Expr_struct_result_add_2_int_literals @@ -3175,7 +3210,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 128, + 135, Expr_testcases }, { From ba96e9b4a69c355bb54ebb5f9d00c8bfd401f073 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 26 Nov 2024 21:42:38 -0800 Subject: [PATCH 11/83] Add multiline string support --- distr/flecs.c | 79 +++++++++++++++++++++++++---------- src/addons/script/tokenizer.c | 35 ++++++++++++++++ src/addons/script/tokenizer.h | 44 +++++++++---------- 3 files changed, 114 insertions(+), 44 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index a6063f30eb..68adb3b575 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4562,28 +4562,28 @@ typedef enum ecs_script_token_kind_t { EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', - EcsTokEq, - EcsTokNeq, - EcsTokGt, - EcsTokGtEq, - EcsTokLt, - EcsTokLtEq, - EcsTokAnd, - EcsTokOr, - EcsTokMatch, - EcsTokShiftLeft, - EcsTokShiftRight, - EcsTokIdentifier, - EcsTokString, - EcsTokNumber, - EcsTokKeywordModule, - EcsTokKeywordUsing, - EcsTokKeywordWith, - EcsTokKeywordIf, - EcsTokKeywordElse, - EcsTokKeywordTemplate, - EcsTokKeywordProp, - EcsTokKeywordConst, + EcsTokEq = 100, + EcsTokNeq = 101, + EcsTokGt = 102, + EcsTokGtEq = 103, + EcsTokLt = 104, + EcsTokLtEq = 105, + EcsTokAnd = 106, + EcsTokOr = 107, + EcsTokMatch = 108, + EcsTokShiftLeft = 109, + EcsTokShiftRight = 110, + EcsTokIdentifier = 111, + EcsTokString = 112, + EcsTokNumber = 113, + EcsTokKeywordModule = 114, + EcsTokKeywordUsing = 115, + EcsTokKeywordWith = 116, + EcsTokKeywordIf = 117, + EcsTokKeywordElse = 118, + EcsTokKeywordTemplate = 119, + EcsTokKeywordProp = 120, + EcsTokKeywordConst = 121, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { @@ -60105,6 +60105,38 @@ const char* flecs_script_string( return end + 2; } +static +const char* flecs_script_multiline_string( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out) +{ + char ch; + const char *end = pos + 1; + while ((ch = end[0]) && (ch != '`')) { + if (ch == '\\' && end[1] == '`') { + ch = '`'; + end ++; + } + end ++; + } + + if (ch != '`') { + return NULL; + } + + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; +} + const char* flecs_script_expr( ecs_script_parser_t *parser, const char *pos, @@ -60352,6 +60384,9 @@ const char* flecs_script_token( } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); + } else if (pos[0] == '`') { + return flecs_script_multiline_string(parser, pos, out); + } else if (flecs_script_is_identifier(pos[0])) { return flecs_script_identifier(parser, pos, out); } diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 985ba7caf0..29a59d3f99 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -379,6 +379,38 @@ const char* flecs_script_string( return end + 2; } +static +const char* flecs_script_multiline_string( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out) +{ + char ch; + const char *end = pos + 1; + while ((ch = end[0]) && (ch != '`')) { + if (ch == '\\' && end[1] == '`') { + ch = '`'; + end ++; + } + end ++; + } + + if (ch != '`') { + return NULL; + } + + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; +} + const char* flecs_script_expr( ecs_script_parser_t *parser, const char *pos, @@ -626,6 +658,9 @@ const char* flecs_script_token( } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); + } else if (pos[0] == '`') { + return flecs_script_multiline_string(parser, pos, out); + } else if (flecs_script_is_identifier(pos[0])) { return flecs_script_identifier(parser, pos, out); } diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index 11f801bf67..d457b999d4 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -32,28 +32,28 @@ typedef enum ecs_script_token_kind_t { EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', - EcsTokEq, - EcsTokNeq, - EcsTokGt, - EcsTokGtEq, - EcsTokLt, - EcsTokLtEq, - EcsTokAnd, - EcsTokOr, - EcsTokMatch, - EcsTokShiftLeft, - EcsTokShiftRight, - EcsTokIdentifier, - EcsTokString, - EcsTokNumber, - EcsTokKeywordModule, - EcsTokKeywordUsing, - EcsTokKeywordWith, - EcsTokKeywordIf, - EcsTokKeywordElse, - EcsTokKeywordTemplate, - EcsTokKeywordProp, - EcsTokKeywordConst, + EcsTokEq = 100, + EcsTokNeq = 101, + EcsTokGt = 102, + EcsTokGtEq = 103, + EcsTokLt = 104, + EcsTokLtEq = 105, + EcsTokAnd = 106, + EcsTokOr = 107, + EcsTokMatch = 108, + EcsTokShiftLeft = 109, + EcsTokShiftRight = 110, + EcsTokIdentifier = 111, + EcsTokString = 112, + EcsTokNumber = 113, + EcsTokKeywordModule = 114, + EcsTokKeywordUsing = 115, + EcsTokKeywordWith = 116, + EcsTokKeywordIf = 117, + EcsTokKeywordElse = 118, + EcsTokKeywordTemplate = 119, + EcsTokKeywordProp = 120, + EcsTokKeywordConst = 121, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { From fe06af4deeb4b9afbf6a761e6817a9a442b7a739 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 26 Nov 2024 21:54:21 -0800 Subject: [PATCH 12/83] Don't use static offsets for opaque types --- distr/flecs.c | 14 +++++++++++++- src/addons/script/expr/ast.h | 1 + src/addons/script/expr/visit_fold.c | 5 +++++ src/addons/script/expr/visit_type.c | 8 +++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 68adb3b575..3fc8741ad3 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4914,6 +4914,7 @@ typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; bool is_collection; + bool dynamic; } ecs_expr_initializer_t; typedef struct ecs_expr_identifier_t { @@ -75827,6 +75828,11 @@ int flecs_expr_initializer_pre_fold( } } + if (node->dynamic) { + *can_fold = false; + return 0; + } + return 0; error: return -1; @@ -76534,6 +76540,10 @@ int flecs_expr_initializer_visit_type( ecs_entity_t type = ecs_meta_get_type(cur); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + /* Opaque types do not have deterministic offsets */ + bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; + node->dynamic = is_opaque; + ecs_meta_push(cur); /* { */ if (ecs_meta_is_collection(cur) != node->is_collection) { @@ -76579,7 +76589,9 @@ int flecs_expr_initializer_visit_type( script, elem->value, elem_type); } - elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + if (!is_opaque) { + elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + } } node->node.type = type; diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 12a0e2f2fb..b50e105661 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -55,6 +55,7 @@ typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; bool is_collection; + bool dynamic; } ecs_expr_initializer_t; typedef struct ecs_expr_identifier_t { diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 728b81e670..7e29cb4b62 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -306,6 +306,11 @@ int flecs_expr_initializer_pre_fold( } } + if (node->dynamic) { + *can_fold = false; + return 0; + } + return 0; error: return -1; diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 7f28730ad9..a76d2feb54 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -285,6 +285,10 @@ int flecs_expr_initializer_visit_type( ecs_entity_t type = ecs_meta_get_type(cur); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + /* Opaque types do not have deterministic offsets */ + bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; + node->dynamic = is_opaque; + ecs_meta_push(cur); /* { */ if (ecs_meta_is_collection(cur) != node->is_collection) { @@ -330,7 +334,9 @@ int flecs_expr_initializer_visit_type( script, elem->value, elem_type); } - elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + if (!is_opaque) { + elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + } } node->node.type = type; From 7e24df70c51c904005ecf9a77ceac50bd9cff7b6 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 27 Nov 2024 00:25:28 -0800 Subject: [PATCH 13/83] Implement basic expression eval runtime --- distr/flecs.c | 639 ++++++++++++++++++++-------- src/addons/script/expr/ast.h | 38 +- src/addons/script/expr/expr.h | 98 +++++ src/addons/script/expr/parser.c | 40 +- src/addons/script/expr/util.c | 100 +++++ src/addons/script/expr/visit.h | 11 +- src/addons/script/expr/visit_eval.c | 235 ++++++++++ src/addons/script/expr/visit_fold.c | 140 +----- src/addons/script/expr/visit_type.c | 14 +- src/addons/script/script.h | 3 +- 10 files changed, 959 insertions(+), 359 deletions(-) create mode 100644 src/addons/script/expr/expr.h create mode 100644 src/addons/script/expr/util.c create mode 100644 src/addons/script/expr/visit_eval.c diff --git a/distr/flecs.c b/distr/flecs.c index 3fc8741ad3..b2739129a2 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4857,6 +4857,14 @@ ecs_script_if_t* flecs_script_insert_if( #endif +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_EXPR_SCRIPT_H +#define FLECS_EXPR_SCRIPT_H + /** * @file addons/script/expr_ast.h * @brief Script expression AST. @@ -4884,24 +4892,31 @@ struct ecs_expr_node_t { const char *pos; }; +typedef union ecs_expr_small_val_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + const char *string; + ecs_entity_t entity; + ecs_id_t id; +} ecs_expr_small_val_t; + typedef struct ecs_expr_val_t { ecs_expr_node_t node; void *ptr; - union { - bool bool_; - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - double f32; - double f64; - const char *string; - ecs_entity_t entity; - } storage; + ecs_expr_small_val_t storage; } ecs_expr_val_t; typedef struct ecs_expr_initializer_element_t { @@ -4925,6 +4940,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *value; + ecs_script_var_t *var; } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { @@ -5024,8 +5040,9 @@ ecs_expr_cast_t* flecs_expr_cast( #define flecs_expr_visit_error(script, node, ...) \ ecs_parser_error( \ - script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ - __VA_ARGS__); + script->name, script->code, \ + ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); int flecs_script_expr_visit_type( ecs_script_t *script, @@ -5037,6 +5054,101 @@ int flecs_script_expr_visit_fold( ecs_expr_node_t **node, const ecs_script_expr_run_desc_t *desc); +int flecs_script_expr_visit_eval( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_value_t *out); + +#endif + + +int flecs_value_copy_to( + ecs_world_t *world, + ecs_value_t *dst, + const ecs_value_t *src); + +int flecs_value_binary( + ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_script_token_kind_t operator); + +#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + #endif /** @@ -75475,135 +75587,358 @@ const char* ecs_script_expr_run( // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (!value->type) { - if (priv_desc.type) { - /* If explicit out type is provided, use that */ - value->type = priv_desc.type; - } else { - /* Otherwise use resolved expression type */ - value->type = out->type; - } + if (flecs_script_expr_visit_eval(script, out, desc, value)) { + goto error; } - if (value->type && !value->ptr) { - value->ptr = ecs_value_new(world, value->type); - } + return result; +error: + return NULL; +} + +#endif - ecs_assert(value->type != 0 && value->ptr != NULL, ECS_INVALID_OPERATION, - "failed to allocate storage for expression result"); +/** + * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. + */ - if (out->kind == EcsExprValue) { - if (value->type == out->type) { - /* Output value is same as expression, copy value */ - ecs_value_copy(world, value->type, value->ptr, - ((ecs_expr_val_t*)out)->ptr); - } else { - /* Cast value to desired output type */ - ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, value->type, value->ptr); - ecs_value_t expr_result = { - .type = out->type, - .ptr = ((ecs_expr_val_t*)out)->ptr - }; +#ifdef FLECS_SCRIPT - if (ecs_meta_set_value(&cur, &expr_result)) { - goto error; - } - } +int flecs_value_copy_to( + ecs_world_t *world, + ecs_value_t *dst, + const ecs_value_t *src) +{ + if (src->type == dst->type) { + /* Outputvalue issame asexpoutsion, copy value */ + ecs_value_copy(world, src->type, dst->ptr, src->ptr); } else { - ecs_abort(ECS_UNSUPPORTED, "can't evaluate dynamic expressions yet"); + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, src)) { + goto error; + } } - return result; + return 0; error: - return NULL; + return -1; +} + +int flecs_value_binary( + ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_script_token_kind_t operator) +{ + switch(operator) { + case EcsTokAdd: + ECS_BINARY_OP(left, right, out, +); + break; + case EcsTokSub: + ECS_BINARY_OP(left, right, out, -); + break; + case EcsTokMul: + ECS_BINARY_OP(left, right, out, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(left, right, out, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(left, right, out, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(left, right, out, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(left, right, out, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(left, right, out, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(left, right, out, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(left, right, out, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(left, right, out, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(left, right, out, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(left, right, out, ||); + break; + case EcsTokBitwiseAnd: + ECS_BINARY_INT_OP(left, right, out, &); + break; + case EcsTokBitwiseOr: + ECS_BINARY_INT_OP(left, right, out, |); + break; + case EcsTokShiftLeft: + ECS_BINARY_INT_OP(left, right, out, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_INT_OP(left, right, out, >>); + break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); + goto error; + } + + return 0; +error: + return -1; } #endif /** - * @file addons/script/expr_fold.c - * @brief Script expression constant folding. + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. */ #ifdef FLECS_SCRIPT -#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) +typedef struct ecs_eval_value_t { + ecs_value_t value; + ecs_expr_small_val_t storage; +} ecs_eval_value_t; -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) +static +int flecs_script_expr_visit_eval_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out); -#define ECS_BINARY_INT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ +static +void flecs_expr_value_alloc( + ecs_script_t *script, + ecs_eval_value_t *val, + ecs_entity_t type) +{ + const EcsPrimitive *p = ecs_get(script->world, type, EcsPrimitive); + if (!p) { + ecs_abort(ECS_UNSUPPORTED, + "non-primitive temporary values not yet supported"); + } + + val->value.type = type; + + switch (p->kind) { + case EcsBool: val->value.ptr = &val->storage.bool_; break; + case EcsChar: val->value.ptr = &val->storage.char_; break; + case EcsByte: val->value.ptr = &val->storage.byte_; break; + case EcsU8: val->value.ptr = &val->storage.u8; break; + case EcsU16: val->value.ptr = &val->storage.u16; break; + case EcsU32: val->value.ptr = &val->storage.u32; break; + case EcsU64: val->value.ptr = &val->storage.u64; break; + case EcsI8: val->value.ptr = &val->storage.i8; break; + case EcsI16: val->value.ptr = &val->storage.i16; break; + case EcsI32: val->value.ptr = &val->storage.i32; break; + case EcsI64: val->value.ptr = &val->storage.i64; break; + case EcsF32: val->value.ptr = &val->storage.f32; break; + case EcsF64: val->value.ptr = &val->storage.f64; break; + case EcsUPtr: val->value.ptr = &val->storage.uptr; break; + case EcsIPtr: val->value.ptr = &val->storage.iptr; break; + case EcsString: val->value.ptr = &val->storage.string; break; + case EcsEntity: val->value.ptr = &val->storage.entity; break; + case EcsId: val->value.ptr = &val->storage.id; break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid primitive kind"); } +} -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ +static +int flecs_expr_value_visit_eval( + ecs_script_t *script, + ecs_expr_val_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + out->value.type = node->node.type; + out->value.ptr = node->ptr; + return 0; +} + +static +int flecs_expr_binary_visit_eval( + ecs_script_t *script, + ecs_expr_binary_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t left = {{0}}; + ecs_eval_value_t right = {{0}}; + + /* Evaluate left & right expressions */ + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &left)) { + goto error; + } + if (flecs_script_expr_visit_eval_priv(script, node->right, desc, &right)) { + goto error; } -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + if (flecs_value_binary( + script, &left.value, &right.value, &out->value, node->operator)) + { + goto error; } -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + return 0; +error: + return -1; +} + +static +int flecs_expr_variable_visit_eval( + ecs_script_t *script, + ecs_expr_variable_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + const ecs_script_var_t *var = node->var; + /* Should've been populated by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + out->value = var->value; + return 0; +} + +static +int flecs_expr_cast_visit_eval( + ecs_script_t *script, + ecs_expr_cast_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + /* Evaluate expression to cast */ + if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + goto error; } -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + /* Copy expression result to storage of casted-to type */ + if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { + goto error; } -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + return 0; +error: + return -1; +} + +static +int flecs_script_expr_visit_eval_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_visit_eval( + script, (ecs_expr_val_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprInitializer: + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_eval( + script, (ecs_expr_binary_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_eval( + script, (ecs_expr_variable_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + if (flecs_expr_cast_visit_eval( + script, (ecs_expr_cast_t*)node, desc, out)) + { + goto error; + } + break; + } + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_eval( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_value_t *out) +{ + ecs_eval_value_t val = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node, desc, &val)) { + goto error; + } + + if (desc && !out->type) { + out->type = desc->type; } + if (!out->type) { + out->type = node->type; + } + + if (out->type && !out->ptr) { + out->ptr = ecs_value_new(script->world, out->type); + } + + flecs_value_copy_to(script->world, out, &val.value); + + return 0; +error: + return -1; +} + +#endif + +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + + +#ifdef FLECS_SCRIPT + int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75665,10 +76000,13 @@ int flecs_expr_binary_visit_fold( } if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals for now */ + /* Only folding literals */ return 0; } + ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; + ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + ecs_expr_val_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); result->ptr = &result->storage.u64; @@ -75682,66 +76020,15 @@ int flecs_expr_binary_visit_fold( ECS_INTERNAL_ERROR, NULL); ecs_assert(node->right->type == node->node.type, ECS_INTERNAL_ERROR, NULL); - ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; - ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; goto done; } - switch(node->operator) { - case EcsTokAdd: - ECS_BINARY_OP(node->left, node->right, result, +); - break; - case EcsTokSub: - ECS_BINARY_OP(node->left, node->right, result, -); - break; - case EcsTokMul: - ECS_BINARY_OP(node->left, node->right, result, *); - break; - case EcsTokDiv: - ECS_BINARY_OP(node->left, node->right, result, /); - break; - case EcsTokMod: - ECS_BINARY_INT_OP(node->left, node->right, result, %); - break; - case EcsTokEq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); - break; - case EcsTokNeq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); - break; - case EcsTokGt: - ECS_BINARY_COND_OP(node->left, node->right, result, >); - break; - case EcsTokGtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, >=); - break; - case EcsTokLt: - ECS_BINARY_COND_OP(node->left, node->right, result, <); - break; - case EcsTokLtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, <=); - break; - case EcsTokAnd: - ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); - break; - case EcsTokOr: - ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); - break; - case EcsTokBitwiseAnd: - ECS_BINARY_INT_OP(node->left, node->right, result, &); - break; - case EcsTokBitwiseOr: - ECS_BINARY_INT_OP(node->left, node->right, result, |); - break; - case EcsTokShiftLeft: - ECS_BINARY_INT_OP(node->left, node->right, result, <<); - break; - case EcsTokShiftRight: - ECS_BINARY_INT_OP(node->left, node->right, result, >>); - break; - default: - flecs_expr_visit_error(script, node->left, "unsupported operator"); + ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; + ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; + ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; + + if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { goto error; } @@ -76699,8 +76986,20 @@ int flecs_expr_variable_visit_type( ecs_expr_variable_t *node, const ecs_script_expr_run_desc_t *desc) { - node->node.type = ecs_id(ecs_entity_t); + ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->value); + if (!var) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->value); + goto error; + } + + node->node.type = var->value.type; + node->var = var; + return 0; +error: + return -1; } static diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index b50e105661..4eff33445a 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -25,24 +25,31 @@ struct ecs_expr_node_t { const char *pos; }; +typedef union ecs_expr_small_val_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + const char *string; + ecs_entity_t entity; + ecs_id_t id; +} ecs_expr_small_val_t; + typedef struct ecs_expr_val_t { ecs_expr_node_t node; void *ptr; - union { - bool bool_; - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - double f32; - double f64; - const char *string; - ecs_entity_t entity; - } storage; + ecs_expr_small_val_t storage; } ecs_expr_val_t; typedef struct ecs_expr_initializer_element_t { @@ -66,6 +73,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *value; + ecs_script_var_t *var; } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h new file mode 100644 index 0000000000..2653603133 --- /dev/null +++ b/src/addons/script/expr/expr.h @@ -0,0 +1,98 @@ +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_EXPR_SCRIPT_H +#define FLECS_EXPR_SCRIPT_H + +#include "ast.h" +#include "visit.h" + +int flecs_value_copy_to( + ecs_world_t *world, + ecs_value_t *dst, + const ecs_value_t *src); + +int flecs_value_binary( + ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_script_token_kind_t operator); + +#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#endif diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 3f389bc21e..519727436a 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -529,44 +529,8 @@ const char* ecs_script_expr_run( // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (!value->type) { - if (priv_desc.type) { - /* If explicit out type is provided, use that */ - value->type = priv_desc.type; - } else { - /* Otherwise use resolved expression type */ - value->type = out->type; - } - } - - if (value->type && !value->ptr) { - value->ptr = ecs_value_new(world, value->type); - } - - ecs_assert(value->type != 0 && value->ptr != NULL, ECS_INVALID_OPERATION, - "failed to allocate storage for expression result"); - - if (out->kind == EcsExprValue) { - if (value->type == out->type) { - /* Output value is same as expression, copy value */ - ecs_value_copy(world, value->type, value->ptr, - ((ecs_expr_val_t*)out)->ptr); - } else { - /* Cast value to desired output type */ - ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, value->type, value->ptr); - - ecs_value_t expr_result = { - .type = out->type, - .ptr = ((ecs_expr_val_t*)out)->ptr - }; - - if (ecs_meta_set_value(&cur, &expr_result)) { - goto error; - } - } - } else { - ecs_abort(ECS_UNSUPPORTED, "can't evaluate dynamic expressions yet"); + if (flecs_script_expr_visit_eval(script, out, desc, value)) { + goto error; } return result; diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c new file mode 100644 index 0000000000..6883d6c522 --- /dev/null +++ b/src/addons/script/expr/util.c @@ -0,0 +1,100 @@ +/** + * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "../script.h" + +int flecs_value_copy_to( + ecs_world_t *world, + ecs_value_t *dst, + const ecs_value_t *src) +{ + if (src->type == dst->type) { + /* Outputvalue issame asexpoutsion, copy value */ + ecs_value_copy(world, src->type, dst->ptr, src->ptr); + } else { + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, src)) { + goto error; + } + } + + return 0; +error: + return -1; +} + +int flecs_value_binary( + ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_script_token_kind_t operator) +{ + switch(operator) { + case EcsTokAdd: + ECS_BINARY_OP(left, right, out, +); + break; + case EcsTokSub: + ECS_BINARY_OP(left, right, out, -); + break; + case EcsTokMul: + ECS_BINARY_OP(left, right, out, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(left, right, out, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(left, right, out, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(left, right, out, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(left, right, out, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(left, right, out, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(left, right, out, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(left, right, out, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(left, right, out, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(left, right, out, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(left, right, out, ||); + break; + case EcsTokBitwiseAnd: + ECS_BINARY_INT_OP(left, right, out, &); + break; + case EcsTokBitwiseOr: + ECS_BINARY_INT_OP(left, right, out, |); + break; + case EcsTokShiftLeft: + ECS_BINARY_INT_OP(left, right, out, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_INT_OP(left, right, out, >>); + break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); + goto error; + } + + return 0; +error: + return -1; +} + +#endif diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index 96e963b5fa..b647ee5bb7 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -8,8 +8,9 @@ #define flecs_expr_visit_error(script, node, ...) \ ecs_parser_error( \ - script->name, script->code, ((ecs_expr_node_t*)node)->pos - script->code, \ - __VA_ARGS__); + script->name, script->code, \ + ((ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); int flecs_script_expr_visit_type( ecs_script_t *script, @@ -21,4 +22,10 @@ int flecs_script_expr_visit_fold( ecs_expr_node_t **node, const ecs_script_expr_run_desc_t *desc); +int flecs_script_expr_visit_eval( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_value_t *out); + #endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c new file mode 100644 index 0000000000..6d224005ac --- /dev/null +++ b/src/addons/script/expr/visit_eval.c @@ -0,0 +1,235 @@ +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "../script.h" + +typedef struct ecs_eval_value_t { + ecs_value_t value; + ecs_expr_small_val_t storage; +} ecs_eval_value_t; + +static +int flecs_script_expr_visit_eval_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out); + +static +void flecs_expr_value_alloc( + ecs_script_t *script, + ecs_eval_value_t *val, + ecs_entity_t type) +{ + const EcsPrimitive *p = ecs_get(script->world, type, EcsPrimitive); + if (!p) { + ecs_abort(ECS_UNSUPPORTED, + "non-primitive temporary values not yet supported"); + } + + val->value.type = type; + + switch (p->kind) { + case EcsBool: val->value.ptr = &val->storage.bool_; break; + case EcsChar: val->value.ptr = &val->storage.char_; break; + case EcsByte: val->value.ptr = &val->storage.byte_; break; + case EcsU8: val->value.ptr = &val->storage.u8; break; + case EcsU16: val->value.ptr = &val->storage.u16; break; + case EcsU32: val->value.ptr = &val->storage.u32; break; + case EcsU64: val->value.ptr = &val->storage.u64; break; + case EcsI8: val->value.ptr = &val->storage.i8; break; + case EcsI16: val->value.ptr = &val->storage.i16; break; + case EcsI32: val->value.ptr = &val->storage.i32; break; + case EcsI64: val->value.ptr = &val->storage.i64; break; + case EcsF32: val->value.ptr = &val->storage.f32; break; + case EcsF64: val->value.ptr = &val->storage.f64; break; + case EcsUPtr: val->value.ptr = &val->storage.uptr; break; + case EcsIPtr: val->value.ptr = &val->storage.iptr; break; + case EcsString: val->value.ptr = &val->storage.string; break; + case EcsEntity: val->value.ptr = &val->storage.entity; break; + case EcsId: val->value.ptr = &val->storage.id; break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid primitive kind"); + } +} + +static +int flecs_expr_value_visit_eval( + ecs_script_t *script, + ecs_expr_val_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + out->value.type = node->node.type; + out->value.ptr = node->ptr; + return 0; +} + +static +int flecs_expr_binary_visit_eval( + ecs_script_t *script, + ecs_expr_binary_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t left = {{0}}; + ecs_eval_value_t right = {{0}}; + + /* Evaluate left & right expressions */ + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &left)) { + goto error; + } + if (flecs_script_expr_visit_eval_priv(script, node->right, desc, &right)) { + goto error; + } + + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + if (flecs_value_binary( + script, &left.value, &right.value, &out->value, node->operator)) + { + goto error; + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_variable_visit_eval( + ecs_script_t *script, + ecs_expr_variable_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + const ecs_script_var_t *var = node->var; + /* Should've been populated by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + out->value = var->value; + return 0; +} + +static +int flecs_expr_cast_visit_eval( + ecs_script_t *script, + ecs_expr_cast_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + /* Evaluate expression to cast */ + if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + goto error; + } + + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + /* Copy expression result to storage of casted-to type */ + if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { + goto error; + } + + return 0; +error: + return -1; +} + +static +int flecs_script_expr_visit_eval_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_visit_eval( + script, (ecs_expr_val_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprInitializer: + break; + case EcsExprUnary: + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_eval( + script, (ecs_expr_binary_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_eval( + script, (ecs_expr_variable_t*)node, desc, out)) + { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + if (flecs_expr_cast_visit_eval( + script, (ecs_expr_cast_t*)node, desc, out)) + { + goto error; + } + break; + } + + return 0; +error: + return -1; +} + +int flecs_script_expr_visit_eval( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_value_t *out) +{ + ecs_eval_value_t val = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node, desc, &val)) { + goto error; + } + + if (desc && !out->type) { + out->type = desc->type; + } + + if (!out->type) { + out->type = node->type; + } + + if (out->type && !out->ptr) { + out->ptr = ecs_value_new(script->world, out->type); + } + + flecs_value_copy_to(script->world, out, &val.value); + + return 0; +error: + return -1; +} + +#endif diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 7e29cb4b62..b57f780c95 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -8,80 +8,6 @@ #ifdef FLECS_SCRIPT #include "../script.h" -#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -143,10 +69,13 @@ int flecs_expr_binary_visit_fold( } if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals for now */ + /* Only folding literals */ return 0; } + ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; + ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + ecs_expr_val_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); result->ptr = &result->storage.u64; @@ -160,66 +89,15 @@ int flecs_expr_binary_visit_fold( ECS_INTERNAL_ERROR, NULL); ecs_assert(node->right->type == node->node.type, ECS_INTERNAL_ERROR, NULL); - ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; - ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; goto done; } - switch(node->operator) { - case EcsTokAdd: - ECS_BINARY_OP(node->left, node->right, result, +); - break; - case EcsTokSub: - ECS_BINARY_OP(node->left, node->right, result, -); - break; - case EcsTokMul: - ECS_BINARY_OP(node->left, node->right, result, *); - break; - case EcsTokDiv: - ECS_BINARY_OP(node->left, node->right, result, /); - break; - case EcsTokMod: - ECS_BINARY_INT_OP(node->left, node->right, result, %); - break; - case EcsTokEq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); - break; - case EcsTokNeq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); - break; - case EcsTokGt: - ECS_BINARY_COND_OP(node->left, node->right, result, >); - break; - case EcsTokGtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, >=); - break; - case EcsTokLt: - ECS_BINARY_COND_OP(node->left, node->right, result, <); - break; - case EcsTokLtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, <=); - break; - case EcsTokAnd: - ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); - break; - case EcsTokOr: - ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); - break; - case EcsTokBitwiseAnd: - ECS_BINARY_INT_OP(node->left, node->right, result, &); - break; - case EcsTokBitwiseOr: - ECS_BINARY_INT_OP(node->left, node->right, result, |); - break; - case EcsTokShiftLeft: - ECS_BINARY_INT_OP(node->left, node->right, result, <<); - break; - case EcsTokShiftRight: - ECS_BINARY_INT_OP(node->left, node->right, result, >>); - break; - default: - flecs_expr_visit_error(script, node->left, "unsupported operator"); + ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; + ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; + ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; + + if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { goto error; } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index a76d2feb54..a63f9cf675 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -444,8 +444,20 @@ int flecs_expr_variable_visit_type( ecs_expr_variable_t *node, const ecs_script_expr_run_desc_t *desc) { - node->node.type = ecs_id(ecs_entity_t); + ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->value); + if (!var) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->value); + goto error; + } + + node->node.type = var->value.type; + node->var = var; + return 0; +error: + return -1; } static diff --git a/src/addons/script/script.h b/src/addons/script/script.h index a9b6416e9b..0bc26f75ae 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -45,8 +45,7 @@ struct ecs_script_parser_t { }; #include "ast.h" -#include "expr/ast.h" -#include "expr/visit.h" +#include "expr/expr.h" #include "visit.h" #include "visit_eval.h" From 57a55c6203da44573ebf41434d82f85cc567d760 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 27 Nov 2024 00:26:36 -0800 Subject: [PATCH 14/83] Remove legacy expr implementation --- distr/flecs.c | 1872 ++------------------------------------ src/addons/script/expr.c | 1767 ----------------------------------- 2 files changed, 100 insertions(+), 3539 deletions(-) delete mode 100644 src/addons/script/expr.c diff --git a/distr/flecs.c b/distr/flecs.c index b2739129a2..4c61a7cf3d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4960,6 +4960,7 @@ typedef struct ecs_expr_member_t { ecs_expr_node_t node; ecs_expr_node_t *left; const char *member_name; + uintptr_t offset; } ecs_expr_member_t; typedef struct ecs_expr_element_t { @@ -5075,6 +5076,12 @@ int flecs_value_binary( ecs_value_t *out, ecs_script_token_kind_t operator); +int flecs_value_unary( + ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_script_token_kind_t operator); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ @@ -54846,1772 +54853,6 @@ ecs_script_if_t* flecs_script_insert_if( #endif -/** - * @file addons/script/expr.c - * @brief Evaluate script expressions. - */ - - -#ifdef FLECS_SCRIPT -#include -#include - -/* String deserializer for script expressions */ - -/* Order in enumeration is important, as it is used for precedence */ -typedef enum ecs_expr_oper_t { - EcsExprOperUnknown, - EcsLeftParen, - EcsCondAnd, - EcsCondOr, - EcsCondEq, - EcsCondNeq, - EcsCondGt, - EcsCondGtEq, - EcsCondLt, - EcsCondLtEq, - EcsShiftLeft, - EcsShiftRight, - EcsAdd, - EcsSub, - EcsMul, - EcsDiv, - EcsMin -} ecs_expr_oper_t; - -/* Used to track temporary values */ -#define FLECS_EXPR_MAX_STACK_SIZE (256) - -typedef struct ecs_expr_value_t { - const ecs_type_info_t *ti; - void *ptr; -} ecs_expr_value_t; - -typedef struct ecs_value_stack_t { - ecs_expr_value_t values[FLECS_EXPR_MAX_STACK_SIZE]; - ecs_stack_cursor_t *cursor; - ecs_stack_t *stack; - ecs_stage_t *stage; - int32_t count; -} ecs_value_stack_t; - -static -const char* flecs_script_expr_run( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *ptr, - ecs_value_t *value, - ecs_expr_oper_t op, - const ecs_script_expr_run_desc_t *desc); - -#define TOK_VARIABLE '$' - -/* -- Private functions -- */ - -static -bool flecs_isident( - char ch) -{ - return isalpha(ch) || (ch == '_'); -} - -static -bool flecs_valid_identifier_start_char( - char ch) -{ - if (ch && (flecs_isident(ch) || (ch == '*') || - (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) - { - return true; - } - - return false; -} - -static -bool flecs_valid_token_start_char( - char ch) -{ - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || (ch == '`') || - flecs_valid_identifier_start_char(ch)) - { - return true; - } - - return false; -} - -static -bool flecs_valid_token_char( - char ch) -{ - if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { - return true; - } - - return false; -} - -static -const char* flecs_parse_ws( - const char *ptr) -{ - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; - } - - return ptr; -} - -static -const char* flecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out, - char delim) -{ - int64_t column = ptr - expr; - - ptr = flecs_parse_ws(ptr); - char *tptr = token_out, ch = ptr[0]; - - if (!flecs_valid_token_start_char(ch)) { - if (ch == '\0' || ch == '\n') { - ecs_parser_error(name, expr, column, - "unexpected end of expression"); - } else { - ecs_parser_error(name, expr, column, - "invalid start of token '%s'", ptr); - } - return NULL; - } - - bool isDigit = isdigit(ch) || (ch == '-'); - - if (ch == '-') { - if (!isdigit(ptr[1]) && ptr[1] != '$' && ptr[1] != '(') { - ecs_parser_error(name, expr, column, - "invalid number token"); - return NULL; - } - if (ptr[1] == '$' || ptr[1] == '(') { - isDigit = false; /* -$var */ - } - } - - bool hasDot = false; - bool hasE = false; - - tptr[0] = ch; - tptr ++; - ptr ++; - - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { - tptr[0] = 0; - return ptr; - } - - int tmpl_nesting = 0; - bool in_str = ch == '"'; - - for (; (ch = *ptr); ptr ++) { - if (ch == '<') { - tmpl_nesting ++; - } else if (ch == '>') { - if (!tmpl_nesting) { - break; - } - tmpl_nesting --; - } else if (ch == '"') { - in_str = !in_str; - } else if (ch == '\\') { - ptr ++; - tptr[0] = ptr[0]; - tptr ++; - continue; - } else if (!flecs_valid_token_char(ch) && !in_str) { - break; - } - - if (isDigit) { - if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - if (ch == '.') { - if (hasDot) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - hasDot = true; - } - if (ch == 'e') { - if (hasE) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - hasE = true; - } - } - - if (delim && (ch == delim)) { - break; - } - - tptr[0] = ch; - tptr ++; - } - - tptr[0] = '\0'; - - if (tmpl_nesting != 0) { - ecs_parser_error(name, expr, column, - "identifier '%s' has mismatching < > pairs", ptr); - return NULL; - } - - const char *next_ptr = flecs_parse_ws(ptr); - if (next_ptr[0] == ':' && next_ptr != ptr) { - /* Whitespace between token and : is significant */ - ptr = next_ptr - 1; - } else { - ptr = next_ptr; - } - - return ptr; -} - -static -void* flecs_expr_value_new( - ecs_value_stack_t *stack, - ecs_entity_t type) -{ - ecs_stage_t *stage = stack->stage; - ecs_world_t *world = stage->world; - ecs_id_record_t *idr = flecs_id_record_get(world, type); - if (!idr) { - return NULL; - } - - const ecs_type_info_t *ti = idr->type_info; - if (!ti) { - return NULL; - } - - ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); - void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); - if (ti->hooks.ctor) { - ti->hooks.ctor(result, 1, ti); - } else { - ecs_os_memset(result, 0, ti->size); - } - if (ti->hooks.dtor) { - /* Track values that have destructors */ - stack->values[stack->count].ti = ti; - stack->values[stack->count].ptr = result; - stack->count ++; - } - - return result; -} - -static -const char* flecs_str_to_expr_oper( - const char *str, - ecs_expr_oper_t *op) -{ - if (!ecs_os_strncmp(str, "+", 1)) { - *op = EcsAdd; - return str + 1; - } else if (!ecs_os_strncmp(str, "-", 1)) { - *op = EcsSub; - return str + 1; - } else if (!ecs_os_strncmp(str, "*", 1)) { - *op = EcsMul; - return str + 1; - } else if (!ecs_os_strncmp(str, "/", 1)) { - *op = EcsDiv; - return str + 1; - } else if (!ecs_os_strncmp(str, "&&", 2)) { - *op = EcsCondAnd; - return str + 2; - } else if (!ecs_os_strncmp(str, "||", 2)) { - *op = EcsCondOr; - return str + 2; - } else if (!ecs_os_strncmp(str, "==", 2)) { - *op = EcsCondEq; - return str + 2; - } else if (!ecs_os_strncmp(str, "!=", 2)) { - *op = EcsCondNeq; - return str + 2; - } else if (!ecs_os_strncmp(str, ">=", 2)) { - *op = EcsCondGtEq; - return str + 2; - } else if (!ecs_os_strncmp(str, "<=", 2)) { - *op = EcsCondLtEq; - return str + 2; - } else if (!ecs_os_strncmp(str, ">>", 2)) { - *op = EcsShiftRight; - return str + 2; - } else if (!ecs_os_strncmp(str, "<<", 2)) { - *op = EcsShiftLeft; - return str + 2; - } else if (!ecs_os_strncmp(str, ">", 1)) { - *op = EcsCondGt; - return str + 1; - } else if (!ecs_os_strncmp(str, "<", 1)) { - *op = EcsCondLt; - return str + 1; - } - - *op = EcsExprOperUnknown; - return NULL; -} - -static -const char *flecs_script_expr_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token) -{ - char *token_ptr = token; - - if (ptr[0] == '/') { - char ch; - if (ptr[1] == '/') { - // Single line comment - for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} - token[0] = 0; - return flecs_parse_ws_eol(ptr); - } else if (ptr[1] == '*') { - // Multi line comment - for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { - if (ch == '*' && ptr[1] == '/') { - token[0] = 0; - return flecs_parse_ws_eol(ptr + 2); - } - } - - ecs_parser_error(name, expr, ptr - expr, - "missing */ for multiline comment"); - return NULL; - } - } - - ecs_expr_oper_t op; - if (ptr[0] == '(') { - token[0] = '('; - token[1] = 0; - return ptr + 1; - } else if (ptr[0] != '-') { - const char *tptr = flecs_str_to_expr_oper(ptr, &op); - if (tptr) { - ecs_os_strncpy(token, ptr, tptr - ptr); - return tptr; - } - } - - while ((ptr = flecs_parse_expr_token(name, expr, ptr, token_ptr, 0))) { - if (ptr[0] == '|' && ptr[1] != '|') { - token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; - token_ptr[0] = '|'; - token_ptr[1] = '\0'; - token_ptr ++; - ptr ++; - } else { - break; - } - } - - return ptr; -} - -static -const char* flecs_parse_multiline_string( - ecs_meta_cursor_t *cur, - const char *name, - const char *expr, - const char *ptr) -{ - /* Multiline string */ - ecs_strbuf_t str = ECS_STRBUF_INIT; - char ch; - while ((ch = ptr[0]) && (ch != '`')) { - if (ch == '\\' && ptr[1] == '`') { - ch = '`'; - ptr ++; - } - ecs_strbuf_appendch(&str, ch); - ptr ++; - } - - if (ch != '`') { - ecs_parser_error(name, expr, ptr - expr, - "missing '`' to close multiline string"); - goto error; - } - - char *strval = ecs_strbuf_get(&str); - if (ecs_meta_set_string(cur, strval) != 0) { - ecs_os_free(strval); - goto error; - } - ecs_os_free(strval); - - return ptr + 1; -error: - return NULL; -} - -static -bool flecs_parse_is_float( - const char *ptr) -{ - ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); - char ch; - while ((ch = (++ptr)[0])) { - if (ch == '.' || ch == 'e') { - return true; - } - if (!isdigit(ch)) { - return false; - } - } - return false; -} - -/* Attempt to resolve variable dotexpression to value (foo.bar) */ -static -ecs_value_t flecs_dotresolve_var( - ecs_world_t *world, - ecs_script_vars_t *vars, - char *token) -{ - char *dot = strchr(token, '.'); - if (!dot) { - return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; - } - - dot[0] = '\0'; - - const ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); - if (!var) { - return (ecs_value_t){0}; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor( - world, var->value.type, var->value.ptr); - ecs_meta_push(&cur); - if (ecs_meta_dotmember(&cur, dot + 1) != 0) { - return (ecs_value_t){0}; - } - - return (ecs_value_t){ - .ptr = ecs_meta_get_ptr(&cur), - .type = ecs_meta_get_type(&cur) - }; -} - -static -int flecs_meta_call( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_meta_cursor_t *cur, - const char *function) -{ - ecs_entity_t type = ecs_meta_get_type(cur); - void *value_ptr = ecs_meta_get_ptr(cur); - - if (!ecs_os_strcmp(function, "parent")) { - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "parent() can only be called on entity"); - return -1; - } - - *(ecs_entity_t*)value_ptr = ecs_get_parent( - world, *(ecs_entity_t*)value_ptr); - } else if (!ecs_os_strcmp(function, "name")) { - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "name() can only be called on entity"); - return -1; - } - - char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); - *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); - *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); - } else if (!ecs_os_strcmp(function, "doc_name")) { -#ifdef FLECS_DOC - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "name() can only be called on entity"); - return -1; - } - - char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); - *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); - *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); -#else - ecs_parser_error(name, expr, ptr - expr, - "doc_name() is not available without FLECS_DOC addon"); - return -1; -#endif - } else { - ecs_parser_error(name, expr, ptr - expr, - "unknown function '%s'", function); - return -1; - } - - return 0; -} - -/* Determine the type of an expression from the first character(s). This allows - * us to initialize a storage for a type if none was provided. */ -static -ecs_entity_t flecs_parse_discover_type( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_entity_t input_type, - const ecs_script_expr_run_desc_t *desc) -{ - /* String literal */ - if (ptr[0] == '"' || ptr[0] == '`') { - if (input_type == ecs_id(ecs_char_t)) { - return input_type; - } - return ecs_id(ecs_string_t); - } - - /* Negative number literal */ - if (ptr[0] == '-') { - if (!isdigit(ptr[1])) { - ecs_parser_error(name, expr, ptr - expr, "invalid literal"); - return 0; - } - if (flecs_parse_is_float(ptr + 1)) { - return ecs_id(ecs_f64_t); - } else { - return ecs_id(ecs_i64_t); - } - } - - /* Positive number literal */ - if (isdigit(ptr[0])) { - if (flecs_parse_is_float(ptr)) { - return ecs_id(ecs_f64_t); - } else { - return ecs_id(ecs_u64_t); - } - } - - /* Variable */ - if (ptr[0] == '$') { - if (!desc || !desc->vars) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable (no variable scope)"); - return 0; - } - - char token[ECS_MAX_TOKEN_SIZE]; - if (flecs_script_expr_parse_token(name, expr, &ptr[1], token) == NULL) { - return 0; - } - - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); - if (!var) { - ecs_size_t len = ecs_os_strlen(token); - if (ptr[len + 1] == '(') { - while ((len > 0) && (token[len] != '.')) { - len --; - } - if (token[len] == '.') { - token[len] = '\0'; - } - - return ecs_id(ecs_entity_t); - } - - ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); - if (v.type) { - return v.type; - } - - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return 0; - } - return var->value.type; - } - - /* Boolean */ - if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { - if (!isalpha(ptr[4]) && ptr[4] != '_') { - return ecs_id(ecs_bool_t); - } - } - - if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { - if (!isalpha(ptr[5]) && ptr[5] != '_') { - return ecs_id(ecs_bool_t); - } - } - - /* Entity identifier */ - if (isalpha(ptr[0])) { - if (!input_type) { /* Identifier could also be enum/bitmask constant */ - char token[ECS_MAX_TOKEN_SIZE]; - const char *tptr = flecs_script_expr_parse_token( - name, expr, ptr, token); - if (!tptr) { - return 0; - } - - if (tptr[0] != '[') { - return ecs_id(ecs_entity_t); - } - - tptr = flecs_script_expr_parse_token( - name, expr, tptr + 1, token); - if (!tptr) { - return 0; - } - - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t type = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved type '%s'", token); - return 0; - } - - if (tptr[0] != ']') { - ecs_parser_error(name, expr, ptr - expr, - "missing ']' after '%s'", token); - return 0; - } - - if (tptr[1] != '.') { - return type; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); - ecs_meta_push(&cur); - - tptr = flecs_script_expr_parse_token( - name, expr, tptr + 2, token); - if (!tptr) { - return 0; - } - - if (ecs_meta_dotmember(&cur, token) != 0) { - ecs_parser_error(name, expr, ptr - expr, - "failed to assign member '%s'", token); - return 0; - } - - return ecs_meta_get_type(&cur); - } - } - - /* If no default type was provided we can't automatically deduce the type of - * composite/collection expressions. */ - if (!input_type) { - if (ptr[0] == '{') { - ecs_parser_error(name, expr, ptr - expr, - "unknown type for composite literal"); - return 0; - } - - if (ptr[0] == '[') { - ecs_parser_error(name, expr, ptr - expr, - "unknown type for collection literal"); - return 0; - } - - ecs_parser_error(name, expr, ptr - expr, "invalid expression"); - } - - return input_type; -} - -/* Normalize types to their largest representation. - * Rather than taking the original type of a value, use the largest - * representation of the type so we don't have to worry about overflowing the - * original type in the operation. */ -static -ecs_entity_t flecs_largest_type( - const EcsPrimitive *type) -{ - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -/** Test if a normalized type can promote to another type in an expression */ -static -bool flecs_is_type_number( - ecs_entity_t type) -{ - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -static -bool flecs_oper_valid_for_type( - ecs_entity_t type, - ecs_expr_oper_t op) -{ - switch(op) { - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsDiv: - return flecs_is_type_number(type); - case EcsCondEq: - case EcsCondNeq: - case EcsCondAnd: - case EcsCondOr: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - return flecs_is_type_number(type) || - (type == ecs_id(ecs_bool_t)) || - (type == ecs_id(ecs_char_t)) || - (type == ecs_id(ecs_entity_t)); - case EcsShiftLeft: - case EcsShiftRight: - return (type == ecs_id(ecs_u64_t)); - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - return false; - default: - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } -} - -/** Promote type to most expressive (f64 > i64 > u64) */ -static -ecs_entity_t flecs_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) -{ - if (type == ecs_id(ecs_u64_t)) { - return promote_to; - } - if (promote_to == ecs_id(ecs_u64_t)) { - return type; - } - if (type == ecs_id(ecs_f64_t)) { - return type; - } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; - } - return ecs_id(ecs_i64_t); -} - -static -int flecs_oper_precedence( - ecs_expr_oper_t left, - ecs_expr_oper_t right) -{ - return (left > right) - (left < right); -} - -static -void flecs_value_cast( - ecs_world_t *world, - ecs_value_stack_t *stack, - ecs_value_t *value, - ecs_entity_t type) -{ - if (value->type == type) { - return; - } - - ecs_value_t result; - result.type = type; - result.ptr = flecs_expr_value_new(stack, type); - - if (value->ptr) { - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); - ecs_meta_set_value(&cur, value); - } - - *value = result; -} - -static -bool flecs_expr_op_is_equality( - ecs_expr_oper_t op) -{ - switch(op) { - case EcsCondEq: - case EcsCondNeq: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - return true; - case EcsCondAnd: - case EcsCondOr: - case EcsShiftLeft: - case EcsShiftRight: - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsDiv: - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - return false; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - -static -ecs_entity_t flecs_binary_expr_type( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *rvalue, - ecs_expr_oper_t op, - ecs_entity_t *operand_type_out) -{ - ecs_entity_t result_type = 0, operand_type = 0; - - switch(op) { - case EcsDiv: - /* Result type of a division is always a float */ - *operand_type_out = ecs_id(ecs_f64_t); - return ecs_id(ecs_f64_t); - case EcsCondAnd: - case EcsCondOr: - /* Result type of a condition operator is always a bool */ - *operand_type_out = ecs_id(ecs_bool_t); - return ecs_id(ecs_bool_t); - case EcsCondEq: - case EcsCondNeq: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - /* Result type of equality operator is always bool, but operand types - * should not be casted to bool */ - result_type = ecs_id(ecs_bool_t); - break; - case EcsShiftLeft: - case EcsShiftRight: - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - break; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } - - /* Result type for arithmetic operators is determined by operands */ - const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); - const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); - if (!ltype_ptr || !rtype_ptr) { - char *lname = ecs_get_path(world, lvalue->type); - char *rname = ecs_get_path(world, rvalue->type); - ecs_parser_error(name, expr, ptr - expr, - "invalid non-primitive type in binary expression (%s, %s)", - lname, rname); - ecs_os_free(lname); - ecs_os_free(rname); - return 0; - } - - ecs_entity_t ltype = flecs_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_largest_type(rtype_ptr); - if (ltype == rtype) { - operand_type = ltype; - goto done; - } - - if (flecs_expr_op_is_equality(op)) { - char *lstr = ecs_id_str(world, ltype); - char *rstr = ecs_id_str(world, rtype); - ecs_parser_error(name, expr, ptr - expr, - "mismatching types in equality expression (%s vs %s)", - lstr, rstr); - ecs_os_free(rstr); - ecs_os_free(lstr); - return 0; - } - - if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { - ecs_parser_error(name, expr, ptr - expr, - "incompatible types in binary expression"); - return 0; - } - - operand_type = flecs_promote_type(ltype, rtype); - -done: - if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { - /* Result of subtracting two unsigned ints can be negative */ - operand_type = ecs_id(ecs_i64_t); - } - - if (!result_type) { - result_type = operand_type; - } - - *operand_type_out = operand_type; - return result_type; -error: - return 0; -} - -/* Macro's to let the compiler do the operations & conversion work for us */ - -#define ECS_VALUE_GET(value, T) (*(T*)value->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -static -int flecs_binary_expr_do( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *rvalue, - ecs_value_t *result, - ecs_expr_oper_t op) -{ - /* Find expression type */ - ecs_entity_t operand_type, type = flecs_binary_expr_type( - world, name, expr, ptr, lvalue, rvalue, op, &operand_type); - if (!type) { - goto error; - } - - if (!flecs_oper_valid_for_type(type, op)) { - ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); - goto error; - } - - flecs_value_cast(world, stack, lvalue, operand_type); - flecs_value_cast(world, stack, rvalue, operand_type); - - ecs_value_t *storage = result; - ecs_value_t tmp_storage = {0}; - if (result->type != type) { - storage = &tmp_storage; - storage->type = type; - storage->ptr = flecs_expr_value_new(stack, type); - } - - switch(op) { - case EcsAdd: - ECS_BINARY_OP(lvalue, rvalue, storage, +); - break; - case EcsSub: - ECS_BINARY_OP(lvalue, rvalue, storage, -); - break; - case EcsMul: - ECS_BINARY_OP(lvalue, rvalue, storage, *); - break; - case EcsDiv: - ECS_BINARY_OP(lvalue, rvalue, storage, /); - break; - case EcsCondEq: - ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); - break; - case EcsCondNeq: - ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); - break; - case EcsCondGt: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); - break; - case EcsCondGtEq: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); - break; - case EcsCondLt: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); - break; - case EcsCondLtEq: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); - break; - case EcsCondAnd: - ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); - break; - case EcsCondOr: - ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); - break; - case EcsShiftLeft: - ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); - break; - case EcsShiftRight: - ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); - break; - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); - goto error; - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - - if (storage->ptr != result->ptr) { - if (!result->ptr) { - *result = *storage; - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor(world, - result->type, result->ptr); - ecs_meta_set_value(&cur, storage); - } - } - - return 0; -error: - return -1; -} - -static -const char* flecs_binary_expr_parse( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *result, - ecs_expr_oper_t left_op, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_entity_t result_type = result->type; - do { - ecs_expr_oper_t op; - - ptr = flecs_str_to_expr_oper(ptr, &op); - if (!ptr) { - ecs_parser_error(name, expr, ptr - expr, "invalid operator"); - return NULL; - } - - ptr = flecs_parse_ws_eol(ptr); - - ecs_value_t rvalue = {0}; - const char *rptr = flecs_script_expr_run( - world, stack, ptr, &rvalue, op, desc); - if (!rptr) { - return NULL; - } - - if (flecs_binary_expr_do(world, stack, name, expr, ptr, - lvalue, &rvalue, result, op)) - { - return NULL; - } - - ptr = rptr; - - ecs_expr_oper_t right_op; - flecs_str_to_expr_oper(rptr, &right_op); - if (right_op > left_op) { - if (result_type) { - /* If result was initialized, preserve its value */ - lvalue->type = result->type; - lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); - ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); - continue; - } else { - /* Otherwise move result to lvalue */ - *lvalue = *result; - ecs_os_zeromem(result); - continue; - } - } - - break; - } while (true); - - return ptr; -} - -static -const char* flecs_funccall_parse( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - char *token, - ecs_meta_cursor_t *cur, - ecs_value_t *value, - bool isvar, - const ecs_script_expr_run_desc_t *desc) -{ - char *sep = strrchr(token, '.'); - if (!sep) { - ecs_parser_error(name, expr, ptr - expr, - "missing object for function call '%s'", token); - return NULL; - } - - sep[0] = '\0'; - const char *function = sep + 1; - - if (!isvar) { - if (ecs_meta_set_string(cur, token) != 0) { - goto error; - } - } else { - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); - ecs_meta_set_value(cur, &var->value); - } - - do { - if (!function[0]) { - ecs_parser_error(name, expr, ptr - expr, - "missing function name for function call '%s'", token); - return NULL; - } - - if (flecs_meta_call(world, stack, name, expr, ptr, cur, - function) != 0) - { - goto error; - } - - ecs_entity_t type = ecs_meta_get_type(cur); - value->ptr = ecs_meta_get_ptr(cur); - value->type = type; - - ptr += 2; - if (ptr[0] != '.') { - break; - } - - ptr ++; - char *paren = strchr(ptr, '('); - if (!paren) { - break; - } - - ecs_size_t len = flecs_ito(int32_t, paren - ptr); - if (len >= ECS_MAX_TOKEN_SIZE) { - ecs_parser_error(name, expr, ptr - expr, - "token exceeds maximum token size"); - goto error; - } - - ecs_os_strncpy(token, ptr, len); - token[len] = '\0'; - function = token; - - ptr = paren; - } while (true); - - return ptr; -error: - return NULL; -} - -static -ecs_entity_t flecs_script_default_lookup( - const ecs_world_t *world, - const char *name, - void *ctx) -{ - (void)ctx; - return ecs_lookup(world, name); -} - -static -const char* flecs_script_expr_run( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *ptr, - ecs_value_t *value, - ecs_expr_oper_t left_op, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; - ecs_value_t result = {0}; - ecs_meta_cursor_t cur = {0}; - const char *name = desc ? desc->name : NULL; - const char *expr = desc ? desc->expr : NULL; - token[0] = '\0'; - expr = expr ? expr : ptr; - - ptr = flecs_parse_ws_eol(ptr); - - /* Check for postfix operators */ - ecs_expr_oper_t unary_op = EcsExprOperUnknown; - if (ptr[0] == '-' && !isdigit(ptr[1])) { - unary_op = EcsMin; - ptr = flecs_parse_ws_eol(ptr + 1); - } - - /* Initialize storage and cursor. If expression starts with a '(' storage - * will be initialized by a nested expression */ - if (ptr[0] != '(') { - ecs_entity_t type = flecs_parse_discover_type( - world, name, expr, ptr, value->type, desc); - if (!type) { - return NULL; - } - - result.type = type; - if (type != value->type) { - result.ptr = flecs_expr_value_new(stack, type); - } else { - result.ptr = value->ptr; - } - - cur = ecs_meta_cursor(world, result.type, result.ptr); - if (!cur.valid) { - return NULL; - } - - cur.lookup_action = desc ? desc->lookup_action : NULL; - cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; - } - - /* Loop that parses all values in a value scope */ - while ((ptr = flecs_script_expr_parse_token(name, expr, ptr, token))) { - /* Used to track of the result of the parsed token can be used as the - * lvalue for a binary expression */ - bool is_lvalue = false; - bool newline = false; - - if (!token[0]) { - /* Comment */ - continue; - } - - if (!ecs_os_strcmp(token, "(")) { - ecs_value_t temp_result, *out; - if (!depth) { - out = &result; - } else { - temp_result.type = ecs_meta_get_type(&cur); - temp_result.ptr = ecs_meta_get_ptr(&cur); - out = &temp_result; - } - - /* Parenthesis, parse nested expression */ - ptr = flecs_script_expr_run( - world, stack, ptr, out, EcsLeftParen, desc); - if (ptr[0] != ')') { - ecs_parser_error(name, expr, ptr - expr, - "missing closing parenthesis"); - return NULL; - } - ptr = flecs_parse_ws(ptr + 1); - is_lvalue = true; - - } else if (!ecs_os_strcmp(token, "{")) { - /* Parse nested value scope */ - ecs_entity_t scope_type = ecs_meta_get_type(&cur); - - depth ++; /* Keep track of depth so we know when parsing is done */ - if (ecs_meta_push(&cur) != 0) { - goto error; - } - - if (ecs_meta_is_collection(&cur)) { - char *path = ecs_get_path(world, scope_type); - ecs_parser_error(name, expr, ptr - expr, - "expected '[' for collection type '%s'", path); - ecs_os_free(path); - return NULL; - } - } - - else if (!ecs_os_strcmp(token, "}")) { - depth --; - - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } - - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "[")) { - /* Open collection value scope */ - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } - - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); - return NULL; - } - } - - else if (!ecs_os_strcmp(token, "]")) { - depth --; - - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; - } - - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "-")) { - if (unary_op != EcsExprOperUnknown) { - ecs_parser_error(name, expr, ptr - expr, - "unexpected unary operator"); - return NULL; - } - unary_op = EcsMin; - } - - else if (!ecs_os_strcmp(token, ",")) { - /* Move to next field */ - if (ecs_meta_next(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } - - is_lvalue = true; - } - - else if (token[0] == '\"') { - /* Regular string */ - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - - is_lvalue = true; - } - - else if (!ecs_os_strcmp(token, "`")) { - /* Multiline string */ - if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { - goto error; - } - - is_lvalue = true; - - } else if (token[0] == '$') { - /* Variable */ - if (!desc || !desc->vars) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s' (no variable scope)", token); - return NULL; - } - - if (!token[1]) { - /* Empty name means default to assigned member */ - const char *member = ecs_meta_get_member(&cur); - if (!member) { - ecs_parser_error(name, expr, ptr - expr, - "invalid default variable outside member assignment"); - return NULL; - } - ecs_os_strcpy(&token[1], member); - } - - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, &token[1]); - if (!var) { - if (ptr[0] == '(') { - /* Function */ - ptr = flecs_funccall_parse(world, stack, name, expr, ptr, - &token[1], &cur, &result, true, desc); - if (!ptr) { - goto error; - } - } else { - ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); - if (!v.ptr) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return NULL; - } else { - ecs_meta_set_value(&cur, &v); - } - } - } else { - ecs_meta_set_value(&cur, &var->value); - } - - is_lvalue = true; - } else { - const char *tptr = flecs_parse_ws(ptr); - for (; ptr != tptr; ptr ++) { - if (ptr[0] == '\n') { - newline = true; - } - } - - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; - if (ecs_meta_dotmember(&cur, token) != 0) { - goto error; - } - } else if (ptr[0] == '(') { - /* Function */ - ptr = flecs_funccall_parse(world, stack, name, expr, ptr, - token, &cur, &result, false, desc); - if (!ptr) { - goto error; - } - } else { - if (ptr[0] != '[') { - /* Entity id expression */ - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } - } else { - /* Component expression */ - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t e = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!e) { - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' not found", token); - goto error; - } - - ptr = flecs_script_expr_parse_token( - name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } - - ecs_entity_t component = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!component) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved component '%s'", token); - goto error; - } - - ecs_entity_t type = ecs_get_typeid(world, component); - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' is not a component", token); - goto error; - } - - result.type = type; - result.ptr = ECS_CONST_CAST(void*, - ecs_get_id(world, e, component)); - if (!result.ptr) { - char *entitystr = ecs_id_str(world, e); - char *idstr = ecs_id_str(world, component); - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' does not have component '%s'", - entitystr, idstr); - ecs_os_free(idstr); - ecs_os_free(entitystr); - goto error; - } - - if (ptr[0] != ']') { - ecs_parser_error(name, expr, ptr - expr, - "missing ] for component operator"); - goto error; - } - - ptr ++; - - if (ptr[0] == '.') { - ecs_meta_cursor_t member_cur = ecs_meta_cursor( - world, result.type, result.ptr); - - ptr = flecs_script_expr_parse_token( - name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } - - ecs_meta_push(&member_cur); - if (ecs_meta_dotmember(&member_cur, token) != 0) { - ecs_parser_error(name, expr, ptr - expr, - "failed to assign member '%s'", token); - goto error; - } - - result.type = ecs_meta_get_type(&member_cur); - result.ptr = ecs_meta_get_ptr(&member_cur); - } - } - } - - is_lvalue = true; - } - - /* If lvalue was parsed, apply operators. Expressions cannot start - * directly after a newline character. */ - if (is_lvalue && !newline) { - if (unary_op != EcsExprOperUnknown) { - if (unary_op == EcsMin) { - int64_t v = -1; - ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; - ecs_value_t *out, rvalue, temp_out = {0}; - - if (!depth) { - rvalue = result; - ecs_os_zeromem(&result); - out = &result; - } else { - ecs_entity_t cur_type = ecs_meta_get_type(&cur); - void *cur_ptr = ecs_meta_get_ptr(&cur); - rvalue.type = cur_type; - rvalue.ptr = cur_ptr; - temp_out.type = cur_type; - temp_out.ptr = cur_ptr; - out = &temp_out; - } - - flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, - &rvalue, out, EcsMul); - } - unary_op = 0; - } - - ecs_expr_oper_t right_op; - flecs_str_to_expr_oper(ptr, &right_op); - if (right_op) { - /* This is a binary expression, test precedence to determine if - * it should be evaluated here */ - if (flecs_oper_precedence(left_op, right_op) < 0) { - ecs_value_t lvalue; - ecs_value_t *op_result = &result; - ecs_value_t temp_storage; - if (!depth) { - /* Root level value, move result to lvalue storage */ - lvalue = result; - ecs_os_zeromem(&result); - } else { - /* Not a root level value. Move the parsed lvalue to a - * temporary storage, and initialize the result value - * for the binary operation with the current cursor */ - ecs_entity_t cur_type = ecs_meta_get_type(&cur); - void *cur_ptr = ecs_meta_get_ptr(&cur); - lvalue.type = cur_type; - lvalue.ptr = flecs_expr_value_new(stack, cur_type); - ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); - temp_storage.type = cur_type; - temp_storage.ptr = cur_ptr; - op_result = &temp_storage; - } - - /* Do the binary expression */ - ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, - &lvalue, op_result, left_op, desc); - if (!ptr) { - return NULL; - } - } - } - } - - if (!depth) { - /* Reached the end of the root scope */ - break; - } - - ptr = flecs_parse_ws_eol(ptr); - } - - if (!value->ptr) { - value->type = result.type; - value->ptr = flecs_expr_value_new(stack, result.type); - } - - if (value->ptr != result.ptr) { - cur = ecs_meta_cursor(world, value->type, value->ptr); - ecs_meta_set_value(&cur, &result); - } - - ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - return ptr; -error: - return NULL; -} - -const char* _ecs_script_expr_run( - ecs_world_t *world, - const char *ptr, - ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_script_expr_run_desc_t priv_desc = {0}; - if (desc) { - priv_desc = *desc; - } - - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - /* Prepare storage for temporary values */ - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_value_stack_t stack; - stack.count = 0; - stack.stage = stage; - stack.stack = &stage->allocators.deser_stack; - stack.cursor = flecs_stack_get_cursor(stack.stack); - - /* Parse expression */ - bool storage_provided = value->ptr != NULL; - ptr = flecs_script_expr_run( - world, &stack, ptr, value, EcsExprOperUnknown, &priv_desc); - - /* If no result value was provided, allocate one as we can't return a - * pointer to a temporary storage */ - if (!storage_provided && value->ptr) { - ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); - void *temp_storage = value->ptr; - value->ptr = ecs_value_new(world, value->type); - ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); - } - - /* Cleanup temporary values */ - int i; - for (i = 0; i < stack.count; i ++) { - const ecs_type_info_t *ti = stack.values[i].ti; - ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); - ti->hooks.dtor(stack.values[i].ptr, 1, ti); - } - flecs_stack_restore_cursor(stack.stack, stack.cursor); - - return ptr; -} - -#endif - /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -60048,11 +58289,16 @@ const char* flecs_script_identifier( 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; do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || - isdigit(c) || (c == '.') || (c == '*'); + isdigit(c) || (c == '*'); + + if (!is_var) { + is_ident = is_ident || (c == '.'); + } /* Retain \. for name lookup operation */ if (!is_ident && c == '\\' && pos[1] == '.') { @@ -75422,6 +73668,7 @@ const char* flecs_script_parse_lhs( Parse_1(')', { break; }) + break; } @@ -75626,6 +73873,25 @@ int flecs_value_copy_to( return -1; } +int flecs_value_unary( + ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_script_token_kind_t operator) +{ + switch(operator) { + case EcsTokNot: + ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + *(bool*)out->ptr = !*(bool*)expr->ptr; + break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); + } + + return 0; +} + int flecs_value_binary( ecs_script_t *script, const ecs_value_t *left, @@ -75687,12 +73953,9 @@ int flecs_value_binary( break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); - goto error; } return 0; -error: - return -1; } #endif @@ -75767,6 +74030,33 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_unary_visit_eval( + ecs_script_t *script, + ecs_expr_unary_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + goto error; + } + + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + if (flecs_value_unary( + script, &expr.value, &out->value, node->operator)) + { + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_expr_binary_visit_eval( ecs_script_t *script, @@ -75841,6 +74131,27 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_member_visit_eval( + ecs_script_t *script, + ecs_expr_member_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); + out->value.type = node->node.type; + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -75861,6 +74172,11 @@ int flecs_script_expr_visit_eval_priv( case EcsExprInitializer: break; case EcsExprUnary: + if (flecs_expr_unary_visit_eval( + script, (ecs_expr_binary_t*)node, desc, out)) + { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_visit_eval( @@ -75881,6 +74197,11 @@ int flecs_script_expr_visit_eval_priv( case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit_eval( + script, (ecs_expr_member_t*)node, desc, out)) + { + goto error; + } break; case EcsExprElement: break; @@ -75957,7 +74278,7 @@ int flecs_expr_unary_visit_fold( } if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ + /* Only folding literals */ return 0; } @@ -75975,7 +74296,13 @@ int flecs_expr_unary_visit_fold( result->node.pos = node->node.pos; result->node.type = ecs_id(ecs_bool_t); result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); + + ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; + ecs_value_t src = { + .ptr = ((ecs_expr_val_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; + if (flecs_value_unary(script, &src, &dst, node->operator)) { + goto error; + } *node_ptr = (ecs_expr_node_t*)result; @@ -77049,6 +75376,7 @@ int flecs_expr_member_visit_type( ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(&cur); + node->offset = (uintptr_t)ecs_meta_get_ptr(&cur); ecs_meta_pop(&cur); /* } */ return 0; diff --git a/src/addons/script/expr.c b/src/addons/script/expr.c deleted file mode 100644 index c8b6d6f9c3..0000000000 --- a/src/addons/script/expr.c +++ /dev/null @@ -1,1767 +0,0 @@ -/** - * @file addons/script/expr.c - * @brief Evaluate script expressions. - */ - -#include "flecs.h" - -#ifdef FLECS_SCRIPT -#include -#include -#include "script.h" - -/* String deserializer for script expressions */ - -/* Order in enumeration is important, as it is used for precedence */ -typedef enum ecs_expr_oper_t { - EcsExprOperUnknown, - EcsLeftParen, - EcsCondAnd, - EcsCondOr, - EcsCondEq, - EcsCondNeq, - EcsCondGt, - EcsCondGtEq, - EcsCondLt, - EcsCondLtEq, - EcsShiftLeft, - EcsShiftRight, - EcsAdd, - EcsSub, - EcsMul, - EcsDiv, - EcsMin -} ecs_expr_oper_t; - -/* Used to track temporary values */ -#define FLECS_EXPR_MAX_STACK_SIZE (256) - -typedef struct ecs_expr_value_t { - const ecs_type_info_t *ti; - void *ptr; -} ecs_expr_value_t; - -typedef struct ecs_value_stack_t { - ecs_expr_value_t values[FLECS_EXPR_MAX_STACK_SIZE]; - ecs_stack_cursor_t *cursor; - ecs_stack_t *stack; - ecs_stage_t *stage; - int32_t count; -} ecs_value_stack_t; - -static -const char* flecs_script_expr_run( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *ptr, - ecs_value_t *value, - ecs_expr_oper_t op, - const ecs_script_expr_run_desc_t *desc); - -#define TOK_VARIABLE '$' - -/* -- Private functions -- */ - -static -bool flecs_isident( - char ch) -{ - return isalpha(ch) || (ch == '_'); -} - -static -bool flecs_valid_identifier_start_char( - char ch) -{ - if (ch && (flecs_isident(ch) || (ch == '*') || - (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) - { - return true; - } - - return false; -} - -static -bool flecs_valid_token_start_char( - char ch) -{ - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || (ch == '`') || - flecs_valid_identifier_start_char(ch)) - { - return true; - } - - return false; -} - -static -bool flecs_valid_token_char( - char ch) -{ - if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { - return true; - } - - return false; -} - -static -const char* flecs_parse_ws( - const char *ptr) -{ - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; - } - - return ptr; -} - -static -const char* flecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out, - char delim) -{ - int64_t column = ptr - expr; - - ptr = flecs_parse_ws(ptr); - char *tptr = token_out, ch = ptr[0]; - - if (!flecs_valid_token_start_char(ch)) { - if (ch == '\0' || ch == '\n') { - ecs_parser_error(name, expr, column, - "unexpected end of expression"); - } else { - ecs_parser_error(name, expr, column, - "invalid start of token '%s'", ptr); - } - return NULL; - } - - bool isDigit = isdigit(ch) || (ch == '-'); - - if (ch == '-') { - if (!isdigit(ptr[1]) && ptr[1] != '$' && ptr[1] != '(') { - ecs_parser_error(name, expr, column, - "invalid number token"); - return NULL; - } - if (ptr[1] == '$' || ptr[1] == '(') { - isDigit = false; /* -$var */ - } - } - - bool hasDot = false; - bool hasE = false; - - tptr[0] = ch; - tptr ++; - ptr ++; - - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { - tptr[0] = 0; - return ptr; - } - - int tmpl_nesting = 0; - bool in_str = ch == '"'; - - for (; (ch = *ptr); ptr ++) { - if (ch == '<') { - tmpl_nesting ++; - } else if (ch == '>') { - if (!tmpl_nesting) { - break; - } - tmpl_nesting --; - } else if (ch == '"') { - in_str = !in_str; - } else if (ch == '\\') { - ptr ++; - tptr[0] = ptr[0]; - tptr ++; - continue; - } else if (!flecs_valid_token_char(ch) && !in_str) { - break; - } - - if (isDigit) { - if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - if (ch == '.') { - if (hasDot) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - hasDot = true; - } - if (ch == 'e') { - if (hasE) { - ecs_parser_error(name, expr, column, "invalid token"); - return NULL; - } - hasE = true; - } - } - - if (delim && (ch == delim)) { - break; - } - - tptr[0] = ch; - tptr ++; - } - - tptr[0] = '\0'; - - if (tmpl_nesting != 0) { - ecs_parser_error(name, expr, column, - "identifier '%s' has mismatching < > pairs", ptr); - return NULL; - } - - const char *next_ptr = flecs_parse_ws(ptr); - if (next_ptr[0] == ':' && next_ptr != ptr) { - /* Whitespace between token and : is significant */ - ptr = next_ptr - 1; - } else { - ptr = next_ptr; - } - - return ptr; -} - -static -void* flecs_expr_value_new( - ecs_value_stack_t *stack, - ecs_entity_t type) -{ - ecs_stage_t *stage = stack->stage; - ecs_world_t *world = stage->world; - ecs_id_record_t *idr = flecs_id_record_get(world, type); - if (!idr) { - return NULL; - } - - const ecs_type_info_t *ti = idr->type_info; - if (!ti) { - return NULL; - } - - ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); - void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); - if (ti->hooks.ctor) { - ti->hooks.ctor(result, 1, ti); - } else { - ecs_os_memset(result, 0, ti->size); - } - if (ti->hooks.dtor) { - /* Track values that have destructors */ - stack->values[stack->count].ti = ti; - stack->values[stack->count].ptr = result; - stack->count ++; - } - - return result; -} - -static -const char* flecs_str_to_expr_oper( - const char *str, - ecs_expr_oper_t *op) -{ - if (!ecs_os_strncmp(str, "+", 1)) { - *op = EcsAdd; - return str + 1; - } else if (!ecs_os_strncmp(str, "-", 1)) { - *op = EcsSub; - return str + 1; - } else if (!ecs_os_strncmp(str, "*", 1)) { - *op = EcsMul; - return str + 1; - } else if (!ecs_os_strncmp(str, "/", 1)) { - *op = EcsDiv; - return str + 1; - } else if (!ecs_os_strncmp(str, "&&", 2)) { - *op = EcsCondAnd; - return str + 2; - } else if (!ecs_os_strncmp(str, "||", 2)) { - *op = EcsCondOr; - return str + 2; - } else if (!ecs_os_strncmp(str, "==", 2)) { - *op = EcsCondEq; - return str + 2; - } else if (!ecs_os_strncmp(str, "!=", 2)) { - *op = EcsCondNeq; - return str + 2; - } else if (!ecs_os_strncmp(str, ">=", 2)) { - *op = EcsCondGtEq; - return str + 2; - } else if (!ecs_os_strncmp(str, "<=", 2)) { - *op = EcsCondLtEq; - return str + 2; - } else if (!ecs_os_strncmp(str, ">>", 2)) { - *op = EcsShiftRight; - return str + 2; - } else if (!ecs_os_strncmp(str, "<<", 2)) { - *op = EcsShiftLeft; - return str + 2; - } else if (!ecs_os_strncmp(str, ">", 1)) { - *op = EcsCondGt; - return str + 1; - } else if (!ecs_os_strncmp(str, "<", 1)) { - *op = EcsCondLt; - return str + 1; - } - - *op = EcsExprOperUnknown; - return NULL; -} - -static -const char *flecs_script_expr_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token) -{ - char *token_ptr = token; - - if (ptr[0] == '/') { - char ch; - if (ptr[1] == '/') { - // Single line comment - for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} - token[0] = 0; - return flecs_parse_ws_eol(ptr); - } else if (ptr[1] == '*') { - // Multi line comment - for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { - if (ch == '*' && ptr[1] == '/') { - token[0] = 0; - return flecs_parse_ws_eol(ptr + 2); - } - } - - ecs_parser_error(name, expr, ptr - expr, - "missing */ for multiline comment"); - return NULL; - } - } - - ecs_expr_oper_t op; - if (ptr[0] == '(') { - token[0] = '('; - token[1] = 0; - return ptr + 1; - } else if (ptr[0] != '-') { - const char *tptr = flecs_str_to_expr_oper(ptr, &op); - if (tptr) { - ecs_os_strncpy(token, ptr, tptr - ptr); - return tptr; - } - } - - while ((ptr = flecs_parse_expr_token(name, expr, ptr, token_ptr, 0))) { - if (ptr[0] == '|' && ptr[1] != '|') { - token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; - token_ptr[0] = '|'; - token_ptr[1] = '\0'; - token_ptr ++; - ptr ++; - } else { - break; - } - } - - return ptr; -} - -static -const char* flecs_parse_multiline_string( - ecs_meta_cursor_t *cur, - const char *name, - const char *expr, - const char *ptr) -{ - /* Multiline string */ - ecs_strbuf_t str = ECS_STRBUF_INIT; - char ch; - while ((ch = ptr[0]) && (ch != '`')) { - if (ch == '\\' && ptr[1] == '`') { - ch = '`'; - ptr ++; - } - ecs_strbuf_appendch(&str, ch); - ptr ++; - } - - if (ch != '`') { - ecs_parser_error(name, expr, ptr - expr, - "missing '`' to close multiline string"); - goto error; - } - - char *strval = ecs_strbuf_get(&str); - if (ecs_meta_set_string(cur, strval) != 0) { - ecs_os_free(strval); - goto error; - } - ecs_os_free(strval); - - return ptr + 1; -error: - return NULL; -} - -static -bool flecs_parse_is_float( - const char *ptr) -{ - ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); - char ch; - while ((ch = (++ptr)[0])) { - if (ch == '.' || ch == 'e') { - return true; - } - if (!isdigit(ch)) { - return false; - } - } - return false; -} - -/* Attempt to resolve variable dotexpression to value (foo.bar) */ -static -ecs_value_t flecs_dotresolve_var( - ecs_world_t *world, - ecs_script_vars_t *vars, - char *token) -{ - char *dot = strchr(token, '.'); - if (!dot) { - return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; - } - - dot[0] = '\0'; - - const ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); - if (!var) { - return (ecs_value_t){0}; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor( - world, var->value.type, var->value.ptr); - ecs_meta_push(&cur); - if (ecs_meta_dotmember(&cur, dot + 1) != 0) { - return (ecs_value_t){0}; - } - - return (ecs_value_t){ - .ptr = ecs_meta_get_ptr(&cur), - .type = ecs_meta_get_type(&cur) - }; -} - -static -int flecs_meta_call( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_meta_cursor_t *cur, - const char *function) -{ - ecs_entity_t type = ecs_meta_get_type(cur); - void *value_ptr = ecs_meta_get_ptr(cur); - - if (!ecs_os_strcmp(function, "parent")) { - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "parent() can only be called on entity"); - return -1; - } - - *(ecs_entity_t*)value_ptr = ecs_get_parent( - world, *(ecs_entity_t*)value_ptr); - } else if (!ecs_os_strcmp(function, "name")) { - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "name() can only be called on entity"); - return -1; - } - - char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); - *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); - *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); - } else if (!ecs_os_strcmp(function, "doc_name")) { -#ifdef FLECS_DOC - if (type != ecs_id(ecs_entity_t)) { - ecs_parser_error(name, expr, ptr - expr, - "name() can only be called on entity"); - return -1; - } - - char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); - *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); - *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); -#else - ecs_parser_error(name, expr, ptr - expr, - "doc_name() is not available without FLECS_DOC addon"); - return -1; -#endif - } else { - ecs_parser_error(name, expr, ptr - expr, - "unknown function '%s'", function); - return -1; - } - - return 0; -} - -/* Determine the type of an expression from the first character(s). This allows - * us to initialize a storage for a type if none was provided. */ -static -ecs_entity_t flecs_parse_discover_type( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_entity_t input_type, - const ecs_script_expr_run_desc_t *desc) -{ - /* String literal */ - if (ptr[0] == '"' || ptr[0] == '`') { - if (input_type == ecs_id(ecs_char_t)) { - return input_type; - } - return ecs_id(ecs_string_t); - } - - /* Negative number literal */ - if (ptr[0] == '-') { - if (!isdigit(ptr[1])) { - ecs_parser_error(name, expr, ptr - expr, "invalid literal"); - return 0; - } - if (flecs_parse_is_float(ptr + 1)) { - return ecs_id(ecs_f64_t); - } else { - return ecs_id(ecs_i64_t); - } - } - - /* Positive number literal */ - if (isdigit(ptr[0])) { - if (flecs_parse_is_float(ptr)) { - return ecs_id(ecs_f64_t); - } else { - return ecs_id(ecs_u64_t); - } - } - - /* Variable */ - if (ptr[0] == '$') { - if (!desc || !desc->vars) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable (no variable scope)"); - return 0; - } - - char token[ECS_MAX_TOKEN_SIZE]; - if (flecs_script_expr_parse_token(name, expr, &ptr[1], token) == NULL) { - return 0; - } - - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); - if (!var) { - ecs_size_t len = ecs_os_strlen(token); - if (ptr[len + 1] == '(') { - while ((len > 0) && (token[len] != '.')) { - len --; - } - if (token[len] == '.') { - token[len] = '\0'; - } - - return ecs_id(ecs_entity_t); - } - - ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); - if (v.type) { - return v.type; - } - - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return 0; - } - return var->value.type; - } - - /* Boolean */ - if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { - if (!isalpha(ptr[4]) && ptr[4] != '_') { - return ecs_id(ecs_bool_t); - } - } - - if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { - if (!isalpha(ptr[5]) && ptr[5] != '_') { - return ecs_id(ecs_bool_t); - } - } - - /* Entity identifier */ - if (isalpha(ptr[0])) { - if (!input_type) { /* Identifier could also be enum/bitmask constant */ - char token[ECS_MAX_TOKEN_SIZE]; - const char *tptr = flecs_script_expr_parse_token( - name, expr, ptr, token); - if (!tptr) { - return 0; - } - - if (tptr[0] != '[') { - return ecs_id(ecs_entity_t); - } - - tptr = flecs_script_expr_parse_token( - name, expr, tptr + 1, token); - if (!tptr) { - return 0; - } - - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t type = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved type '%s'", token); - return 0; - } - - if (tptr[0] != ']') { - ecs_parser_error(name, expr, ptr - expr, - "missing ']' after '%s'", token); - return 0; - } - - if (tptr[1] != '.') { - return type; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); - ecs_meta_push(&cur); - - tptr = flecs_script_expr_parse_token( - name, expr, tptr + 2, token); - if (!tptr) { - return 0; - } - - if (ecs_meta_dotmember(&cur, token) != 0) { - ecs_parser_error(name, expr, ptr - expr, - "failed to assign member '%s'", token); - return 0; - } - - return ecs_meta_get_type(&cur); - } - } - - /* If no default type was provided we can't automatically deduce the type of - * composite/collection expressions. */ - if (!input_type) { - if (ptr[0] == '{') { - ecs_parser_error(name, expr, ptr - expr, - "unknown type for composite literal"); - return 0; - } - - if (ptr[0] == '[') { - ecs_parser_error(name, expr, ptr - expr, - "unknown type for collection literal"); - return 0; - } - - ecs_parser_error(name, expr, ptr - expr, "invalid expression"); - } - - return input_type; -} - -/* Normalize types to their largest representation. - * Rather than taking the original type of a value, use the largest - * representation of the type so we don't have to worry about overflowing the - * original type in the operation. */ -static -ecs_entity_t flecs_largest_type( - const EcsPrimitive *type) -{ - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -/** Test if a normalized type can promote to another type in an expression */ -static -bool flecs_is_type_number( - ecs_entity_t type) -{ - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -static -bool flecs_oper_valid_for_type( - ecs_entity_t type, - ecs_expr_oper_t op) -{ - switch(op) { - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsDiv: - return flecs_is_type_number(type); - case EcsCondEq: - case EcsCondNeq: - case EcsCondAnd: - case EcsCondOr: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - return flecs_is_type_number(type) || - (type == ecs_id(ecs_bool_t)) || - (type == ecs_id(ecs_char_t)) || - (type == ecs_id(ecs_entity_t)); - case EcsShiftLeft: - case EcsShiftRight: - return (type == ecs_id(ecs_u64_t)); - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - return false; - default: - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } -} - -/** Promote type to most expressive (f64 > i64 > u64) */ -static -ecs_entity_t flecs_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) -{ - if (type == ecs_id(ecs_u64_t)) { - return promote_to; - } - if (promote_to == ecs_id(ecs_u64_t)) { - return type; - } - if (type == ecs_id(ecs_f64_t)) { - return type; - } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; - } - return ecs_id(ecs_i64_t); -} - -static -int flecs_oper_precedence( - ecs_expr_oper_t left, - ecs_expr_oper_t right) -{ - return (left > right) - (left < right); -} - -static -void flecs_value_cast( - ecs_world_t *world, - ecs_value_stack_t *stack, - ecs_value_t *value, - ecs_entity_t type) -{ - if (value->type == type) { - return; - } - - ecs_value_t result; - result.type = type; - result.ptr = flecs_expr_value_new(stack, type); - - if (value->ptr) { - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); - ecs_meta_set_value(&cur, value); - } - - *value = result; -} - -static -bool flecs_expr_op_is_equality( - ecs_expr_oper_t op) -{ - switch(op) { - case EcsCondEq: - case EcsCondNeq: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - return true; - case EcsCondAnd: - case EcsCondOr: - case EcsShiftLeft: - case EcsShiftRight: - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsDiv: - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - return false; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - -static -ecs_entity_t flecs_binary_expr_type( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *rvalue, - ecs_expr_oper_t op, - ecs_entity_t *operand_type_out) -{ - ecs_entity_t result_type = 0, operand_type = 0; - - switch(op) { - case EcsDiv: - /* Result type of a division is always a float */ - *operand_type_out = ecs_id(ecs_f64_t); - return ecs_id(ecs_f64_t); - case EcsCondAnd: - case EcsCondOr: - /* Result type of a condition operator is always a bool */ - *operand_type_out = ecs_id(ecs_bool_t); - return ecs_id(ecs_bool_t); - case EcsCondEq: - case EcsCondNeq: - case EcsCondGt: - case EcsCondGtEq: - case EcsCondLt: - case EcsCondLtEq: - /* Result type of equality operator is always bool, but operand types - * should not be casted to bool */ - result_type = ecs_id(ecs_bool_t); - break; - case EcsShiftLeft: - case EcsShiftRight: - case EcsAdd: - case EcsSub: - case EcsMul: - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - break; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } - - /* Result type for arithmetic operators is determined by operands */ - const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); - const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); - if (!ltype_ptr || !rtype_ptr) { - char *lname = ecs_get_path(world, lvalue->type); - char *rname = ecs_get_path(world, rvalue->type); - ecs_parser_error(name, expr, ptr - expr, - "invalid non-primitive type in binary expression (%s, %s)", - lname, rname); - ecs_os_free(lname); - ecs_os_free(rname); - return 0; - } - - ecs_entity_t ltype = flecs_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_largest_type(rtype_ptr); - if (ltype == rtype) { - operand_type = ltype; - goto done; - } - - if (flecs_expr_op_is_equality(op)) { - char *lstr = ecs_id_str(world, ltype); - char *rstr = ecs_id_str(world, rtype); - ecs_parser_error(name, expr, ptr - expr, - "mismatching types in equality expression (%s vs %s)", - lstr, rstr); - ecs_os_free(rstr); - ecs_os_free(lstr); - return 0; - } - - if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { - ecs_parser_error(name, expr, ptr - expr, - "incompatible types in binary expression"); - return 0; - } - - operand_type = flecs_promote_type(ltype, rtype); - -done: - if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { - /* Result of subtracting two unsigned ints can be negative */ - operand_type = ecs_id(ecs_i64_t); - } - - if (!result_type) { - result_type = operand_type; - } - - *operand_type_out = operand_type; - return result_type; -error: - return 0; -} - -/* Macro's to let the compiler do the operations & conversion work for us */ - -#define ECS_VALUE_GET(value, T) (*(T*)value->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -static -int flecs_binary_expr_do( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *rvalue, - ecs_value_t *result, - ecs_expr_oper_t op) -{ - /* Find expression type */ - ecs_entity_t operand_type, type = flecs_binary_expr_type( - world, name, expr, ptr, lvalue, rvalue, op, &operand_type); - if (!type) { - goto error; - } - - if (!flecs_oper_valid_for_type(type, op)) { - ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); - goto error; - } - - flecs_value_cast(world, stack, lvalue, operand_type); - flecs_value_cast(world, stack, rvalue, operand_type); - - ecs_value_t *storage = result; - ecs_value_t tmp_storage = {0}; - if (result->type != type) { - storage = &tmp_storage; - storage->type = type; - storage->ptr = flecs_expr_value_new(stack, type); - } - - switch(op) { - case EcsAdd: - ECS_BINARY_OP(lvalue, rvalue, storage, +); - break; - case EcsSub: - ECS_BINARY_OP(lvalue, rvalue, storage, -); - break; - case EcsMul: - ECS_BINARY_OP(lvalue, rvalue, storage, *); - break; - case EcsDiv: - ECS_BINARY_OP(lvalue, rvalue, storage, /); - break; - case EcsCondEq: - ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); - break; - case EcsCondNeq: - ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); - break; - case EcsCondGt: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); - break; - case EcsCondGtEq: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); - break; - case EcsCondLt: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); - break; - case EcsCondLtEq: - ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); - break; - case EcsCondAnd: - ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); - break; - case EcsCondOr: - ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); - break; - case EcsShiftLeft: - ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); - break; - case EcsShiftRight: - ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); - break; - case EcsExprOperUnknown: - case EcsLeftParen: - case EcsMin: - ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); - goto error; - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - - if (storage->ptr != result->ptr) { - if (!result->ptr) { - *result = *storage; - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor(world, - result->type, result->ptr); - ecs_meta_set_value(&cur, storage); - } - } - - return 0; -error: - return -1; -} - -static -const char* flecs_binary_expr_parse( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - ecs_value_t *lvalue, - ecs_value_t *result, - ecs_expr_oper_t left_op, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_entity_t result_type = result->type; - do { - ecs_expr_oper_t op; - - ptr = flecs_str_to_expr_oper(ptr, &op); - if (!ptr) { - ecs_parser_error(name, expr, ptr - expr, "invalid operator"); - return NULL; - } - - ptr = flecs_parse_ws_eol(ptr); - - ecs_value_t rvalue = {0}; - const char *rptr = flecs_script_expr_run( - world, stack, ptr, &rvalue, op, desc); - if (!rptr) { - return NULL; - } - - if (flecs_binary_expr_do(world, stack, name, expr, ptr, - lvalue, &rvalue, result, op)) - { - return NULL; - } - - ptr = rptr; - - ecs_expr_oper_t right_op; - flecs_str_to_expr_oper(rptr, &right_op); - if (right_op > left_op) { - if (result_type) { - /* If result was initialized, preserve its value */ - lvalue->type = result->type; - lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); - ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); - continue; - } else { - /* Otherwise move result to lvalue */ - *lvalue = *result; - ecs_os_zeromem(result); - continue; - } - } - - break; - } while (true); - - return ptr; -} - -static -const char* flecs_funccall_parse( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *name, - const char *expr, - const char *ptr, - char *token, - ecs_meta_cursor_t *cur, - ecs_value_t *value, - bool isvar, - const ecs_script_expr_run_desc_t *desc) -{ - char *sep = strrchr(token, '.'); - if (!sep) { - ecs_parser_error(name, expr, ptr - expr, - "missing object for function call '%s'", token); - return NULL; - } - - sep[0] = '\0'; - const char *function = sep + 1; - - if (!isvar) { - if (ecs_meta_set_string(cur, token) != 0) { - goto error; - } - } else { - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); - ecs_meta_set_value(cur, &var->value); - } - - do { - if (!function[0]) { - ecs_parser_error(name, expr, ptr - expr, - "missing function name for function call '%s'", token); - return NULL; - } - - if (flecs_meta_call(world, stack, name, expr, ptr, cur, - function) != 0) - { - goto error; - } - - ecs_entity_t type = ecs_meta_get_type(cur); - value->ptr = ecs_meta_get_ptr(cur); - value->type = type; - - ptr += 2; - if (ptr[0] != '.') { - break; - } - - ptr ++; - char *paren = strchr(ptr, '('); - if (!paren) { - break; - } - - ecs_size_t len = flecs_ito(int32_t, paren - ptr); - if (len >= ECS_MAX_TOKEN_SIZE) { - ecs_parser_error(name, expr, ptr - expr, - "token exceeds maximum token size"); - goto error; - } - - ecs_os_strncpy(token, ptr, len); - token[len] = '\0'; - function = token; - - ptr = paren; - } while (true); - - return ptr; -error: - return NULL; -} - -static -ecs_entity_t flecs_script_default_lookup( - const ecs_world_t *world, - const char *name, - void *ctx) -{ - (void)ctx; - return ecs_lookup(world, name); -} - -static -const char* flecs_script_expr_run( - ecs_world_t *world, - ecs_value_stack_t *stack, - const char *ptr, - ecs_value_t *value, - ecs_expr_oper_t left_op, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; - ecs_value_t result = {0}; - ecs_meta_cursor_t cur = {0}; - const char *name = desc ? desc->name : NULL; - const char *expr = desc ? desc->expr : NULL; - token[0] = '\0'; - expr = expr ? expr : ptr; - - ptr = flecs_parse_ws_eol(ptr); - - /* Check for postfix operators */ - ecs_expr_oper_t unary_op = EcsExprOperUnknown; - if (ptr[0] == '-' && !isdigit(ptr[1])) { - unary_op = EcsMin; - ptr = flecs_parse_ws_eol(ptr + 1); - } - - /* Initialize storage and cursor. If expression starts with a '(' storage - * will be initialized by a nested expression */ - if (ptr[0] != '(') { - ecs_entity_t type = flecs_parse_discover_type( - world, name, expr, ptr, value->type, desc); - if (!type) { - return NULL; - } - - result.type = type; - if (type != value->type) { - result.ptr = flecs_expr_value_new(stack, type); - } else { - result.ptr = value->ptr; - } - - cur = ecs_meta_cursor(world, result.type, result.ptr); - if (!cur.valid) { - return NULL; - } - - cur.lookup_action = desc ? desc->lookup_action : NULL; - cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; - } - - /* Loop that parses all values in a value scope */ - while ((ptr = flecs_script_expr_parse_token(name, expr, ptr, token))) { - /* Used to track of the result of the parsed token can be used as the - * lvalue for a binary expression */ - bool is_lvalue = false; - bool newline = false; - - if (!token[0]) { - /* Comment */ - continue; - } - - if (!ecs_os_strcmp(token, "(")) { - ecs_value_t temp_result, *out; - if (!depth) { - out = &result; - } else { - temp_result.type = ecs_meta_get_type(&cur); - temp_result.ptr = ecs_meta_get_ptr(&cur); - out = &temp_result; - } - - /* Parenthesis, parse nested expression */ - ptr = flecs_script_expr_run( - world, stack, ptr, out, EcsLeftParen, desc); - if (ptr[0] != ')') { - ecs_parser_error(name, expr, ptr - expr, - "missing closing parenthesis"); - return NULL; - } - ptr = flecs_parse_ws(ptr + 1); - is_lvalue = true; - - } else if (!ecs_os_strcmp(token, "{")) { - /* Parse nested value scope */ - ecs_entity_t scope_type = ecs_meta_get_type(&cur); - - depth ++; /* Keep track of depth so we know when parsing is done */ - if (ecs_meta_push(&cur) != 0) { - goto error; - } - - if (ecs_meta_is_collection(&cur)) { - char *path = ecs_get_path(world, scope_type); - ecs_parser_error(name, expr, ptr - expr, - "expected '[' for collection type '%s'", path); - ecs_os_free(path); - return NULL; - } - } - - else if (!ecs_os_strcmp(token, "}")) { - depth --; - - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } - - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "[")) { - /* Open collection value scope */ - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } - - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); - return NULL; - } - } - - else if (!ecs_os_strcmp(token, "]")) { - depth --; - - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; - } - - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "-")) { - if (unary_op != EcsExprOperUnknown) { - ecs_parser_error(name, expr, ptr - expr, - "unexpected unary operator"); - return NULL; - } - unary_op = EcsMin; - } - - else if (!ecs_os_strcmp(token, ",")) { - /* Move to next field */ - if (ecs_meta_next(&cur) != 0) { - goto error; - } - } - - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } - - is_lvalue = true; - } - - else if (token[0] == '\"') { - /* Regular string */ - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - - is_lvalue = true; - } - - else if (!ecs_os_strcmp(token, "`")) { - /* Multiline string */ - if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { - goto error; - } - - is_lvalue = true; - - } else if (token[0] == '$') { - /* Variable */ - if (!desc || !desc->vars) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s' (no variable scope)", token); - return NULL; - } - - if (!token[1]) { - /* Empty name means default to assigned member */ - const char *member = ecs_meta_get_member(&cur); - if (!member) { - ecs_parser_error(name, expr, ptr - expr, - "invalid default variable outside member assignment"); - return NULL; - } - ecs_os_strcpy(&token[1], member); - } - - const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, &token[1]); - if (!var) { - if (ptr[0] == '(') { - /* Function */ - ptr = flecs_funccall_parse(world, stack, name, expr, ptr, - &token[1], &cur, &result, true, desc); - if (!ptr) { - goto error; - } - } else { - ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); - if (!v.ptr) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return NULL; - } else { - ecs_meta_set_value(&cur, &v); - } - } - } else { - ecs_meta_set_value(&cur, &var->value); - } - - is_lvalue = true; - } else { - const char *tptr = flecs_parse_ws(ptr); - for (; ptr != tptr; ptr ++) { - if (ptr[0] == '\n') { - newline = true; - } - } - - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; - if (ecs_meta_dotmember(&cur, token) != 0) { - goto error; - } - } else if (ptr[0] == '(') { - /* Function */ - ptr = flecs_funccall_parse(world, stack, name, expr, ptr, - token, &cur, &result, false, desc); - if (!ptr) { - goto error; - } - } else { - if (ptr[0] != '[') { - /* Entity id expression */ - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } - } else { - /* Component expression */ - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t e = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!e) { - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' not found", token); - goto error; - } - - ptr = flecs_script_expr_parse_token( - name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } - - ecs_entity_t component = desc->lookup_action( - world, token, desc->lookup_ctx); - if (!component) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved component '%s'", token); - goto error; - } - - ecs_entity_t type = ecs_get_typeid(world, component); - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' is not a component", token); - goto error; - } - - result.type = type; - result.ptr = ECS_CONST_CAST(void*, - ecs_get_id(world, e, component)); - if (!result.ptr) { - char *entitystr = ecs_id_str(world, e); - char *idstr = ecs_id_str(world, component); - ecs_parser_error(name, expr, ptr - expr, - "entity '%s' does not have component '%s'", - entitystr, idstr); - ecs_os_free(idstr); - ecs_os_free(entitystr); - goto error; - } - - if (ptr[0] != ']') { - ecs_parser_error(name, expr, ptr - expr, - "missing ] for component operator"); - goto error; - } - - ptr ++; - - if (ptr[0] == '.') { - ecs_meta_cursor_t member_cur = ecs_meta_cursor( - world, result.type, result.ptr); - - ptr = flecs_script_expr_parse_token( - name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } - - ecs_meta_push(&member_cur); - if (ecs_meta_dotmember(&member_cur, token) != 0) { - ecs_parser_error(name, expr, ptr - expr, - "failed to assign member '%s'", token); - goto error; - } - - result.type = ecs_meta_get_type(&member_cur); - result.ptr = ecs_meta_get_ptr(&member_cur); - } - } - } - - is_lvalue = true; - } - - /* If lvalue was parsed, apply operators. Expressions cannot start - * directly after a newline character. */ - if (is_lvalue && !newline) { - if (unary_op != EcsExprOperUnknown) { - if (unary_op == EcsMin) { - int64_t v = -1; - ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; - ecs_value_t *out, rvalue, temp_out = {0}; - - if (!depth) { - rvalue = result; - ecs_os_zeromem(&result); - out = &result; - } else { - ecs_entity_t cur_type = ecs_meta_get_type(&cur); - void *cur_ptr = ecs_meta_get_ptr(&cur); - rvalue.type = cur_type; - rvalue.ptr = cur_ptr; - temp_out.type = cur_type; - temp_out.ptr = cur_ptr; - out = &temp_out; - } - - flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, - &rvalue, out, EcsMul); - } - unary_op = 0; - } - - ecs_expr_oper_t right_op; - flecs_str_to_expr_oper(ptr, &right_op); - if (right_op) { - /* This is a binary expression, test precedence to determine if - * it should be evaluated here */ - if (flecs_oper_precedence(left_op, right_op) < 0) { - ecs_value_t lvalue; - ecs_value_t *op_result = &result; - ecs_value_t temp_storage; - if (!depth) { - /* Root level value, move result to lvalue storage */ - lvalue = result; - ecs_os_zeromem(&result); - } else { - /* Not a root level value. Move the parsed lvalue to a - * temporary storage, and initialize the result value - * for the binary operation with the current cursor */ - ecs_entity_t cur_type = ecs_meta_get_type(&cur); - void *cur_ptr = ecs_meta_get_ptr(&cur); - lvalue.type = cur_type; - lvalue.ptr = flecs_expr_value_new(stack, cur_type); - ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); - temp_storage.type = cur_type; - temp_storage.ptr = cur_ptr; - op_result = &temp_storage; - } - - /* Do the binary expression */ - ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, - &lvalue, op_result, left_op, desc); - if (!ptr) { - return NULL; - } - } - } - } - - if (!depth) { - /* Reached the end of the root scope */ - break; - } - - ptr = flecs_parse_ws_eol(ptr); - } - - if (!value->ptr) { - value->type = result.type; - value->ptr = flecs_expr_value_new(stack, result.type); - } - - if (value->ptr != result.ptr) { - cur = ecs_meta_cursor(world, value->type, value->ptr); - ecs_meta_set_value(&cur, &result); - } - - ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - return ptr; -error: - return NULL; -} - -const char* _ecs_script_expr_run( - ecs_world_t *world, - const char *ptr, - ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) -{ - ecs_script_expr_run_desc_t priv_desc = {0}; - if (desc) { - priv_desc = *desc; - } - - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - /* Prepare storage for temporary values */ - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_value_stack_t stack; - stack.count = 0; - stack.stage = stage; - stack.stack = &stage->allocators.deser_stack; - stack.cursor = flecs_stack_get_cursor(stack.stack); - - /* Parse expression */ - bool storage_provided = value->ptr != NULL; - ptr = flecs_script_expr_run( - world, &stack, ptr, value, EcsExprOperUnknown, &priv_desc); - - /* If no result value was provided, allocate one as we can't return a - * pointer to a temporary storage */ - if (!storage_provided && value->ptr) { - ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); - void *temp_storage = value->ptr; - value->ptr = ecs_value_new(world, value->type); - ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); - } - - /* Cleanup temporary values */ - int i; - for (i = 0; i < stack.count; i ++) { - const ecs_type_info_t *ti = stack.values[i].ti; - ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); - ti->hooks.dtor(stack.values[i].ptr, 1, ti); - } - flecs_stack_restore_cursor(stack.stack, stack.cursor); - - return ptr; -} - -#endif From fc5eacb8ea31bc0b68fd90197219f476041c5b75 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 27 Nov 2024 11:54:18 -0800 Subject: [PATCH 15/83] Implement unary expression support for runtime --- src/addons/script/expr/ast.h | 1 + src/addons/script/expr/expr.h | 6 ++ src/addons/script/expr/parser.c | 1 + src/addons/script/expr/util.c | 22 +++++- src/addons/script/expr/visit_eval.c | 58 +++++++++++++++ src/addons/script/expr/visit_fold.c | 10 ++- src/addons/script/expr/visit_type.c | 1 + src/addons/script/tokenizer.c | 7 +- test/script/project.json | 5 ++ test/script/src/Expr.c | 106 ++++++++++++++++++++++++++++ test/script/src/main.c | 27 ++++++- 11 files changed, 237 insertions(+), 7 deletions(-) diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 4eff33445a..16cec2c77d 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -93,6 +93,7 @@ typedef struct ecs_expr_member_t { ecs_expr_node_t node; ecs_expr_node_t *left; const char *member_name; + uintptr_t offset; } ecs_expr_member_t; typedef struct ecs_expr_element_t { diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 2653603133..d6d721356c 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -21,6 +21,12 @@ int flecs_value_binary( ecs_value_t *out, ecs_script_token_kind_t operator); +int flecs_value_unary( + ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_script_token_kind_t operator); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 519727436a..eecbe8a2c7 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -364,6 +364,7 @@ const char* flecs_script_parse_lhs( Parse_1(')', { break; }) + break; } diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 6883d6c522..910cc8950a 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -28,6 +28,25 @@ int flecs_value_copy_to( return -1; } +int flecs_value_unary( + ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_script_token_kind_t operator) +{ + switch(operator) { + case EcsTokNot: + ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + *(bool*)out->ptr = !*(bool*)expr->ptr; + break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); + } + + return 0; +} + int flecs_value_binary( ecs_script_t *script, const ecs_value_t *left, @@ -89,12 +108,9 @@ int flecs_value_binary( break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); - goto error; } return 0; -error: - return -1; } #endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 6d224005ac..93f8571957 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -70,6 +70,33 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_unary_visit_eval( + ecs_script_t *script, + ecs_expr_unary_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + goto error; + } + + /* Initialize storage of casted-to type */ + flecs_expr_value_alloc(script, out, node->node.type); + + if (flecs_value_unary( + script, &expr.value, &out->value, node->operator)) + { + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_expr_binary_visit_eval( ecs_script_t *script, @@ -144,6 +171,27 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_member_visit_eval( + ecs_script_t *script, + ecs_expr_member_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); + out->value.type = node->node.type; + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -164,6 +212,11 @@ int flecs_script_expr_visit_eval_priv( case EcsExprInitializer: break; case EcsExprUnary: + if (flecs_expr_unary_visit_eval( + script, (ecs_expr_binary_t*)node, desc, out)) + { + goto error; + } break; case EcsExprBinary: if (flecs_expr_binary_visit_eval( @@ -184,6 +237,11 @@ int flecs_script_expr_visit_eval_priv( case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit_eval( + script, (ecs_expr_member_t*)node, desc, out)) + { + goto error; + } break; case EcsExprElement: break; diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index b57f780c95..75b0746b0a 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -26,7 +26,7 @@ int flecs_expr_unary_visit_fold( } if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ + /* Only folding literals */ return 0; } @@ -44,7 +44,13 @@ int flecs_expr_unary_visit_fold( result->node.pos = node->node.pos; result->node.type = ecs_id(ecs_bool_t); result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); + + ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; + ecs_value_t src = { + .ptr = ((ecs_expr_val_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; + if (flecs_value_unary(script, &src, &dst, node->operator)) { + goto error; + } *node_ptr = (ecs_expr_node_t*)result; diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index a63f9cf675..321986f0d3 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -507,6 +507,7 @@ int flecs_expr_member_visit_type( ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(&cur); + node->offset = (uintptr_t)ecs_meta_get_ptr(&cur); ecs_meta_pop(&cur); /* } */ return 0; diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 29a59d3f99..9b1acffc4c 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -209,11 +209,16 @@ const char* flecs_script_identifier( 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; do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || - isdigit(c) || (c == '.') || (c == '*'); + isdigit(c) || (c == '*'); + + if (!is_var) { + is_ident = is_ident || (c == '.'); + } /* Retain \. for name lookup operation */ if (!is_ident && c == '\\' && pos[1] == '.') { diff --git a/test/script/project.json b/test/script/project.json index 9d86890573..3774827755 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -469,6 +469,11 @@ "struct_w_min_var", "struct_w_min_lparen_int_rparen", "struct_w_min_lparen_var_rparen", + "not_bool", + "not_int", + "not_paren_int", + "not_paren_expr", + "not_var", "shift_left_int", "shift_right_int", "shift_left_int_add_int", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 0afb78ce20..cedeacd170 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1953,6 +1953,112 @@ void Expr_struct_w_min_lparen_var_rparen(void) { ecs_fini(world); } +void Expr_not_bool(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "!false", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + test_assert(ecs_script_expr_run(world, "!true", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_not_int(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "!0", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + test_assert(ecs_script_expr_run(world, "!10", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_not_paren_int(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "!(0)", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + test_assert(ecs_script_expr_run(world, "!(10)", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_not_paren_expr(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "!(10 - 10)", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + test_assert(ecs_script_expr_run(world, "!(5 + 5)", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_not_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i32_t); + test_assert(var != NULL); + *(int32_t*)var->value.ptr = 10; + + ecs_value_t v = {0}; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_uint(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + *(int32_t*)var->value.ptr = 0; + test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_uint(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Expr_shift_left_int(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index ac4664980c..98fba1d70b 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -459,6 +459,11 @@ void Expr_min_lparen_int_rparen_to_i32(void); void Expr_struct_w_min_var(void); void Expr_struct_w_min_lparen_int_rparen(void); void Expr_struct_w_min_lparen_var_rparen(void); +void Expr_not_bool(void); +void Expr_not_int(void); +void Expr_not_paren_int(void); +void Expr_not_paren_expr(void); +void Expr_not_var(void); void Expr_shift_left_int(void); void Expr_shift_right_int(void); void Expr_shift_left_int_add_int(void); @@ -2436,6 +2441,26 @@ bake_test_case Expr_testcases[] = { "struct_w_min_lparen_var_rparen", Expr_struct_w_min_lparen_var_rparen }, + { + "not_bool", + Expr_not_bool + }, + { + "not_int", + Expr_not_int + }, + { + "not_paren_int", + Expr_not_paren_int + }, + { + "not_paren_expr", + Expr_not_paren_expr + }, + { + "not_var", + Expr_not_var + }, { "shift_left_int", Expr_shift_left_int @@ -3210,7 +3235,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 135, + 140, Expr_testcases }, { From cf06dcd5dd48760fe390601f7916fbedc589e498 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 27 Nov 2024 13:58:31 -0800 Subject: [PATCH 16/83] Implement initializer expression support for runtime --- distr/flecs.c | 194 +++++++++++++++++++--------- src/addons/script/expr/ast.c | 17 +++ src/addons/script/expr/ast.h | 15 ++- src/addons/script/expr/visit_eval.c | 117 ++++++++++++----- src/addons/script/expr/visit_fold.c | 36 ++---- src/addons/script/expr/visit_type.c | 9 +- test/script/src/Expr.c | 6 +- 7 files changed, 269 insertions(+), 125 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 4c61a7cf3d..88477fcbb0 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4873,6 +4873,8 @@ ecs_script_if_t* flecs_script_insert_if( #ifndef FLECS_SCRIPT_EXPR_AST_H #define FLECS_SCRIPT_EXPR_AST_H +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInitializer, @@ -4889,6 +4891,7 @@ typedef enum ecs_expr_node_kind_t { struct ecs_expr_node_t { ecs_expr_node_kind_t kind; ecs_entity_t type; + const ecs_type_info_t *type_info; const char *pos; }; @@ -4911,6 +4914,9 @@ typedef union ecs_expr_small_val_t { const char *string; ecs_entity_t entity; ecs_id_t id; + + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; } ecs_expr_small_val_t; typedef struct ecs_expr_val_t { @@ -4928,8 +4934,9 @@ typedef struct ecs_expr_initializer_element_t { typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; + const ecs_type_info_t *type_info; bool is_collection; - bool dynamic; + bool is_dynamic; } ecs_expr_initializer_t; typedef struct ecs_expr_identifier_t { @@ -4974,8 +4981,10 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; -ecs_expr_val_t* flecs_expr_value( - ecs_script_parser_t *parser); +ecs_expr_val_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, @@ -73155,6 +73164,21 @@ void* flecs_expr_ast_new_( return result; } +ecs_expr_val_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type) +{ + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node ? node->pos : NULL; + result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + return result; +} + ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) @@ -73299,6 +73323,8 @@ ecs_expr_cast_t* flecs_expr_cast( result->node.kind = EcsExprCast; result->node.pos = expr->pos; result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + ecs_assert(result->node.type_info != NULL, ECS_INTERNAL_ERROR, NULL); result->expr = expr; return result; } @@ -73984,37 +74010,19 @@ static void flecs_expr_value_alloc( ecs_script_t *script, ecs_eval_value_t *val, - ecs_entity_t type) + const ecs_type_info_t *ti) { - const EcsPrimitive *p = ecs_get(script->world, type, EcsPrimitive); - if (!p) { + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + val->value.type = ti->component; + + if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { + if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { + val->value.ptr = val->storage.small_data; + } + } else { ecs_abort(ECS_UNSUPPORTED, - "non-primitive temporary values not yet supported"); - } - - val->value.type = type; - - switch (p->kind) { - case EcsBool: val->value.ptr = &val->storage.bool_; break; - case EcsChar: val->value.ptr = &val->storage.char_; break; - case EcsByte: val->value.ptr = &val->storage.byte_; break; - case EcsU8: val->value.ptr = &val->storage.u8; break; - case EcsU16: val->value.ptr = &val->storage.u16; break; - case EcsU32: val->value.ptr = &val->storage.u32; break; - case EcsU64: val->value.ptr = &val->storage.u64; break; - case EcsI8: val->value.ptr = &val->storage.i8; break; - case EcsI16: val->value.ptr = &val->storage.i16; break; - case EcsI32: val->value.ptr = &val->storage.i32; break; - case EcsI64: val->value.ptr = &val->storage.i64; break; - case EcsF32: val->value.ptr = &val->storage.f32; break; - case EcsF64: val->value.ptr = &val->storage.f64; break; - case EcsUPtr: val->value.ptr = &val->storage.uptr; break; - case EcsIPtr: val->value.ptr = &val->storage.iptr; break; - case EcsString: val->value.ptr = &val->storage.string; break; - case EcsEntity: val->value.ptr = &val->storage.entity; break; - case EcsId: val->value.ptr = &val->storage.id; break; - default: - ecs_abort(ECS_INTERNAL_ERROR, "invalid primitive kind"); + "non-trivial temporary values not yet supported"); } } @@ -74030,6 +74038,64 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval( + script, (ecs_expr_initializer_t*)elem->value, desc, value)) + { + goto error; + } + continue; + } + + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv( + script, elem->value, desc, &expr)) + { + goto error; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem_value->node.type; + + if (ecs_value_copy(script->world, type, + ECS_OFFSET(value, elem->offset), expr.value.ptr)) + { + goto error; + } + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_initializer_visit_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + /* Initialize storage for initializer's type */ + flecs_expr_value_alloc(script, out, node->node.type_info); + return flecs_expr_initializer_eval(script, node, desc, out->value.ptr); +} + static int flecs_expr_unary_visit_eval( ecs_script_t *script, @@ -74044,7 +74110,7 @@ int flecs_expr_unary_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); if (flecs_value_unary( script, &expr.value, &out->value, node->operator)) @@ -74076,7 +74142,7 @@ int flecs_expr_binary_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); if (flecs_value_binary( script, &left.value, &right.value, &out->value, node->operator)) @@ -74119,10 +74185,11 @@ int flecs_expr_cast_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); /* Copy expression result to storage of casted-to type */ if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { + flecs_expr_visit_error(script, node, "failed to cast value"); goto error; } @@ -74170,10 +74237,15 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprInitializer: + if (flecs_expr_initializer_visit_eval( + script, (ecs_expr_initializer_t*)node, desc, out)) + { + goto error; + } break; case EcsExprUnary: if (flecs_expr_unary_visit_eval( - script, (ecs_expr_binary_t*)node, desc, out)) + script, (ecs_expr_unary_t*)node, desc, out)) { goto error; } @@ -74243,7 +74315,10 @@ int flecs_script_expr_visit_eval( out->ptr = ecs_value_new(script->world, out->type); } - flecs_value_copy_to(script->world, out, &val.value); + if (flecs_value_copy_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } return 0; error: @@ -74290,11 +74365,8 @@ int flecs_expr_unary_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = ecs_id(ecs_bool_t); + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); result->ptr = &result->storage.bool_; ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; @@ -74334,12 +74406,8 @@ int flecs_expr_binary_visit_fold( ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->ptr = &result->storage.u64; - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); /* Handle bitmask separately since it's not done by switch */ if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { @@ -74401,7 +74469,10 @@ int flecs_expr_cast_visit_fold( .ptr = expr->ptr }; - ecs_meta_set_value(&cur, &value); + if (ecs_meta_set_value(&cur, &value)) { + flecs_expr_visit_error(script, node, "failed to assign value"); + goto error; + } expr->node.type = dst_type; @@ -74442,7 +74513,7 @@ int flecs_expr_initializer_pre_fold( } } - if (node->dynamic) { + if (node->is_dynamic) { *can_fold = false; return 0; } @@ -74511,11 +74582,8 @@ int flecs_expr_initializer_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; *node_ptr = (ecs_expr_node_t*)result; } @@ -74533,11 +74601,8 @@ int flecs_expr_identifier_visit_fold( ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_entity_t type = node->node.type; - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); if (type == ecs_id(ecs_entity_t)) { result->storage.entity = desc->lookup_action( @@ -75156,7 +75221,7 @@ int flecs_expr_initializer_visit_type( /* Opaque types do not have deterministic offsets */ bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; - node->dynamic = is_opaque; + node->is_dynamic = is_opaque; ecs_meta_push(cur); /* { */ @@ -75524,6 +75589,13 @@ int flecs_script_expr_visit_type_priv( break; } + ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); + + if (node->type) { + node->type_info = ecs_get_type_info(script->world, node->type); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + } + return 0; error: return -1; diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index bb86e6a468..30b5126df0 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -25,6 +25,21 @@ void* flecs_expr_ast_new_( return result; } +ecs_expr_val_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type) +{ + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node ? node->pos : NULL; + result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + return result; +} + ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) @@ -169,6 +184,8 @@ ecs_expr_cast_t* flecs_expr_cast( result->node.kind = EcsExprCast; result->node.pos = expr->pos; result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + ecs_assert(result->node.type_info != NULL, ECS_INTERNAL_ERROR, NULL); result->expr = expr; return result; } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 16cec2c77d..052c6093b6 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -6,6 +6,8 @@ #ifndef FLECS_SCRIPT_EXPR_AST_H #define FLECS_SCRIPT_EXPR_AST_H +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInitializer, @@ -22,6 +24,7 @@ typedef enum ecs_expr_node_kind_t { struct ecs_expr_node_t { ecs_expr_node_kind_t kind; ecs_entity_t type; + const ecs_type_info_t *type_info; const char *pos; }; @@ -44,6 +47,9 @@ typedef union ecs_expr_small_val_t { const char *string; ecs_entity_t entity; ecs_id_t id; + + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; } ecs_expr_small_val_t; typedef struct ecs_expr_val_t { @@ -61,8 +67,9 @@ typedef struct ecs_expr_initializer_element_t { typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; + const ecs_type_info_t *type_info; bool is_collection; - bool dynamic; + bool is_dynamic; } ecs_expr_initializer_t; typedef struct ecs_expr_identifier_t { @@ -107,8 +114,10 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; -ecs_expr_val_t* flecs_expr_value( - ecs_script_parser_t *parser); +ecs_expr_val_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); ecs_expr_val_t* flecs_expr_bool( ecs_script_parser_t *parser, diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 93f8571957..310ba34a21 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -24,37 +24,19 @@ static void flecs_expr_value_alloc( ecs_script_t *script, ecs_eval_value_t *val, - ecs_entity_t type) + const ecs_type_info_t *ti) { - const EcsPrimitive *p = ecs_get(script->world, type, EcsPrimitive); - if (!p) { - ecs_abort(ECS_UNSUPPORTED, - "non-primitive temporary values not yet supported"); - } + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + val->value.type = ti->component; - val->value.type = type; - - switch (p->kind) { - case EcsBool: val->value.ptr = &val->storage.bool_; break; - case EcsChar: val->value.ptr = &val->storage.char_; break; - case EcsByte: val->value.ptr = &val->storage.byte_; break; - case EcsU8: val->value.ptr = &val->storage.u8; break; - case EcsU16: val->value.ptr = &val->storage.u16; break; - case EcsU32: val->value.ptr = &val->storage.u32; break; - case EcsU64: val->value.ptr = &val->storage.u64; break; - case EcsI8: val->value.ptr = &val->storage.i8; break; - case EcsI16: val->value.ptr = &val->storage.i16; break; - case EcsI32: val->value.ptr = &val->storage.i32; break; - case EcsI64: val->value.ptr = &val->storage.i64; break; - case EcsF32: val->value.ptr = &val->storage.f32; break; - case EcsF64: val->value.ptr = &val->storage.f64; break; - case EcsUPtr: val->value.ptr = &val->storage.uptr; break; - case EcsIPtr: val->value.ptr = &val->storage.iptr; break; - case EcsString: val->value.ptr = &val->storage.string; break; - case EcsEntity: val->value.ptr = &val->storage.entity; break; - case EcsId: val->value.ptr = &val->storage.id; break; - default: - ecs_abort(ECS_INTERNAL_ERROR, "invalid primitive kind"); + if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { + if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { + val->value.ptr = val->storage.small_data; + } + } else { + ecs_abort(ECS_UNSUPPORTED, + "non-trivial temporary values not yet supported"); } } @@ -70,6 +52,64 @@ int flecs_expr_value_visit_eval( return 0; } +static +int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval( + script, (ecs_expr_initializer_t*)elem->value, desc, value)) + { + goto error; + } + continue; + } + + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv( + script, elem->value, desc, &expr)) + { + goto error; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem_value->node.type; + + if (ecs_value_copy(script->world, type, + ECS_OFFSET(value, elem->offset), expr.value.ptr)) + { + goto error; + } + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_initializer_visit_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + /* Initialize storage for initializer's type */ + flecs_expr_value_alloc(script, out, node->node.type_info); + return flecs_expr_initializer_eval(script, node, desc, out->value.ptr); +} + static int flecs_expr_unary_visit_eval( ecs_script_t *script, @@ -84,7 +124,7 @@ int flecs_expr_unary_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); if (flecs_value_unary( script, &expr.value, &out->value, node->operator)) @@ -116,7 +156,7 @@ int flecs_expr_binary_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); if (flecs_value_binary( script, &left.value, &right.value, &out->value, node->operator)) @@ -159,10 +199,11 @@ int flecs_expr_cast_visit_eval( } /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type); + flecs_expr_value_alloc(script, out, node->node.type_info); /* Copy expression result to storage of casted-to type */ if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { + flecs_expr_visit_error(script, node, "failed to cast value"); goto error; } @@ -210,10 +251,15 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprInitializer: + if (flecs_expr_initializer_visit_eval( + script, (ecs_expr_initializer_t*)node, desc, out)) + { + goto error; + } break; case EcsExprUnary: if (flecs_expr_unary_visit_eval( - script, (ecs_expr_binary_t*)node, desc, out)) + script, (ecs_expr_unary_t*)node, desc, out)) { goto error; } @@ -283,7 +329,10 @@ int flecs_script_expr_visit_eval( out->ptr = ecs_value_new(script->world, out->type); } - flecs_value_copy_to(script->world, out, &val.value); + if (flecs_value_copy_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } return 0; error: diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 75b0746b0a..97b2f99143 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -38,11 +38,8 @@ int flecs_expr_unary_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = ecs_id(ecs_bool_t); + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); result->ptr = &result->storage.bool_; ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; @@ -82,12 +79,8 @@ int flecs_expr_binary_visit_fold( ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->ptr = &result->storage.u64; - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); /* Handle bitmask separately since it's not done by switch */ if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { @@ -149,7 +142,10 @@ int flecs_expr_cast_visit_fold( .ptr = expr->ptr }; - ecs_meta_set_value(&cur, &value); + if (ecs_meta_set_value(&cur, &value)) { + flecs_expr_visit_error(script, node, "failed to assign value"); + goto error; + } expr->node.type = dst_type; @@ -190,7 +186,7 @@ int flecs_expr_initializer_pre_fold( } } - if (node->dynamic) { + if (node->is_dynamic) { *can_fold = false; return 0; } @@ -259,11 +255,8 @@ int flecs_expr_initializer_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; *node_ptr = (ecs_expr_node_t*)result; } @@ -281,11 +274,8 @@ int flecs_expr_identifier_visit_fold( ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_entity_t type = node->node.type; - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = type; + ecs_expr_val_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); if (type == ecs_id(ecs_entity_t)) { result->storage.entity = desc->lookup_action( diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 321986f0d3..1d0f6f4573 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -287,7 +287,7 @@ int flecs_expr_initializer_visit_type( /* Opaque types do not have deterministic offsets */ bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; - node->dynamic = is_opaque; + node->is_dynamic = is_opaque; ecs_meta_push(cur); /* { */ @@ -655,6 +655,13 @@ int flecs_script_expr_visit_type_priv( break; } + ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); + + if (node->type) { + node->type_info = ecs_get_type_info(script->world, node->type); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + } + return 0; error: return -1; diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index cedeacd170..f3dfac6994 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1878,8 +1878,8 @@ void Expr_struct_w_min_var(void) { ecs_script_vars_t *vars = ecs_script_vars_init(world); ecs_script_var_t *var = ecs_script_vars_define( - vars, "foo", ecs_u64_t); - *(ecs_u64_t*)var->value.ptr = 10; + vars, "foo", ecs_i64_t); + *(ecs_i64_t*)var->value.ptr = 10; Mass v = {0}; ecs_script_expr_run_desc_t desc = { .vars = vars }; @@ -1889,7 +1889,7 @@ void Expr_struct_w_min_var(void) { test_assert(ptr != NULL); test_assert(!ptr[0]); - test_uint(v.value, -10); + test_int(v.value, -10); ecs_script_vars_fini(vars); ecs_fini(world); From 20f61fd0cb73b2466f85d35fb23bc5ff6907f9f1 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 27 Nov 2024 23:50:59 -0800 Subject: [PATCH 17/83] Implement element expression support for runtime --- distr/flecs.c | 41 +++ src/addons/script/expr/ast.h | 1 + src/addons/script/expr/visit_eval.c | 32 +++ src/addons/script/expr/visit_type.c | 8 + test/script/project.json | 6 + test/script/src/Expr.c | 378 ++++++++++++++++++++++++++++ test/script/src/main.c | 32 ++- 7 files changed, 497 insertions(+), 1 deletion(-) diff --git a/distr/flecs.c b/distr/flecs.c index 88477fcbb0..c63a057ce5 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4974,6 +4974,7 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *index; + ecs_size_t elem_size; } ecs_expr_element_t; typedef struct ecs_expr_cast_t { @@ -74219,6 +74220,33 @@ int flecs_expr_member_visit_eval( return -1; } +static +int flecs_expr_element_visit_eval( + ecs_script_t *script, + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + ecs_eval_value_t index = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->index, desc, &index)) { + goto error; + } + + int32_t index_value = *(int64_t*)index.value.ptr; + + out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); + out->value.type = node->node.type; + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -74276,6 +74304,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprElement: + if (flecs_expr_element_visit_eval( + script, (ecs_expr_element_t*)node, desc, out)) + { + goto error; + } break; case EcsExprCast: if (flecs_expr_cast_visit_eval( @@ -75504,10 +75537,18 @@ int flecs_expr_element_visit_type( const EcsArray *type_array = ecs_get(world, left_type, EcsArray); ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); node->node.type = type_array->type; + const ecs_type_info_t *elem_ti = ecs_get_type_info( + world, type_array->type); + ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + node->elem_size = elem_ti->size; } else if (type->kind == EcsVectorType) { const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); node->node.type = type_vector->type; + const ecs_type_info_t *elem_ti = ecs_get_type_info( + world, type_vector->type); + ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + node->elem_size = elem_ti->size; } else { char *type_str = ecs_get_path(script->world, node->left->type); flecs_expr_visit_error(script, node, diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 052c6093b6..aca0ba97e4 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -107,6 +107,7 @@ typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *index; + ecs_size_t elem_size; } ecs_expr_element_t; typedef struct ecs_expr_cast_t { diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 310ba34a21..06c3821c7d 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -233,6 +233,33 @@ int flecs_expr_member_visit_eval( return -1; } +static +int flecs_expr_element_visit_eval( + ecs_script_t *script, + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + ecs_eval_value_t index = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->index, desc, &index)) { + goto error; + } + + int32_t index_value = *(int64_t*)index.value.ptr; + + out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); + out->value.type = node->node.type; + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -290,6 +317,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprElement: + if (flecs_expr_element_visit_eval( + script, (ecs_expr_element_t*)node, desc, out)) + { + goto error; + } break; case EcsExprCast: if (flecs_expr_cast_visit_eval( diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 1d0f6f4573..2082c76ce4 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -570,10 +570,18 @@ int flecs_expr_element_visit_type( const EcsArray *type_array = ecs_get(world, left_type, EcsArray); ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); node->node.type = type_array->type; + const ecs_type_info_t *elem_ti = ecs_get_type_info( + world, type_array->type); + ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + node->elem_size = elem_ti->size; } else if (type->kind == EcsVectorType) { const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); node->node.type = type_vector->type; + const ecs_type_info_t *elem_ti = ecs_get_type_info( + world, type_vector->type); + ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + node->elem_size = elem_ti->size; } else { char *type_str = ecs_get_path(script->world, node->left->type); flecs_expr_visit_error(script, node, diff --git a/test/script/project.json b/test/script/project.json index 3774827755..b52d7b6c80 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -433,6 +433,12 @@ "add_to_var", "add_var_to", "var_member", + "var_member_member", + "var_element", + "var_element_element", + "var_member_element", + "var_member_element_inline", + "var_element_member", "bool_cond_and_bool", "bool_cond_or_bool", "int_cond_and_int", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index f3dfac6994..2c742b96e7 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1049,6 +1049,384 @@ void Expr_var_member(void) { ecs_fini(world); } +void Expr_var_member_member(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t ecs_id(Point) = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t ecs_id(Line) = ecs_struct(world, { + .members = { + {"start", ecs_id(Point)}, + {"stop", ecs_id(Point)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", Line); + *(Line*)var->value.ptr = (Line){{10, 20}, {30, 40}}; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.start.x", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.start.y", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.stop.x", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 30); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.stop.y", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 40); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + +void Expr_var_element(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t array = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define_id( + vars, "foo", array); + ((int*)var->value.ptr)[0] = 10; + ((int*)var->value.ptr)[1] = 20; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + +void Expr_var_element_element(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ints = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_entity_t arrays = ecs_array(world, { + .type = ints, + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define_id( + vars, "foo", arrays); + + typedef int32_t Ints[2]; + Ints value[] = {{10, 20}, {30, 40}, {50, 60}}; + ecs_os_memcpy(var->value.ptr, value, sizeof(value)); + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[0][0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[0][1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[1][0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 30); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[1][1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 40); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + +void Expr_var_member_element(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x[2]; + int32_t y[2]; + } Points; + + ecs_entity_t Ints = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_entity_t ecs_id(Points) = ecs_struct(world, { + .members = { + {"x", Ints }, + {"y", Ints } + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", Points); + *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.x[1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.y[0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 30); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.y[1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 40); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + +void Expr_var_member_element_inline(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x[2]; + int32_t y[2]; + } Points; + + ecs_entity_t ecs_id(Points) = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t), .count = 2}, + {"y", ecs_id(ecs_i32_t), .count = 2} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", Points); + *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.x[1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.y[0]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 30); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo.y[1]", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 40); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + +void Expr_var_element_member(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ecs_entity_t ecs_id(Point) = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t array = ecs_array(world, { + .type = ecs_id(Point), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define_id( + vars, "foo", array); + ((Point*)var->value.ptr)[0] = (Point){10, 20}; + ((Point*)var->value.ptr)[1] = (Point){30, 40}; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[0].x", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[0].y", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[1].x", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 30); + ecs_value_free(world, v.type, v.ptr); + } + { + ecs_value_t v = {0}; + const char *ptr = ecs_script_expr_run(world, "$foo[1].y", &v, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + test_uint(v.type, ecs_id(ecs_i32_t)); + test_int(*(ecs_i32_t*)v.ptr, 40); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_script_vars_fini(vars); + ecs_fini(world); +} + void Expr_bool_cond_and_bool(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 98fba1d70b..2294daff23 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -423,6 +423,12 @@ void Expr_struct_result_lparen_int_rparen(void); void Expr_add_to_var(void); void Expr_add_var_to(void); void Expr_var_member(void); +void Expr_var_member_member(void); +void Expr_var_element(void); +void Expr_var_element_element(void); +void Expr_var_member_element(void); +void Expr_var_member_element_inline(void); +void Expr_var_element_member(void); void Expr_bool_cond_and_bool(void); void Expr_bool_cond_or_bool(void); void Expr_int_cond_and_int(void); @@ -2297,6 +2303,30 @@ bake_test_case Expr_testcases[] = { "var_member", Expr_var_member }, + { + "var_member_member", + Expr_var_member_member + }, + { + "var_element", + Expr_var_element + }, + { + "var_element_element", + Expr_var_element_element + }, + { + "var_member_element", + Expr_var_member_element + }, + { + "var_member_element_inline", + Expr_var_member_element_inline + }, + { + "var_element_member", + Expr_var_element_member + }, { "bool_cond_and_bool", Expr_bool_cond_and_bool @@ -3235,7 +3265,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 140, + 146, Expr_testcases }, { From 69c3c3205ddbf9a1d50e3f35200f18c6550bf364 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 28 Nov 2024 14:32:31 -0800 Subject: [PATCH 18/83] Implement accessing elements of inline array --- distr/flecs.c | 95 +++++++++++++++++------------ src/addons/script/expr/visit_type.c | 95 +++++++++++++++++------------ test/script/src/Expr.c | 1 + 3 files changed, 111 insertions(+), 80 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index c63a057ce5..80d2b2cde9 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -75256,7 +75256,9 @@ int flecs_expr_initializer_visit_type( bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; node->is_dynamic = is_opaque; - ecs_meta_push(cur); /* { */ + if (ecs_meta_push(cur)) { + goto error; + } if (ecs_meta_is_collection(cur) != node->is_collection) { char *type_str = ecs_get_path(script->world, type); @@ -75292,7 +75294,10 @@ int flecs_expr_initializer_visit_type( } ecs_entity_t elem_type = ecs_meta_get_type(cur); - if (flecs_script_expr_visit_type_priv(script, elem->value, cur, desc)) { + ecs_meta_cursor_t elem_cur = *cur; + if (flecs_script_expr_visit_type_priv( + script, elem->value, &elem_cur, desc)) + { goto error; } @@ -75308,7 +75313,9 @@ int flecs_expr_initializer_visit_type( node->node.type = type; - ecs_meta_pop(cur); /* } */ + if (ecs_meta_pop(cur)) { + goto error; + } return 0; error: @@ -75396,10 +75403,11 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (cur) { + if (cur->valid) { node->node.type = ecs_meta_get_type(cur); } else { node->node.type = ecs_id(ecs_entity_t); + *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); } return 0; @@ -75409,6 +75417,7 @@ static int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( @@ -75422,6 +75431,8 @@ int flecs_expr_variable_visit_type( node->node.type = var->value.type; node->var = var; + *cur = ecs_meta_cursor(script->world, var->value.type, NULL); + return 0; error: return -1; @@ -75431,9 +75442,10 @@ static int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -75444,8 +75456,8 @@ int flecs_expr_member_visit_type( if (!type) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, - "cannot resolve member on value of type '%s' (missing reflection data)", - type_str); + "cannot resolve member on value of type '%s' " + "(missing reflection data)", type_str); ecs_os_free(type_str); goto error; } @@ -75453,16 +75465,15 @@ int flecs_expr_member_visit_type( if (type->kind != EcsStructType) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, - "cannot resolve member on non-struct type '%s'", - type_str); + "cannot resolve member on non-struct type '%s'", type_str); ecs_os_free(type_str); goto error; } - ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); - ecs_meta_push(&cur); /* { */ + ecs_meta_push(cur); /* { */ + int prev_log = ecs_log_set_level(-4); - if (ecs_meta_dotmember(&cur, node->member_name)) { + if (ecs_meta_dotmember(cur, node->member_name)) { ecs_log_set_level(prev_log); char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, @@ -75473,9 +75484,10 @@ int flecs_expr_member_visit_type( } ecs_log_set_level(prev_log); - node->node.type = ecs_meta_get_type(&cur); - node->offset = (uintptr_t)ecs_meta_get_ptr(&cur); - ecs_meta_pop(&cur); /* } */ + node->node.type = ecs_meta_get_type(cur); + const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + node->offset = (uintptr_t)ecs_meta_get_ptr(cur); return 0; error: @@ -75486,9 +75498,10 @@ static int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -75498,6 +75511,7 @@ int flecs_expr_element_visit_type( ecs_world_t *world = script->world; ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); if (!type) { char *type_str = ecs_get_path(world, left_type); @@ -75533,31 +75547,30 @@ int flecs_expr_element_visit_type( "invalid component expression"); goto error; } - } else if (type->kind == EcsArrayType) { - const EcsArray *type_array = ecs_get(world, left_type, EcsArray); - ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_array->type; - const ecs_type_info_t *elem_ti = ecs_get_type_info( - world, type_array->type); - ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); - node->elem_size = elem_ti->size; - } else if (type->kind == EcsVectorType) { - const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); - ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_vector->type; + } else { + if (ecs_meta_push(cur)) { + goto not_a_collection; + } + + if (!ecs_meta_is_collection(cur)) { + goto not_a_collection; + } + + node->node.type = ecs_meta_get_type(cur); + const ecs_type_info_t *elem_ti = ecs_get_type_info( - world, type_vector->type); - ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + script->world, node->node.type); node->elem_size = elem_ti->size; - } else { - char *type_str = ecs_get_path(script->world, node->left->type); - flecs_expr_visit_error(script, node, - "invalid usage of [] on non collection/entity type '%s'", type_str); - ecs_os_free(type_str); - goto error; } return 0; + +not_a_collection: { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); +} error: return -1; } @@ -75605,7 +75618,7 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprVariable: if (flecs_expr_variable_visit_type( - script, (ecs_expr_variable_t*)node, desc)) + script, (ecs_expr_variable_t*)node, cur, desc)) { goto error; } @@ -75614,14 +75627,14 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprMember: if (flecs_expr_member_visit_type( - script, (ecs_expr_member_t*)node, desc)) + script, (ecs_expr_member_t*)node, cur, desc)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_type( - script, (ecs_expr_element_t*)node, desc)) + script, (ecs_expr_element_t*)node, cur, desc)) { goto error; } @@ -75652,7 +75665,9 @@ int flecs_script_expr_visit_type( script->world, desc->type, NULL); return flecs_script_expr_visit_type_priv(script, node, &cur, desc); } else { - return flecs_script_expr_visit_type_priv(script, node, NULL, desc); + ecs_meta_cursor_t cur; + ecs_os_zeromem(&cur); + return flecs_script_expr_visit_type_priv(script, node, &cur, desc); } } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 2082c76ce4..564de5db7e 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -289,7 +289,9 @@ int flecs_expr_initializer_visit_type( bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; node->is_dynamic = is_opaque; - ecs_meta_push(cur); /* { */ + if (ecs_meta_push(cur)) { + goto error; + } if (ecs_meta_is_collection(cur) != node->is_collection) { char *type_str = ecs_get_path(script->world, type); @@ -325,7 +327,10 @@ int flecs_expr_initializer_visit_type( } ecs_entity_t elem_type = ecs_meta_get_type(cur); - if (flecs_script_expr_visit_type_priv(script, elem->value, cur, desc)) { + ecs_meta_cursor_t elem_cur = *cur; + if (flecs_script_expr_visit_type_priv( + script, elem->value, &elem_cur, desc)) + { goto error; } @@ -341,7 +346,9 @@ int flecs_expr_initializer_visit_type( node->node.type = type; - ecs_meta_pop(cur); /* } */ + if (ecs_meta_pop(cur)) { + goto error; + } return 0; error: @@ -429,10 +436,11 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (cur) { + if (cur->valid) { node->node.type = ecs_meta_get_type(cur); } else { node->node.type = ecs_id(ecs_entity_t); + *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); } return 0; @@ -442,6 +450,7 @@ static int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( @@ -455,6 +464,8 @@ int flecs_expr_variable_visit_type( node->node.type = var->value.type; node->var = var; + *cur = ecs_meta_cursor(script->world, var->value.type, NULL); + return 0; error: return -1; @@ -464,9 +475,10 @@ static int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -477,8 +489,8 @@ int flecs_expr_member_visit_type( if (!type) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, - "cannot resolve member on value of type '%s' (missing reflection data)", - type_str); + "cannot resolve member on value of type '%s' " + "(missing reflection data)", type_str); ecs_os_free(type_str); goto error; } @@ -486,16 +498,15 @@ int flecs_expr_member_visit_type( if (type->kind != EcsStructType) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, - "cannot resolve member on non-struct type '%s'", - type_str); + "cannot resolve member on non-struct type '%s'", type_str); ecs_os_free(type_str); goto error; } - ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); - ecs_meta_push(&cur); /* { */ + ecs_meta_push(cur); /* { */ + int prev_log = ecs_log_set_level(-4); - if (ecs_meta_dotmember(&cur, node->member_name)) { + if (ecs_meta_dotmember(cur, node->member_name)) { ecs_log_set_level(prev_log); char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, @@ -506,9 +517,10 @@ int flecs_expr_member_visit_type( } ecs_log_set_level(prev_log); - node->node.type = ecs_meta_get_type(&cur); - node->offset = (uintptr_t)ecs_meta_get_ptr(&cur); - ecs_meta_pop(&cur); /* } */ + node->node.type = ecs_meta_get_type(cur); + const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + node->offset = (uintptr_t)ecs_meta_get_ptr(cur); return 0; error: @@ -519,9 +531,10 @@ static int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, + ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, NULL, desc)) { + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -531,6 +544,7 @@ int flecs_expr_element_visit_type( ecs_world_t *world = script->world; ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); if (!type) { char *type_str = ecs_get_path(world, left_type); @@ -566,31 +580,30 @@ int flecs_expr_element_visit_type( "invalid component expression"); goto error; } - } else if (type->kind == EcsArrayType) { - const EcsArray *type_array = ecs_get(world, left_type, EcsArray); - ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_array->type; - const ecs_type_info_t *elem_ti = ecs_get_type_info( - world, type_array->type); - ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); - node->elem_size = elem_ti->size; - } else if (type->kind == EcsVectorType) { - const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); - ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_vector->type; + } else { + if (ecs_meta_push(cur)) { + goto not_a_collection; + } + + if (!ecs_meta_is_collection(cur)) { + goto not_a_collection; + } + + node->node.type = ecs_meta_get_type(cur); + const ecs_type_info_t *elem_ti = ecs_get_type_info( - world, type_vector->type); - ecs_assert(elem_ti != NULL, ECS_INTERNAL_ERROR, NULL); + script->world, node->node.type); node->elem_size = elem_ti->size; - } else { - char *type_str = ecs_get_path(script->world, node->left->type); - flecs_expr_visit_error(script, node, - "invalid usage of [] on non collection/entity type '%s'", type_str); - ecs_os_free(type_str); - goto error; } return 0; + +not_a_collection: { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); +} error: return -1; } @@ -638,7 +651,7 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprVariable: if (flecs_expr_variable_visit_type( - script, (ecs_expr_variable_t*)node, desc)) + script, (ecs_expr_variable_t*)node, cur, desc)) { goto error; } @@ -647,14 +660,14 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprMember: if (flecs_expr_member_visit_type( - script, (ecs_expr_member_t*)node, desc)) + script, (ecs_expr_member_t*)node, cur, desc)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_type( - script, (ecs_expr_element_t*)node, desc)) + script, (ecs_expr_element_t*)node, cur, desc)) { goto error; } @@ -685,7 +698,9 @@ int flecs_script_expr_visit_type( script->world, desc->type, NULL); return flecs_script_expr_visit_type_priv(script, node, &cur, desc); } else { - return flecs_script_expr_visit_type_priv(script, node, NULL, desc); + ecs_meta_cursor_t cur; + ecs_os_zeromem(&cur); + return flecs_script_expr_visit_type_priv(script, node, &cur, desc); } } diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 2c742b96e7..b69186e542 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1128,6 +1128,7 @@ void Expr_var_element(void) { ecs_world_t *world = ecs_init(); ecs_entity_t array = ecs_array(world, { + .entity = ecs_entity(world, { .name = "array" }), .type = ecs_id(ecs_i32_t), .count = 2 }); From 316274fa3217fe78fc41cfb754edad70422f7329 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 29 Nov 2024 01:16:22 -0800 Subject: [PATCH 19/83] Implement component expressions for runtime --- distr/flecs.c | 111 +++++++++++++++++++++++++- src/addons/script/expr/ast.h | 7 ++ src/addons/script/expr/visit_eval.c | 47 +++++++++++ src/addons/script/expr/visit_fold.c | 43 ++++++++++ src/addons/script/expr/visit_to_str.c | 1 + src/addons/script/expr/visit_type.c | 13 ++- 6 files changed, 220 insertions(+), 2 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 80d2b2cde9..a4990df0c4 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4885,6 +4885,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprFunction, EcsExprMember, EcsExprElement, + EcsExprComponent, EcsExprCast } ecs_expr_node_kind_t; @@ -4977,6 +4978,12 @@ typedef struct ecs_expr_element_t { ecs_size_t elem_size; } ecs_expr_element_t; +typedef struct ecs_expr_component_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_id_t component; +} ecs_expr_component_t; + typedef struct ecs_expr_cast_t { ecs_expr_node_t node; ecs_expr_node_t *expr; @@ -74247,6 +74254,46 @@ int flecs_expr_element_visit_eval( return -1; } +static +int flecs_expr_component_visit_eval( + ecs_script_t *script, + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + /* Left side of expression must be of entity type */ + ecs_assert(expr.value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + + /* Component must be resolvable at parse time */ + ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t entity = *(ecs_entity_t*)expr.value.ptr; + ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; + + out->value.type = node->node.type; + out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + + if (!out->value.ptr) { + char *estr = ecs_get_path(script->world, entity); + char *cstr = ecs_get_path(script->world, component); + flecs_expr_visit_error(script, node, + "entity '%s' does not have component '%s'", estr, cstr); + ecs_os_free(estr); + ecs_os_free(cstr); + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -74310,6 +74357,13 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprComponent: + if (flecs_expr_component_visit_eval( + script, (ecs_expr_element_t*)node, desc, out)) + { + goto error; + } + break; case EcsExprCast: if (flecs_expr_cast_visit_eval( script, (ecs_expr_cast_t*)node, desc, out)) @@ -74662,6 +74716,42 @@ int flecs_expr_identifier_visit_fold( return -1; } +int flecs_expr_member_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + +int flecs_expr_element_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->index, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -74698,8 +74788,15 @@ int flecs_script_expr_visit_fold( case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprElement: + case EcsExprComponent: + if (flecs_expr_element_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprCast: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { @@ -74927,6 +75024,7 @@ int flecs_expr_node_to_str( } break; case EcsExprElement: + case EcsExprComponent: if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { goto error; } @@ -75505,7 +75603,10 @@ int flecs_expr_element_visit_type( goto error; } - if (flecs_script_expr_visit_type_priv(script, node->index, NULL, desc)) { + ecs_meta_cursor_t index_cur = {0}; + if (flecs_script_expr_visit_type_priv( + script, node->index, &index_cur, desc)) + { goto error; } @@ -75542,6 +75643,10 @@ int flecs_expr_element_visit_type( ident->value); goto error; } + + node->node.kind = EcsExprComponent; + + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); } else { flecs_expr_visit_error(script, node, "invalid component expression"); @@ -75641,6 +75746,10 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprCast: break; + case EcsExprComponent: + /* Component expressions are derived by type visitor */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + break; } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index aca0ba97e4..3a0d59f92c 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -18,6 +18,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprFunction, EcsExprMember, EcsExprElement, + EcsExprComponent, EcsExprCast } ecs_expr_node_kind_t; @@ -110,6 +111,12 @@ typedef struct ecs_expr_element_t { ecs_size_t elem_size; } ecs_expr_element_t; +typedef struct ecs_expr_component_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_id_t component; +} ecs_expr_component_t; + typedef struct ecs_expr_cast_t { ecs_expr_node_t node; ecs_expr_node_t *expr; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 06c3821c7d..ed7e378af8 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -260,6 +260,46 @@ int flecs_expr_element_visit_eval( return -1; } +static +int flecs_expr_component_visit_eval( + ecs_script_t *script, + ecs_expr_element_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + /* Left side of expression must be of entity type */ + ecs_assert(expr.value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + + /* Component must be resolvable at parse time */ + ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t entity = *(ecs_entity_t*)expr.value.ptr; + ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; + + out->value.type = node->node.type; + out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + + if (!out->value.ptr) { + char *estr = ecs_get_path(script->world, entity); + char *cstr = ecs_get_path(script->world, component); + flecs_expr_visit_error(script, node, + "entity '%s' does not have component '%s'", estr, cstr); + ecs_os_free(estr); + ecs_os_free(cstr); + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_script_expr_visit_eval_priv( ecs_script_t *script, @@ -323,6 +363,13 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprComponent: + if (flecs_expr_component_visit_eval( + script, (ecs_expr_element_t*)node, desc, out)) + { + goto error; + } + break; case EcsExprCast: if (flecs_expr_cast_visit_eval( script, (ecs_expr_cast_t*)node, desc, out)) diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 97b2f99143..64c0489f96 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -302,6 +302,42 @@ int flecs_expr_identifier_visit_fold( return -1; } +int flecs_expr_member_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + +int flecs_expr_element_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, &node->index, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_script_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -338,8 +374,15 @@ int flecs_script_expr_visit_fold( case EcsExprFunction: break; case EcsExprMember: + if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprElement: + case EcsExprComponent: + if (flecs_expr_element_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprCast: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 2fd4bcbd58..9c8b584d7b 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -212,6 +212,7 @@ int flecs_expr_node_to_str( } break; case EcsExprElement: + case EcsExprComponent: if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { goto error; } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 564de5db7e..7b1a8ff321 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -538,7 +538,10 @@ int flecs_expr_element_visit_type( goto error; } - if (flecs_script_expr_visit_type_priv(script, node->index, NULL, desc)) { + ecs_meta_cursor_t index_cur = {0}; + if (flecs_script_expr_visit_type_priv( + script, node->index, &index_cur, desc)) + { goto error; } @@ -575,6 +578,10 @@ int flecs_expr_element_visit_type( ident->value); goto error; } + + node->node.kind = EcsExprComponent; + + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); } else { flecs_expr_visit_error(script, node, "invalid component expression"); @@ -674,6 +681,10 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprCast: break; + case EcsExprComponent: + /* Component expressions are derived by type visitor */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + break; } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); From 4a2d67e9f37f6df524a669786e1fb209c8839b48 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 29 Nov 2024 16:25:40 -0800 Subject: [PATCH 20/83] Implement basic function support for scripts --- distr/flecs.c | 372 ++++++++++++++++++++++++-- distr/flecs.h | 35 +++ include/flecs/addons/script.h | 35 +++ src/addons/script/builtin_functions.c | 119 ++++++++ src/addons/script/expr/ast.c | 8 + src/addons/script/expr/ast.h | 10 + src/addons/script/expr/parser.c | 11 + src/addons/script/expr/visit_eval.c | 42 +++ src/addons/script/expr/visit_fold.c | 19 ++ src/addons/script/expr/visit_to_str.c | 19 ++ src/addons/script/expr/visit_type.c | 102 ++++++- src/addons/script/script.c | 22 ++ src/addons/script/script.h | 9 + src/addons/script/tokenizer.c | 12 +- 14 files changed, 781 insertions(+), 34 deletions(-) create mode 100644 src/addons/script/builtin_functions.c diff --git a/distr/flecs.c b/distr/flecs.c index a4990df0c4..3c6ef131a7 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4646,6 +4646,12 @@ struct ecs_script_parser_t { ecs_term_ref_t *extra_args; }; +typedef struct ecs_function_calldata_t { + ecs_entity_t function; + ecs_function_callback_t callback; + void *ctx; +} ecs_function_calldata_t; + /** * @file addons/script/ast.h * @brief Script AST. @@ -4971,6 +4977,13 @@ typedef struct ecs_expr_member_t { uintptr_t offset; } ecs_expr_member_t; +typedef struct ecs_expr_function_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *function_name; + ecs_function_calldata_t calldata; +} ecs_expr_function_t; + typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; @@ -5038,6 +5051,9 @@ ecs_expr_binary_t* flecs_expr_binary( ecs_expr_member_t* flecs_expr_member( ecs_script_parser_t *parser); +ecs_expr_function_t* flecs_expr_function( + ecs_script_parser_t *parser); + ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); @@ -5387,6 +5403,9 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +void flecs_script_register_builtin_functions( + ecs_world_t *world); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H @@ -54870,6 +54889,124 @@ ecs_script_if_t* flecs_script_insert_if( #endif +/** + * @file addons/script/builtin_functions.c + * @brief Builtin script functions. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_meta_entity_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_name(ctx->world, entity); +} + +static +void flecs_meta_entity_path( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_path(ctx->world, entity); +} + +static +void flecs_meta_entity_parent( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); +} + +#ifdef FLECS_DOC + +static +void flecs_meta_entity_doc_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_doc_get_name(ctx->world, entity); +} + +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_entity(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + })) + }); + + ecs_doc_set_brief(world, name, "Returns entity doc name"); +} + +#else +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + (void)world; +} +#endif + +void flecs_script_register_builtin_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_entity(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + })) + }); + + ecs_doc_set_brief(world, name, "Returns entity name"); + + ecs_entity_t path = ecs_entity(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + })) + }); + + ecs_doc_set_brief(world, path, "Returns entity path"); + + ecs_entity_t parent = ecs_entity(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + })) + }); + + ecs_doc_set_brief(world, parent, "Returns entity parent"); + + flecs_script_register_builtin_doc_functions(world); +} + +#endif + /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -56920,6 +57057,8 @@ const char* ecs_query_args_parse( #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScript); +ECS_COMPONENT_DECLARE(EcsScriptFunction); +ECS_COMPONENT_DECLARE(EcsScriptMethod); static ECS_MOVE(EcsScript, dst, src, { @@ -57180,6 +57319,10 @@ void FlecsScriptImport( ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), @@ -57203,9 +57346,25 @@ void FlecsScriptImport( .type.serialize = EcsScript_serialize }); + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); + + flecs_script_register_builtin_functions(world); } #endif @@ -58308,6 +58467,7 @@ const char* flecs_script_identifier( ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; char *outpos = parser->token_cur; + do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || @@ -58336,8 +58496,9 @@ const char* flecs_script_identifier( indent --; } else if (!c) { ecs_parser_error(parser->script->pub.name, - parser->script->pub.code, pos - parser->script->pub.code, - "< without > in identifier"); + parser->script->pub.code, + pos - parser->script->pub.code, + "< without > in identifier"); return NULL; } @@ -58354,8 +58515,10 @@ const char* flecs_script_identifier( parser->token_cur = outpos + 1; return pos; } else if (c == '>') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "> without < in identifier"); + ecs_parser_error(parser->script->pub.name, + parser->script->pub.code, + pos - parser->script->pub.code, + "> without < in identifier"); return NULL; } else { *outpos = '\0'; @@ -73313,6 +73476,14 @@ ecs_expr_member_t* flecs_expr_member( return result; } +ecs_expr_function_t* flecs_expr_function( + ecs_script_parser_t *parser) +{ + ecs_expr_function_t *result = flecs_expr_ast_new( + parser, ecs_expr_function_t, EcsExprFunction); + return result; +} + ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser) { @@ -73553,6 +73724,7 @@ const char* flecs_script_parse_rhs( case EcsTokShiftRight: case EcsTokBracketOpen: case EcsTokMember: + case EcsTokParenOpen: { ecs_script_token_kind_t oper = lookahead_token.kind; @@ -73596,6 +73768,16 @@ const char* flecs_script_parse_rhs( break; } + case EcsTokParenOpen: { + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + + Parse_1(EcsTokParenClose, { break; }); + + *out = (ecs_expr_node_t*)result; + break; + } + default: { ecs_expr_binary_t *result = flecs_expr_binary(parser); result->left = *out; @@ -74027,6 +74209,9 @@ void flecs_expr_value_alloc( if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { val->value.ptr = val->storage.small_data; + } else { + ecs_abort(ECS_UNSUPPORTED, + "non-trivial temporary values not yet supported"); } } else { ecs_abort(ECS_UNSUPPORTED, @@ -74206,6 +74391,40 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_function_visit_eval( + ecs_script_t *script, + ecs_expr_function_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + if (node->left) { + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + if (!out->value.ptr) { + flecs_expr_value_alloc(script, out, node->node.type_info); + } + + ecs_function_ctx_t ctx = { + .world = script->world, + .function = node->calldata.function, + .ctx = node->calldata.ctx + }; + + ecs_assert(expr.value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + node->calldata.callback(&ctx, 1, &expr.value, &out->value); + } + + return 0; +error: + return -1; +} + static int flecs_expr_member_visit_eval( ecs_script_t *script, @@ -74342,6 +74561,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprFunction: + if (flecs_expr_function_visit_eval( + script, (ecs_expr_function_t*)node, desc, out)) + { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_eval( @@ -74716,6 +74940,22 @@ int flecs_expr_identifier_visit_fold( return -1; } +int flecs_expr_function_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -74786,6 +75026,9 @@ int flecs_script_expr_visit_fold( case EcsExprVariable: break; case EcsExprFunction: + if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { @@ -74943,6 +75186,22 @@ int flecs_expr_member_to_str( return 0; } +int flecs_expr_function_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_function_t *node) +{ + if (node->left) { + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + ecs_strbuf_appendlit(v->buf, "."); + } + + ecs_strbuf_append(v->buf, "%s%s()%s", + ECS_CYAN, node->function_name, ECS_NORMAL); + return 0; +} + int flecs_expr_element_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_element_t *node) @@ -75017,6 +75276,9 @@ int flecs_expr_node_to_str( } break; case EcsExprFunction: + if (flecs_expr_function_to_str(v, (ecs_expr_function_t*)node)) { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { @@ -75114,12 +75376,12 @@ static bool flecs_expr_is_type_integer( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return false; + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return false; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; @@ -75129,12 +75391,12 @@ static bool flecs_expr_is_type_number( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; @@ -75469,7 +75731,9 @@ int flecs_expr_binary_visit_type( goto error; } - if (!flecs_expr_oper_valid_for_type(script->world, result_type, node->operator)) { + if (!flecs_expr_oper_valid_for_type( + script->world, result_type, node->operator)) + { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_script_token_str(node->operator), type_str); @@ -75536,6 +75800,75 @@ int flecs_expr_variable_visit_type( return -1; } +static +int flecs_expr_function_visit_type( + ecs_script_t *script, + ecs_expr_function_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) +{ + bool is_method = false; + + if (node->left->kind == EcsExprIdentifier) { + /* If identifier contains '.' separator(s), this is a method call, + * otherwise it's a regular function. */ + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; + char *last_elem = strrchr(ident->value, '.'); + if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + is_method = true; + } + } else if (node->left->kind == EcsExprMember) { + /* This is a method. Just like identifiers, method strings can contain + * separators. Split off last separator to get the method. */ + ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; + char *last_elem = strrchr(member->member_name, '.'); + if (!last_elem) { + node->left = member->left; + node->function_name = member->member_name; + } else { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + } + is_method = true; + } + + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; + } + + /* If this is a method, lookup function entity in scope of type */ + ecs_world_t *world = script->world; + if (is_method) { + ecs_entity_t func = ecs_lookup_from( + world, node->left->type, node->function_name); + if (!func) { + flecs_expr_visit_error(script, node, + "unresolved method identifier '%s'", node->function_name); + goto error; + } + + const EcsScriptMethod *method = ecs_get(world, func, EcsScriptMethod); + if (!method) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.type = method->return_type; + node->calldata.function = func; + node->calldata.callback = method->callback; + node->calldata.ctx = method->ctx; + } + + return 0; +error: + return -1; +} + static int flecs_expr_member_visit_type( ecs_script_t *script, @@ -75729,6 +76062,11 @@ int flecs_script_expr_visit_type_priv( } break; case EcsExprFunction: + if (flecs_expr_function_visit_type( + script, (ecs_expr_function_t*)node, cur, desc)) + { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_type( diff --git a/distr/flecs.h b/distr/flecs.h index 5f9877abe5..86af97d822 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14212,6 +14212,8 @@ extern "C" { FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); +extern ECS_COMPONENT_DECLARE(EcsScriptFunction); +extern ECS_COMPONENT_DECLARE(EcsScriptMethod); typedef struct ecs_script_template_t ecs_script_template_t; @@ -14234,6 +14236,20 @@ typedef struct ecs_script_vars_t { ecs_allocator_t *allocator; } ecs_script_vars_t; +/** Script function context */ +typedef struct ecs_function_ctx_t { + ecs_world_t *world; + ecs_entity_t function; + void *ctx; +} ecs_function_ctx_t; + +/** Script function callback */ +typedef void(*ecs_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result); + /** Script object. */ typedef struct ecs_script_t { ecs_world_t *world; @@ -14249,6 +14265,25 @@ typedef struct EcsScript { ecs_script_template_t *template_; /* Only set for template scripts */ } EcsScript; +/** Function component. + * This component describes a function that can be called from a script. + */ +typedef struct EcsScriptFunction { + ecs_entity_t return_type; + ecs_function_callback_t callback; + void *ctx; +} EcsScriptFunction; + +/** Method component. + * This component describes a method that can be called from a script. Methods + * are functions that can be called on instances of a type. A method entity is + * stored in the scope of the type it belongs to. + */ +typedef struct EcsScriptMethod { + ecs_entity_t return_type; + ecs_function_callback_t callback; + void *ctx; +} EcsScriptMethod; /* Parsing & running scripts */ diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index eee55e87bd..109aab8a9a 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -33,6 +33,8 @@ extern "C" { FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); +extern ECS_COMPONENT_DECLARE(EcsScriptFunction); +extern ECS_COMPONENT_DECLARE(EcsScriptMethod); typedef struct ecs_script_template_t ecs_script_template_t; @@ -55,6 +57,20 @@ typedef struct ecs_script_vars_t { ecs_allocator_t *allocator; } ecs_script_vars_t; +/** Script function context */ +typedef struct ecs_function_ctx_t { + ecs_world_t *world; + ecs_entity_t function; + void *ctx; +} ecs_function_ctx_t; + +/** Script function callback */ +typedef void(*ecs_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result); + /** Script object. */ typedef struct ecs_script_t { ecs_world_t *world; @@ -70,6 +86,25 @@ typedef struct EcsScript { ecs_script_template_t *template_; /* Only set for template scripts */ } EcsScript; +/** Function component. + * This component describes a function that can be called from a script. + */ +typedef struct EcsScriptFunction { + ecs_entity_t return_type; + ecs_function_callback_t callback; + void *ctx; +} EcsScriptFunction; + +/** Method component. + * This component describes a method that can be called from a script. Methods + * are functions that can be called on instances of a type. A method entity is + * stored in the scope of the type it belongs to. + */ +typedef struct EcsScriptMethod { + ecs_entity_t return_type; + ecs_function_callback_t callback; + void *ctx; +} EcsScriptMethod; /* Parsing & running scripts */ diff --git a/src/addons/script/builtin_functions.c b/src/addons/script/builtin_functions.c new file mode 100644 index 0000000000..9197eea3de --- /dev/null +++ b/src/addons/script/builtin_functions.c @@ -0,0 +1,119 @@ +/** + * @file addons/script/builtin_functions.c + * @brief Builtin script functions. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +static +void flecs_meta_entity_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_name(ctx->world, entity); +} + +static +void flecs_meta_entity_path( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_path(ctx->world, entity); +} + +static +void flecs_meta_entity_parent( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); +} + +#ifdef FLECS_DOC + +static +void flecs_meta_entity_doc_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_doc_get_name(ctx->world, entity); +} + +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_entity(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + })) + }); + + ecs_doc_set_brief(world, name, "Returns entity doc name"); +} + +#else +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + (void)world; +} +#endif + +void flecs_script_register_builtin_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_entity(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + })) + }); + + ecs_doc_set_brief(world, name, "Returns entity name"); + + ecs_entity_t path = ecs_entity(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + })) + }); + + ecs_doc_set_brief(world, path, "Returns entity path"); + + ecs_entity_t parent = ecs_entity(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .set = ecs_values(ecs_value(EcsScriptMethod, { + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + })) + }); + + ecs_doc_set_brief(world, parent, "Returns entity parent"); + + flecs_script_register_builtin_doc_functions(world); +} + +#endif diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 30b5126df0..b6992f9196 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -166,6 +166,14 @@ ecs_expr_member_t* flecs_expr_member( return result; } +ecs_expr_function_t* flecs_expr_function( + ecs_script_parser_t *parser) +{ + ecs_expr_function_t *result = flecs_expr_ast_new( + parser, ecs_expr_function_t, EcsExprFunction); + return result; +} + ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser) { diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 3a0d59f92c..54bf6f64bf 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -104,6 +104,13 @@ typedef struct ecs_expr_member_t { uintptr_t offset; } ecs_expr_member_t; +typedef struct ecs_expr_function_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *function_name; + ecs_function_calldata_t calldata; +} ecs_expr_function_t; + typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; @@ -171,6 +178,9 @@ ecs_expr_binary_t* flecs_expr_binary( ecs_expr_member_t* flecs_expr_member( ecs_script_parser_t *parser); +ecs_expr_function_t* flecs_expr_function( + ecs_script_parser_t *parser); + ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index eecbe8a2c7..c4a7c4cc11 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -215,6 +215,7 @@ const char* flecs_script_parse_rhs( case EcsTokShiftRight: case EcsTokBracketOpen: case EcsTokMember: + case EcsTokParenOpen: { ecs_script_token_kind_t oper = lookahead_token.kind; @@ -258,6 +259,16 @@ const char* flecs_script_parse_rhs( break; } + case EcsTokParenOpen: { + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + + Parse_1(EcsTokParenClose, { break; }); + + *out = (ecs_expr_node_t*)result; + break; + } + default: { ecs_expr_binary_t *result = flecs_expr_binary(parser); result->left = *out; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index ed7e378af8..34b9f627ea 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -33,6 +33,9 @@ void flecs_expr_value_alloc( if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { val->value.ptr = val->storage.small_data; + } else { + ecs_abort(ECS_UNSUPPORTED, + "non-trivial temporary values not yet supported"); } } else { ecs_abort(ECS_UNSUPPORTED, @@ -212,6 +215,40 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_function_visit_eval( + ecs_script_t *script, + ecs_expr_function_t *node, + const ecs_script_expr_run_desc_t *desc, + ecs_eval_value_t *out) +{ + if (node->left) { + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + goto error; + } + + if (!out->value.ptr) { + flecs_expr_value_alloc(script, out, node->node.type_info); + } + + ecs_function_ctx_t ctx = { + .world = script->world, + .function = node->calldata.function, + .ctx = node->calldata.ctx + }; + + ecs_assert(expr.value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + node->calldata.callback(&ctx, 1, &expr.value, &out->value); + } + + return 0; +error: + return -1; +} + static int flecs_expr_member_visit_eval( ecs_script_t *script, @@ -348,6 +385,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprFunction: + if (flecs_expr_function_visit_eval( + script, (ecs_expr_function_t*)node, desc, out)) + { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_eval( diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 64c0489f96..3540de839b 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -302,6 +302,22 @@ int flecs_expr_identifier_visit_fold( return -1; } +int flecs_expr_function_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + goto error; + } + + return 0; +error: + return -1; +} + int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -372,6 +388,9 @@ int flecs_script_expr_visit_fold( case EcsExprVariable: break; case EcsExprFunction: + if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 9c8b584d7b..d043ccb321 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -131,6 +131,22 @@ int flecs_expr_member_to_str( return 0; } +int flecs_expr_function_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_function_t *node) +{ + if (node->left) { + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + ecs_strbuf_appendlit(v->buf, "."); + } + + ecs_strbuf_append(v->buf, "%s%s()%s", + ECS_CYAN, node->function_name, ECS_NORMAL); + return 0; +} + int flecs_expr_element_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_element_t *node) @@ -205,6 +221,9 @@ int flecs_expr_node_to_str( } break; case EcsExprFunction: + if (flecs_expr_function_to_str(v, (ecs_expr_function_t*)node)) { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 7b1a8ff321..49da2489bd 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -49,12 +49,12 @@ static bool flecs_expr_is_type_integer( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return false; + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return false; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; @@ -64,12 +64,12 @@ static bool flecs_expr_is_type_number( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; @@ -404,7 +404,9 @@ int flecs_expr_binary_visit_type( goto error; } - if (!flecs_expr_oper_valid_for_type(script->world, result_type, node->operator)) { + if (!flecs_expr_oper_valid_for_type( + script->world, result_type, node->operator)) + { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_script_token_str(node->operator), type_str); @@ -471,6 +473,75 @@ int flecs_expr_variable_visit_type( return -1; } +static +int flecs_expr_function_visit_type( + ecs_script_t *script, + ecs_expr_function_t *node, + ecs_meta_cursor_t *cur, + const ecs_script_expr_run_desc_t *desc) +{ + bool is_method = false; + + if (node->left->kind == EcsExprIdentifier) { + /* If identifier contains '.' separator(s), this is a method call, + * otherwise it's a regular function. */ + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; + char *last_elem = strrchr(ident->value, '.'); + if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + is_method = true; + } + } else if (node->left->kind == EcsExprMember) { + /* This is a method. Just like identifiers, method strings can contain + * separators. Split off last separator to get the method. */ + ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; + char *last_elem = strrchr(member->member_name, '.'); + if (!last_elem) { + node->left = member->left; + node->function_name = member->member_name; + } else { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + } + is_method = true; + } + + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; + } + + /* If this is a method, lookup function entity in scope of type */ + ecs_world_t *world = script->world; + if (is_method) { + ecs_entity_t func = ecs_lookup_from( + world, node->left->type, node->function_name); + if (!func) { + flecs_expr_visit_error(script, node, + "unresolved method identifier '%s'", node->function_name); + goto error; + } + + const EcsScriptMethod *method = ecs_get(world, func, EcsScriptMethod); + if (!method) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.type = method->return_type; + node->calldata.function = func; + node->calldata.callback = method->callback; + node->calldata.ctx = method->ctx; + } + + return 0; +error: + return -1; +} + static int flecs_expr_member_visit_type( ecs_script_t *script, @@ -664,6 +735,11 @@ int flecs_script_expr_visit_type_priv( } break; case EcsExprFunction: + if (flecs_expr_function_visit_type( + script, (ecs_expr_function_t*)node, cur, desc)) + { + goto error; + } break; case EcsExprMember: if (flecs_expr_member_visit_type( diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 2cfa85df73..5bacdc1eaa 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -9,6 +9,8 @@ #include "script.h" ECS_COMPONENT_DECLARE(EcsScript); +ECS_COMPONENT_DECLARE(EcsScriptFunction); +ECS_COMPONENT_DECLARE(EcsScriptMethod); static ECS_MOVE(EcsScript, dst, src, { @@ -269,6 +271,10 @@ void FlecsScriptImport( ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), @@ -292,9 +298,25 @@ void FlecsScriptImport( .type.serialize = EcsScript_serialize }); + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); + + flecs_script_register_builtin_functions(world); } #endif diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 0bc26f75ae..c96ae7b8a7 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -44,6 +44,12 @@ struct ecs_script_parser_t { ecs_term_ref_t *extra_args; }; +typedef struct ecs_function_calldata_t { + ecs_entity_t function; + ecs_function_callback_t callback; + void *ctx; +} ecs_function_calldata_t; + #include "ast.h" #include "expr/expr.h" #include "visit.h" @@ -101,5 +107,8 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +void flecs_script_register_builtin_functions( + ecs_world_t *world); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 9b1acffc4c..a35318ce5d 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -211,6 +211,7 @@ const char* flecs_script_identifier( ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; char *outpos = parser->token_cur; + do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || @@ -239,8 +240,9 @@ const char* flecs_script_identifier( indent --; } else if (!c) { ecs_parser_error(parser->script->pub.name, - parser->script->pub.code, pos - parser->script->pub.code, - "< without > in identifier"); + parser->script->pub.code, + pos - parser->script->pub.code, + "< without > in identifier"); return NULL; } @@ -257,8 +259,10 @@ const char* flecs_script_identifier( parser->token_cur = outpos + 1; return pos; } else if (c == '>') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "> without < in identifier"); + ecs_parser_error(parser->script->pub.name, + parser->script->pub.code, + pos - parser->script->pub.code, + "> without < in identifier"); return NULL; } else { *outpos = '\0'; From 559360315aacf12e90717e057c9530cd510c66c0 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 2 Dec 2024 05:08:37 +0000 Subject: [PATCH 21/83] Implement expression AST cleanup --- distr/flecs.c | 326 +++++++++++++++++++++++--- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/addons/script/builtin_functions.c | 4 +- src/addons/script/expr/ast.c | 3 +- src/addons/script/expr/expr.h | 5 + src/addons/script/expr/parser.c | 44 ++-- src/addons/script/expr/util.c | 36 ++- src/addons/script/expr/visit.h | 4 + src/addons/script/expr/visit_eval.c | 43 +++- src/addons/script/expr/visit_fold.c | 28 ++- src/addons/script/expr/visit_free.c | 148 ++++++++++++ src/addons/script/expr/visit_type.c | 5 +- src/addons/script/script.c | 7 + test/script/project.json | 1 + test/script/src/Expr.c | 29 +++ test/script/src/main.c | 7 +- 17 files changed, 619 insertions(+), 75 deletions(-) create mode 100644 src/addons/script/expr/visit_free.c diff --git a/distr/flecs.c b/distr/flecs.c index 3c6ef131a7..9af0c42ebc 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5094,6 +5094,10 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out); +void flecs_script_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node); + #endif @@ -5102,6 +5106,11 @@ int flecs_value_copy_to( ecs_value_t *dst, const ecs_value_t *src); +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src); + int flecs_value_binary( ecs_script_t *script, const ecs_value_t *left, @@ -54905,7 +54914,7 @@ void flecs_meta_entity_name( ecs_value_t *result) { ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_get_name(ctx->world, entity); + *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } static @@ -54940,7 +54949,7 @@ void flecs_meta_entity_doc_name( ecs_value_t *result) { ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_doc_get_name(ctx->world, entity); + *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); } void flecs_script_register_builtin_doc_functions( @@ -57360,6 +57369,13 @@ void FlecsScriptImport( } }); + /* Dedicated string type used to indicate that a string is owned by the + * parser and should be copied when returning to the application. */ + ecs_primitive(world, { + .entity = ecs_entity(world, { .name = "string" }), + .kind = EcsString + }); + ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); @@ -73406,7 +73422,8 @@ ecs_expr_val_t* flecs_expr_string( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; - result->node.type = ecs_id(ecs_string_t); + result->node.type = ecs_lookup( + parser->script->pub.world, "flecs.script.string"); return result; } @@ -73559,6 +73576,14 @@ const char* flecs_script_parse_lhs( ecs_script_token_kind_t left_oper, ecs_expr_node_t **out); +static +void flecs_script_parser_expr_free( + ecs_script_parser_t *parser, + ecs_expr_node_t *node) +{ + flecs_script_expr_visit_free(&parser->script->pub, node); +} + static bool flecs_has_precedence( ecs_script_token_kind_t first, @@ -73740,6 +73765,8 @@ const char* flecs_script_parse_rhs( case EcsTokBracketOpen: { ecs_expr_element_t *result = flecs_expr_element(parser); result->left = *out; + + *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_lhs( parser, pos, tokenizer, 0, &result->index); @@ -73748,7 +73775,6 @@ const char* flecs_script_parse_rhs( } Parse_1(']', { - *out = (ecs_expr_node_t*)result; break; }); @@ -73756,10 +73782,9 @@ const char* flecs_script_parse_rhs( } case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; - Parse_1(EcsTokIdentifier, { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; result->member_name = Token(1); *out = (ecs_expr_node_t*)result; break; @@ -73769,12 +73794,13 @@ const char* flecs_script_parse_rhs( } case EcsTokParenOpen: { - ecs_expr_function_t *result = flecs_expr_function(parser); - result->left = *out; - - Parse_1(EcsTokParenClose, { break; }); + Parse_1(EcsTokParenClose, { + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + *out = (ecs_expr_node_t*)result; + break; + }); - *out = (ecs_expr_node_t*)result; break; } @@ -73854,6 +73880,7 @@ const char* flecs_script_parse_lhs( ecs_expr_unary_t *node = flecs_expr_unary(parser); pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -73866,6 +73893,7 @@ const char* flecs_script_parse_lhs( ecs_expr_binary_t *node = flecs_expr_binary(parser); pos = flecs_script_parse_expr(parser, pos, 0, &node->right); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -73892,6 +73920,7 @@ const char* flecs_script_parse_lhs( ecs_expr_initializer_t *node = flecs_expr_initializer(parser); pos = flecs_script_parse_initializer(parser, pos, node); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -73909,6 +73938,7 @@ const char* flecs_script_parse_lhs( pos = flecs_script_parse_collection_initializer(parser, pos, node); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -73928,8 +73958,7 @@ const char* flecs_script_parse_lhs( } /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs( - parser, pos, tokenizer, *out, left_oper, out); + return flecs_script_parse_rhs(parser, pos, tokenizer, *out, left_oper, out); error: return NULL; } @@ -74008,8 +74037,8 @@ const char* ecs_script_expr_run( if (!priv_desc.type) { priv_desc.type = value->type; } else if (desc && (value->type != desc->type)) { - ecs_throw("type of value parameter does not match desc->type", - ECS_INVALID_PARAMETER, NULL); + ecs_throw(ECS_INVALID_PARAMETER, + "type of value parameter does not match desc->type"); } if (!priv_desc.lookup_action) { @@ -74050,12 +74079,16 @@ const char* ecs_script_expr_run( // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (flecs_script_expr_visit_eval(script, out, desc, value)) { + if (flecs_script_expr_visit_eval(script, out, &priv_desc, value)) { goto error; } + flecs_script_expr_visit_free(script, out); + ecs_script_free(script); return result; error: + flecs_script_expr_visit_free(script, out); + ecs_script_free(script); return NULL; } @@ -74073,8 +74106,11 @@ int flecs_value_copy_to( ecs_value_t *dst, const ecs_value_t *src) { + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + if (src->type == dst->type) { - /* Outputvalue issame asexpoutsion, copy value */ ecs_value_copy(world, src->type, dst->ptr, src->ptr); } else { /* Cast value to desired output type */ @@ -74089,6 +74125,37 @@ int flecs_value_copy_to( return -1; } +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src) +{ + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + + if (src->type == dst->type) { + ecs_value_move(world, src->type, dst->ptr, src->ptr); + } else { + ecs_value_t tmp; + tmp.type = src->type; + tmp.ptr = ecs_value_new(world, src->type); + ecs_value_move(world, src->type, tmp.ptr, src->ptr); + + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, &tmp)) { + goto error; + } + + ecs_value_free(world, src->type, tmp.ptr); + } + + return 0; +error: + return -1; +} + int flecs_value_unary( ecs_script_t *script, const ecs_value_t *expr, @@ -74207,15 +74274,36 @@ void flecs_expr_value_alloc( val->value.type = ti->component; if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { - if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { - val->value.ptr = val->storage.small_data; - } else { - ecs_abort(ECS_UNSUPPORTED, - "non-trivial temporary values not yet supported"); - } + val->value.ptr = val->storage.small_data; } else { - ecs_abort(ECS_UNSUPPORTED, - "non-trivial temporary values not yet supported"); + val->value.ptr = flecs_alloc( + &((ecs_script_impl_t*)script)->allocator, ti->size); + } + + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(val->value.ptr, 1, ti); + } +} + +static +void flecs_expr_value_free( + ecs_script_t *script, + ecs_eval_value_t *val, + const ecs_type_info_t *ti) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(val->value.ptr, 1, ti); + } + + val->value.type = ti->component; + + if (ti->size > FLECS_EXPR_SMALL_DATA_SIZE) { + flecs_free(&((ecs_script_impl_t*)script)->allocator, + ti->size, val->value.ptr); } } @@ -74622,11 +74710,15 @@ int flecs_script_expr_visit_eval( out->type = node->type; } + if (out->type == ecs_lookup(script->world, "flecs.script.string")) { + out->type = ecs_id(ecs_string_t); + } + if (out->type && !out->ptr) { out->ptr = ecs_value_new(script->world, out->type); } - if (flecs_value_copy_to(script->world, out, &val.value)) { + if (flecs_value_move_to(script->world, out, &val.value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } @@ -74646,6 +74738,16 @@ int flecs_script_expr_visit_eval( #ifdef FLECS_SCRIPT +void flecs_visit_fold_replace( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + ecs_expr_node_t *with) +{ + ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); + flecs_script_expr_visit_free(script, *node_ptr); + *node_ptr = with; +} + int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -74687,7 +74789,7 @@ int flecs_expr_unary_visit_fold( goto error; } - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: @@ -74739,7 +74841,7 @@ int flecs_expr_binary_visit_fold( } done: - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: return -1; @@ -74762,10 +74864,6 @@ int flecs_expr_cast_visit_fold( } ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; - - /* Reuse existing node to hold casted value */ - *node_ptr = (ecs_expr_node_t*)expr; - ecs_entity_t dst_type = node->node.type; ecs_entity_t src_type = expr->node.type; @@ -74787,6 +74885,9 @@ int flecs_expr_cast_visit_fold( expr->node.type = dst_type; + node->expr = NULL; /* Prevent cleanup */ + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); + return 0; error: return -1; @@ -74896,7 +74997,8 @@ int flecs_expr_initializer_visit_fold( ecs_expr_val_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; - *node_ptr = (ecs_expr_node_t*)result; + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } return 0; @@ -74919,6 +75021,7 @@ int flecs_expr_identifier_visit_fold( result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (!result->storage.entity) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); goto error; @@ -74928,12 +75031,13 @@ int flecs_expr_identifier_visit_fold( ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, type, &result->storage.u64); if (ecs_meta_set_string(&cur, node->value)) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; } - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: @@ -75055,6 +75159,153 @@ int flecs_script_expr_visit_fold( #endif +/** + * @file addons/script/expr/visit_free.c + * @brief Visitor to free expression AST. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_expr_value_visit_free( + ecs_script_t *script, + ecs_expr_val_t *node) +{ + if (node->ptr != &node->storage) { + ecs_value_free(script->world, node->node.type, node->ptr); + } +} + +static +void flecs_expr_initializer_visit_free( + ecs_script_t *script, + ecs_expr_initializer_t *node) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + flecs_script_expr_visit_free(script, elem->value); + } + + ecs_vec_fini_t(&script->world->allocator, &node->elements, + ecs_expr_initializer_element_t); +} + +static +void flecs_expr_unary_visit_free( + ecs_script_t *script, + ecs_expr_unary_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + +static +void flecs_expr_binary_visit_free( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, node->right); +} + +static +void flecs_expr_function_visit_free( + ecs_script_t *script, + ecs_expr_function_t *node) +{ + flecs_script_expr_visit_free(script, node->left); +} + +static +void flecs_expr_member_visit_free( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + flecs_script_expr_visit_free(script, node->left); +} + +static +void flecs_expr_element_visit_free( + ecs_script_t *script, + ecs_expr_element_t *node) +{ + flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, node->index); +} + +static +void flecs_expr_cast_visit_free( + ecs_script_t *script, + ecs_expr_cast_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + +void flecs_script_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node) +{ + if (!node) { + return; + } + + ecs_allocator_t *a = &script->world->allocator; + + switch(node->kind) { + case EcsExprValue: + flecs_expr_value_visit_free( + script, (ecs_expr_val_t*)node); + flecs_free_t(a, ecs_expr_val_t, node); + break; + case EcsExprInitializer: + flecs_expr_initializer_visit_free( + script, (ecs_expr_initializer_t*)node); + flecs_free_t(a, ecs_expr_initializer_t, node); + break; + case EcsExprUnary: + flecs_expr_unary_visit_free( + script, (ecs_expr_unary_t*)node); + flecs_free_t(a, ecs_expr_unary_t, node); + break; + case EcsExprBinary: + flecs_expr_binary_visit_free( + script, (ecs_expr_binary_t*)node); + flecs_free_t(a, ecs_expr_binary_t, node); + break; + case EcsExprIdentifier: + flecs_free_t(a, ecs_expr_identifier_t, node); + break; + case EcsExprVariable: + flecs_free_t(a, ecs_expr_variable_t, node); + break; + case EcsExprFunction: + flecs_expr_function_visit_free( + script, (ecs_expr_function_t*)node); + flecs_free_t(a, ecs_expr_function_t, node); + break; + case EcsExprMember: + flecs_expr_member_visit_free( + script, (ecs_expr_member_t*)node); + flecs_free_t(a, ecs_expr_member_t, node); + break; + case EcsExprElement: + case EcsExprComponent: + flecs_expr_element_visit_free( + script, (ecs_expr_element_t*)node); + flecs_free_t(a, ecs_expr_element_t, node); + break; + case EcsExprCast: + flecs_expr_cast_visit_free( + script, (ecs_expr_cast_t*)node); + flecs_free_t(a, ecs_expr_cast_t, node); + break; + } +} + +#endif + /** * @file addons/script/expr_to_str.c * @brief Script expression AST to string visitor. @@ -75604,7 +75855,7 @@ int flecs_expr_initializer_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (!cur) { + if (!cur || !cur->valid) { flecs_expr_visit_error(script, node, "missing type for initializer"); goto error; } @@ -75827,6 +76078,9 @@ int flecs_expr_function_visit_type( if (!last_elem) { node->left = member->left; node->function_name = member->member_name; + + member->left = NULL; /* Prevent cleanup */ + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)member); } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; diff --git a/distr/flecs.h b/distr/flecs.h index 86af97d822..e7bce59436 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -264,7 +264,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index 8dde6591f1..38d2eae2a1 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -262,7 +262,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/addons/script/builtin_functions.c b/src/addons/script/builtin_functions.c index 9197eea3de..0f27ff6ba8 100644 --- a/src/addons/script/builtin_functions.c +++ b/src/addons/script/builtin_functions.c @@ -16,7 +16,7 @@ void flecs_meta_entity_name( ecs_value_t *result) { ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_get_name(ctx->world, entity); + *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } static @@ -51,7 +51,7 @@ void flecs_meta_entity_doc_name( ecs_value_t *result) { ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_doc_get_name(ctx->world, entity); + *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); } void flecs_script_register_builtin_doc_functions( diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index b6992f9196..a2f7a06c85 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -96,7 +96,8 @@ ecs_expr_val_t* flecs_expr_string( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; - result->node.type = ecs_id(ecs_string_t); + result->node.type = ecs_lookup( + parser->script->pub.world, "flecs.script.string"); return result; } diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index d6d721356c..2a51c678eb 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -14,6 +14,11 @@ int flecs_value_copy_to( ecs_value_t *dst, const ecs_value_t *src); +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src); + int flecs_value_binary( ecs_script_t *script, const ecs_value_t *left, diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index c4a7c4cc11..c94fc72aec 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -50,6 +50,14 @@ const char* flecs_script_parse_lhs( ecs_script_token_kind_t left_oper, ecs_expr_node_t **out); +static +void flecs_script_parser_expr_free( + ecs_script_parser_t *parser, + ecs_expr_node_t *node) +{ + flecs_script_expr_visit_free(&parser->script->pub, node); +} + static bool flecs_has_precedence( ecs_script_token_kind_t first, @@ -231,6 +239,8 @@ const char* flecs_script_parse_rhs( case EcsTokBracketOpen: { ecs_expr_element_t *result = flecs_expr_element(parser); result->left = *out; + + *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_lhs( parser, pos, tokenizer, 0, &result->index); @@ -239,7 +249,6 @@ const char* flecs_script_parse_rhs( } Parse_1(']', { - *out = (ecs_expr_node_t*)result; break; }); @@ -247,10 +256,9 @@ const char* flecs_script_parse_rhs( } case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; - Parse_1(EcsTokIdentifier, { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; result->member_name = Token(1); *out = (ecs_expr_node_t*)result; break; @@ -260,12 +268,13 @@ const char* flecs_script_parse_rhs( } case EcsTokParenOpen: { - ecs_expr_function_t *result = flecs_expr_function(parser); - result->left = *out; - - Parse_1(EcsTokParenClose, { break; }); + Parse_1(EcsTokParenClose, { + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + *out = (ecs_expr_node_t*)result; + break; + }); - *out = (ecs_expr_node_t*)result; break; } @@ -345,6 +354,7 @@ const char* flecs_script_parse_lhs( ecs_expr_unary_t *node = flecs_expr_unary(parser); pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -357,6 +367,7 @@ const char* flecs_script_parse_lhs( ecs_expr_binary_t *node = flecs_expr_binary(parser); pos = flecs_script_parse_expr(parser, pos, 0, &node->right); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -383,6 +394,7 @@ const char* flecs_script_parse_lhs( ecs_expr_initializer_t *node = flecs_expr_initializer(parser); pos = flecs_script_parse_initializer(parser, pos, node); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -400,6 +412,7 @@ const char* flecs_script_parse_lhs( pos = flecs_script_parse_collection_initializer(parser, pos, node); if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } @@ -419,8 +432,7 @@ const char* flecs_script_parse_lhs( } /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs( - parser, pos, tokenizer, *out, left_oper, out); + return flecs_script_parse_rhs(parser, pos, tokenizer, *out, left_oper, out); error: return NULL; } @@ -499,8 +511,8 @@ const char* ecs_script_expr_run( if (!priv_desc.type) { priv_desc.type = value->type; } else if (desc && (value->type != desc->type)) { - ecs_throw("type of value parameter does not match desc->type", - ECS_INVALID_PARAMETER, NULL); + ecs_throw(ECS_INVALID_PARAMETER, + "type of value parameter does not match desc->type"); } if (!priv_desc.lookup_action) { @@ -541,12 +553,16 @@ const char* ecs_script_expr_run( // printf("%s\n", ecs_script_expr_to_str(world, out)); - if (flecs_script_expr_visit_eval(script, out, desc, value)) { + if (flecs_script_expr_visit_eval(script, out, &priv_desc, value)) { goto error; } + flecs_script_expr_visit_free(script, out); + ecs_script_free(script); return result; error: + flecs_script_expr_visit_free(script, out); + ecs_script_free(script); return NULL; } diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 910cc8950a..d30fddce7b 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -12,8 +12,11 @@ int flecs_value_copy_to( ecs_value_t *dst, const ecs_value_t *src) { + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + if (src->type == dst->type) { - /* Outputvalue issame asexpoutsion, copy value */ ecs_value_copy(world, src->type, dst->ptr, src->ptr); } else { /* Cast value to desired output type */ @@ -28,6 +31,37 @@ int flecs_value_copy_to( return -1; } +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src) +{ + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + + if (src->type == dst->type) { + ecs_value_move(world, src->type, dst->ptr, src->ptr); + } else { + ecs_value_t tmp; + tmp.type = src->type; + tmp.ptr = ecs_value_new(world, src->type); + ecs_value_move(world, src->type, tmp.ptr, src->ptr); + + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, &tmp)) { + goto error; + } + + ecs_value_free(world, src->type, tmp.ptr); + } + + return 0; +error: + return -1; +} + int flecs_value_unary( ecs_script_t *script, const ecs_value_t *expr, diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index b647ee5bb7..c73c201c08 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -28,4 +28,8 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out); +void flecs_script_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node); + #endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 34b9f627ea..bd46df1d31 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -31,15 +31,36 @@ void flecs_expr_value_alloc( val->value.type = ti->component; if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { - if (!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR)) { - val->value.ptr = val->storage.small_data; - } else { - ecs_abort(ECS_UNSUPPORTED, - "non-trivial temporary values not yet supported"); - } + val->value.ptr = val->storage.small_data; } else { - ecs_abort(ECS_UNSUPPORTED, - "non-trivial temporary values not yet supported"); + val->value.ptr = flecs_alloc( + &((ecs_script_impl_t*)script)->allocator, ti->size); + } + + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(val->value.ptr, 1, ti); + } +} + +static +void flecs_expr_value_free( + ecs_script_t *script, + ecs_eval_value_t *val, + const ecs_type_info_t *ti) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(val->value.ptr, 1, ti); + } + + val->value.type = ti->component; + + if (ti->size > FLECS_EXPR_SMALL_DATA_SIZE) { + flecs_free(&((ecs_script_impl_t*)script)->allocator, + ti->size, val->value.ptr); } } @@ -446,11 +467,15 @@ int flecs_script_expr_visit_eval( out->type = node->type; } + if (out->type == ecs_lookup(script->world, "flecs.script.string")) { + out->type = ecs_id(ecs_string_t); + } + if (out->type && !out->ptr) { out->ptr = ecs_value_new(script->world, out->type); } - if (flecs_value_copy_to(script->world, out, &val.value)) { + if (flecs_value_move_to(script->world, out, &val.value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 3540de839b..28ae6f6664 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -8,6 +8,16 @@ #ifdef FLECS_SCRIPT #include "../script.h" +void flecs_visit_fold_replace( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + ecs_expr_node_t *with) +{ + ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); + flecs_script_expr_visit_free(script, *node_ptr); + *node_ptr = with; +} + int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -49,7 +59,7 @@ int flecs_expr_unary_visit_fold( goto error; } - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: @@ -101,7 +111,7 @@ int flecs_expr_binary_visit_fold( } done: - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: return -1; @@ -124,10 +134,6 @@ int flecs_expr_cast_visit_fold( } ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; - - /* Reuse existing node to hold casted value */ - *node_ptr = (ecs_expr_node_t*)expr; - ecs_entity_t dst_type = node->node.type; ecs_entity_t src_type = expr->node.type; @@ -149,6 +155,9 @@ int flecs_expr_cast_visit_fold( expr->node.type = dst_type; + node->expr = NULL; /* Prevent cleanup */ + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); + return 0; error: return -1; @@ -258,7 +267,8 @@ int flecs_expr_initializer_visit_fold( ecs_expr_val_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; - *node_ptr = (ecs_expr_node_t*)result; + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } return 0; @@ -281,6 +291,7 @@ int flecs_expr_identifier_visit_fold( result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (!result->storage.entity) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); goto error; @@ -290,12 +301,13 @@ int flecs_expr_identifier_visit_fold( ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, type, &result->storage.u64); if (ecs_meta_set_string(&cur, node->value)) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; } - *node_ptr = (ecs_expr_node_t*)result; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c new file mode 100644 index 0000000000..2bb5c920f0 --- /dev/null +++ b/src/addons/script/expr/visit_free.c @@ -0,0 +1,148 @@ +/** + * @file addons/script/expr/visit_free.c + * @brief Visitor to free expression AST. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "../script.h" + +static +void flecs_expr_value_visit_free( + ecs_script_t *script, + ecs_expr_val_t *node) +{ + if (node->ptr != &node->storage) { + ecs_value_free(script->world, node->node.type, node->ptr); + } +} + +static +void flecs_expr_initializer_visit_free( + ecs_script_t *script, + ecs_expr_initializer_t *node) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + flecs_script_expr_visit_free(script, elem->value); + } + + ecs_vec_fini_t(&script->world->allocator, &node->elements, + ecs_expr_initializer_element_t); +} + +static +void flecs_expr_unary_visit_free( + ecs_script_t *script, + ecs_expr_unary_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + +static +void flecs_expr_binary_visit_free( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, node->right); +} + +static +void flecs_expr_function_visit_free( + ecs_script_t *script, + ecs_expr_function_t *node) +{ + flecs_script_expr_visit_free(script, node->left); +} + +static +void flecs_expr_member_visit_free( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + flecs_script_expr_visit_free(script, node->left); +} + +static +void flecs_expr_element_visit_free( + ecs_script_t *script, + ecs_expr_element_t *node) +{ + flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, node->index); +} + +static +void flecs_expr_cast_visit_free( + ecs_script_t *script, + ecs_expr_cast_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + +void flecs_script_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node) +{ + if (!node) { + return; + } + + ecs_allocator_t *a = &script->world->allocator; + + switch(node->kind) { + case EcsExprValue: + flecs_expr_value_visit_free( + script, (ecs_expr_val_t*)node); + flecs_free_t(a, ecs_expr_val_t, node); + break; + case EcsExprInitializer: + flecs_expr_initializer_visit_free( + script, (ecs_expr_initializer_t*)node); + flecs_free_t(a, ecs_expr_initializer_t, node); + break; + case EcsExprUnary: + flecs_expr_unary_visit_free( + script, (ecs_expr_unary_t*)node); + flecs_free_t(a, ecs_expr_unary_t, node); + break; + case EcsExprBinary: + flecs_expr_binary_visit_free( + script, (ecs_expr_binary_t*)node); + flecs_free_t(a, ecs_expr_binary_t, node); + break; + case EcsExprIdentifier: + flecs_free_t(a, ecs_expr_identifier_t, node); + break; + case EcsExprVariable: + flecs_free_t(a, ecs_expr_variable_t, node); + break; + case EcsExprFunction: + flecs_expr_function_visit_free( + script, (ecs_expr_function_t*)node); + flecs_free_t(a, ecs_expr_function_t, node); + break; + case EcsExprMember: + flecs_expr_member_visit_free( + script, (ecs_expr_member_t*)node); + flecs_free_t(a, ecs_expr_member_t, node); + break; + case EcsExprElement: + case EcsExprComponent: + flecs_expr_element_visit_free( + script, (ecs_expr_element_t*)node); + flecs_free_t(a, ecs_expr_element_t, node); + break; + case EcsExprCast: + flecs_expr_cast_visit_free( + script, (ecs_expr_cast_t*)node); + flecs_free_t(a, ecs_expr_cast_t, node); + break; + } +} + +#endif diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 49da2489bd..22cea1cff0 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -277,7 +277,7 @@ int flecs_expr_initializer_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { - if (!cur) { + if (!cur || !cur->valid) { flecs_expr_visit_error(script, node, "missing type for initializer"); goto error; } @@ -500,6 +500,9 @@ int flecs_expr_function_visit_type( if (!last_elem) { node->left = member->left; node->function_name = member->member_name; + + member->left = NULL; /* Prevent cleanup */ + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)member); } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 5bacdc1eaa..7a3f689e52 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -312,6 +312,13 @@ void FlecsScriptImport( } }); + /* Dedicated string type used to indicate that a string is owned by the + * parser and should be copied when returning to the application. */ + ecs_primitive(world, { + .entity = ecs_entity(world, { .name = "string" }), + .kind = EcsString + }); + ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); diff --git a/test/script/project.json b/test/script/project.json index b52d7b6c80..4836c73304 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -493,6 +493,7 @@ "entity_parent_func", "entity_name_func", "entity_doc_name_func", + "entity_path_func", "entity_chain_func", "var_parent_func", "var_name_func", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index b69186e542..408bd35967 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -2341,6 +2341,8 @@ void Expr_not_bool(void) { test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); ecs_value_free(world, v.type, v.ptr); + + ecs_os_zeromem(&v); test_assert(ecs_script_expr_run(world, "!true", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); @@ -2361,6 +2363,8 @@ void Expr_not_int(void) { test_bool(*(bool*)v.ptr, true); ecs_value_free(world, v.type, v.ptr); + ecs_os_zeromem(&v); + test_assert(ecs_script_expr_run(world, "!10", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2380,6 +2384,8 @@ void Expr_not_paren_int(void) { test_bool(*(bool*)v.ptr, true); ecs_value_free(world, v.type, v.ptr); + ecs_os_zeromem(&v); + test_assert(ecs_script_expr_run(world, "!(10)", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2399,6 +2405,8 @@ void Expr_not_paren_expr(void) { test_bool(*(bool*)v.ptr, true); ecs_value_free(world, v.type, v.ptr); + ecs_os_zeromem(&v); + test_assert(ecs_script_expr_run(world, "!(5 + 5)", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2426,6 +2434,8 @@ void Expr_not_var(void) { test_uint(*(bool*)v.ptr, false); ecs_value_free(world, v.type, v.ptr); + ecs_os_zeromem(&v); + *(int32_t*)var->value.ptr = 0; test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); @@ -2683,6 +2693,25 @@ void Expr_var_parent_func(void) { ecs_fini(world); } +void Expr_entity_path_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "parent.foo.path()", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "parent.foo"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + void Expr_var_name_func(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 2294daff23..90fe17218a 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -483,6 +483,7 @@ void Expr_entity_path_expr(void); void Expr_entity_parent_func(void); void Expr_entity_name_func(void); void Expr_entity_doc_name_func(void); +void Expr_entity_path_func(void); void Expr_entity_chain_func(void); void Expr_var_parent_func(void); void Expr_var_name_func(void); @@ -2543,6 +2544,10 @@ bake_test_case Expr_testcases[] = { "entity_doc_name_func", Expr_entity_doc_name_func }, + { + "entity_path_func", + Expr_entity_path_func + }, { "entity_chain_func", Expr_entity_chain_func @@ -3265,7 +3270,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 146, + 147, Expr_testcases }, { From 2d8ae6c46accdccab85af477ba302b6dd8675eeb Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 3 Dec 2024 00:24:52 +0000 Subject: [PATCH 22/83] Implement support for dynamic initializers --- distr/flecs.c | 80 +++++++++++++++++++++++++++++ src/addons/script/expr/visit_eval.c | 80 +++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/distr/flecs.c b/distr/flecs.c index 9af0c42ebc..0dfd617e41 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -74321,6 +74321,13 @@ int flecs_expr_value_visit_eval( static int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value); + +static +int flecs_expr_initializer_eval_static( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, @@ -74365,6 +74372,79 @@ int flecs_expr_initializer_eval( return -1; } +static +int flecs_expr_initializer_eval_dynamic( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, node->node.type, value); + ecs_meta_push(&cur); + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (i) { + ecs_meta_next(&cur); + } + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval( + script, (ecs_expr_initializer_t*)elem->value, desc, value)) + { + goto error; + } + continue; + } + + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv( + script, elem->value, desc, &expr)) + { + goto error; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + if (elem->member) { + ecs_meta_member(&cur, elem->member); + } + + ecs_value_t v_elem_value = { + .ptr = elem_value->ptr, + .type = elem_value->node.type + }; + + if (ecs_meta_set_value(&cur, &v_elem_value)) { + goto error; + } + } + + ecs_meta_pop(&cur); + + return 0; +error: + return -1; +} + +static +int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + if (node->is_dynamic) { + return flecs_expr_initializer_eval_dynamic(script, node, desc, value); + } else { + return flecs_expr_initializer_eval_static(script, node, desc, value); + } +} + static int flecs_expr_initializer_visit_eval( ecs_script_t *script, diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index bd46df1d31..ac1cad5086 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -78,6 +78,13 @@ int flecs_expr_value_visit_eval( static int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value); + +static +int flecs_expr_initializer_eval_static( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, @@ -122,6 +129,79 @@ int flecs_expr_initializer_eval( return -1; } +static +int flecs_expr_initializer_eval_dynamic( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, node->node.type, value); + ecs_meta_push(&cur); + + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (i) { + ecs_meta_next(&cur); + } + + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval( + script, (ecs_expr_initializer_t*)elem->value, desc, value)) + { + goto error; + } + continue; + } + + ecs_eval_value_t expr = {{0}}; + if (flecs_script_expr_visit_eval_priv( + script, elem->value, desc, &expr)) + { + goto error; + } + + ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + + if (elem->member) { + ecs_meta_member(&cur, elem->member); + } + + ecs_value_t v_elem_value = { + .ptr = elem_value->ptr, + .type = elem_value->node.type + }; + + if (ecs_meta_set_value(&cur, &v_elem_value)) { + goto error; + } + } + + ecs_meta_pop(&cur); + + return 0; +error: + return -1; +} + +static +int flecs_expr_initializer_eval( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + void *value) +{ + if (node->is_dynamic) { + return flecs_expr_initializer_eval_dynamic(script, node, desc, value); + } else { + return flecs_expr_initializer_eval_static(script, node, desc, value); + } +} + static int flecs_expr_initializer_visit_eval( ecs_script_t *script, From b1a762264a8dfa853d004adad74b363aa51f3bd7 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 3 Dec 2024 01:15:38 +0000 Subject: [PATCH 23/83] Improve management of non-trivial temporary values --- distr/flecs.c | 58 ++-- src/addons/script/expr/ast.c | 3 +- src/addons/script/expr/visit_eval.c | 39 ++- src/addons/script/expr/visit_type.c | 9 +- src/addons/script/script.c | 7 - test/script/project.json | 17 +- test/script/src/Deserialize.c | 161 ++++++--- test/script/src/Expr.c | 501 +++++++++++++++++++++++++++- test/script/src/main.c | 79 ++++- 9 files changed, 780 insertions(+), 94 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 0dfd617e41..86ce4c0678 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -57369,13 +57369,6 @@ void FlecsScriptImport( } }); - /* Dedicated string type used to indicate that a string is owned by the - * parser and should be copied when returning to the application. */ - ecs_primitive(world, { - .entity = ecs_entity(world, { .name = "string" }), - .kind = EcsString - }); - ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); @@ -73422,8 +73415,7 @@ ecs_expr_val_t* flecs_expr_string( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; - result->node.type = ecs_lookup( - parser->script->pub.world, "flecs.script.string"); + result->node.type = ecs_id(ecs_string_t); return result; } @@ -74254,6 +74246,8 @@ int flecs_value_binary( typedef struct ecs_eval_value_t { ecs_value_t value; ecs_expr_small_val_t storage; + const ecs_type_info_t *type_info; + bool can_move; /* Can value be moved to output */ } ecs_eval_value_t; static @@ -74272,6 +74266,8 @@ void flecs_expr_value_alloc( ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); val->value.type = ti->component; + val->type_info = ti; + val->can_move = true; if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { val->value.ptr = val->storage.small_data; @@ -74289,10 +74285,13 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( ecs_script_t *script, - ecs_eval_value_t *val, - const ecs_type_info_t *ti) + ecs_eval_value_t *val) { - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = val->type_info; + + if (!ti) { + return; + } ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { @@ -74316,6 +74315,7 @@ int flecs_expr_value_visit_eval( { out->value.type = node->node.type; out->value.ptr = node->ptr; + out->can_move = false; return 0; } @@ -74365,6 +74365,8 @@ int flecs_expr_initializer_eval_static( { goto error; } + + flecs_expr_value_free(script, &expr); } return 0; @@ -74422,6 +74424,8 @@ int flecs_expr_initializer_eval_dynamic( if (ecs_meta_set_value(&cur, &v_elem_value)) { goto error; } + + flecs_expr_value_free(script, &expr); } ecs_meta_pop(&cur); @@ -74528,6 +74532,7 @@ int flecs_expr_variable_visit_eval( ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; + out->can_move = false; return 0; } @@ -74608,6 +74613,7 @@ int flecs_expr_member_visit_eval( out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); out->value.type = node->node.type; + out->can_move = false; return 0; error: @@ -74635,6 +74641,7 @@ int flecs_expr_element_visit_eval( out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); out->value.type = node->node.type; + out->can_move = false; return 0; error: @@ -74665,6 +74672,7 @@ int flecs_expr_component_visit_eval( out->value.type = node->node.type; out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + out->can_move = false; if (!out->value.ptr) { char *estr = ecs_get_path(script->world, entity); @@ -74790,17 +74798,20 @@ int flecs_script_expr_visit_eval( out->type = node->type; } - if (out->type == ecs_lookup(script->world, "flecs.script.string")) { - out->type = ecs_id(ecs_string_t); - } - if (out->type && !out->ptr) { out->ptr = ecs_value_new(script->world, out->type); } - if (flecs_value_move_to(script->world, out, &val.value)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; + if (val.can_move) { + if (flecs_value_move_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } else { + if (flecs_value_copy_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } } return 0; @@ -76168,6 +76179,10 @@ int flecs_expr_function_visit_type( is_method = true; } + /* Left of function expression should not inherit lvalue type, since the + * function return type is what's going to be assigned. */ + ecs_os_zeromem(cur); + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -76178,8 +76193,11 @@ int flecs_expr_function_visit_type( ecs_entity_t func = ecs_lookup_from( world, node->left->type, node->function_name); if (!func) { + char *type_str = ecs_get_path(script->world, node->left->type); flecs_expr_visit_error(script, node, - "unresolved method identifier '%s'", node->function_name); + "unresolved method identifier '%s' for type '%s'", + node->function_name, type_str); + ecs_os_free(type_str); goto error; } diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index a2f7a06c85..b6992f9196 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -96,8 +96,7 @@ ecs_expr_val_t* flecs_expr_string( parser, ecs_expr_val_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; - result->node.type = ecs_lookup( - parser->script->pub.world, "flecs.script.string"); + result->node.type = ecs_id(ecs_string_t); return result; } diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index ac1cad5086..14e04c92b8 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -11,6 +11,8 @@ typedef struct ecs_eval_value_t { ecs_value_t value; ecs_expr_small_val_t storage; + const ecs_type_info_t *type_info; + bool can_move; /* Can value be moved to output */ } ecs_eval_value_t; static @@ -29,6 +31,8 @@ void flecs_expr_value_alloc( ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); val->value.type = ti->component; + val->type_info = ti; + val->can_move = true; if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { val->value.ptr = val->storage.small_data; @@ -46,10 +50,13 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( ecs_script_t *script, - ecs_eval_value_t *val, - const ecs_type_info_t *ti) + ecs_eval_value_t *val) { - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = val->type_info; + + if (!ti) { + return; + } ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { @@ -73,6 +80,7 @@ int flecs_expr_value_visit_eval( { out->value.type = node->node.type; out->value.ptr = node->ptr; + out->can_move = false; return 0; } @@ -122,6 +130,8 @@ int flecs_expr_initializer_eval_static( { goto error; } + + flecs_expr_value_free(script, &expr); } return 0; @@ -179,6 +189,8 @@ int flecs_expr_initializer_eval_dynamic( if (ecs_meta_set_value(&cur, &v_elem_value)) { goto error; } + + flecs_expr_value_free(script, &expr); } ecs_meta_pop(&cur); @@ -285,6 +297,7 @@ int flecs_expr_variable_visit_eval( ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; + out->can_move = false; return 0; } @@ -365,6 +378,7 @@ int flecs_expr_member_visit_eval( out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); out->value.type = node->node.type; + out->can_move = false; return 0; error: @@ -392,6 +406,7 @@ int flecs_expr_element_visit_eval( out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); out->value.type = node->node.type; + out->can_move = false; return 0; error: @@ -422,6 +437,7 @@ int flecs_expr_component_visit_eval( out->value.type = node->node.type; out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + out->can_move = false; if (!out->value.ptr) { char *estr = ecs_get_path(script->world, entity); @@ -547,17 +563,20 @@ int flecs_script_expr_visit_eval( out->type = node->type; } - if (out->type == ecs_lookup(script->world, "flecs.script.string")) { - out->type = ecs_id(ecs_string_t); - } - if (out->type && !out->ptr) { out->ptr = ecs_value_new(script->world, out->type); } - if (flecs_value_move_to(script->world, out, &val.value)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; + if (val.can_move) { + if (flecs_value_move_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } else { + if (flecs_value_copy_to(script->world, out, &val.value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } } return 0; diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 22cea1cff0..d8d4e8b56c 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -510,6 +510,10 @@ int flecs_expr_function_visit_type( is_method = true; } + /* Left of function expression should not inherit lvalue type, since the + * function return type is what's going to be assigned. */ + ecs_os_zeromem(cur); + if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -520,8 +524,11 @@ int flecs_expr_function_visit_type( ecs_entity_t func = ecs_lookup_from( world, node->left->type, node->function_name); if (!func) { + char *type_str = ecs_get_path(script->world, node->left->type); flecs_expr_visit_error(script, node, - "unresolved method identifier '%s'", node->function_name); + "unresolved method identifier '%s' for type '%s'", + node->function_name, type_str); + ecs_os_free(type_str); goto error; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 7a3f689e52..5bacdc1eaa 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -312,13 +312,6 @@ void FlecsScriptImport( } }); - /* Dedicated string type used to indicate that a string is owned by the - * parser and should be copied when returning to the application. */ - ecs_primitive(world, { - .entity = ecs_entity(world, { .name = "string" }), - .kind = EcsString - }); - ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); diff --git a/test/script/project.json b/test/script/project.json index 4836c73304..cd458e1583 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -522,7 +522,19 @@ "iter_to_vars_w_1_query_var", "iter_to_vars_w_2_query_vars", "component_expr", - "component_member_expr" + "component_member_expr", + "component_elem_expr", + "component_expr_string", + "component_member_expr_string", + "component_elem_expr_string", + "component_inline_elem_expr_string", + "var_expr", + "var_member_expr", + "var_elem_expr", + "var_expr_string", + "var_member_expr_string", + "var_elem_expr_string", + "var_inline_elem_expr_string" ] }, { "id": "Vars", @@ -644,6 +656,9 @@ "struct_i32", "struct_i32_neg", "struct_i32_i32", + "struct_string", + "struct_string_from_name", + "struct_string_from_path", "struct_entity", "struct_id", "struct_nested_i32", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index e3e089bf58..68dc23f619 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -588,7 +588,7 @@ void Deserialize_struct_enum(void) { test_assert(e != 0); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"v", e} @@ -648,7 +648,7 @@ void Deserialize_struct_bitmask(void) { test_assert(b != 0); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"v", b} @@ -709,7 +709,7 @@ void Deserialize_struct_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)} @@ -734,7 +734,7 @@ void Deserialize_struct_i32_neg(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)} @@ -760,7 +760,7 @@ void Deserialize_struct_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -780,6 +780,89 @@ void Deserialize_struct_i32_i32(void) { ecs_fini(world); } +void Deserialize_struct_string(void) { + typedef struct { + char *value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + T value = {0}; + + const char *ptr = ecs_script_expr_run(world, "{\"Hello World\"}", &(ecs_value_t){t, &value}, NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(value.value, "Hello World"); + + ecs_os_free(value.value); + + ecs_fini(world); +} + +void Deserialize_struct_string_from_name(void) { + typedef struct { + char *value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + T value = {0}; + + const char *ptr = ecs_script_expr_run(world, "{flecs.core.name()}", + &(ecs_value_t){t, &value}, NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(value.value, "core"); + + ecs_os_free(value.value); + + ecs_fini(world); +} + +void Deserialize_struct_string_from_path(void) { + typedef struct { + char *value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + T value = {0}; + + const char *ptr = ecs_script_expr_run(world, "{flecs.core.path()}", + &(ecs_value_t){t, &value}, NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(value.value, "flecs.core"); + + ecs_os_free(value.value); + + ecs_fini(world); +} + void Deserialize_struct_entity(void) { typedef struct { ecs_entity_t entity; @@ -787,7 +870,7 @@ void Deserialize_struct_entity(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"entity", ecs_id(ecs_entity_t)} @@ -812,7 +895,7 @@ void Deserialize_struct_id(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"entity", ecs_id(ecs_id_t)} @@ -841,14 +924,14 @@ void Deserialize_struct_nested_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)} } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -878,7 +961,7 @@ void Deserialize_struct_nested_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -886,7 +969,7 @@ void Deserialize_struct_nested_i32_i32(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -918,7 +1001,7 @@ void Deserialize_struct_2_nested_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -926,7 +1009,7 @@ void Deserialize_struct_2_nested_i32_i32(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -955,7 +1038,7 @@ void Deserialize_struct_member_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)} @@ -980,7 +1063,7 @@ void Deserialize_struct_member_i32_neg(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)} @@ -1006,7 +1089,7 @@ void Deserialize_struct_member_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1037,14 +1120,14 @@ void Deserialize_struct_member_nested_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)} } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -1074,7 +1157,7 @@ void Deserialize_struct_member_nested_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1082,7 +1165,7 @@ void Deserialize_struct_member_nested_i32_i32(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -1114,7 +1197,7 @@ void Deserialize_struct_member_2_nested_i32_i32(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1122,7 +1205,7 @@ void Deserialize_struct_member_2_nested_i32_i32(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -1163,7 +1246,7 @@ void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1171,7 +1254,7 @@ void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -1206,7 +1289,7 @@ void Deserialize_struct_i32_array_3(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"x", ecs_id(ecs_i32_t), 3} @@ -1237,14 +1320,14 @@ void Deserialize_struct_struct_i32_array_3(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)} } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1, 3} @@ -1276,7 +1359,7 @@ void Deserialize_struct_struct_i32_i32_array_3(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1284,7 +1367,7 @@ void Deserialize_struct_struct_i32_i32_array_3(void) { } }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1, 3} @@ -1322,7 +1405,7 @@ void Deserialize_struct_w_array_type_i32_i32(void) { .count = 2 }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1} @@ -1357,7 +1440,7 @@ void Deserialize_struct_w_2_array_type_i32_i32(void) { .count = 2 }); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", n1}, @@ -1393,7 +1476,7 @@ void Deserialize_struct_w_array_type_struct(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1411,7 +1494,7 @@ void Deserialize_struct_w_array_type_struct(void) { test_assert(a1 != 0); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", a1} @@ -1449,7 +1532,7 @@ void Deserialize_struct_w_2_array_type_struct(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t n1 = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t n1 = ecs_struct(world, { .entity = ecs_entity(world, {.name = "N1"}), .members = { {"x", ecs_id(ecs_i32_t)}, @@ -1467,7 +1550,7 @@ void Deserialize_struct_w_2_array_type_struct(void) { test_assert(a1 != 0); - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .entity = ecs_entity(world, {.name = "T"}), .members = { {"n_1", a1}, @@ -1657,7 +1740,7 @@ void Deserialize_opaque_struct(void) { ECS_COMPONENT(world, OpaqueStruct); - ecs_entity_t s = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t s = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)}, @@ -1687,7 +1770,7 @@ void Deserialize_opaque_struct_w_member(void) { ECS_COMPONENT(world, OpaqueStruct); - ecs_entity_t s = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t s = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)}, @@ -1717,7 +1800,7 @@ void Deserialize_opaque_struct_w_member_reverse(void) { ECS_COMPONENT(world, OpaqueStruct); - ecs_entity_t s = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t s = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)}, @@ -1767,7 +1850,7 @@ void Deserialize_struct_w_opaque_member(void) { .type.assign_string = Opaque_string_set }); - ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_struct(world, { .entity = ecs_id(Struct_w_opaque), .members = { {"str", ecs_id(Opaque_string)}, diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 408bd35967..50404a569d 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -603,7 +603,7 @@ void Expr_struct_result_implicit_members(void) { int32_t y; } Position; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)} @@ -631,7 +631,7 @@ void Expr_struct_result_explicit_members(void) { int32_t y; } Position; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)} @@ -660,7 +660,7 @@ void Expr_struct_result_explicit_members_reverse(void) { int32_t y; } Position; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)} @@ -868,7 +868,7 @@ void Expr_struct_result_add_2_int_literals(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -894,7 +894,7 @@ void Expr_struct_result_add_2_2_fields_int_literals(void) { int32_t y; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"x", ecs_id(ecs_i32_t)}, {"y", ecs_id(ecs_i32_t)} @@ -921,7 +921,7 @@ void Expr_struct_result_add_3_int_literals(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -946,7 +946,7 @@ void Expr_struct_result_lparen_int_rparen(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -2248,7 +2248,7 @@ void Expr_struct_w_min_var(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -2281,7 +2281,7 @@ void Expr_struct_w_min_lparen_int_rparen(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -2306,7 +2306,7 @@ void Expr_struct_w_min_lparen_var_rparen(void) { int32_t value; } Mass; - ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t t = ecs_struct(world, { .members = { {"value", ecs_id(ecs_i32_t)} } @@ -3527,7 +3527,7 @@ void Expr_iter_to_vars_w_2_query_vars(void) { void Expr_component_expr(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3554,7 +3554,7 @@ void Expr_component_expr(void) { void Expr_component_member_expr(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3587,3 +3587,480 @@ void Expr_component_member_expr(void) { ecs_fini(world); } + +void Expr_component_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *value; + } String; + + ecs_entity_t ecs_id(String) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "String" }), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_set(world, e, String, { "Hello World" }); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "e[String]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(String)); + test_assert(v.ptr != NULL); + { + String *ptr = v.ptr; + test_str(ptr->value, "Hello World"); + ecs_value_free(world, v.type, v.ptr); + } + + const String *s = ecs_get(world, e, String); + test_assert(s != NULL); + test_str(s->value, "Hello World"); + + ecs_fini(world); +} + +void Expr_component_member_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *value; + } String; + + ecs_entity_t ecs_id(String) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "String" }), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_set(world, e, String, { "Hello World" }); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "e[String].value", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "Hello World"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_component_elem_expr(void) { + ecs_world_t *world = ecs_init(); + + typedef char* Strings[2]; + + ecs_entity_t ecs_id(Strings) = ecs_array(world, { + .entity = ecs_entity(world, { .name = "Strings" }), + .type = ecs_id(ecs_string_t), + .count = 2 + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_set(world, e, Strings, { "Hello", "World" }); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "e[Strings][0]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "Hello"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_os_zeromem(&v); + + test_assert(ecs_script_expr_run(world, "e[Strings][1]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "World"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_component_elem_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *value[2]; + } String; + + ecs_entity_t arr = ecs_array(world, { + .type = ecs_id(ecs_string_t), + .count = 2 + }); + + ecs_entity_t ecs_id(String) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "String" }), + .members = { + {"value", arr} + } + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_set(world, e, String, {{ "Hello", "World" }}); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "Hello"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_os_zeromem(&v); + + test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "World"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_component_inline_elem_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *value[2]; + } String; + + ecs_entity_t ecs_id(String) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "String" }), + .members = { + {"value", ecs_id(ecs_string_t), .count = 2} + } + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_set(world, e, String, {{ "Hello", "World" }}); + + ecs_value_t v = {0}; + test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "Hello"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_os_zeromem(&v); + + test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + { + char **ptr = v.ptr; + test_str(*ptr, "World"); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_var_expr(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); + *(int32_t*)foo->value.ptr = 10; + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + const char *ptr = ecs_script_expr_run( + world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 10); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_member_expr(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(PositionI) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "PositionI"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", PositionI); + *(PositionI*)foo->value.ptr = (PositionI){10, 20}; + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.x", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 10); + } + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.y", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_elem_expr(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .entity = ecs_entity(world, { .name = "Ints" }), + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Ints); + Ints *var = foo->value.ptr; + (*var)[0] = 10; + (*var)[1] = 20; + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo[0]", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 10); + } + + { + const char *ptr = ecs_script_expr_run( + world, "$foo[1]", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_expr_string(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); + *(char**)foo->value.ptr = "Hello World"; + + char* v = NULL; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + const char *ptr = ecs_script_expr_run( + world, "$foo", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "Hello World"); + + ecs_os_free(v); + + *(char**)foo->value.ptr = NULL; + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_member_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *x; + char *y; + } Strings; + + ecs_entity_t ecs_id(Strings) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Strings"}), + .members = { + {"x", ecs_id(ecs_string_t)}, + {"y", ecs_id(ecs_string_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Strings); + Strings *var = foo->value.ptr; + var->x = "Hello"; + var->y = "World"; + + char *v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.x", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "Hello"); + ecs_os_free(v); + } + + v = NULL; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.y", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "World"); + ecs_os_free(v); + } + + test_str(var->x, "Hello"); + test_str(var->y, "World"); + + var->x = NULL; + var->y = NULL; + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_elem_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef char* Strings[2]; + + ecs_entity_t ecs_id(Strings) = ecs_array(world, { + .entity = ecs_entity(world, { .name = "Strings" }), + .type = ecs_id(ecs_string_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Strings); + Strings *var = foo->value.ptr; + (*var)[0] = "Hello"; + (*var)[1] = "World"; + + char *v = NULL; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo[0]", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "Hello"); + ecs_os_free(v); + } + + v = NULL; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo[1]", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "World"); + ecs_os_free(v); + } + + test_str((*var)[0], "Hello"); + test_str((*var)[1], "World"); + (*var)[0] = NULL; + (*var)[1] = NULL; + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_var_inline_elem_expr_string(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *value[2]; + } Strings; + + ecs_entity_t ecs_id(Strings) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Strings"}), + .members = { + {"value", ecs_id(ecs_string_t), .count = 2}, + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Strings); + Strings *var = foo->value.ptr; + var->value[0] = "Hello"; + var->value[1] = "World"; + + char *v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.value[0]", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "Hello"); + ecs_os_free(v); + } + + v = NULL; + + { + const char *ptr = ecs_script_expr_run( + world, "$foo.value[1]", &ecs_value_ptr(ecs_string_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_str(v, "World"); + ecs_os_free(v); + } + + test_str(var->value[0], "Hello"); + test_str(var->value[1], "World"); + + var->value[0] = NULL; + var->value[1] = NULL; + ecs_script_vars_fini(vars); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 90fe17218a..d31518bdd8 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -513,6 +513,18 @@ void Expr_iter_to_vars_w_1_query_var(void); void Expr_iter_to_vars_w_2_query_vars(void); void Expr_component_expr(void); void Expr_component_member_expr(void); +void Expr_component_elem_expr(void); +void Expr_component_expr_string(void); +void Expr_component_member_expr_string(void); +void Expr_component_elem_expr_string(void); +void Expr_component_inline_elem_expr_string(void); +void Expr_var_expr(void); +void Expr_var_member_expr(void); +void Expr_var_elem_expr(void); +void Expr_var_expr_string(void); +void Expr_var_member_expr_string(void); +void Expr_var_elem_expr_string(void); +void Expr_var_inline_elem_expr_string(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -628,6 +640,9 @@ void Deserialize_struct_bitmask(void); void Deserialize_struct_i32(void); void Deserialize_struct_i32_neg(void); void Deserialize_struct_i32_i32(void); +void Deserialize_struct_string(void); +void Deserialize_struct_string_from_name(void); +void Deserialize_struct_string_from_path(void); void Deserialize_struct_entity(void); void Deserialize_struct_id(void); void Deserialize_struct_nested_i32(void); @@ -2663,6 +2678,54 @@ bake_test_case Expr_testcases[] = { { "component_member_expr", Expr_component_member_expr + }, + { + "component_elem_expr", + Expr_component_elem_expr + }, + { + "component_expr_string", + Expr_component_expr_string + }, + { + "component_member_expr_string", + Expr_component_member_expr_string + }, + { + "component_elem_expr_string", + Expr_component_elem_expr_string + }, + { + "component_inline_elem_expr_string", + Expr_component_inline_elem_expr_string + }, + { + "var_expr", + Expr_var_expr + }, + { + "var_member_expr", + Expr_var_member_expr + }, + { + "var_elem_expr", + Expr_var_elem_expr + }, + { + "var_expr_string", + Expr_var_expr_string + }, + { + "var_member_expr_string", + Expr_var_member_expr_string + }, + { + "var_elem_expr_string", + Expr_var_elem_expr_string + }, + { + "var_inline_elem_expr_string", + Expr_var_inline_elem_expr_string } }; @@ -3109,6 +3172,18 @@ bake_test_case Deserialize_testcases[] = { "struct_i32_i32", Deserialize_struct_i32_i32 }, + { + "struct_string", + Deserialize_struct_string + }, + { + "struct_string_from_name", + Deserialize_struct_string_from_name + }, + { + "struct_string_from_path", + Deserialize_struct_string_from_path + }, { "struct_entity", Deserialize_struct_entity @@ -3270,7 +3345,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 147, + 159, Expr_testcases }, { @@ -3291,7 +3366,7 @@ static bake_test_suite suites[] = { "Deserialize", NULL, NULL, - 73, + 76, Deserialize_testcases } }; From 84f895387a5bc47a073ee9423733846634922a29 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 3 Dec 2024 05:24:58 +0000 Subject: [PATCH 24/83] Implement deducing variable name from initializer member --- distr/flecs.c | 24 +- src/addons/script/expr/ast.c | 2 +- src/addons/script/expr/ast.h | 2 +- src/addons/script/expr/visit_to_str.c | 2 +- src/addons/script/expr/visit_type.c | 18 +- test/script/project.json | 8 + test/script/src/Deserialize.c | 351 ++++++++++++++++++++++++++ test/script/src/main.c | 42 ++- 8 files changed, 438 insertions(+), 11 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 86ce4c0678..d323d2fc4b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4953,7 +4953,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; - const char *value; + const char *name; ecs_script_var_t *var; } ecs_expr_variable_t; @@ -73457,7 +73457,7 @@ ecs_expr_variable_t* flecs_expr_variable( { ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); - result->value = value; + result->name = value; return result; } @@ -75511,7 +75511,7 @@ int flecs_expr_variable_to_str( const ecs_expr_variable_t *node) { ecs_strbuf_appendlit(v->buf, "$"); - ecs_strbuf_appendstr(v->buf, node->value); + ecs_strbuf_appendstr(v->buf, node->name); return 0; } @@ -75995,6 +75995,19 @@ int flecs_expr_initializer_visit_type( } } + /* Check for "member: $" syntax */ + if (elem->value->kind == EcsExprVariable) { + ecs_expr_variable_t *var = (ecs_expr_variable_t*)elem->value; + if (var->name && !var->name[0]) { + var->name = ecs_meta_get_member(cur); + if (!var->name) { + flecs_expr_visit_error(script, node, + "cannot deduce variable name: not a member"); + goto error; + } + } + } + ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; if (flecs_script_expr_visit_type_priv( @@ -76125,10 +76138,10 @@ int flecs_expr_variable_visit_type( const ecs_script_expr_run_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->value); + desc->vars, node->name); if (!var) { flecs_expr_visit_error(script, node, "unresolved variable '%s'", - node->value); + node->name); goto error; } @@ -76161,6 +76174,7 @@ int flecs_expr_function_visit_type( last_elem[0] = '\0'; is_method = true; } + } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index b6992f9196..fb4bb52bb8 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -138,7 +138,7 @@ ecs_expr_variable_t* flecs_expr_variable( { ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); - result->value = value; + result->name = value; return result; } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 54bf6f64bf..c324ff531d 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -80,7 +80,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; - const char *value; + const char *name; ecs_script_var_t *var; } ecs_expr_variable_t; diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index d043ccb321..b40d560623 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -114,7 +114,7 @@ int flecs_expr_variable_to_str( const ecs_expr_variable_t *node) { ecs_strbuf_appendlit(v->buf, "$"); - ecs_strbuf_appendstr(v->buf, node->value); + ecs_strbuf_appendstr(v->buf, node->name); return 0; } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index d8d4e8b56c..41f11b64c2 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -326,6 +326,19 @@ int flecs_expr_initializer_visit_type( } } + /* Check for "member: $" syntax */ + if (elem->value->kind == EcsExprVariable) { + ecs_expr_variable_t *var = (ecs_expr_variable_t*)elem->value; + if (var->name && !var->name[0]) { + var->name = ecs_meta_get_member(cur); + if (!var->name) { + flecs_expr_visit_error(script, node, + "cannot deduce variable name: not a member"); + goto error; + } + } + } + ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; if (flecs_script_expr_visit_type_priv( @@ -456,10 +469,10 @@ int flecs_expr_variable_visit_type( const ecs_script_expr_run_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->value); + desc->vars, node->name); if (!var) { flecs_expr_visit_error(script, node, "unresolved variable '%s'", - node->value); + node->name); goto error; } @@ -492,6 +505,7 @@ int flecs_expr_function_visit_type( last_elem[0] = '\0'; is_method = true; } + } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ diff --git a/test/script/project.json b/test/script/project.json index cd458e1583..922d7a9060 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -659,6 +659,7 @@ "struct_string", "struct_string_from_name", "struct_string_from_path", + "struct_string_from_var", "struct_entity", "struct_id", "struct_nested_i32", @@ -671,6 +672,13 @@ "struct_member_nested_i32_i32", "struct_member_2_nested_i32_i32", "struct_member_2_nested_i32_i32_reverse", + "struct_from_var", + "struct_member_from_var", + "struct_member_auto_var", + "struct_member_auto_vars", + "struct_nested_member_auto_var", + "struct_nested_member_auto_vars", + "struct_auto_vars", "struct_i32_array_3", "struct_struct_i32_array_3", "struct_struct_i32_i32_array_3", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 68dc23f619..be6c12f46b 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -863,6 +863,44 @@ void Deserialize_struct_string_from_path(void) { ecs_fini(world); } +void Deserialize_struct_string_from_var(void) { + typedef struct { + char *value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_string_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_string_t); + *(char**)foo->value.ptr = "Hello World"; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + const char *ptr = ecs_script_expr_run(world, "{$foo}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(value.value, "Hello World"); + + ecs_os_free(value.value); + + *(char**)foo->value.ptr = NULL; + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Deserialize_struct_entity(void) { typedef struct { ecs_entity_t entity; @@ -1233,6 +1271,319 @@ void Deserialize_struct_member_2_nested_i32_i32(void) { ecs_fini(world); } +void Deserialize_struct_from_var(void) { + typedef struct { + int32_t x; + int32_t y; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(T) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", T); + T *var = foo->value.ptr; + var->x = 10; + var->y = 20; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "$foo", + &(ecs_value_t){ecs_id(T), &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_member_from_var(void) { + typedef struct { + int32_t value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_i32_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + T *var = foo->value.ptr; + var->value = 10; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ $foo }", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.value, 10); + } + + value.value = 0; + + { + const char *ptr = ecs_script_expr_run(world, "{ value: $foo }", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.value, 10); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_member_auto_var(void) { + typedef struct { + int32_t value; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"value", ecs_id(ecs_i32_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "value", ecs_i32_t); + T *var = foo->value.ptr; + var->value = 10; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ value: $ }", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.value, 10); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_member_auto_vars(void) { + typedef struct { + int32_t x; + int32_t y; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *x = ecs_script_vars_define(vars, "x", ecs_i32_t); + *(int32_t*)x->value.ptr = 10; + ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); + *(int32_t*)y->value.ptr = 20; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ x: $, y: $ }", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_nested_member_auto_var(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t ecs_id(Line) = ecs_struct(world, { + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "x", ecs_i32_t); + *(int32_t*)foo->value.ptr = 10; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ stop: { x: $ }}", + &(ecs_value_t){ecs_id(Line), &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.start.x, 0); + test_int(value.start.y, 0); + test_int(value.stop.x, 10); + test_int(value.stop.y, 0); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_nested_member_auto_vars(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + typedef struct { + Point start; + Point stop; + } Line; + + ecs_entity_t point = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t ecs_id(Line) = ecs_struct(world, { + .members = { + {"start", point}, + {"stop", point} + } + }); + + Line value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *x = ecs_script_vars_define(vars, "x", ecs_i32_t); + *(int32_t*)x->value.ptr = 10; + ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); + *(int32_t*)y->value.ptr = 20; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ stop: { x: $, y: $ }}", + &(ecs_value_t){ecs_id(Line), &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.start.x, 0); + test_int(value.start.y, 0); + test_int(value.stop.x, 10); + test_int(value.stop.y, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Deserialize_struct_auto_vars(void) { + typedef struct { + int32_t x; + int32_t y; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + T value = {0}; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *x = ecs_script_vars_define(vars, "x", ecs_i32_t); + *(int32_t*)x->value.ptr = 10; + ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); + *(int32_t*)y->value.ptr = 20; + + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + { + const char *ptr = ecs_script_expr_run(world, "{ $, $ }", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { typedef struct { ecs_i32_t x; diff --git a/test/script/src/main.c b/test/script/src/main.c index d31518bdd8..129f801efe 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -643,6 +643,7 @@ void Deserialize_struct_i32_i32(void); void Deserialize_struct_string(void); void Deserialize_struct_string_from_name(void); void Deserialize_struct_string_from_path(void); +void Deserialize_struct_string_from_var(void); void Deserialize_struct_entity(void); void Deserialize_struct_id(void); void Deserialize_struct_nested_i32(void); @@ -655,6 +656,13 @@ void Deserialize_struct_member_nested_i32(void); void Deserialize_struct_member_nested_i32_i32(void); void Deserialize_struct_member_2_nested_i32_i32(void); void Deserialize_struct_member_2_nested_i32_i32_reverse(void); +void Deserialize_struct_from_var(void); +void Deserialize_struct_member_from_var(void); +void Deserialize_struct_member_auto_var(void); +void Deserialize_struct_member_auto_vars(void); +void Deserialize_struct_nested_member_auto_var(void); +void Deserialize_struct_nested_member_auto_vars(void); +void Deserialize_struct_auto_vars(void); void Deserialize_struct_i32_array_3(void); void Deserialize_struct_struct_i32_array_3(void); void Deserialize_struct_struct_i32_i32_array_3(void); @@ -3184,6 +3192,10 @@ bake_test_case Deserialize_testcases[] = { "struct_string_from_path", Deserialize_struct_string_from_path }, + { + "struct_string_from_var", + Deserialize_struct_string_from_var + }, { "struct_entity", Deserialize_struct_entity @@ -3232,6 +3244,34 @@ bake_test_case Deserialize_testcases[] = { "struct_member_2_nested_i32_i32_reverse", Deserialize_struct_member_2_nested_i32_i32_reverse }, + { + "struct_from_var", + Deserialize_struct_from_var + }, + { + "struct_member_from_var", + Deserialize_struct_member_from_var + }, + { + "struct_member_auto_var", + Deserialize_struct_member_auto_var + }, + { + "struct_member_auto_vars", + Deserialize_struct_member_auto_vars + }, + { + "struct_nested_member_auto_var", + Deserialize_struct_nested_member_auto_var + }, + { + "struct_nested_member_auto_vars", + Deserialize_struct_nested_member_auto_vars + }, + { + "struct_auto_vars", + Deserialize_struct_auto_vars + }, { "struct_i32_array_3", Deserialize_struct_i32_array_3 @@ -3366,7 +3406,7 @@ static bake_test_suite suites[] = { "Deserialize", NULL, NULL, - 76, + 84, Deserialize_testcases } }; From 054c16efbfde806e05466e4fee633576617645fb Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 3 Dec 2024 06:51:25 +0000 Subject: [PATCH 25/83] Fix issue with array initializers --- distr/flecs.c | 5 +++ src/addons/meta/cursor.c | 4 +++ src/addons/script/expr/visit_fold.c | 1 + test/meta/project.json | 4 ++- test/meta/src/Cursor.c | 44 +++++++++++++++++++++++ test/meta/src/main.c | 12 ++++++- test/script/project.json | 2 ++ test/script/src/Deserialize.c | 55 +++++++++++++++++++++++++++++ test/script/src/main.c | 12 ++++++- 9 files changed, 136 insertions(+), 3 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d323d2fc4b..a4d045a25b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -48500,6 +48500,10 @@ bool ecs_meta_is_collection( ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { + if (cursor->depth == 0) { + return cursor->scope[0].type; + } + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->type; @@ -75071,6 +75075,7 @@ int flecs_expr_initializer_visit_fold( bool can_fold = true; ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { goto error; } diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 0bc67a321b..e0e3f74292 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -692,6 +692,10 @@ bool ecs_meta_is_collection( ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { + if (cursor->depth == 0) { + return cursor->scope[0].type; + } + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->type; diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 28ae6f6664..be4abc352d 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -250,6 +250,7 @@ int flecs_expr_initializer_visit_fold( bool can_fold = true; ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { goto error; } diff --git a/test/meta/project.json b/test/meta/project.json index d736524f27..a6b187baab 100644 --- a/test/meta/project.json +++ b/test/meta/project.json @@ -475,7 +475,9 @@ "opaque_vec_w_opaque_elem", "next_out_of_bounds", "set_out_of_bounds", - "get_member_id" + "get_member_id", + "get_array_type", + "get_vector_type" ] }, { "id": "DeserializeFromJson", diff --git a/test/meta/src/Cursor.c b/test/meta/src/Cursor.c index 8f79b2332b..f61cce8d05 100644 --- a/test/meta/src/Cursor.c +++ b/test/meta/src/Cursor.c @@ -4576,3 +4576,47 @@ void Cursor_get_member_id(void) { ecs_fini(world); } + +void Cursor_get_array_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, t, NULL); + test_assert(ecs_meta_get_type(&cur) == t); + + test_int(0, ecs_meta_push(&cur)); + test_assert(ecs_meta_get_type(&cur) == ecs_id(ecs_i32_t)); + test_int(0, ecs_meta_pop(&cur)); + + test_assert(ecs_meta_get_type(&cur) == t); + + ecs_fini(world); +} + +void Cursor_get_vector_type(void) { + test_quarantine("2 Dec 2024"); + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_vector(world, { + .entity = ecs_entity(world, { .name = "foo" }), + .type = ecs_id(ecs_i32_t) + }); + + int32_t array = 10; + ecs_vec_t v = { .count = 1, .array = &array }; + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, t, &v); + test_assert(ecs_meta_get_type(&cur) == t); + + test_int(0, ecs_meta_push(&cur)); + test_assert(ecs_meta_get_type(&cur) == ecs_id(ecs_i32_t)); + test_int(0, ecs_meta_pop(&cur)); + + test_assert(ecs_meta_get_type(&cur) == t); + + ecs_fini(world); +} diff --git a/test/meta/src/main.c b/test/meta/src/main.c index 4845f069e9..45d9906f51 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -451,6 +451,8 @@ void Cursor_opaque_vec_w_opaque_elem(void); void Cursor_next_out_of_bounds(void); void Cursor_set_out_of_bounds(void); void Cursor_get_member_id(void); +void Cursor_get_array_type(void); +void Cursor_get_vector_type(void); // Testsuite 'DeserializeFromJson' void DeserializeFromJson_struct_bool(void); @@ -2728,6 +2730,14 @@ bake_test_case Cursor_testcases[] = { { "get_member_id", Cursor_get_member_id + }, + { + "get_array_type", + Cursor_get_array_type + }, + { + "get_vector_type", + Cursor_get_vector_type } }; @@ -4989,7 +4999,7 @@ static bake_test_suite suites[] = { "Cursor", NULL, NULL, - 135, + 137, Cursor_testcases }, { diff --git a/test/script/project.json b/test/script/project.json index 922d7a9060..5cc355892a 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -686,6 +686,8 @@ "struct_w_array_type_struct", "struct_w_2_array_type_i32_i32", "struct_w_2_array_type_struct", + "array_i32_2", + "array_string_2", "discover_type_int", "discover_type_negative_int", "discover_type_float", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index be6c12f46b..edf2296a50 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -1932,6 +1932,61 @@ void Deserialize_struct_w_2_array_type_struct(void) { ecs_fini(world); } +void Deserialize_array_i32_2(void) { + typedef int32_t Ints[2]; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .entity = ecs_entity(world, {.name = "Ints"}), + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + test_assert(ecs_id(Ints) != 0); + + Ints value = {0, 0}; + + const char *ptr = ecs_script_expr_run(world, + "[10, 20]", &(ecs_value_t){ecs_id(Ints), &value}, NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_int(value[0], 10); + test_int(value[1], 20); + + ecs_fini(world); +} + +void Deserialize_array_string_2(void) { + typedef char* Strings[2]; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Strings) = ecs_array(world, { + .entity = ecs_entity(world, {.name = "Strings"}), + .type = ecs_id(ecs_string_t), + .count = 2 + }); + + test_assert(ecs_id(Strings) != 0); + + Strings value = {0, 0}; + + const char *ptr = ecs_script_expr_run(world, + "[\"Hello\", \"World\"]", &(ecs_value_t){ecs_id(Strings), &value}, NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(value[0], "Hello"); + test_str(value[1], "World"); + + ecs_os_free(value[0]); + ecs_os_free(value[1]); + + ecs_fini(world); +} + void Deserialize_discover_type_int(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 129f801efe..fa10269158 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -670,6 +670,8 @@ void Deserialize_struct_w_array_type_i32_i32(void); void Deserialize_struct_w_array_type_struct(void); void Deserialize_struct_w_2_array_type_i32_i32(void); void Deserialize_struct_w_2_array_type_struct(void); +void Deserialize_array_i32_2(void); +void Deserialize_array_string_2(void); void Deserialize_discover_type_int(void); void Deserialize_discover_type_negative_int(void); void Deserialize_discover_type_float(void); @@ -3300,6 +3302,14 @@ bake_test_case Deserialize_testcases[] = { "struct_w_2_array_type_struct", Deserialize_struct_w_2_array_type_struct }, + { + "array_i32_2", + Deserialize_array_i32_2 + }, + { + "array_string_2", + Deserialize_array_string_2 + }, { "discover_type_int", Deserialize_discover_type_int @@ -3406,7 +3416,7 @@ static bake_test_suite suites[] = { "Deserialize", NULL, NULL, - 84, + 86, Deserialize_testcases } }; From f79b6359ab98ddabf52afb74454e7a7f268ce76c Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 3 Dec 2024 23:37:36 +0000 Subject: [PATCH 26/83] Implement separate public functions for parsing/evaluating expressions --- distr/flecs.c | 174 +++++++++++++++------------- distr/flecs.h | 61 +++++++--- include/flecs/addons/script.h | 61 +++++++--- src/addons/script/expr/expr.h | 4 +- src/addons/script/expr/parser.c | 119 ++++++++++--------- src/addons/script/expr/util.c | 4 +- src/addons/script/expr/visit.h | 2 +- src/addons/script/expr/visit_eval.c | 38 +++--- src/addons/script/script.c | 1 + src/addons/script/script.h | 2 + src/addons/script/vars.c | 4 + test/script/project.json | 11 +- test/script/src/Error.c | 13 +++ test/script/src/Expr.c | 122 +++++++++++++++++++ test/script/src/main.c | 39 ++++++- 15 files changed, 453 insertions(+), 202 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index a4d045a25b..32eb4b892b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4519,7 +4519,9 @@ typedef struct ecs_script_impl_t { ecs_script_t pub; ecs_allocator_t allocator; ecs_script_scope_t *root; + ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; + const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; } ecs_script_impl_t; @@ -5089,7 +5091,7 @@ int flecs_script_expr_visit_fold( const ecs_script_expr_run_desc_t *desc); int flecs_script_expr_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_value_t *out); @@ -5112,14 +5114,14 @@ int flecs_value_move_to( ecs_value_t *src); int flecs_value_binary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_script_token_kind_t operator); int flecs_value_unary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator); @@ -57178,6 +57180,7 @@ void ecs_script_free( ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { flecs_script_visit_free(script); + flecs_script_expr_visit_free(script, impl->expr); flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); @@ -59122,6 +59125,10 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name) { + if (!vars) { + return NULL; + } + uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); @@ -73805,12 +73812,14 @@ const char* flecs_script_parse_rhs( result->left = *out; result->operator = oper; + *out = (ecs_expr_node_t*)result; + pos = flecs_script_parse_lhs(parser, pos, tokenizer, result->operator, &result->right); if (!pos) { goto error; } - *out = (ecs_expr_node_t*)result; + break; } }; @@ -73975,49 +73984,86 @@ const char* flecs_script_parse_expr( ParserEnd; } -ecs_expr_node_t* ecs_script_parse_expr( +ecs_script_t* ecs_script_expr_parse( ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr) + const char *expr, + const ecs_script_expr_run_desc_t *desc) { - if (!script) { - script = flecs_script_new(world); + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; } + ecs_script_t *script = flecs_script_new(world); + ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, + .script = impl, + .scope = impl->root, .significant_newline = false }; - ecs_script_impl_t *impl = flecs_script_impl(script); - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; impl->token_buffer = flecs_alloc( &impl->allocator, impl->token_buffer_size); parser.token_cur = impl->token_buffer; - ecs_expr_node_t *out = NULL; - - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { + const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); + if (!ptr) { goto error; } - if (flecs_script_expr_visit_type(script, out, NULL)) { + impl->next_token = ptr; + + if (flecs_script_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &out, NULL)) { + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } - return out; + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + return script; error: + ecs_script_free(script); return NULL; } +int ecs_script_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + + if (flecs_script_expr_visit_eval(script, impl->expr, &priv_desc, value)) { + goto error; + } + + return 0; +error: + return -1; +} + FLECS_API const char* ecs_script_expr_run( ecs_world_t *world, @@ -74037,54 +74083,22 @@ const char* ecs_script_expr_run( "type of value parameter does not match desc->type"); } - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - ecs_script_t *script = flecs_script_new(world); - - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, - .significant_newline = false - }; - - ecs_script_impl_t *impl = flecs_script_impl(script); - - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); - parser.token_cur = impl->token_buffer; - - ecs_expr_node_t *out = NULL; - - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { + ecs_script_t *s = ecs_script_expr_parse(world, expr, &priv_desc); + if (!s) { goto error; } - if (flecs_script_expr_visit_type(script, out, &priv_desc)) { + if (ecs_script_expr_eval(s, value, &priv_desc)) { + ecs_script_free(s); goto error; } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + const char *result = flecs_script_impl(s)->next_token; - if (flecs_script_expr_visit_fold(script, &out, &priv_desc)) { - goto error; - } + ecs_script_free(s); - // printf("%s\n", ecs_script_expr_to_str(world, out)); - - if (flecs_script_expr_visit_eval(script, out, &priv_desc, value)) { - goto error; - } - - flecs_script_expr_visit_free(script, out); - ecs_script_free(script); return result; error: - flecs_script_expr_visit_free(script, out); - ecs_script_free(script); return NULL; } @@ -74153,7 +74167,7 @@ int flecs_value_move_to( } int flecs_value_unary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator) @@ -74172,7 +74186,7 @@ int flecs_value_unary( } int flecs_value_binary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, @@ -74256,14 +74270,14 @@ typedef struct ecs_eval_value_t { static int flecs_script_expr_visit_eval_priv( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out); static void flecs_expr_value_alloc( - ecs_script_t *script, + const ecs_script_t *script, ecs_eval_value_t *val, const ecs_type_info_t *ti) { @@ -74288,7 +74302,7 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( - ecs_script_t *script, + const ecs_script_t *script, ecs_eval_value_t *val) { const ecs_type_info_t *ti = val->type_info; @@ -74312,7 +74326,7 @@ void flecs_expr_value_free( static int flecs_expr_value_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_val_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74325,14 +74339,14 @@ int flecs_expr_value_visit_eval( static int flecs_expr_initializer_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value); static int flecs_expr_initializer_eval_static( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -74380,7 +74394,7 @@ int flecs_expr_initializer_eval_static( static int flecs_expr_initializer_eval_dynamic( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -74441,7 +74455,7 @@ int flecs_expr_initializer_eval_dynamic( static int flecs_expr_initializer_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -74455,7 +74469,7 @@ int flecs_expr_initializer_eval( static int flecs_expr_initializer_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74467,7 +74481,7 @@ int flecs_expr_initializer_visit_eval( static int flecs_expr_unary_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_unary_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74494,7 +74508,7 @@ int flecs_expr_unary_visit_eval( static int flecs_expr_binary_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_binary_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74526,7 +74540,7 @@ int flecs_expr_binary_visit_eval( static int flecs_expr_variable_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_variable_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74542,7 +74556,7 @@ int flecs_expr_variable_visit_eval( static int flecs_expr_cast_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_cast_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74570,7 +74584,7 @@ int flecs_expr_cast_visit_eval( static int flecs_expr_function_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_function_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74604,7 +74618,7 @@ int flecs_expr_function_visit_eval( static int flecs_expr_member_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_member_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74626,7 +74640,7 @@ int flecs_expr_member_visit_eval( static int flecs_expr_element_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_element_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74654,7 +74668,7 @@ int flecs_expr_element_visit_eval( static int flecs_expr_component_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_element_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74695,7 +74709,7 @@ int flecs_expr_component_visit_eval( static int flecs_script_expr_visit_eval_priv( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -74783,7 +74797,7 @@ int flecs_script_expr_visit_eval_priv( } int flecs_script_expr_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) diff --git a/distr/flecs.h b/distr/flecs.h index e7bce59436..9e2761e368 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14607,18 +14607,18 @@ typedef struct ecs_script_expr_run_desc_t { ecs_entity_t type; } ecs_script_expr_run_desc_t; -/** Parse standalone expression into value. - * This operation parses a flecs expression into the provided pointer. The - * memory pointed to must be large enough to contain a value of the used type. +/** Run expression. + * This operation runs an expression and stores the result in the provided + * value. If the value contains a type that is different from the type of the + * expression, the expression will be cast to the value. * - * If no type and pointer are provided for the value argument, the operation - * will discover the type from the expression and allocate storage for the - * value. The allocated value must be freed with ecs_value_free(). + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. * * @param world The world. * @param ptr The pointer to the expression to parse. * @param value The value containing type & pointer to write to. - * @param desc Configuration parameters for deserializer. + * @param desc Configuration parameters for the parser. * @return Pointer to the character after the last one read, or NULL if failed. */ FLECS_API @@ -14628,6 +14628,41 @@ const char* ecs_script_expr_run( ecs_value_t *value, const ecs_script_expr_run_desc_t *desc); +/** Parse expression. + * This operation parses an expression and returns an object that can be + * evaluated multiple times with ecs_script_expr_eval(). + * + * @param world The world. + * @param expr The expression string. + * @param desc Configuration parameters for the parser. + * @return A script object if parsing is successful, NULL if parsing failed. + */ +FLECS_API +ecs_script_t* ecs_script_expr_parse( + ecs_world_t *world, + const char *ptr, + const ecs_script_expr_run_desc_t *desc); + +/** Evaluate expression. + * This operation evaluates an expression parsed with ecs_script_expr_parse() + * and stores the result in the provided value. If the value contains a type + * that is different from the type of the expression, the expression will be + * cast to the value. + * + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. + * + * @param script The script containing the expression. + * @param value The value in which to store the expression result. + * @param desc Configuration parameters for the parser. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_script_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc); + /** Evaluate interpolated expressions in string. * This operation evaluates expressions in a string, and replaces them with * their evaluated result. Supported expression formats are: @@ -14715,18 +14750,6 @@ int ecs_ptr_to_str_buf( typedef struct ecs_expr_node_t ecs_expr_node_t; -FLECS_API -ecs_expr_node_t* ecs_script_parse_expr( - ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr); - -FLECS_API -char* ecs_script_expr_to_str( - const ecs_world_t *world, - const ecs_expr_node_t *expr); - /** Script module import function. * Usage: * @code diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 109aab8a9a..0cf6b4f603 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -428,18 +428,18 @@ typedef struct ecs_script_expr_run_desc_t { ecs_entity_t type; } ecs_script_expr_run_desc_t; -/** Parse standalone expression into value. - * This operation parses a flecs expression into the provided pointer. The - * memory pointed to must be large enough to contain a value of the used type. +/** Run expression. + * This operation runs an expression and stores the result in the provided + * value. If the value contains a type that is different from the type of the + * expression, the expression will be cast to the value. * - * If no type and pointer are provided for the value argument, the operation - * will discover the type from the expression and allocate storage for the - * value. The allocated value must be freed with ecs_value_free(). + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. * * @param world The world. * @param ptr The pointer to the expression to parse. * @param value The value containing type & pointer to write to. - * @param desc Configuration parameters for deserializer. + * @param desc Configuration parameters for the parser. * @return Pointer to the character after the last one read, or NULL if failed. */ FLECS_API @@ -449,6 +449,41 @@ const char* ecs_script_expr_run( ecs_value_t *value, const ecs_script_expr_run_desc_t *desc); +/** Parse expression. + * This operation parses an expression and returns an object that can be + * evaluated multiple times with ecs_script_expr_eval(). + * + * @param world The world. + * @param expr The expression string. + * @param desc Configuration parameters for the parser. + * @return A script object if parsing is successful, NULL if parsing failed. + */ +FLECS_API +ecs_script_t* ecs_script_expr_parse( + ecs_world_t *world, + const char *ptr, + const ecs_script_expr_run_desc_t *desc); + +/** Evaluate expression. + * This operation evaluates an expression parsed with ecs_script_expr_parse() + * and stores the result in the provided value. If the value contains a type + * that is different from the type of the expression, the expression will be + * cast to the value. + * + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. + * + * @param script The script containing the expression. + * @param value The value in which to store the expression result. + * @param desc Configuration parameters for the parser. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_script_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc); + /** Evaluate interpolated expressions in string. * This operation evaluates expressions in a string, and replaces them with * their evaluated result. Supported expression formats are: @@ -536,18 +571,6 @@ int ecs_ptr_to_str_buf( typedef struct ecs_expr_node_t ecs_expr_node_t; -FLECS_API -ecs_expr_node_t* ecs_script_parse_expr( - ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr); - -FLECS_API -char* ecs_script_expr_to_str( - const ecs_world_t *world, - const ecs_expr_node_t *expr); - /** Script module import function. * Usage: * @code diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 2a51c678eb..80d92d5aff 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -20,14 +20,14 @@ int flecs_value_move_to( ecs_value_t *src); int flecs_value_binary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_script_token_kind_t operator); int flecs_value_unary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator); diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index c94fc72aec..cb5562a55a 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -283,12 +283,14 @@ const char* flecs_script_parse_rhs( result->left = *out; result->operator = oper; + *out = (ecs_expr_node_t*)result; + pos = flecs_script_parse_lhs(parser, pos, tokenizer, result->operator, &result->right); if (!pos) { goto error; } - *out = (ecs_expr_node_t*)result; + break; } }; @@ -453,49 +455,86 @@ const char* flecs_script_parse_expr( ParserEnd; } -ecs_expr_node_t* ecs_script_parse_expr( +ecs_script_t* ecs_script_expr_parse( ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr) + const char *expr, + const ecs_script_expr_run_desc_t *desc) { - if (!script) { - script = flecs_script_new(world); + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; } + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + + ecs_script_t *script = flecs_script_new(world); + ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, + .script = impl, + .scope = impl->root, .significant_newline = false }; - ecs_script_impl_t *impl = flecs_script_impl(script); - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; impl->token_buffer = flecs_alloc( &impl->allocator, impl->token_buffer_size); parser.token_cur = impl->token_buffer; - ecs_expr_node_t *out = NULL; - - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { + const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); + if (!ptr) { goto error; } - if (flecs_script_expr_visit_type(script, out, NULL)) { + impl->next_token = ptr; + + if (flecs_script_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &out, NULL)) { + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } - return out; + // printf("%s\n", ecs_script_expr_to_str(world, out)); + + return script; error: + ecs_script_free(script); return NULL; } +int ecs_script_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + + if (flecs_script_expr_visit_eval(script, impl->expr, &priv_desc, value)) { + goto error; + } + + return 0; +error: + return -1; +} + FLECS_API const char* ecs_script_expr_run( ecs_world_t *world, @@ -515,54 +554,22 @@ const char* ecs_script_expr_run( "type of value parameter does not match desc->type"); } - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - ecs_script_t *script = flecs_script_new(world); - - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, - .significant_newline = false - }; - - ecs_script_impl_t *impl = flecs_script_impl(script); - - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); - parser.token_cur = impl->token_buffer; - - ecs_expr_node_t *out = NULL; - - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { - goto error; - } - - if (flecs_script_expr_visit_type(script, out, &priv_desc)) { + ecs_script_t *s = ecs_script_expr_parse(world, expr, &priv_desc); + if (!s) { goto error; } - // printf("%s\n", ecs_script_expr_to_str(world, out)); - - if (flecs_script_expr_visit_fold(script, &out, &priv_desc)) { + if (ecs_script_expr_eval(s, value, &priv_desc)) { + ecs_script_free(s); goto error; } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + const char *result = flecs_script_impl(s)->next_token; - if (flecs_script_expr_visit_eval(script, out, &priv_desc, value)) { - goto error; - } + ecs_script_free(s); - flecs_script_expr_visit_free(script, out); - ecs_script_free(script); return result; error: - flecs_script_expr_visit_free(script, out); - ecs_script_free(script); return NULL; } diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index d30fddce7b..6cc68f0a93 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -63,7 +63,7 @@ int flecs_value_move_to( } int flecs_value_unary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator) @@ -82,7 +82,7 @@ int flecs_value_unary( } int flecs_value_binary( - ecs_script_t *script, + const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index c73c201c08..7d1c468dff 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -23,7 +23,7 @@ int flecs_script_expr_visit_fold( const ecs_script_expr_run_desc_t *desc); int flecs_script_expr_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_value_t *out); diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 14e04c92b8..0c77506ead 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -17,14 +17,14 @@ typedef struct ecs_eval_value_t { static int flecs_script_expr_visit_eval_priv( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out); static void flecs_expr_value_alloc( - ecs_script_t *script, + const ecs_script_t *script, ecs_eval_value_t *val, const ecs_type_info_t *ti) { @@ -49,7 +49,7 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( - ecs_script_t *script, + const ecs_script_t *script, ecs_eval_value_t *val) { const ecs_type_info_t *ti = val->type_info; @@ -73,7 +73,7 @@ void flecs_expr_value_free( static int flecs_expr_value_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_val_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -86,14 +86,14 @@ int flecs_expr_value_visit_eval( static int flecs_expr_initializer_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value); static int flecs_expr_initializer_eval_static( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -141,7 +141,7 @@ int flecs_expr_initializer_eval_static( static int flecs_expr_initializer_eval_dynamic( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -202,7 +202,7 @@ int flecs_expr_initializer_eval_dynamic( static int flecs_expr_initializer_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, void *value) @@ -216,7 +216,7 @@ int flecs_expr_initializer_eval( static int flecs_expr_initializer_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -228,7 +228,7 @@ int flecs_expr_initializer_visit_eval( static int flecs_expr_unary_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_unary_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -255,7 +255,7 @@ int flecs_expr_unary_visit_eval( static int flecs_expr_binary_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_binary_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -287,7 +287,7 @@ int flecs_expr_binary_visit_eval( static int flecs_expr_variable_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_variable_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -303,7 +303,7 @@ int flecs_expr_variable_visit_eval( static int flecs_expr_cast_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_cast_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -331,7 +331,7 @@ int flecs_expr_cast_visit_eval( static int flecs_expr_function_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_function_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -365,7 +365,7 @@ int flecs_expr_function_visit_eval( static int flecs_expr_member_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_member_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -387,7 +387,7 @@ int flecs_expr_member_visit_eval( static int flecs_expr_element_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_element_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -415,7 +415,7 @@ int flecs_expr_element_visit_eval( static int flecs_expr_component_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_element_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -456,7 +456,7 @@ int flecs_expr_component_visit_eval( static int flecs_script_expr_visit_eval_priv( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) @@ -544,7 +544,7 @@ int flecs_script_expr_visit_eval_priv( } int flecs_script_expr_visit_eval( - ecs_script_t *script, + const ecs_script_t *script, ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 5bacdc1eaa..b891d74479 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -117,6 +117,7 @@ void ecs_script_free( ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { flecs_script_visit_free(script); + flecs_script_expr_visit_free(script, impl->expr); flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); diff --git a/src/addons/script/script.h b/src/addons/script/script.h index c96ae7b8a7..bcfc92e9dd 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -19,7 +19,9 @@ typedef struct ecs_script_impl_t { ecs_script_t pub; ecs_allocator_t allocator; ecs_script_scope_t *root; + ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; + const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; } ecs_script_impl_t; diff --git a/src/addons/script/vars.c b/src/addons/script/vars.c index 5c4491a487..7399c7ee6a 100644 --- a/src/addons/script/vars.c +++ b/src/addons/script/vars.c @@ -169,6 +169,10 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name) { + if (!vars) { + return NULL; + } + uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); diff --git a/test/script/project.json b/test/script/project.json index 5cc355892a..4fb0587ae2 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -371,7 +371,8 @@ "template_redeclare_const_as_const", "run_template_after_error", "update_template_after_error", - "template_in_template" + "template_in_template", + "unterminated_binary" ] }, { "id": "Expr", @@ -534,7 +535,13 @@ "var_expr_string", "var_member_expr_string", "var_elem_expr_string", - "var_inline_elem_expr_string" + "var_inline_elem_expr_string", + "var_expr_no_desc", + "var_expr_desc_w_no_vars", + "parse_eval", + "parse_eval_multiple_times", + "parse_error", + "parse_eval_error" ] }, { "id": "Vars", diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 5f2f1b0542..1f3b3425a9 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1231,3 +1231,16 @@ void Error_template_in_template(void) { ecs_fini(world); } + +void Error_unterminated_binary(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 0; + + ecs_log_set_level(-4); + const char *ptr = ecs_script_expr_run(world, "10 +", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr == NULL); + + ecs_fini(world); +} diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 50404a569d..0d80f31002 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -4064,3 +4064,125 @@ void Expr_var_inline_elem_expr_string(void) { ecs_fini(world); } + +void Expr_var_expr_no_desc(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 0; + + ecs_log_set_level(-4); + const char *ptr = ecs_script_expr_run( + world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr == NULL); + + ecs_fini(world); +} + +void Expr_var_expr_desc_w_no_vars(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 0; + + ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .vars = NULL }; + const char *ptr = ecs_script_expr_run( + world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr == NULL); + + ecs_fini(world); +} + +void Expr_parse_eval(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); + *(int32_t*)foo->value.ptr = 10; + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_expr_eval(s, + &ecs_value_ptr(ecs_i32_t, &v), &desc)); + test_int(v, 30); + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_parse_eval_multiple_times(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); + *(int32_t*)foo->value.ptr = 10; + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .vars = vars }; + + ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_expr_eval(s, + &ecs_value_ptr(ecs_i32_t, &v), &desc)); + test_int(v, 30); + + *(int32_t*)foo->value.ptr = 30; + + test_int(0, ecs_script_expr_eval(s, + &ecs_value_ptr(ecs_i32_t, &v), &desc)); + test_int(v, 50); + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_parse_error(void) { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_script_expr_parse(world, "10 +", NULL); + test_assert(s == NULL); + + ecs_fini(world); +} + +void Expr_parse_eval_error(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + { "x", ecs_id(ecs_f32_t) }, + { "y", ecs_id(ecs_f32_t) } + } + }); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + ecs_add(world, e, Position); + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_script_expr_parse(world, "e[Position]", NULL); + test_assert(s != NULL); + + ecs_remove(world, e, Position); + + Position v; + test_assert(0 != ecs_script_expr_eval(s, + &ecs_value_ptr(Position, &v), NULL)); + + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index fa10269158..6d4517a11f 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -364,6 +364,7 @@ void Error_template_redeclare_const_as_const(void); void Error_run_template_after_error(void); void Error_update_template_after_error(void); void Error_template_in_template(void); +void Error_unterminated_binary(void); // Testsuite 'Expr' void Expr_add_2_int_literals(void); @@ -525,6 +526,12 @@ void Expr_var_expr_string(void); void Expr_var_member_expr_string(void); void Expr_var_elem_expr_string(void); void Expr_var_inline_elem_expr_string(void); +void Expr_var_expr_no_desc(void); +void Expr_var_expr_desc_w_no_vars(void); +void Expr_parse_eval(void); +void Expr_parse_eval_multiple_times(void); +void Expr_parse_error(void); +void Expr_parse_eval_error(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -2097,6 +2104,10 @@ bake_test_case Error_testcases[] = { { "template_in_template", Error_template_in_template + }, + { + "unterminated_binary", + Error_unterminated_binary } }; @@ -2736,6 +2747,30 @@ bake_test_case Expr_testcases[] = { { "var_inline_elem_expr_string", Expr_var_inline_elem_expr_string + }, + { + "var_expr_no_desc", + Expr_var_expr_no_desc + }, + { + "var_expr_desc_w_no_vars", + Expr_var_expr_desc_w_no_vars + }, + { + "parse_eval", + Expr_parse_eval + }, + { + "parse_eval_multiple_times", + Expr_parse_eval_multiple_times + }, + { + "parse_error", + Expr_parse_error + }, + { + "parse_eval_error", + Expr_parse_eval_error } }; @@ -3388,14 +3423,14 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 63, + 64, Error_testcases }, { "Expr", NULL, NULL, - 159, + 165, Expr_testcases }, { From d77ab97957e84955e36595d0826d021a6f5898e2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 4 Dec 2024 02:43:04 +0000 Subject: [PATCH 27/83] Add tests that ensure expr parser stops at right location --- test/script/project.json | 9 ++- test/script/src/Expr.c | 115 +++++++++++++++++++++++++++++++++++++++ test/script/src/main.c | 37 ++++++++++++- 3 files changed, 159 insertions(+), 2 deletions(-) diff --git a/test/script/project.json b/test/script/project.json index 4fb0587ae2..b1a4f8887a 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -541,7 +541,14 @@ "parse_eval", "parse_eval_multiple_times", "parse_error", - "parse_eval_error" + "parse_eval_error", + "remainder_after_number", + "remainder_after_string", + "remainder_after_unary", + "remainder_after_binary", + "remainder_after_parens", + "remainder_after_initializer", + "remainder_after_collection_initializer" ] }, { "id": "Vars", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 0d80f31002..d835eb04c1 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -4186,3 +4186,118 @@ void Expr_parse_eval_error(void) { ecs_fini(world); } + +void Expr_remainder_after_number(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 0; + const char *ptr = ecs_script_expr_run(world, "10 foo", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v, 10); + + ecs_fini(world); +} + +void Expr_remainder_after_string(void) { + ecs_world_t *world = ecs_init(); + + char *v = 0; + const char *ptr = ecs_script_expr_run(world, "\"bar\" foo", + &ecs_value_ptr(ecs_string_t, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_str(v, "bar"); + + ecs_os_free(v); + + ecs_fini(world); +} + +void Expr_remainder_after_unary(void) { + ecs_world_t *world = ecs_init(); + + bool v = false; + const char *ptr = ecs_script_expr_run(world, "!false foo", + &ecs_value_ptr(ecs_bool_t, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_bool(v, true); + + ecs_fini(world); +} + +void Expr_remainder_after_binary(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = false; + const char *ptr = ecs_script_expr_run(world, "10 + 20 foo", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v, 30); + + ecs_fini(world); +} + +void Expr_remainder_after_parens(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = false; + const char *ptr = ecs_script_expr_run(world, "(10 + 20) foo", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v, 30); + + ecs_fini(world); +} + +void Expr_remainder_after_initializer(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + { "x", ecs_id(ecs_f32_t) }, + { "y", ecs_id(ecs_f32_t) } + } + }); + + Position v = {0, 0}; + const char *ptr = ecs_script_expr_run(world, "{10, 20} foo", + &ecs_value_ptr(Position, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v.x, 10); + test_int(v.y, 20); + + ecs_fini(world); +} + +void Expr_remainder_after_collection_initializer(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ECS_COMPONENT(world, Ints); + + ecs_array(world, { + .entity = ecs_id(Ints), + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + Ints v = {0, 0}; + const char *ptr = ecs_script_expr_run(world, "[10, 20] foo", + &ecs_value_ptr(Ints, &v), NULL); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v[0], 10); + test_int(v[1], 20); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 6d4517a11f..75cfd1303b 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -532,6 +532,13 @@ void Expr_parse_eval(void); void Expr_parse_eval_multiple_times(void); void Expr_parse_error(void); void Expr_parse_eval_error(void); +void Expr_remainder_after_number(void); +void Expr_remainder_after_string(void); +void Expr_remainder_after_unary(void); +void Expr_remainder_after_binary(void); +void Expr_remainder_after_parens(void); +void Expr_remainder_after_initializer(void); +void Expr_remainder_after_collection_initializer(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -2771,6 +2778,34 @@ bake_test_case Expr_testcases[] = { { "parse_eval_error", Expr_parse_eval_error + }, + { + "remainder_after_number", + Expr_remainder_after_number + }, + { + "remainder_after_string", + Expr_remainder_after_string + }, + { + "remainder_after_unary", + Expr_remainder_after_unary + }, + { + "remainder_after_binary", + Expr_remainder_after_binary + }, + { + "remainder_after_parens", + Expr_remainder_after_parens + }, + { + "remainder_after_initializer", + Expr_remainder_after_initializer + }, + { + "remainder_after_collection_initializer", + Expr_remainder_after_collection_initializer } }; @@ -3430,7 +3465,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 165, + 172, Expr_testcases }, { From 5e71055ce8d7e69fd000105297c936c6309c6308 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 4 Dec 2024 07:48:37 +0000 Subject: [PATCH 28/83] Integrate script and expression parsers --- distr/flecs.c | 473 ++++++++++++++-------------- src/addons/meta/cursor.c | 1 + src/addons/script/ast.h | 8 +- src/addons/script/expr/ast.h | 2 +- src/addons/script/expr/expr.h | 12 + src/addons/script/expr/parser.c | 50 +-- src/addons/script/expr/visit_eval.c | 24 +- src/addons/script/expr/visit_fold.c | 1 + src/addons/script/expr/visit_free.c | 1 + src/addons/script/expr/visit_type.c | 15 +- src/addons/script/parser.c | 91 +++--- src/addons/script/parser.h | 45 ++- src/addons/script/template.c | 8 +- src/addons/script/tokenizer.c | 120 ------- src/addons/script/tokenizer.h | 6 - src/addons/script/visit_eval.c | 50 +-- src/addons/script/visit_eval.h | 2 +- src/addons/script/visit_free.c | 33 +- src/addons/script/visit_to_str.c | 4 +- test/script/project.json | 4 +- test/script/src/Expr.c | 26 ++ test/script/src/main.c | 12 +- 22 files changed, 504 insertions(+), 484 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 32eb4b892b..f463c70ec0 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4603,12 +4603,6 @@ typedef struct ecs_script_tokenizer_t { ecs_script_token_t *tokens; } ecs_script_tokenizer_t; -const char* flecs_script_expr( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - char until); - const char* flecs_script_until( ecs_script_parser_t *parser, const char *ptr, @@ -4710,14 +4704,14 @@ typedef struct ecs_script_tag_t { typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; - const char *expr; + ecs_expr_node_t *expr; ecs_value_t eval; bool is_collection; } ecs_script_component_t; typedef struct ecs_script_default_component_t { ecs_script_node_t node; - const char *expr; + ecs_expr_node_t *expr; ecs_value_t eval; } ecs_script_default_component_t; @@ -4783,14 +4777,14 @@ typedef struct ecs_script_var_node_t { ecs_script_node_t node; const char *name; const char *type; - const char *expr; + ecs_expr_node_t *expr; } ecs_script_var_node_t; typedef struct ecs_script_if_t { ecs_script_node_t node; ecs_script_scope_t *if_true; ecs_script_scope_t *if_false; - const char *expr; + ecs_expr_node_t *expr; } ecs_script_if_t; #define ecs_script_node(kind, node)\ @@ -4886,6 +4880,7 @@ ecs_script_if_t* flecs_script_insert_if( typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInitializer, + EcsExprEmptyInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, @@ -4956,7 +4951,6 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; - ecs_script_var_t *var; } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { @@ -5126,6 +5120,18 @@ int flecs_value_unary( ecs_value_t *out, ecs_script_token_kind_t operator); +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +const char* flecs_script_parse_initializer( + ecs_script_parser_t *parser, + const char *pos, + char until, + ecs_expr_initializer_t **node_out); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ @@ -5334,7 +5340,7 @@ const ecs_type_info_t* flecs_script_get_type_info( int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, - const char *expr, + ecs_expr_node_t **expr_ptr, ecs_value_t *value); int flecs_script_eval_template( @@ -47991,6 +47997,7 @@ int flecs_meta_cursor_push_type( if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); + ecs_abort(ECS_INTERNAL_ERROR, NULL); ecs_os_free(str); return -1; } @@ -55271,17 +55278,46 @@ char* ecs_script_string_interpolate( /* Parse expression */ #define Expr(until, ...)\ {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ + ecs_expr_node_t *EXPR = NULL;\ + if (until == '}' || until == ']') {\ + pos --;\ + if (until == '}') {\ + ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ + } else if (until == ']') {\ + ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ + }\ + }\ + parser->significant_newline = false;\ + if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ + goto error;\ + }\ + parser->significant_newline = true;\ + __VA_ARGS__\ + } + +/* Parse initializer */ +#define Initializer(until, ...)\ + {\ + ecs_expr_node_t *INITIALIZER = NULL;\ + ecs_expr_initializer_t *_initializer = NULL;\ + if (until != '\n') {\ + parser->significant_newline = false;\ + }\ + if (!(pos = flecs_script_parse_initializer(\ + parser, pos, until, &_initializer))) \ + {\ + flecs_script_expr_visit_free(\ + &parser->script->pub, (ecs_expr_node_t*)_initializer);\ goto error;\ }\ - if (!t->value[0] && (until == '\n' || until == '{')) {\ - pos ++;\ - Error("empty expression");\ + parser->significant_newline = true;\ + if (pos[0] != until) {\ + Error("expected '%c'", until);\ }\ - }\ - Parse_1(until, __VA_ARGS__) + INITIALIZER = (ecs_expr_node_t*)_initializer;\ + pos ++;\ + __VA_ARGS__\ + } /* Parse token until character */ #define Until(until, ...)\ @@ -55542,11 +55578,11 @@ const char* flecs_script_with_expr( pos = lookahead; // Position ( expr ) - Expr(')', + Initializer(')', ecs_script_component_t *component = flecs_script_insert_component(parser, Token(0)); component->node.kind = EcsAstWithComponent; - component->expr = Token(2); + component->expr = INITIALIZER; EndOfRule; ) ) @@ -55573,12 +55609,12 @@ const char* flecs_script_with_expr( pos = lookahead; // (Eats, Apples) ( expr ) - Expr(')', + Initializer(')', ecs_script_component_t *component = flecs_script_insert_pair_component(parser, Token(1), Token(3)); component->node.kind = EcsAstWithComponent; - component->expr = Token(6); + component->expr = INITIALIZER; EndOfRule; ) ) @@ -55636,13 +55672,13 @@ const char* flecs_script_paren_expr( { ParserBegin; - Expr(')', + Initializer(')', entity->kind_w_expr = true; Scope(entity->scope, ecs_script_component_t *component = flecs_script_insert_component(parser, kind); - component->expr = Token(0); + component->expr = INITIALIZER; ) Parse( @@ -55837,15 +55873,15 @@ prop_var: { LookAhead_1('{', // prop color = Color: {expr} pos = lookahead; - Expr('}', - var->expr = Token(6); + Initializer('}', + var->expr = INITIALIZER; EndOfRule; ) ) // prop color = Color : expr\n - Expr('\n', - var->expr = Token(5); + Initializer('\n', + var->expr = INITIALIZER; EndOfRule; ) ) @@ -55872,22 +55908,22 @@ const_var: { LookAhead_1('{', // const color = Color: {expr} pos = lookahead; - Expr('}', - var->expr = Token(6); + Initializer('}', + var->expr = INITIALIZER; EndOfRule; ) ) // const color = Color: expr\n - Expr('\n', - var->expr = Token(5); + Initializer('\n', + var->expr = INITIALIZER; EndOfRule; ) ) // const PI = expr\n Expr('\n', - var->expr = Token(3); + var->expr = EXPR; EndOfRule; ) } @@ -55897,26 +55933,29 @@ const_var: { // if if_stmt: { - // if expr { - Expr('{', - ecs_script_if_t *stmt = flecs_script_insert_if(parser); - stmt->expr = Token(1); - pos = flecs_script_scope(parser, stmt->if_true, pos); - if (!pos) { - goto error; - } + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } - // if expr { } else - LookAhead_1(EcsTokKeywordElse, - pos = lookahead; + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; - // if expr { } else { - Parse_1('{', - return flecs_script_scope(parser, stmt->if_false, pos); + // if expr { } else { + Parse_1('{', + return flecs_script_scope(parser, stmt->if_false, pos); + ) ) - ) - EndOfRule; + EndOfRule; + }); ) } @@ -55948,11 +55987,11 @@ pair: { // (Eats, Apples): { Parse_1('{', // (Eats, Apples): { expr } - Expr('}', + Initializer('}', ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); - comp->expr = Token(7); + comp->expr = INITIALIZER; EndOfRule; ) ) @@ -55999,7 +56038,7 @@ identifier_flag: { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(3), Token(5)); - comp->expr = Token(9); + comp->expr = EXPR; EndOfRule; }) ) @@ -56026,7 +56065,7 @@ identifier_flag: { Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(2)); - comp->expr = Token(5); + comp->expr = EXPR; EndOfRule; }) ) @@ -56092,11 +56131,11 @@ identifier_assign: { // x = Position: { Parse_1('{', { // x = Position: {expr} - Expr('}', + Expr('}', Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(2)); - comp->expr = Token(5); + comp->expr = EXPR; ) // x = Position: {expr}\n @@ -56109,11 +56148,11 @@ identifier_assign: { ) // x = f32\n - Expr('\n', + Initializer('\n', Scope(entity->scope, ecs_script_default_component_t *comp = flecs_script_insert_default_component(parser); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) EndOfRule; @@ -56166,7 +56205,7 @@ identifier_identifier: { // SpaceShip( identifier_paren: { // SpaceShip() - Expr(')', + Initializer(')', Parse( // SpaceShip(expr)\n EcsTokEndOfStatement: { @@ -56176,7 +56215,7 @@ identifier_paren: { Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) EndOfRule; @@ -56190,7 +56229,7 @@ identifier_paren: { Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) return flecs_script_scope(parser, entity->scope, pos); @@ -56206,7 +56245,7 @@ component_expr_scope: { Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); - comp->expr = Token(3); + comp->expr = EXPR; EndOfRule; }) } @@ -56217,7 +56256,7 @@ component_expr_collection: { Expr(']', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); - comp->expr = Token(3); + comp->expr = EXPR; comp->is_collection = true; EndOfRule; }) @@ -57966,7 +58005,8 @@ void flecs_script_template_on_set( const ecs_member_t *member = &members[m]; /* Assign template property from template instance */ - ecs_script_var_t *var = ecs_script_vars_declare(vars, member->name); + ecs_script_var_t *var = ecs_script_vars_declare( + vars, member->name); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } @@ -57982,7 +58022,8 @@ void flecs_script_template_on_set( ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { - ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_poly_is(world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } @@ -58038,7 +58079,7 @@ int flecs_script_template_eval_prop( var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); var->type_info = ti; - if (flecs_script_eval_expr(v, node->expr, &var->value)) { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } @@ -58692,126 +58733,6 @@ const char* flecs_script_multiline_string( return end + 2; } -const char* flecs_script_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out, - char until) -{ - parser->pos = pos; - - int32_t scope_depth = until == '}' ? 1 : 0; - int32_t paren_depth = until == ')' ? 1 : 0; - - const char *start = pos = flecs_scan_whitespace(parser, pos); - char ch; - - for (; (ch = pos[0]); pos ++) { - if (ch == '{') { - if (ch == until) { - break; - } - scope_depth ++; - } else - if (ch == '}') { - scope_depth --; - if (!scope_depth && until == '}') { - break; - } - if (scope_depth < 0) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching { }"); - return NULL; - } - } else - if (ch == '(') { - paren_depth ++; - } else - if (ch == ')') { - paren_depth --; - if (!paren_depth && until == ')') { - break; - } - if (paren_depth < 0) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching ( )"); - return NULL; - } - } else - if (ch == '"') { - pos = flecs_script_skip_string(parser, pos + 1, '"'); - if (!pos) { - return NULL; - } - } else - if (ch == '`') { - pos = flecs_script_skip_string(parser, pos + 1, '`'); - if (!pos) { - return NULL; - } - } else - if (ch == until) { - break; - } - } - - if (!pos[0]) { - if (until == '\0') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected end of script"); - return NULL; - } else - if (until == '\n') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected newline"); - return NULL; - } else { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected '%c'", until); - return NULL; - } - } - - if (scope_depth) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching { }"); - return NULL; - } - if (paren_depth) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching ( )"); - return NULL; - } - - if (until != ']') { - parser->token_cur[0] = '{'; - } else { - parser->token_cur[0] = '['; - } - - int32_t len = flecs_ito(int32_t, pos - start); - ecs_os_memcpy(parser->token_cur + 1, start, len); - out->value = parser->token_cur; - parser->token_cur += len + 1; - - while (isspace(parser->token_cur[-1])) { - parser->token_cur --; - } - - if (until != ']') { - parser->token_cur[0] = '}'; - } else { - parser->token_cur[0] = ']'; - } - - parser->token_cur ++; - - parser->token_cur[0] = '\0'; - parser->token_cur ++; - - return pos; -} - const char* flecs_script_until( ecs_script_parser_t *parser, const char *pos, @@ -59715,26 +59636,38 @@ int flecs_script_eval_id( int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, - const char *expr, + ecs_expr_node_t **expr_ptr, ecs_value_t *value) { - if (!value->type && expr[0] == '{') { - expr ++; - } + ecs_expr_node_t *expr = *expr_ptr; + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; ecs_script_expr_run_desc_t desc = { - .name = v->base.script->pub.name, - .expr = expr, + .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, - .vars = v->vars + .vars = v->vars, + .type = value->type }; - if (!ecs_script_expr_run(v->world, expr, value, &desc)) { - return -1; + if (!expr->type_info) { + if (flecs_script_expr_visit_type(script, expr, &desc)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; + } + } + + if (flecs_script_expr_visit_eval(script, *expr_ptr, &desc, value)) { + goto error; } return 0; +error: + return -1; } static @@ -59979,7 +59912,7 @@ int flecs_script_eval_component( ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); - if (node->expr && node->expr[0]) { + if (node->expr) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { @@ -60035,10 +59968,8 @@ int flecs_script_eval_component( ti->hooks.ctor(value.ptr, 1, ti); } - if (ecs_os_strcmp(node->expr, "{}")) { - if (flecs_script_eval_expr(v, node->expr, &value)) { - return -1; - } + if (flecs_script_eval_expr(v, &node->expr, &value)) { + return -1; } ecs_modified_id(v->world, src, node->id.eval); @@ -60138,7 +60069,7 @@ int flecs_script_eval_default_component( .type = default_type }; - if (flecs_script_eval_expr(v, node->expr, &value)) { + if (flecs_script_eval_expr(v, &node->expr, &value)) { return -1; } @@ -60212,7 +60143,7 @@ int flecs_script_eval_with_component( value->type = node->id.eval; value->ptr = NULL; - if (node->expr && node->expr[0]) { + if (node->expr) { if (!ti) { return -1; } @@ -60224,7 +60155,7 @@ int flecs_script_eval_with_component( ti->hooks.ctor(value->ptr, 1, ti); } - if (flecs_script_eval_expr(v, node->expr, value)) { + if (flecs_script_eval_expr(v, &node->expr, value)) { return -1; } @@ -60373,7 +60304,7 @@ int flecs_script_eval_const( ti->hooks.ctor(var->value.ptr, 1, ti); } - if (flecs_script_eval_expr(v, node->expr, &var->value)) { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); @@ -60383,7 +60314,7 @@ int flecs_script_eval_const( /* We don't know the type yet, so we can't create a storage for it yet. * Run the expression first to deduce the type. */ ecs_value_t value = {0}; - if (flecs_script_eval_expr(v, node->expr, &value)) { + if (flecs_script_eval_expr(v, &node->expr, &value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); @@ -60476,7 +60407,7 @@ int flecs_script_eval_if( ecs_script_if_t *node) { ecs_value_t condval = { .type = 0, .ptr = NULL }; - if (flecs_script_eval_expr(v, node->expr, &condval)) { + if (flecs_script_eval_expr(v, &node->expr, &condval)) { return -1; } @@ -60690,6 +60621,31 @@ void flecs_script_if_free( { flecs_script_scope_free(v, node->if_true); flecs_script_scope_free(v, node->if_false); + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_component_free( + ecs_script_visit_t *v, + ecs_script_component_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_default_component_free( + ecs_script_visit_t *v, + ecs_script_default_component_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_var_node_free( + ecs_script_visit_t *v, + ecs_script_var_node_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); } static @@ -60726,9 +60682,13 @@ int flecs_script_stmt_free( flecs_free_t(a, ecs_script_tag_t, node); break; case EcsAstComponent: + case EcsAstWithComponent: + flecs_script_component_free(v, (ecs_script_component_t*)node); flecs_free_t(a, ecs_script_component_t, node); break; case EcsAstDefaultComponent: + flecs_script_default_component_free(v, + (ecs_script_default_component_t*)node); flecs_free_t(a, ecs_script_default_component_t, node); break; case EcsAstVarComponent: @@ -60740,9 +60700,6 @@ int flecs_script_stmt_free( case EcsAstWithTag: flecs_free_t(a, ecs_script_tag_t, node); break; - case EcsAstWithComponent: - flecs_free_t(a, ecs_script_component_t, node); - break; case EcsAstUsing: flecs_free_t(a, ecs_script_using_t, node); break; @@ -60754,6 +60711,7 @@ int flecs_script_stmt_free( break; case EcsAstProp: case EcsAstConst: + flecs_script_var_node_free(v, (ecs_script_var_node_t*)node); flecs_free_t(a, ecs_script_var_node_t, node); break; } @@ -60864,10 +60822,10 @@ void flecs_script_id_to_str( static void flecs_script_expr_to_str( ecs_script_str_visitor_t *v, - const char *expr) + const ecs_expr_node_t *expr) { if (expr) { - flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); + flecs_scriptbuf_appendstr(v, "TODO"); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -73564,13 +73522,6 @@ static int flecs_expr_precedence[] = { [EcsTokOr] = 12, }; -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - static const char* flecs_script_parse_lhs( ecs_script_parser_t *parser, @@ -73608,14 +73559,15 @@ ecs_entity_t flecs_script_default_lookup( return ecs_lookup(world, name); } -static const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, - ecs_expr_initializer_t *node) + char until, + ecs_expr_initializer_t **node_out) { bool first = true; + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { @@ -73623,10 +73575,15 @@ const char* flecs_script_parse_initializer( if (first) { /* End of initializer */ - LookAhead_1('}', { - pos = lookahead; - EndOfRule; - }) + LookAhead( + case ')': + case '}': { + if (lookahead_token.kind != until) { + Error("expected '%c'", until); + } + node->node.kind = EcsExprEmptyInitializer; + EndOfRule; + }) first = false; } @@ -73657,7 +73614,12 @@ const char* flecs_script_parse_initializer( pos = lookahead; break; } + case '\n': + case ')': case '}': { + if (lookahead_token.kind != until) { + Error("expected '%c'", until); + } EndOfRule; } ) @@ -73671,10 +73633,11 @@ static const char* flecs_script_parse_collection_initializer( ecs_script_parser_t *parser, const char *pos, - ecs_expr_initializer_t *node) + ecs_expr_initializer_t **node_out) { bool first = true; - + + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { @@ -73684,6 +73647,7 @@ const char* flecs_script_parse_collection_initializer( /* End of initializer */ LookAhead_1(']', { pos = lookahead; + node->node.kind = EcsExprEmptyInitializer; EndOfRule; }) @@ -73826,7 +73790,6 @@ const char* flecs_script_parse_rhs( /* Ensures lookahead tokens in token buffer don't get overwritten */ parser->token_keep = parser->token_cur; - break; } ) @@ -73922,8 +73885,8 @@ const char* flecs_script_parse_lhs( } case '{': { - ecs_expr_initializer_t *node = flecs_expr_initializer(parser); - pos = flecs_script_parse_initializer(parser, pos, node); + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_initializer(parser, pos, '}', &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; @@ -73938,15 +73901,15 @@ const char* flecs_script_parse_lhs( } case '[': { - ecs_expr_initializer_t *node = flecs_expr_initializer(parser); - node->is_collection = true; - - pos = flecs_script_parse_collection_initializer(parser, pos, node); + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_collection_initializer(parser, pos, &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } + node->is_collection = true; + Parse_1(']', { break; }) @@ -73968,7 +73931,6 @@ const char* flecs_script_parse_lhs( return NULL; } -static const char* flecs_script_parse_expr( ecs_script_parser_t *parser, const char *pos, @@ -74401,10 +74363,13 @@ int flecs_expr_initializer_eval_dynamic( { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, node->node.type, value); - ecs_meta_push(&cur); + + if (ecs_meta_push(&cur)) { + goto error; + } - ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; @@ -74446,7 +74411,9 @@ int flecs_expr_initializer_eval_dynamic( flecs_expr_value_free(script, &expr); } - ecs_meta_pop(&cur); + if (ecs_meta_pop(&cur)) { + goto error; + } return 0; error: @@ -74545,13 +74512,22 @@ int flecs_expr_variable_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) { - const ecs_script_var_t *var = node->var; + const ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->name); + if (!var) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->name); + goto error; + } + /* Should've been populated by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; out->can_move = false; return 0; +error: + return -1; } static @@ -74724,6 +74700,8 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprEmptyInitializer: + break; case EcsExprInitializer: if (flecs_expr_initializer_visit_eval( script, (ecs_expr_initializer_t*)node, desc, out)) @@ -75218,6 +75196,7 @@ int flecs_script_expr_visit_fold( case EcsExprValue: break; case EcsExprInitializer: + case EcsExprEmptyInitializer: if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { goto error; } @@ -75370,6 +75349,7 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_val_t, node); break; case EcsExprInitializer: + case EcsExprEmptyInitializer: flecs_expr_initializer_visit_free( script, (ecs_expr_initializer_t*)node); flecs_free_t(a, ecs_expr_initializer_t, node); @@ -76165,7 +76145,6 @@ int flecs_expr_variable_visit_type( } node->node.type = var->value.type; - node->var = var; *cur = ecs_meta_cursor(script->world, var->value.type, NULL); @@ -76286,7 +76265,9 @@ int flecs_expr_member_visit_type( goto error; } - ecs_meta_push(cur); /* { */ + if (ecs_meta_push(cur)) { + goto error; + } int prev_log = ecs_log_set_level(-4); if (ecs_meta_dotmember(cur, node->member_name)) { @@ -76411,6 +76392,8 @@ int flecs_script_expr_visit_type_priv( case EcsExprValue: /* Value types are assigned by the AST */ break; + case EcsExprEmptyInitializer: + break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( script, (ecs_expr_initializer_t*)node, cur, desc)) @@ -76492,6 +76475,14 @@ int flecs_script_expr_visit_type( ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc) { + if (node->kind == EcsExprEmptyInitializer) { + node->type = desc->type; + if (node->type) { + node->type_info = ecs_get_type_info(script->world, node->type); + } + return 0; + } + if (desc->type) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, desc->type, NULL); diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index e0e3f74292..5b5c39895c 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -181,6 +181,7 @@ int flecs_meta_cursor_push_type( if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); + ecs_abort(ECS_INTERNAL_ERROR, NULL); ecs_os_free(str); return -1; } diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index 42dd8a76ac..58126678a9 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -54,14 +54,14 @@ typedef struct ecs_script_tag_t { typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; - const char *expr; + ecs_expr_node_t *expr; ecs_value_t eval; bool is_collection; } ecs_script_component_t; typedef struct ecs_script_default_component_t { ecs_script_node_t node; - const char *expr; + ecs_expr_node_t *expr; ecs_value_t eval; } ecs_script_default_component_t; @@ -127,14 +127,14 @@ typedef struct ecs_script_var_node_t { ecs_script_node_t node; const char *name; const char *type; - const char *expr; + ecs_expr_node_t *expr; } ecs_script_var_node_t; typedef struct ecs_script_if_t { ecs_script_node_t node; ecs_script_scope_t *if_true; ecs_script_scope_t *if_false; - const char *expr; + ecs_expr_node_t *expr; } ecs_script_if_t; #define ecs_script_node(kind, node)\ diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index c324ff531d..f88a669710 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -11,6 +11,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInitializer, + EcsExprEmptyInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, @@ -81,7 +82,6 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; - ecs_script_var_t *var; } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 80d92d5aff..755f0e6d75 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -32,6 +32,18 @@ int flecs_value_unary( ecs_value_t *out, ecs_script_token_kind_t operator); +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); + +const char* flecs_script_parse_initializer( + ecs_script_parser_t *parser, + const char *pos, + char until, + ecs_expr_initializer_t **node_out); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index cb5562a55a..8a23e79717 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -35,13 +35,6 @@ static int flecs_expr_precedence[] = { [EcsTokOr] = 12, }; -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - static const char* flecs_script_parse_lhs( ecs_script_parser_t *parser, @@ -79,14 +72,15 @@ ecs_entity_t flecs_script_default_lookup( return ecs_lookup(world, name); } -static const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, - ecs_expr_initializer_t *node) + char until, + ecs_expr_initializer_t **node_out) { bool first = true; + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { @@ -94,10 +88,15 @@ const char* flecs_script_parse_initializer( if (first) { /* End of initializer */ - LookAhead_1('}', { - pos = lookahead; - EndOfRule; - }) + LookAhead( + case ')': + case '}': { + if (lookahead_token.kind != until) { + Error("expected '%c'", until); + } + node->node.kind = EcsExprEmptyInitializer; + EndOfRule; + }) first = false; } @@ -128,7 +127,12 @@ const char* flecs_script_parse_initializer( pos = lookahead; break; } + case '\n': + case ')': case '}': { + if (lookahead_token.kind != until) { + Error("expected '%c'", until); + } EndOfRule; } ) @@ -142,10 +146,11 @@ static const char* flecs_script_parse_collection_initializer( ecs_script_parser_t *parser, const char *pos, - ecs_expr_initializer_t *node) + ecs_expr_initializer_t **node_out) { bool first = true; - + + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { @@ -155,6 +160,7 @@ const char* flecs_script_parse_collection_initializer( /* End of initializer */ LookAhead_1(']', { pos = lookahead; + node->node.kind = EcsExprEmptyInitializer; EndOfRule; }) @@ -297,7 +303,6 @@ const char* flecs_script_parse_rhs( /* Ensures lookahead tokens in token buffer don't get overwritten */ parser->token_keep = parser->token_cur; - break; } ) @@ -393,8 +398,8 @@ const char* flecs_script_parse_lhs( } case '{': { - ecs_expr_initializer_t *node = flecs_expr_initializer(parser); - pos = flecs_script_parse_initializer(parser, pos, node); + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_initializer(parser, pos, '}', &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; @@ -409,15 +414,15 @@ const char* flecs_script_parse_lhs( } case '[': { - ecs_expr_initializer_t *node = flecs_expr_initializer(parser); - node->is_collection = true; - - pos = flecs_script_parse_collection_initializer(parser, pos, node); + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_collection_initializer(parser, pos, &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } + node->is_collection = true; + Parse_1(']', { break; }) @@ -439,7 +444,6 @@ const char* flecs_script_parse_lhs( return NULL; } -static const char* flecs_script_parse_expr( ecs_script_parser_t *parser, const char *pos, diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 0c77506ead..2f0545a612 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -148,10 +148,13 @@ int flecs_expr_initializer_eval_dynamic( { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, node->node.type, value); - ecs_meta_push(&cur); + + if (ecs_meta_push(&cur)) { + goto error; + } - ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; @@ -193,7 +196,9 @@ int flecs_expr_initializer_eval_dynamic( flecs_expr_value_free(script, &expr); } - ecs_meta_pop(&cur); + if (ecs_meta_pop(&cur)) { + goto error; + } return 0; error: @@ -292,13 +297,22 @@ int flecs_expr_variable_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) { - const ecs_script_var_t *var = node->var; + const ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->name); + if (!var) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->name); + goto error; + } + /* Should've been populated by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; out->can_move = false; return 0; +error: + return -1; } static @@ -471,6 +485,8 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprEmptyInitializer: + break; case EcsExprInitializer: if (flecs_expr_initializer_visit_eval( script, (ecs_expr_initializer_t*)node, desc, out)) diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index be4abc352d..52e73ea93e 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -379,6 +379,7 @@ int flecs_script_expr_visit_fold( case EcsExprValue: break; case EcsExprInitializer: + case EcsExprEmptyInitializer: if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { goto error; } diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 2bb5c920f0..588c147d79 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -101,6 +101,7 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_val_t, node); break; case EcsExprInitializer: + case EcsExprEmptyInitializer: flecs_expr_initializer_visit_free( script, (ecs_expr_initializer_t*)node); flecs_free_t(a, ecs_expr_initializer_t, node); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 41f11b64c2..8e57d6afdb 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -477,7 +477,6 @@ int flecs_expr_variable_visit_type( } node->node.type = var->value.type; - node->var = var; *cur = ecs_meta_cursor(script->world, var->value.type, NULL); @@ -598,7 +597,9 @@ int flecs_expr_member_visit_type( goto error; } - ecs_meta_push(cur); /* { */ + if (ecs_meta_push(cur)) { + goto error; + } int prev_log = ecs_log_set_level(-4); if (ecs_meta_dotmember(cur, node->member_name)) { @@ -723,6 +724,8 @@ int flecs_script_expr_visit_type_priv( case EcsExprValue: /* Value types are assigned by the AST */ break; + case EcsExprEmptyInitializer: + break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( script, (ecs_expr_initializer_t*)node, cur, desc)) @@ -804,6 +807,14 @@ int flecs_script_expr_visit_type( ecs_expr_node_t *node, const ecs_script_expr_run_desc_t *desc) { + if (node->kind == EcsExprEmptyInitializer) { + node->type = desc->type; + if (node->type) { + node->type_info = ecs_get_type_info(script->world, node->type); + } + return 0; + } + if (desc->type) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, desc->type, NULL); diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 9812840c82..6fe840c9cd 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -111,11 +111,11 @@ const char* flecs_script_with_expr( pos = lookahead; // Position ( expr ) - Expr(')', + Initializer(')', ecs_script_component_t *component = flecs_script_insert_component(parser, Token(0)); component->node.kind = EcsAstWithComponent; - component->expr = Token(2); + component->expr = INITIALIZER; EndOfRule; ) ) @@ -142,12 +142,12 @@ const char* flecs_script_with_expr( pos = lookahead; // (Eats, Apples) ( expr ) - Expr(')', + Initializer(')', ecs_script_component_t *component = flecs_script_insert_pair_component(parser, Token(1), Token(3)); component->node.kind = EcsAstWithComponent; - component->expr = Token(6); + component->expr = INITIALIZER; EndOfRule; ) ) @@ -205,13 +205,13 @@ const char* flecs_script_paren_expr( { ParserBegin; - Expr(')', + Initializer(')', entity->kind_w_expr = true; Scope(entity->scope, ecs_script_component_t *component = flecs_script_insert_component(parser, kind); - component->expr = Token(0); + component->expr = INITIALIZER; ) Parse( @@ -406,15 +406,15 @@ prop_var: { LookAhead_1('{', // prop color = Color: {expr} pos = lookahead; - Expr('}', - var->expr = Token(6); + Initializer('}', + var->expr = INITIALIZER; EndOfRule; ) ) // prop color = Color : expr\n - Expr('\n', - var->expr = Token(5); + Initializer('\n', + var->expr = INITIALIZER; EndOfRule; ) ) @@ -441,22 +441,22 @@ const_var: { LookAhead_1('{', // const color = Color: {expr} pos = lookahead; - Expr('}', - var->expr = Token(6); + Initializer('}', + var->expr = INITIALIZER; EndOfRule; ) ) // const color = Color: expr\n - Expr('\n', - var->expr = Token(5); + Initializer('\n', + var->expr = INITIALIZER; EndOfRule; ) ) // const PI = expr\n Expr('\n', - var->expr = Token(3); + var->expr = EXPR; EndOfRule; ) } @@ -466,26 +466,29 @@ const_var: { // if if_stmt: { - // if expr { - Expr('{', - ecs_script_if_t *stmt = flecs_script_insert_if(parser); - stmt->expr = Token(1); - pos = flecs_script_scope(parser, stmt->if_true, pos); - if (!pos) { - goto error; - } + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } - // if expr { } else - LookAhead_1(EcsTokKeywordElse, - pos = lookahead; + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; - // if expr { } else { - Parse_1('{', - return flecs_script_scope(parser, stmt->if_false, pos); + // if expr { } else { + Parse_1('{', + return flecs_script_scope(parser, stmt->if_false, pos); + ) ) - ) - EndOfRule; + EndOfRule; + }); ) } @@ -517,11 +520,11 @@ pair: { // (Eats, Apples): { Parse_1('{', // (Eats, Apples): { expr } - Expr('}', + Initializer('}', ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); - comp->expr = Token(7); + comp->expr = INITIALIZER; EndOfRule; ) ) @@ -568,7 +571,7 @@ identifier_flag: { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(3), Token(5)); - comp->expr = Token(9); + comp->expr = EXPR; EndOfRule; }) ) @@ -595,7 +598,7 @@ identifier_flag: { Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(2)); - comp->expr = Token(5); + comp->expr = EXPR; EndOfRule; }) ) @@ -661,11 +664,11 @@ identifier_assign: { // x = Position: { Parse_1('{', { // x = Position: {expr} - Expr('}', + Expr('}', Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(2)); - comp->expr = Token(5); + comp->expr = EXPR; ) // x = Position: {expr}\n @@ -678,11 +681,11 @@ identifier_assign: { ) // x = f32\n - Expr('\n', + Initializer('\n', Scope(entity->scope, ecs_script_default_component_t *comp = flecs_script_insert_default_component(parser); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) EndOfRule; @@ -735,7 +738,7 @@ identifier_identifier: { // SpaceShip( identifier_paren: { // SpaceShip() - Expr(')', + Initializer(')', Parse( // SpaceShip(expr)\n EcsTokEndOfStatement: { @@ -745,7 +748,7 @@ identifier_paren: { Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) EndOfRule; @@ -759,7 +762,7 @@ identifier_paren: { Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); - comp->expr = Token(2); + comp->expr = INITIALIZER; ) return flecs_script_scope(parser, entity->scope, pos); @@ -775,7 +778,7 @@ component_expr_scope: { Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); - comp->expr = Token(3); + comp->expr = EXPR; EndOfRule; }) } @@ -786,7 +789,7 @@ component_expr_collection: { Expr(']', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); - comp->expr = Token(3); + comp->expr = EXPR; comp->is_collection = true; EndOfRule; }) diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index 07a6facf9b..67829ccd74 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -65,17 +65,46 @@ /* Parse expression */ #define Expr(until, ...)\ {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ + ecs_expr_node_t *EXPR = NULL;\ + if (until == '}' || until == ']') {\ + pos --;\ + if (until == '}') {\ + ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ + } else if (until == ']') {\ + ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ + }\ + }\ + parser->significant_newline = false;\ + if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ + goto error;\ + }\ + parser->significant_newline = true;\ + __VA_ARGS__\ + } + +/* Parse initializer */ +#define Initializer(until, ...)\ + {\ + ecs_expr_node_t *INITIALIZER = NULL;\ + ecs_expr_initializer_t *_initializer = NULL;\ + if (until != '\n') {\ + parser->significant_newline = false;\ + }\ + if (!(pos = flecs_script_parse_initializer(\ + parser, pos, until, &_initializer))) \ + {\ + flecs_script_expr_visit_free(\ + &parser->script->pub, (ecs_expr_node_t*)_initializer);\ goto error;\ }\ - if (!t->value[0] && (until == '\n' || until == '{')) {\ - pos ++;\ - Error("empty expression");\ + parser->significant_newline = true;\ + if (pos[0] != until) {\ + Error("expected '%c'", until);\ }\ - }\ - Parse_1(until, __VA_ARGS__) + INITIALIZER = (ecs_expr_node_t*)_initializer;\ + pos ++;\ + __VA_ARGS__\ + } /* Parse token until character */ #define Until(until, ...)\ diff --git a/src/addons/script/template.c b/src/addons/script/template.c index db0f8903d5..90d94a1b8b 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -123,7 +123,8 @@ void flecs_script_template_on_set( const ecs_member_t *member = &members[m]; /* Assign template property from template instance */ - ecs_script_var_t *var = ecs_script_vars_declare(vars, member->name); + ecs_script_var_t *var = ecs_script_vars_declare( + vars, member->name); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } @@ -139,7 +140,8 @@ void flecs_script_template_on_set( ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { - ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_poly_is(world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } @@ -195,7 +197,7 @@ int flecs_script_template_eval_prop( var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); var->type_info = ti; - if (flecs_script_eval_expr(v, node->expr, &var->value)) { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index a35318ce5d..9a6e551418 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -420,126 +420,6 @@ const char* flecs_script_multiline_string( return end + 2; } -const char* flecs_script_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out, - char until) -{ - parser->pos = pos; - - int32_t scope_depth = until == '}' ? 1 : 0; - int32_t paren_depth = until == ')' ? 1 : 0; - - const char *start = pos = flecs_scan_whitespace(parser, pos); - char ch; - - for (; (ch = pos[0]); pos ++) { - if (ch == '{') { - if (ch == until) { - break; - } - scope_depth ++; - } else - if (ch == '}') { - scope_depth --; - if (!scope_depth && until == '}') { - break; - } - if (scope_depth < 0) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching { }"); - return NULL; - } - } else - if (ch == '(') { - paren_depth ++; - } else - if (ch == ')') { - paren_depth --; - if (!paren_depth && until == ')') { - break; - } - if (paren_depth < 0) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching ( )"); - return NULL; - } - } else - if (ch == '"') { - pos = flecs_script_skip_string(parser, pos + 1, '"'); - if (!pos) { - return NULL; - } - } else - if (ch == '`') { - pos = flecs_script_skip_string(parser, pos + 1, '`'); - if (!pos) { - return NULL; - } - } else - if (ch == until) { - break; - } - } - - if (!pos[0]) { - if (until == '\0') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected end of script"); - return NULL; - } else - if (until == '\n') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected newline"); - return NULL; - } else { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected '%c'", until); - return NULL; - } - } - - if (scope_depth) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching { }"); - return NULL; - } - if (paren_depth) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "mismatching ( )"); - return NULL; - } - - if (until != ']') { - parser->token_cur[0] = '{'; - } else { - parser->token_cur[0] = '['; - } - - int32_t len = flecs_ito(int32_t, pos - start); - ecs_os_memcpy(parser->token_cur + 1, start, len); - out->value = parser->token_cur; - parser->token_cur += len + 1; - - while (isspace(parser->token_cur[-1])) { - parser->token_cur --; - } - - if (until != ']') { - parser->token_cur[0] = '}'; - } else { - parser->token_cur[0] = ']'; - } - - parser->token_cur ++; - - parser->token_cur[0] = '\0'; - parser->token_cur ++; - - return pos; -} - const char* flecs_script_until( ecs_script_parser_t *parser, const char *pos, diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index d457b999d4..0091100949 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -71,12 +71,6 @@ typedef struct ecs_script_tokenizer_t { ecs_script_token_t *tokens; } ecs_script_tokenizer_t; -const char* flecs_script_expr( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - char until); - const char* flecs_script_until( ecs_script_parser_t *parser, const char *ptr, diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index c8bcd894ff..6ee4ed8e08 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -333,26 +333,38 @@ int flecs_script_eval_id( int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, - const char *expr, + ecs_expr_node_t **expr_ptr, ecs_value_t *value) { - if (!value->type && expr[0] == '{') { - expr ++; - } + ecs_expr_node_t *expr = *expr_ptr; + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; ecs_script_expr_run_desc_t desc = { - .name = v->base.script->pub.name, - .expr = expr, + .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, - .vars = v->vars + .vars = v->vars, + .type = value->type }; - if (!ecs_script_expr_run(v->world, expr, value, &desc)) { - return -1; + if (!expr->type_info) { + if (flecs_script_expr_visit_type(script, expr, &desc)) { + goto error; + } + + if (flecs_script_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; + } + } + + if (flecs_script_expr_visit_eval(script, *expr_ptr, &desc, value)) { + goto error; } return 0; +error: + return -1; } static @@ -597,7 +609,7 @@ int flecs_script_eval_component( ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); - if (node->expr && node->expr[0]) { + if (node->expr) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { @@ -653,10 +665,8 @@ int flecs_script_eval_component( ti->hooks.ctor(value.ptr, 1, ti); } - if (ecs_os_strcmp(node->expr, "{}")) { - if (flecs_script_eval_expr(v, node->expr, &value)) { - return -1; - } + if (flecs_script_eval_expr(v, &node->expr, &value)) { + return -1; } ecs_modified_id(v->world, src, node->id.eval); @@ -756,7 +766,7 @@ int flecs_script_eval_default_component( .type = default_type }; - if (flecs_script_eval_expr(v, node->expr, &value)) { + if (flecs_script_eval_expr(v, &node->expr, &value)) { return -1; } @@ -830,7 +840,7 @@ int flecs_script_eval_with_component( value->type = node->id.eval; value->ptr = NULL; - if (node->expr && node->expr[0]) { + if (node->expr) { if (!ti) { return -1; } @@ -842,7 +852,7 @@ int flecs_script_eval_with_component( ti->hooks.ctor(value->ptr, 1, ti); } - if (flecs_script_eval_expr(v, node->expr, value)) { + if (flecs_script_eval_expr(v, &node->expr, value)) { return -1; } @@ -991,7 +1001,7 @@ int flecs_script_eval_const( ti->hooks.ctor(var->value.ptr, 1, ti); } - if (flecs_script_eval_expr(v, node->expr, &var->value)) { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); @@ -1001,7 +1011,7 @@ int flecs_script_eval_const( /* We don't know the type yet, so we can't create a storage for it yet. * Run the expression first to deduce the type. */ ecs_value_t value = {0}; - if (flecs_script_eval_expr(v, node->expr, &value)) { + if (flecs_script_eval_expr(v, &node->expr, &value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); @@ -1094,7 +1104,7 @@ int flecs_script_eval_if( ecs_script_if_t *node) { ecs_value_t condval = { .type = 0, .ptr = NULL }; - if (flecs_script_eval_expr(v, node->expr, &condval)) { + if (flecs_script_eval_expr(v, &node->expr, &condval)) { return -1; } diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index 2e7e9336a7..a493be5708 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -49,7 +49,7 @@ const ecs_type_info_t* flecs_script_get_type_info( int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, - const char *expr, + ecs_expr_node_t **expr_ptr, ecs_value_t *value); int flecs_script_eval_template( diff --git a/src/addons/script/visit_free.c b/src/addons/script/visit_free.c index ec2f3c5f52..acca690dd9 100644 --- a/src/addons/script/visit_free.c +++ b/src/addons/script/visit_free.c @@ -58,6 +58,31 @@ void flecs_script_if_free( { flecs_script_scope_free(v, node->if_true); flecs_script_scope_free(v, node->if_false); + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_component_free( + ecs_script_visit_t *v, + ecs_script_component_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_default_component_free( + ecs_script_visit_t *v, + ecs_script_default_component_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); +} + +static +void flecs_script_var_node_free( + ecs_script_visit_t *v, + ecs_script_var_node_t *node) +{ + flecs_script_expr_visit_free(&v->script->pub, node->expr); } static @@ -94,9 +119,13 @@ int flecs_script_stmt_free( flecs_free_t(a, ecs_script_tag_t, node); break; case EcsAstComponent: + case EcsAstWithComponent: + flecs_script_component_free(v, (ecs_script_component_t*)node); flecs_free_t(a, ecs_script_component_t, node); break; case EcsAstDefaultComponent: + flecs_script_default_component_free(v, + (ecs_script_default_component_t*)node); flecs_free_t(a, ecs_script_default_component_t, node); break; case EcsAstVarComponent: @@ -108,9 +137,6 @@ int flecs_script_stmt_free( case EcsAstWithTag: flecs_free_t(a, ecs_script_tag_t, node); break; - case EcsAstWithComponent: - flecs_free_t(a, ecs_script_component_t, node); - break; case EcsAstUsing: flecs_free_t(a, ecs_script_using_t, node); break; @@ -122,6 +148,7 @@ int flecs_script_stmt_free( break; case EcsAstProp: case EcsAstConst: + flecs_script_var_node_free(v, (ecs_script_var_node_t*)node); flecs_free_t(a, ecs_script_var_node_t, node); break; } diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 0c7d6b55de..dc5485ff2b 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -82,10 +82,10 @@ void flecs_script_id_to_str( static void flecs_script_expr_to_str( ecs_script_str_visitor_t *v, - const char *expr) + const ecs_expr_node_t *expr) { if (expr) { - flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); + flecs_scriptbuf_appendstr(v, "TODO"); } else { flecs_scriptbuf_appendstr(v, "{}"); } diff --git a/test/script/project.json b/test/script/project.json index b1a4f8887a..4a780b6812 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -548,7 +548,9 @@ "remainder_after_binary", "remainder_after_parens", "remainder_after_initializer", - "remainder_after_collection_initializer" + "remainder_after_collection_initializer", + "space_at_start", + "newline_at_start" ] }, { "id": "Vars", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index d835eb04c1..1fd889ad36 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -4301,3 +4301,29 @@ void Expr_remainder_after_collection_initializer(void) { ecs_fini(world); } + +void Expr_space_at_start(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = false; + const char *ptr = ecs_script_expr_run(world, " 10 + 20", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 30); + + ecs_fini(world); +} + +void Expr_newline_at_start(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = false; + const char *ptr = ecs_script_expr_run(world, "\n10 + 20", + &ecs_value_ptr(ecs_i32_t, &v), NULL); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 30); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 75cfd1303b..e7fe097ee0 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -539,6 +539,8 @@ void Expr_remainder_after_binary(void); void Expr_remainder_after_parens(void); void Expr_remainder_after_initializer(void); void Expr_remainder_after_collection_initializer(void); +void Expr_space_at_start(void); +void Expr_newline_at_start(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -2806,6 +2808,14 @@ bake_test_case Expr_testcases[] = { { "remainder_after_collection_initializer", Expr_remainder_after_collection_initializer + }, + { + "space_at_start", + Expr_space_at_start + }, + { + "newline_at_start", + Expr_newline_at_start } }; @@ -3465,7 +3475,7 @@ static bake_test_suite suites[] = { "Expr", NULL, NULL, - 172, + 174, Expr_testcases }, { From 964e9f1e1582a19adfec9dd32f7b7187e79a4cb2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 4 Dec 2024 08:06:53 +0000 Subject: [PATCH 29/83] Fix warnings --- distr/flecs.c | 180 ++++++++++++++++++++++---- src/addons/script/builtin_functions.c | 8 ++ src/addons/script/expr/expr.h | 5 + src/addons/script/expr/visit_eval.c | 7 +- src/addons/script/expr/visit_to_str.c | 67 +++++++--- src/addons/script/expr/visit_type.c | 91 +++++++++++++ src/addons/script/visit_to_str.c | 2 +- 7 files changed, 316 insertions(+), 44 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index f463c70ec0..363b789d5f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5132,6 +5132,11 @@ const char* flecs_script_parse_initializer( char until, ecs_expr_initializer_t **node_out); +void flecs_script_expr_to_str_buf( + const ecs_world_t *world, + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ @@ -54926,6 +54931,7 @@ void flecs_meta_entity_name( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } @@ -54937,6 +54943,7 @@ void flecs_meta_entity_path( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_get_path(ctx->world, entity); } @@ -54948,6 +54955,7 @@ void flecs_meta_entity_parent( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } @@ -54961,10 +54969,12 @@ void flecs_meta_entity_doc_name( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); } +static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { @@ -54981,11 +54991,14 @@ void flecs_script_register_builtin_doc_functions( } #else + +static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { (void)world; } + #endif void flecs_script_register_builtin_functions( @@ -60825,7 +60838,7 @@ void flecs_script_expr_to_str( const ecs_expr_node_t *expr) { if (expr) { - flecs_scriptbuf_appendstr(v, "TODO"); + flecs_script_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -74293,6 +74306,8 @@ int flecs_expr_value_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) { + (void)script; + (void)desc; out->value.type = node->node.type; out->value.ptr = node->ptr; out->can_move = false; @@ -74631,7 +74646,7 @@ int flecs_expr_element_visit_eval( goto error; } - int32_t index_value = *(int64_t*)index.value.ptr; + int64_t index_value = *(int64_t*)index.value.ptr; out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); out->value.type = node->node.type; @@ -74665,7 +74680,8 @@ int flecs_expr_component_visit_eval( ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; out->value.type = node->node.type; - out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + out->value.ptr = ECS_CONST_CAST(void*, + ecs_get_id(script->world, entity, component)); out->can_move = false; if (!out->value.ptr) { @@ -75411,10 +75427,12 @@ typedef struct ecs_expr_str_visitor_t { bool newline; } ecs_expr_str_visitor_t; +static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); +static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_val_t *node) @@ -75423,6 +75441,7 @@ int flecs_expr_value_to_str( v->world, node->node.type, node->ptr, v->buf); } +static int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) @@ -75438,6 +75457,7 @@ int flecs_expr_unary_to_str( return -1; } +static int flecs_expr_initializer_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_initializer_t *node) @@ -75469,6 +75489,7 @@ int flecs_expr_initializer_to_str( return -1; } +static int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -75496,6 +75517,7 @@ int flecs_expr_binary_to_str( return -1; } +static int flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) @@ -75505,6 +75527,7 @@ int flecs_expr_identifier_to_str( return 0; } +static int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) @@ -75514,6 +75537,7 @@ int flecs_expr_variable_to_str( return 0; } +static int flecs_expr_member_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_member_t *node) @@ -75527,6 +75551,7 @@ int flecs_expr_member_to_str( return 0; } +static int flecs_expr_function_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_function_t *node) @@ -75543,6 +75568,7 @@ int flecs_expr_function_to_str( return 0; } +static int flecs_expr_element_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_element_t *node) @@ -75559,6 +75585,7 @@ int flecs_expr_element_to_str( return 0; } +static int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_cast_t *node) @@ -75566,6 +75593,7 @@ int flecs_expr_cast_to_str( return flecs_expr_node_to_str(v, node->expr); } +static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) @@ -75587,53 +75615,74 @@ int flecs_expr_node_to_str( switch(node->kind) { case EcsExprValue: - if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + if (flecs_expr_value_to_str(v, + (const ecs_expr_val_t*)node)) + { goto error; } break; case EcsExprInitializer: - if (flecs_expr_initializer_to_str(v, (ecs_expr_initializer_t*)node)) { + case EcsExprEmptyInitializer: + if (flecs_expr_initializer_to_str(v, + (const ecs_expr_initializer_t*)node)) + { goto error; } break; case EcsExprUnary: - if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + if (flecs_expr_unary_to_str(v, + (const ecs_expr_unary_t*)node)) + { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + if (flecs_expr_binary_to_str(v, + (const ecs_expr_binary_t*)node)) + { goto error; } break; case EcsExprIdentifier: - if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_to_str(v, + (const ecs_expr_identifier_t*)node)) + { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_to_str(v, + (const ecs_expr_variable_t*)node)) + { goto error; } break; case EcsExprFunction: - if (flecs_expr_function_to_str(v, (ecs_expr_function_t*)node)) { + if (flecs_expr_function_to_str(v, + (const ecs_expr_function_t*)node)) + { goto error; } break; case EcsExprMember: - if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_to_str(v, + (const ecs_expr_member_t*)node)) + { goto error; } break; case EcsExprElement: case EcsExprComponent: - if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_to_str(v, + (const ecs_expr_element_t*)node)) + { goto error; } break; case EcsExprCast: - if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + if (flecs_expr_cast_to_str(v, + (const ecs_expr_cast_t*)node)) + { goto error; } break; @@ -75651,19 +75700,15 @@ int flecs_expr_node_to_str( return -1; } -FLECS_API -char* ecs_script_expr_to_str( +void flecs_script_expr_to_str_buf( const ecs_world_t *world, - const ecs_expr_node_t *expr) + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + ecs_expr_str_visitor_t v = { .world = world, .buf = buf }; if (flecs_expr_node_to_str(&v, expr)) { - ecs_strbuf_reset(&buf); - return NULL; + ecs_strbuf_reset(buf); } - - return ecs_strbuf_get(&buf); } #endif @@ -75706,6 +75751,36 @@ bool flecs_expr_operator_is_equality( case EcsTokBitwiseAnd: case EcsTokBitwiseOr: return false; + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } @@ -75827,6 +75902,36 @@ bool flecs_expr_oper_valid_for_type( (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } @@ -75872,6 +75977,36 @@ int flecs_expr_type_for_oper( case EcsTokSub: case EcsTokMul: break; + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } @@ -76119,6 +76254,7 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { + (void)desc; if (cur->valid) { node->node.type = ecs_meta_get_type(cur); } else { diff --git a/src/addons/script/builtin_functions.c b/src/addons/script/builtin_functions.c index 0f27ff6ba8..53099ab14d 100644 --- a/src/addons/script/builtin_functions.c +++ b/src/addons/script/builtin_functions.c @@ -15,6 +15,7 @@ void flecs_meta_entity_name( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } @@ -26,6 +27,7 @@ void flecs_meta_entity_path( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_get_path(ctx->world, entity); } @@ -37,6 +39,7 @@ void flecs_meta_entity_parent( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } @@ -50,10 +53,12 @@ void flecs_meta_entity_doc_name( const ecs_value_t *argv, ecs_value_t *result) { + (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); } +static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { @@ -70,11 +75,14 @@ void flecs_script_register_builtin_doc_functions( } #else + +static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { (void)world; } + #endif void flecs_script_register_builtin_functions( diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 755f0e6d75..90b531ba2c 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -44,6 +44,11 @@ const char* flecs_script_parse_initializer( char until, ecs_expr_initializer_t **node_out); +void flecs_script_expr_to_str_buf( + const ecs_world_t *world, + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 2f0545a612..32382f0552 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -78,6 +78,8 @@ int flecs_expr_value_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_eval_value_t *out) { + (void)script; + (void)desc; out->value.type = node->node.type; out->value.ptr = node->ptr; out->can_move = false; @@ -416,7 +418,7 @@ int flecs_expr_element_visit_eval( goto error; } - int32_t index_value = *(int64_t*)index.value.ptr; + int64_t index_value = *(int64_t*)index.value.ptr; out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); out->value.type = node->node.type; @@ -450,7 +452,8 @@ int flecs_expr_component_visit_eval( ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; out->value.type = node->node.type; - out->value.ptr = (void*)ecs_get_id(script->world, entity, component); + out->value.ptr = ECS_CONST_CAST(void*, + ecs_get_id(script->world, entity, component)); out->can_move = false; if (!out->value.ptr) { diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index b40d560623..5794b21086 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -15,10 +15,12 @@ typedef struct ecs_expr_str_visitor_t { bool newline; } ecs_expr_str_visitor_t; +static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); +static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_val_t *node) @@ -27,6 +29,7 @@ int flecs_expr_value_to_str( v->world, node->node.type, node->ptr, v->buf); } +static int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) @@ -42,6 +45,7 @@ int flecs_expr_unary_to_str( return -1; } +static int flecs_expr_initializer_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_initializer_t *node) @@ -73,6 +77,7 @@ int flecs_expr_initializer_to_str( return -1; } +static int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) @@ -100,6 +105,7 @@ int flecs_expr_binary_to_str( return -1; } +static int flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) @@ -109,6 +115,7 @@ int flecs_expr_identifier_to_str( return 0; } +static int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) @@ -118,6 +125,7 @@ int flecs_expr_variable_to_str( return 0; } +static int flecs_expr_member_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_member_t *node) @@ -131,6 +139,7 @@ int flecs_expr_member_to_str( return 0; } +static int flecs_expr_function_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_function_t *node) @@ -147,6 +156,7 @@ int flecs_expr_function_to_str( return 0; } +static int flecs_expr_element_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_element_t *node) @@ -163,6 +173,7 @@ int flecs_expr_element_to_str( return 0; } +static int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_cast_t *node) @@ -170,6 +181,7 @@ int flecs_expr_cast_to_str( return flecs_expr_node_to_str(v, node->expr); } +static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) @@ -191,53 +203,74 @@ int flecs_expr_node_to_str( switch(node->kind) { case EcsExprValue: - if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + if (flecs_expr_value_to_str(v, + (const ecs_expr_val_t*)node)) + { goto error; } break; case EcsExprInitializer: - if (flecs_expr_initializer_to_str(v, (ecs_expr_initializer_t*)node)) { + case EcsExprEmptyInitializer: + if (flecs_expr_initializer_to_str(v, + (const ecs_expr_initializer_t*)node)) + { goto error; } break; case EcsExprUnary: - if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + if (flecs_expr_unary_to_str(v, + (const ecs_expr_unary_t*)node)) + { goto error; } break; case EcsExprBinary: - if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + if (flecs_expr_binary_to_str(v, + (const ecs_expr_binary_t*)node)) + { goto error; } break; case EcsExprIdentifier: - if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + if (flecs_expr_identifier_to_str(v, + (const ecs_expr_identifier_t*)node)) + { goto error; } break; case EcsExprVariable: - if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + if (flecs_expr_variable_to_str(v, + (const ecs_expr_variable_t*)node)) + { goto error; } break; case EcsExprFunction: - if (flecs_expr_function_to_str(v, (ecs_expr_function_t*)node)) { + if (flecs_expr_function_to_str(v, + (const ecs_expr_function_t*)node)) + { goto error; } break; case EcsExprMember: - if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + if (flecs_expr_member_to_str(v, + (const ecs_expr_member_t*)node)) + { goto error; } break; case EcsExprElement: case EcsExprComponent: - if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + if (flecs_expr_element_to_str(v, + (const ecs_expr_element_t*)node)) + { goto error; } break; case EcsExprCast: - if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + if (flecs_expr_cast_to_str(v, + (const ecs_expr_cast_t*)node)) + { goto error; } break; @@ -255,19 +288,15 @@ int flecs_expr_node_to_str( return -1; } -FLECS_API -char* ecs_script_expr_to_str( +void flecs_script_expr_to_str_buf( const ecs_world_t *world, - const ecs_expr_node_t *expr) + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + ecs_expr_str_visitor_t v = { .world = world, .buf = buf }; if (flecs_expr_node_to_str(&v, expr)) { - ecs_strbuf_reset(&buf); - return NULL; + ecs_strbuf_reset(buf); } - - return ecs_strbuf_get(&buf); } #endif diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 8e57d6afdb..f40535ff0d 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -38,6 +38,36 @@ bool flecs_expr_operator_is_equality( case EcsTokBitwiseAnd: case EcsTokBitwiseOr: return false; + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } @@ -159,6 +189,36 @@ bool flecs_expr_oper_valid_for_type( (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } @@ -204,6 +264,36 @@ int flecs_expr_type_for_oper( case EcsTokSub: case EcsTokMul: break; + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokMod: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } @@ -451,6 +541,7 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t *cur, const ecs_script_expr_run_desc_t *desc) { + (void)desc; if (cur->valid) { node->node.type = ecs_meta_get_type(cur); } else { diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index dc5485ff2b..689bab4837 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -85,7 +85,7 @@ void flecs_script_expr_to_str( const ecs_expr_node_t *expr) { if (expr) { - flecs_scriptbuf_appendstr(v, "TODO"); + flecs_script_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); } else { flecs_scriptbuf_appendstr(v, "{}"); } From 739d17d9e9fbe7e4d536eedcdeb9b70426522409 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 02:10:01 +0000 Subject: [PATCH 30/83] Implement expression stack for runtime --- distr/flecs.c | 686 +++++++++++++++----------- project.json | 8 +- src/addons/script/expr/ast.c | 42 +- src/addons/script/expr/ast.h | 44 +- src/addons/script/expr/expr.h | 5 +- src/addons/script/expr/stack.c | 122 +++++ src/addons/script/expr/stack.h | 76 +++ src/addons/script/expr/visit_eval.c | 371 +++++++------- src/addons/script/expr/visit_fold.c | 18 +- src/addons/script/expr/visit_free.c | 6 +- src/addons/script/expr/visit_to_str.c | 4 +- test/script/src/Eval.c | 47 -- 12 files changed, 831 insertions(+), 598 deletions(-) create mode 100644 src/addons/script/expr/stack.c create mode 100644 src/addons/script/expr/stack.h diff --git a/distr/flecs.c b/distr/flecs.c index 363b789d5f..d64d2f3aa3 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4860,13 +4860,89 @@ ecs_script_if_t* flecs_script_insert_if( #endif /** - * @file addons/script/exor_visit.h - * @brief Script AST visitor utilities. + * @file addons/script/expr/expr.h + * @brief Script expression support. */ #ifndef FLECS_EXPR_SCRIPT_H #define FLECS_EXPR_SCRIPT_H +/** + * @file addons/script/expr/stack.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_STACK_H +#define FLECS_SCRIPT_EXPR_STACK_H + +#define FLECS_EXPR_STACK_MAX (256) +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + + +typedef union ecs_expr_small_value_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + const char *string; + ecs_entity_t entity; + ecs_id_t id; + + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; +} ecs_expr_small_value_t; + +typedef struct ecs_expr_value_t { + ecs_value_t value; + const ecs_type_info_t *type_info; + bool owned; /* Is value owned by the runtime */ +} ecs_expr_value_t; + +typedef struct ecs_expr_stack_frame_t { + ecs_stack_cursor_t *cur; + int32_t sp; +} ecs_expr_stack_frame_t; + +typedef struct ecs_expr_stack_t { + ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; + ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; + ecs_stack_t stack; + int32_t frame; +} ecs_expr_stack_t; + +void flecs_expr_stack_init( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack); + +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti); + +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node); + +void flecs_expr_stack_push( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack); + +#endif + /** * @file addons/script/expr_ast.h * @brief Script expression AST. @@ -4899,35 +4975,11 @@ struct ecs_expr_node_t { const char *pos; }; -typedef union ecs_expr_small_val_t { - bool bool_; - char char_; - ecs_byte_t byte_; - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - intptr_t iptr; - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - uintptr_t uptr; - double f32; - double f64; - const char *string; - ecs_entity_t entity; - ecs_id_t id; - - /* Avoid allocations for small trivial types */ - char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; -} ecs_expr_small_val_t; - -typedef struct ecs_expr_val_t { +typedef struct ecs_expr_value_node_t { ecs_expr_node_t node; void *ptr; - ecs_expr_small_val_t storage; -} ecs_expr_val_t; + ecs_expr_small_value_t storage; +} ecs_expr_value_node_t; typedef struct ecs_expr_initializer_element_t { const char *member; @@ -4998,32 +5050,32 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; -ecs_expr_val_t* flecs_expr_value_from( +ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); -ecs_expr_val_t* flecs_expr_bool( +ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); -ecs_expr_val_t* flecs_expr_int( +ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value); -ecs_expr_val_t* flecs_expr_uint( +ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value); -ecs_expr_val_t* flecs_expr_float( +ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value); -ecs_expr_val_t* flecs_expr_string( +ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); -ecs_expr_val_t* flecs_expr_entity( +ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value); @@ -73326,13 +73378,13 @@ void* flecs_expr_ast_new_( return result; } -ecs_expr_val_t* flecs_expr_value_from( +ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type) { - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + ecs_expr_value_node_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_value_node_t); result->ptr = &result->storage.u64; result->node.kind = EcsExprValue; result->node.pos = node ? node->pos : NULL; @@ -73341,72 +73393,72 @@ ecs_expr_val_t* flecs_expr_value_from( return result; } -ecs_expr_val_t* flecs_expr_bool( +ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.bool_ = value; result->ptr = &result->storage.bool_; result->node.type = ecs_id(ecs_bool_t); return result; } -ecs_expr_val_t* flecs_expr_int( +ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.i64 = value; result->ptr = &result->storage.i64; result->node.type = ecs_id(ecs_i64_t); return result; } -ecs_expr_val_t* flecs_expr_uint( +ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.u64 = value; result->ptr = &result->storage.u64; result->node.type = ecs_id(ecs_i64_t); return result; } -ecs_expr_val_t* flecs_expr_float( +ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.f64 = value; result->ptr = &result->storage.f64; result->node.type = ecs_id(ecs_f64_t); return result; } -ecs_expr_val_t* flecs_expr_string( +ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); return result; } -ecs_expr_val_t* flecs_expr_entity( +ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.entity = value; result->ptr = &result->storage.entity; result->node.type = ecs_id(ecs_entity_t); @@ -74079,6 +74131,127 @@ const char* ecs_script_expr_run( #endif +/** + * @file addons/script/expr/stack.c + * @brief Script expression stack implementation. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_expr_value_alloc( + ecs_expr_stack_t *stack, + ecs_expr_value_t *v, + const ecs_type_info_t *ti) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + v->type_info = ti; + v->value.type = ti->component; + v->value.ptr = flecs_stack_alloc(&stack->stack, ti->size, ti->alignment); + + if (ti->hooks.ctor) { + ti->hooks.ctor(v->value.ptr, 1, ti); + } +} + +static +void flecs_expr_value_free( + ecs_expr_stack_t *stack, + ecs_expr_value_t *v) +{ + const ecs_type_info_t *ti = v->type_info; + v->type_info = NULL; + + if (!v->owned) { + return; /* Runtime doesn't own value, don't destruct */ + } + + if (ti && ti->hooks.dtor) { + ecs_assert(v->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ti->hooks.dtor(v->value.ptr, 1, ti); + flecs_stack_free(v->value.ptr, ti->size); + } + + v->value.ptr = NULL; +} + +void flecs_expr_stack_init( + ecs_expr_stack_t *stack) +{ + ecs_os_zeromem(stack); + flecs_stack_init(&stack->stack); +} + +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack) +{ + ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); + flecs_stack_fini(&stack->stack); +} + +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_expr_stack_alloc(stack, node->type_info); +} + +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti) +{ + ecs_assert(stack->frame > 0, ECS_INTERNAL_ERROR, NULL); + + int32_t sp = stack->frames[stack->frame - 1].sp ++; + ecs_assert(sp < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + ecs_expr_value_t *v = &stack->values[sp]; + + if (ti) { + flecs_expr_value_alloc(stack, v, ti); + } + + return v; +} + +void flecs_expr_stack_push( + ecs_expr_stack_t *stack) +{ + int32_t frame = stack->frame ++; + ecs_assert(frame < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + stack->frames[frame].cur = flecs_stack_get_cursor(&stack->stack); + if (frame) { + stack->frames[frame].sp = stack->frames[frame - 1].sp; + } else { + stack->frames[frame].sp = 0; + } +} + +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack) +{ + int32_t frame = -- stack->frame; + ecs_assert(frame >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t sp, start = 0, end = stack->frames[frame].sp; + if (frame) { + start = stack->frames[frame - 1].sp; + } + + for (sp = end - 1; sp >= start; sp --) { + flecs_expr_value_free(stack, &stack->values[sp]); + } + + flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); +} + +#endif + /** * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. */ @@ -74236,148 +74409,103 @@ int flecs_value_binary( #ifdef FLECS_SCRIPT -typedef struct ecs_eval_value_t { - ecs_value_t value; - ecs_expr_small_val_t storage; - const ecs_type_info_t *type_info; - bool can_move; /* Can value be moved to output */ -} ecs_eval_value_t; +typedef struct ecs_script_eval_ctx_t { + const ecs_script_t *script; + ecs_world_t *world; + const ecs_script_expr_run_desc_t *desc; + ecs_expr_stack_t *stack; +} ecs_script_eval_ctx_t; static int flecs_script_expr_visit_eval_priv( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out); - -static -void flecs_expr_value_alloc( - const ecs_script_t *script, - ecs_eval_value_t *val, - const ecs_type_info_t *ti) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - val->value.type = ti->component; - val->type_info = ti; - val->can_move = true; - - if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { - val->value.ptr = val->storage.small_data; - } else { - val->value.ptr = flecs_alloc( - &((ecs_script_impl_t*)script)->allocator, ti->size); - } - - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - ctor(val->value.ptr, 1, ti); - } -} - -static -void flecs_expr_value_free( - const ecs_script_t *script, - ecs_eval_value_t *val) -{ - const ecs_type_info_t *ti = val->type_info; - - if (!ti) { - return; - } - - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - dtor(val->value.ptr, 1, ti); - } - - val->value.type = ti->component; - - if (ti->size > FLECS_EXPR_SMALL_DATA_SIZE) { - flecs_free(&((ecs_script_impl_t*)script)->allocator, - ti->size, val->value.ptr); - } -} + ecs_expr_value_t *out); static int flecs_expr_value_visit_eval( - const ecs_script_t *script, - ecs_expr_val_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_script_eval_ctx_t *ctx, + ecs_expr_value_node_t *node, + ecs_expr_value_t *out) { - (void)script; - (void)desc; + (void)ctx; out->value.type = node->node.type; out->value.ptr = node->ptr; - out->can_move = false; + out->owned = false; return 0; } static int flecs_expr_initializer_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value); static int flecs_expr_initializer_eval_static( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { + flecs_expr_stack_push(ctx->stack); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval( - script, (ecs_expr_initializer_t*)elem->value, desc, value)) + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv( - script, elem->value, desc, &expr)) - { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ ecs_entity_t type = elem_value->node.type; - if (ecs_value_copy(script->world, type, - ECS_OFFSET(value, elem->offset), expr.value.ptr)) - { - goto error; + if (expr->owned) { + if (ecs_value_move(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } else { + if (ecs_value_copy(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } } - - flecs_expr_value_free(script, &expr); } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval_dynamic( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { + flecs_expr_stack_push(ctx->stack); + ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, node->node.type, value); + ctx->world, node->node.type, value); if (ecs_meta_push(&cur)) { goto error; @@ -74393,22 +74521,20 @@ int flecs_expr_initializer_eval_dynamic( } if (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval( - script, (ecs_expr_initializer_t*)elem->value, desc, value)) + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv( - script, elem->value, desc, &expr)) - { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; if (elem->member) { ecs_meta_member(&cur, elem->member); @@ -74422,115 +74548,110 @@ int flecs_expr_initializer_eval_dynamic( if (ecs_meta_set_value(&cur, &v_elem_value)) { goto error; } - - flecs_expr_value_free(script, &expr); } if (ecs_meta_pop(&cur)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { if (node->is_dynamic) { - return flecs_expr_initializer_eval_dynamic(script, node, desc, value); + return flecs_expr_initializer_eval_dynamic(ctx, node, value); } else { - return flecs_expr_initializer_eval_static(script, node, desc, value); + return flecs_expr_initializer_eval_static(ctx, node, value); } } static int flecs_expr_initializer_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - /* Initialize storage for initializer's type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - return flecs_expr_initializer_eval(script, node, desc, out->value.ptr); + out->owned = true; + return flecs_expr_initializer_eval(ctx, node, out->value.ptr); } static int flecs_expr_unary_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_unary_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); - if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - if (flecs_value_unary( - script, &expr.value, &out->value, node->operator)) + ctx->script, &expr->value, &out->value, node->operator)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_binary_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_binary_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t left = {{0}}; - ecs_eval_value_t right = {{0}}; + flecs_expr_stack_push(ctx->stack); /* Evaluate left & right expressions */ - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &left)) { + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } - if (flecs_script_expr_visit_eval_priv(script, node->right, desc, &right)) { + + ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); + if (flecs_script_expr_visit_eval_priv(ctx, node->right, right)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - if (flecs_value_binary( - script, &left.value, &right.value, &out->value, node->operator)) + ctx->script, &left->value, &right->value, &out->value, node->operator)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_variable_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { const ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ctx->desc->vars, node->name); if (!var) { - flecs_expr_visit_error(script, node, "unresolved variable '%s'", + flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); goto error; } @@ -74539,7 +74660,7 @@ int flecs_expr_variable_visit_eval( ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; - out->can_move = false; + out->owned = false; return 0; error: return -1; @@ -74547,110 +74668,110 @@ int flecs_expr_variable_visit_eval( static int flecs_expr_cast_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); /* Evaluate expression to cast */ - if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - /* Copy expression result to storage of casted-to type */ - if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { - flecs_expr_visit_error(script, node, "failed to cast value"); + if (flecs_value_copy_to(ctx->world, &out->value, &expr->value)) { + flecs_expr_visit_error(ctx->script, node, "failed to cast value"); goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_function_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { + flecs_expr_stack_push(ctx->stack); + if (node->left) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - if (!out->value.ptr) { - flecs_expr_value_alloc(script, out, node->node.type_info); - } - - ecs_function_ctx_t ctx = { - .world = script->world, + ecs_function_ctx_t call_ctx = { + .world = ctx->world, .function = node->calldata.function, .ctx = node->calldata.ctx }; - ecs_assert(expr.value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - node->calldata.callback(&ctx, 1, &expr.value, &out->value); + node->calldata.callback(&call_ctx, 1, &expr->value, &out->value); + out->owned = true; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_member_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_member_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); out->value.type = node->node.type; - out->can_move = false; + out->owned = false; + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_element_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - ecs_eval_value_t index = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->index, desc, &index)) { + ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); + if (flecs_script_expr_visit_eval_priv(ctx, node->index, index)) { goto error; } - int64_t index_value = *(int64_t*)index.value.ptr; + int64_t index_value = *(int64_t*)index->value.ptr; - out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->elem_size * index_value); out->value.type = node->node.type; - out->can_move = false; + out->owned = false; return 0; error: @@ -74659,35 +74780,34 @@ int flecs_expr_element_visit_eval( static int flecs_expr_component_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } /* Left side of expression must be of entity type */ - ecs_assert(expr.value.type == ecs_id(ecs_entity_t), + ecs_assert(left->value.type == ecs_id(ecs_entity_t), ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t entity = *(ecs_entity_t*)expr.value.ptr; - ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; + ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; + ecs_entity_t component = ((ecs_expr_value_node_t*)node->index)->storage.entity; - out->value.type = node->node.type; + ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, - ecs_get_id(script->world, entity, component)); - out->can_move = false; + ecs_get_id(ctx->world, entity, component)); + out->owned = false; if (!out->value.ptr) { - char *estr = ecs_get_path(script->world, entity); - char *cstr = ecs_get_path(script->world, component); - flecs_expr_visit_error(script, node, + char *estr = ecs_get_path(ctx->world, entity); + char *cstr = ecs_get_path(ctx->world, component); + flecs_expr_visit_error(ctx->script, node, "entity '%s' does not have component '%s'", estr, cstr); ecs_os_free(estr); ecs_os_free(cstr); @@ -74701,17 +74821,16 @@ int flecs_expr_component_visit_eval( static int flecs_script_expr_visit_eval_priv( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); switch(node->kind) { case EcsExprValue: if (flecs_expr_value_visit_eval( - script, (ecs_expr_val_t*)node, desc, out)) + ctx, (ecs_expr_value_node_t*)node, out)) { goto error; } @@ -74720,21 +74839,21 @@ int flecs_script_expr_visit_eval_priv( break; case EcsExprInitializer: if (flecs_expr_initializer_visit_eval( - script, (ecs_expr_initializer_t*)node, desc, out)) + ctx, (ecs_expr_initializer_t*)node, out)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_visit_eval( - script, (ecs_expr_unary_t*)node, desc, out)) + ctx, (ecs_expr_unary_t*)node, out)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_visit_eval( - script, (ecs_expr_binary_t*)node, desc, out)) + ctx, (ecs_expr_binary_t*)node, out)) { goto error; } @@ -74743,42 +74862,42 @@ int flecs_script_expr_visit_eval_priv( break; case EcsExprVariable: if (flecs_expr_variable_visit_eval( - script, (ecs_expr_variable_t*)node, desc, out)) + ctx, (ecs_expr_variable_t*)node, out)) { goto error; } break; case EcsExprFunction: if (flecs_expr_function_visit_eval( - script, (ecs_expr_function_t*)node, desc, out)) + ctx, (ecs_expr_function_t*)node, out)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_visit_eval( - script, (ecs_expr_member_t*)node, desc, out)) + ctx, (ecs_expr_member_t*)node, out)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_eval( - script, (ecs_expr_element_t*)node, desc, out)) + ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprComponent: if (flecs_expr_component_visit_eval( - script, (ecs_expr_element_t*)node, desc, out)) + ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprCast: if (flecs_expr_cast_visit_eval( - script, (ecs_expr_cast_t*)node, desc, out)) + ctx, (ecs_expr_cast_t*)node, out)) { goto error; } @@ -74796,9 +74915,20 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) { - ecs_eval_value_t val = {{0}}; + ecs_expr_stack_t stack; + flecs_expr_stack_init(&stack); + flecs_expr_stack_push(&stack); - if (flecs_script_expr_visit_eval_priv(script, node, desc, &val)) { + ecs_expr_value_t *val = flecs_expr_stack_result(&stack, node); + + ecs_script_eval_ctx_t ctx = { + .script = script, + .world = script->world, + .stack = &stack, + .desc = desc + }; + + if (flecs_script_expr_visit_eval_priv(&ctx, node, val)) { goto error; } @@ -74811,23 +74941,29 @@ int flecs_script_expr_visit_eval( } if (out->type && !out->ptr) { - out->ptr = ecs_value_new(script->world, out->type); + out->ptr = ecs_value_new(ctx.world, out->type); } - if (val.can_move) { - if (flecs_value_move_to(script->world, out, &val.value)) { + if (val->owned) { + /* Values owned by the runtime can be moved to output */ + if (flecs_value_move_to(ctx.world, out, &val->value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } else { - if (flecs_value_copy_to(script->world, out, &val.value)) { + /* Values not owned by runtime should be copied */ + if (flecs_value_copy_to(ctx.world, out, &val->value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } + flecs_expr_stack_pop(&stack); + flecs_expr_stack_fini(&stack); return 0; -error: +error: + flecs_expr_stack_pop(&stack); + flecs_expr_stack_fini(&stack); return -1; } @@ -74881,13 +75017,13 @@ int flecs_expr_unary_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); result->ptr = &result->storage.bool_; ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; ecs_value_t src = { - .ptr = ((ecs_expr_val_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; + .ptr = ((ecs_expr_value_node_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; if (flecs_value_unary(script, &src, &dst, node->operator)) { goto error; } @@ -74919,10 +75055,10 @@ int flecs_expr_binary_visit_fold( return 0; } - ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; - ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; + ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); /* Handle bitmask separately since it's not done by switch */ @@ -74966,7 +75102,7 @@ int flecs_expr_cast_visit_fold( return 0; } - ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + ecs_expr_value_node_t *expr = (ecs_expr_value_node_t*)node->expr; ecs_entity_t dst_type = node->node.type; ecs_entity_t src_type = expr->node.type; @@ -75057,7 +75193,7 @@ int flecs_expr_initializer_post_fold( continue; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ @@ -75098,7 +75234,7 @@ int flecs_expr_initializer_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; @@ -75118,7 +75254,7 @@ int flecs_expr_identifier_visit_fold( ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_entity_t type = node->node.type; - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); if (type == ecs_id(ecs_entity_t)) { @@ -75275,7 +75411,7 @@ int flecs_script_expr_visit_fold( static void flecs_expr_value_visit_free( ecs_script_t *script, - ecs_expr_val_t *node) + ecs_expr_value_node_t *node) { if (node->ptr != &node->storage) { ecs_value_free(script->world, node->node.type, node->ptr); @@ -75361,8 +75497,8 @@ void flecs_script_expr_visit_free( switch(node->kind) { case EcsExprValue: flecs_expr_value_visit_free( - script, (ecs_expr_val_t*)node); - flecs_free_t(a, ecs_expr_val_t, node); + script, (ecs_expr_value_node_t*)node); + flecs_free_t(a, ecs_expr_value_node_t, node); break; case EcsExprInitializer: case EcsExprEmptyInitializer: @@ -75435,7 +75571,7 @@ int flecs_expr_node_to_str( static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, - const ecs_expr_val_t *node) + const ecs_expr_value_node_t *node) { return ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); @@ -75616,7 +75752,7 @@ int flecs_expr_node_to_str( switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, - (const ecs_expr_val_t*)node)) + (const ecs_expr_value_node_t*)node)) { goto error; } diff --git a/project.json b/project.json index badfe5196b..85a1c2e79b 100644 --- a/project.json +++ b/project.json @@ -21,7 +21,13 @@ }, "lang.c": { "${os linux}": { - "lib": ["rt", "pthread"] + "lib": ["rt", "pthread"], + "${cfg debug}": { + "export-symbols": true + }, + "${cfg sanitize}": { + "export-symbols": true + } }, "${os windows}": { "lib": ["ws2_32"] diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index fb4bb52bb8..251ad3de33 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -25,13 +25,13 @@ void* flecs_expr_ast_new_( return result; } -ecs_expr_val_t* flecs_expr_value_from( +ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type) { - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + ecs_expr_value_node_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_value_node_t); result->ptr = &result->storage.u64; result->node.kind = EcsExprValue; result->node.pos = node ? node->pos : NULL; @@ -40,72 +40,72 @@ ecs_expr_val_t* flecs_expr_value_from( return result; } -ecs_expr_val_t* flecs_expr_bool( +ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.bool_ = value; result->ptr = &result->storage.bool_; result->node.type = ecs_id(ecs_bool_t); return result; } -ecs_expr_val_t* flecs_expr_int( +ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.i64 = value; result->ptr = &result->storage.i64; result->node.type = ecs_id(ecs_i64_t); return result; } -ecs_expr_val_t* flecs_expr_uint( +ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.u64 = value; result->ptr = &result->storage.u64; result->node.type = ecs_id(ecs_i64_t); return result; } -ecs_expr_val_t* flecs_expr_float( +ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.f64 = value; result->ptr = &result->storage.f64; result->node.type = ecs_id(ecs_f64_t); return result; } -ecs_expr_val_t* flecs_expr_string( +ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.string = value; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); return result; } -ecs_expr_val_t* flecs_expr_entity( +ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value) { - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); result->storage.entity = value; result->ptr = &result->storage.entity; result->node.type = ecs_id(ecs_entity_t); diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index f88a669710..e94f8b2d53 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -30,35 +30,11 @@ struct ecs_expr_node_t { const char *pos; }; -typedef union ecs_expr_small_val_t { - bool bool_; - char char_; - ecs_byte_t byte_; - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - intptr_t iptr; - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - uintptr_t uptr; - double f32; - double f64; - const char *string; - ecs_entity_t entity; - ecs_id_t id; - - /* Avoid allocations for small trivial types */ - char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; -} ecs_expr_small_val_t; - -typedef struct ecs_expr_val_t { +typedef struct ecs_expr_value_node_t { ecs_expr_node_t node; void *ptr; - ecs_expr_small_val_t storage; -} ecs_expr_val_t; + ecs_expr_small_value_t storage; +} ecs_expr_value_node_t; typedef struct ecs_expr_initializer_element_t { const char *member; @@ -129,32 +105,32 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; -ecs_expr_val_t* flecs_expr_value_from( +ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); -ecs_expr_val_t* flecs_expr_bool( +ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); -ecs_expr_val_t* flecs_expr_int( +ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value); -ecs_expr_val_t* flecs_expr_uint( +ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value); -ecs_expr_val_t* flecs_expr_float( +ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value); -ecs_expr_val_t* flecs_expr_string( +ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); -ecs_expr_val_t* flecs_expr_entity( +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 90b531ba2c..cf23c6b487 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -1,11 +1,12 @@ /** - * @file addons/script/exor_visit.h - * @brief Script AST visitor utilities. + * @file addons/script/expr/expr.h + * @brief Script expression support. */ #ifndef FLECS_EXPR_SCRIPT_H #define FLECS_EXPR_SCRIPT_H +#include "stack.h" #include "ast.h" #include "visit.h" diff --git a/src/addons/script/expr/stack.c b/src/addons/script/expr/stack.c new file mode 100644 index 0000000000..65327696f0 --- /dev/null +++ b/src/addons/script/expr/stack.c @@ -0,0 +1,122 @@ +/** + * @file addons/script/expr/stack.c + * @brief Script expression stack implementation. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "../script.h" + +static +void flecs_expr_value_alloc( + ecs_expr_stack_t *stack, + ecs_expr_value_t *v, + const ecs_type_info_t *ti) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + v->type_info = ti; + v->value.type = ti->component; + v->value.ptr = flecs_stack_alloc(&stack->stack, ti->size, ti->alignment); + + if (ti->hooks.ctor) { + ti->hooks.ctor(v->value.ptr, 1, ti); + } +} + +static +void flecs_expr_value_free( + ecs_expr_stack_t *stack, + ecs_expr_value_t *v) +{ + const ecs_type_info_t *ti = v->type_info; + v->type_info = NULL; + + if (!v->owned) { + return; /* Runtime doesn't own value, don't destruct */ + } + + if (ti && ti->hooks.dtor) { + ecs_assert(v->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ti->hooks.dtor(v->value.ptr, 1, ti); + flecs_stack_free(v->value.ptr, ti->size); + } + + v->value.ptr = NULL; +} + +void flecs_expr_stack_init( + ecs_expr_stack_t *stack) +{ + ecs_os_zeromem(stack); + flecs_stack_init(&stack->stack); +} + +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack) +{ + ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); + flecs_stack_fini(&stack->stack); +} + +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_expr_stack_alloc(stack, node->type_info); +} + +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti) +{ + ecs_assert(stack->frame > 0, ECS_INTERNAL_ERROR, NULL); + + int32_t sp = stack->frames[stack->frame - 1].sp ++; + ecs_assert(sp < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + ecs_expr_value_t *v = &stack->values[sp]; + + if (ti) { + flecs_expr_value_alloc(stack, v, ti); + } + + return v; +} + +void flecs_expr_stack_push( + ecs_expr_stack_t *stack) +{ + int32_t frame = stack->frame ++; + ecs_assert(frame < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + stack->frames[frame].cur = flecs_stack_get_cursor(&stack->stack); + if (frame) { + stack->frames[frame].sp = stack->frames[frame - 1].sp; + } else { + stack->frames[frame].sp = 0; + } +} + +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack) +{ + int32_t frame = -- stack->frame; + ecs_assert(frame >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t sp, start = 0, end = stack->frames[frame].sp; + if (frame) { + start = stack->frames[frame - 1].sp; + } + + for (sp = end - 1; sp >= start; sp --) { + flecs_expr_value_free(stack, &stack->values[sp]); + } + + flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); +} + +#endif diff --git a/src/addons/script/expr/stack.h b/src/addons/script/expr/stack.h new file mode 100644 index 0000000000..e181fda6b8 --- /dev/null +++ b/src/addons/script/expr/stack.h @@ -0,0 +1,76 @@ +/** + * @file addons/script/expr/stack.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_STACK_H +#define FLECS_SCRIPT_EXPR_STACK_H + +#define FLECS_EXPR_STACK_MAX (256) +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + +#include "flecs.h" + +typedef union ecs_expr_small_value_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + const char *string; + ecs_entity_t entity; + ecs_id_t id; + + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; +} ecs_expr_small_value_t; + +typedef struct ecs_expr_value_t { + ecs_value_t value; + const ecs_type_info_t *type_info; + bool owned; /* Is value owned by the runtime */ +} ecs_expr_value_t; + +typedef struct ecs_expr_stack_frame_t { + ecs_stack_cursor_t *cur; + int32_t sp; +} ecs_expr_stack_frame_t; + +typedef struct ecs_expr_stack_t { + ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; + ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; + ecs_stack_t stack; + int32_t frame; +} ecs_expr_stack_t; + +void flecs_expr_stack_init( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack); + +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti); + +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node); + +void flecs_expr_stack_push( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack); + +#endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 32382f0552..431e0c9311 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -8,148 +8,103 @@ #ifdef FLECS_SCRIPT #include "../script.h" -typedef struct ecs_eval_value_t { - ecs_value_t value; - ecs_expr_small_val_t storage; - const ecs_type_info_t *type_info; - bool can_move; /* Can value be moved to output */ -} ecs_eval_value_t; +typedef struct ecs_script_eval_ctx_t { + const ecs_script_t *script; + ecs_world_t *world; + const ecs_script_expr_run_desc_t *desc; + ecs_expr_stack_t *stack; +} ecs_script_eval_ctx_t; static int flecs_script_expr_visit_eval_priv( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out); - -static -void flecs_expr_value_alloc( - const ecs_script_t *script, - ecs_eval_value_t *val, - const ecs_type_info_t *ti) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - val->value.type = ti->component; - val->type_info = ti; - val->can_move = true; - - if (ti->size <= FLECS_EXPR_SMALL_DATA_SIZE) { - val->value.ptr = val->storage.small_data; - } else { - val->value.ptr = flecs_alloc( - &((ecs_script_impl_t*)script)->allocator, ti->size); - } - - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - ctor(val->value.ptr, 1, ti); - } -} - -static -void flecs_expr_value_free( - const ecs_script_t *script, - ecs_eval_value_t *val) -{ - const ecs_type_info_t *ti = val->type_info; - - if (!ti) { - return; - } - - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - dtor(val->value.ptr, 1, ti); - } - - val->value.type = ti->component; - - if (ti->size > FLECS_EXPR_SMALL_DATA_SIZE) { - flecs_free(&((ecs_script_impl_t*)script)->allocator, - ti->size, val->value.ptr); - } -} + ecs_expr_value_t *out); static int flecs_expr_value_visit_eval( - const ecs_script_t *script, - ecs_expr_val_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_script_eval_ctx_t *ctx, + ecs_expr_value_node_t *node, + ecs_expr_value_t *out) { - (void)script; - (void)desc; + (void)ctx; out->value.type = node->node.type; out->value.ptr = node->ptr; - out->can_move = false; + out->owned = false; return 0; } static int flecs_expr_initializer_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value); static int flecs_expr_initializer_eval_static( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { + flecs_expr_stack_push(ctx->stack); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval( - script, (ecs_expr_initializer_t*)elem->value, desc, value)) + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv( - script, elem->value, desc, &expr)) - { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ ecs_entity_t type = elem_value->node.type; - if (ecs_value_copy(script->world, type, - ECS_OFFSET(value, elem->offset), expr.value.ptr)) - { - goto error; + if (expr->owned) { + if (ecs_value_move(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } else { + if (ecs_value_copy(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } } - - flecs_expr_value_free(script, &expr); } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval_dynamic( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { + flecs_expr_stack_push(ctx->stack); + ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, node->node.type, value); + ctx->world, node->node.type, value); if (ecs_meta_push(&cur)) { goto error; @@ -165,22 +120,20 @@ int flecs_expr_initializer_eval_dynamic( } if (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval( - script, (ecs_expr_initializer_t*)elem->value, desc, value)) + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv( - script, elem->value, desc, &expr)) - { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; if (elem->member) { ecs_meta_member(&cur, elem->member); @@ -194,115 +147,110 @@ int flecs_expr_initializer_eval_dynamic( if (ecs_meta_set_value(&cur, &v_elem_value)) { goto error; } - - flecs_expr_value_free(script, &expr); } if (ecs_meta_pop(&cur)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, void *value) { if (node->is_dynamic) { - return flecs_expr_initializer_eval_dynamic(script, node, desc, value); + return flecs_expr_initializer_eval_dynamic(ctx, node, value); } else { - return flecs_expr_initializer_eval_static(script, node, desc, value); + return flecs_expr_initializer_eval_static(ctx, node, value); } } static int flecs_expr_initializer_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - /* Initialize storage for initializer's type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - return flecs_expr_initializer_eval(script, node, desc, out->value.ptr); + out->owned = true; + return flecs_expr_initializer_eval(ctx, node, out->value.ptr); } static int flecs_expr_unary_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_unary_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); - if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - if (flecs_value_unary( - script, &expr.value, &out->value, node->operator)) + ctx->script, &expr->value, &out->value, node->operator)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_binary_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_binary_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t left = {{0}}; - ecs_eval_value_t right = {{0}}; + flecs_expr_stack_push(ctx->stack); /* Evaluate left & right expressions */ - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &left)) { + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } - if (flecs_script_expr_visit_eval_priv(script, node->right, desc, &right)) { + + ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); + if (flecs_script_expr_visit_eval_priv(ctx, node->right, right)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - if (flecs_value_binary( - script, &left.value, &right.value, &out->value, node->operator)) + ctx->script, &left->value, &right->value, &out->value, node->operator)) { goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_variable_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { const ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ctx->desc->vars, node->name); if (!var) { - flecs_expr_visit_error(script, node, "unresolved variable '%s'", + flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); goto error; } @@ -311,7 +259,7 @@ int flecs_expr_variable_visit_eval( ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; - out->can_move = false; + out->owned = false; return 0; error: return -1; @@ -319,110 +267,110 @@ int flecs_expr_variable_visit_eval( static int flecs_expr_cast_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); /* Evaluate expression to cast */ - if (flecs_script_expr_visit_eval_priv(script, node->expr, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } - /* Initialize storage of casted-to type */ - flecs_expr_value_alloc(script, out, node->node.type_info); - /* Copy expression result to storage of casted-to type */ - if (flecs_value_copy_to(script->world, &out->value, &expr.value)) { - flecs_expr_visit_error(script, node, "failed to cast value"); + if (flecs_value_copy_to(ctx->world, &out->value, &expr->value)) { + flecs_expr_visit_error(ctx->script, node, "failed to cast value"); goto error; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_function_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { + flecs_expr_stack_push(ctx->stack); + if (node->left) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - if (!out->value.ptr) { - flecs_expr_value_alloc(script, out, node->node.type_info); - } - - ecs_function_ctx_t ctx = { - .world = script->world, + ecs_function_ctx_t call_ctx = { + .world = ctx->world, .function = node->calldata.function, .ctx = node->calldata.ctx }; - ecs_assert(expr.value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - node->calldata.callback(&ctx, 1, &expr.value, &out->value); + node->calldata.callback(&call_ctx, 1, &expr->value, &out->value); + out->owned = true; } + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_member_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_member_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; + flecs_expr_stack_push(ctx->stack); - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - out->value.ptr = ECS_OFFSET(expr.value.ptr, node->offset); + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); out->value.type = node->node.type; - out->can_move = false; + out->owned = false; + flecs_expr_stack_pop(ctx->stack); return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_element_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } - ecs_eval_value_t index = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->index, desc, &index)) { + ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); + if (flecs_script_expr_visit_eval_priv(ctx, node->index, index)) { goto error; } - int64_t index_value = *(int64_t*)index.value.ptr; + int64_t index_value = *(int64_t*)index->value.ptr; - out->value.ptr = ECS_OFFSET(expr.value.ptr, node->elem_size * index_value); + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->elem_size * index_value); out->value.type = node->node.type; - out->can_move = false; + out->owned = false; return 0; error: @@ -431,35 +379,34 @@ int flecs_expr_element_visit_eval( static int flecs_expr_component_visit_eval( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { - ecs_eval_value_t expr = {{0}}; - if (flecs_script_expr_visit_eval_priv(script, node->left, desc, &expr)) { + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } /* Left side of expression must be of entity type */ - ecs_assert(expr.value.type == ecs_id(ecs_entity_t), + ecs_assert(left->value.type == ecs_id(ecs_entity_t), ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t entity = *(ecs_entity_t*)expr.value.ptr; - ecs_entity_t component = ((ecs_expr_val_t*)node->index)->storage.entity; + ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; + ecs_entity_t component = ((ecs_expr_value_node_t*)node->index)->storage.entity; - out->value.type = node->node.type; + ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, - ecs_get_id(script->world, entity, component)); - out->can_move = false; + ecs_get_id(ctx->world, entity, component)); + out->owned = false; if (!out->value.ptr) { - char *estr = ecs_get_path(script->world, entity); - char *cstr = ecs_get_path(script->world, component); - flecs_expr_visit_error(script, node, + char *estr = ecs_get_path(ctx->world, entity); + char *cstr = ecs_get_path(ctx->world, component); + flecs_expr_visit_error(ctx->script, node, "entity '%s' does not have component '%s'", estr, cstr); ecs_os_free(estr); ecs_os_free(cstr); @@ -473,17 +420,16 @@ int flecs_expr_component_visit_eval( static int flecs_script_expr_visit_eval_priv( - const ecs_script_t *script, + ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, - ecs_eval_value_t *out) + ecs_expr_value_t *out) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); switch(node->kind) { case EcsExprValue: if (flecs_expr_value_visit_eval( - script, (ecs_expr_val_t*)node, desc, out)) + ctx, (ecs_expr_value_node_t*)node, out)) { goto error; } @@ -492,21 +438,21 @@ int flecs_script_expr_visit_eval_priv( break; case EcsExprInitializer: if (flecs_expr_initializer_visit_eval( - script, (ecs_expr_initializer_t*)node, desc, out)) + ctx, (ecs_expr_initializer_t*)node, out)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_visit_eval( - script, (ecs_expr_unary_t*)node, desc, out)) + ctx, (ecs_expr_unary_t*)node, out)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_visit_eval( - script, (ecs_expr_binary_t*)node, desc, out)) + ctx, (ecs_expr_binary_t*)node, out)) { goto error; } @@ -515,42 +461,42 @@ int flecs_script_expr_visit_eval_priv( break; case EcsExprVariable: if (flecs_expr_variable_visit_eval( - script, (ecs_expr_variable_t*)node, desc, out)) + ctx, (ecs_expr_variable_t*)node, out)) { goto error; } break; case EcsExprFunction: if (flecs_expr_function_visit_eval( - script, (ecs_expr_function_t*)node, desc, out)) + ctx, (ecs_expr_function_t*)node, out)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_visit_eval( - script, (ecs_expr_member_t*)node, desc, out)) + ctx, (ecs_expr_member_t*)node, out)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_eval( - script, (ecs_expr_element_t*)node, desc, out)) + ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprComponent: if (flecs_expr_component_visit_eval( - script, (ecs_expr_element_t*)node, desc, out)) + ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprCast: if (flecs_expr_cast_visit_eval( - script, (ecs_expr_cast_t*)node, desc, out)) + ctx, (ecs_expr_cast_t*)node, out)) { goto error; } @@ -568,9 +514,20 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) { - ecs_eval_value_t val = {{0}}; + ecs_expr_stack_t stack; + flecs_expr_stack_init(&stack); + flecs_expr_stack_push(&stack); + + ecs_expr_value_t *val = flecs_expr_stack_result(&stack, node); - if (flecs_script_expr_visit_eval_priv(script, node, desc, &val)) { + ecs_script_eval_ctx_t ctx = { + .script = script, + .world = script->world, + .stack = &stack, + .desc = desc + }; + + if (flecs_script_expr_visit_eval_priv(&ctx, node, val)) { goto error; } @@ -583,23 +540,29 @@ int flecs_script_expr_visit_eval( } if (out->type && !out->ptr) { - out->ptr = ecs_value_new(script->world, out->type); + out->ptr = ecs_value_new(ctx.world, out->type); } - if (val.can_move) { - if (flecs_value_move_to(script->world, out, &val.value)) { + if (val->owned) { + /* Values owned by the runtime can be moved to output */ + if (flecs_value_move_to(ctx.world, out, &val->value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } else { - if (flecs_value_copy_to(script->world, out, &val.value)) { + /* Values not owned by runtime should be copied */ + if (flecs_value_copy_to(ctx.world, out, &val->value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } + flecs_expr_stack_pop(&stack); + flecs_expr_stack_fini(&stack); return 0; -error: +error: + flecs_expr_stack_pop(&stack); + flecs_expr_stack_fini(&stack); return -1; } diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 52e73ea93e..79a760011c 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -48,13 +48,13 @@ int flecs_expr_unary_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); result->ptr = &result->storage.bool_; ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; ecs_value_t src = { - .ptr = ((ecs_expr_val_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; + .ptr = ((ecs_expr_value_node_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; if (flecs_value_unary(script, &src, &dst, node->operator)) { goto error; } @@ -86,10 +86,10 @@ int flecs_expr_binary_visit_fold( return 0; } - ecs_expr_val_t *left = (ecs_expr_val_t*)node->left; - ecs_expr_val_t *right = (ecs_expr_val_t*)node->right; + ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; + ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); /* Handle bitmask separately since it's not done by switch */ @@ -133,7 +133,7 @@ int flecs_expr_cast_visit_fold( return 0; } - ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + ecs_expr_value_node_t *expr = (ecs_expr_value_node_t*)node->expr; ecs_entity_t dst_type = node->node.type; ecs_entity_t src_type = expr->node.type; @@ -224,7 +224,7 @@ int flecs_expr_initializer_post_fold( continue; } - ecs_expr_val_t *elem_value = (ecs_expr_val_t*)elem->value; + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ @@ -265,7 +265,7 @@ int flecs_expr_initializer_visit_fold( goto error; } - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; @@ -285,7 +285,7 @@ int flecs_expr_identifier_visit_fold( ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_entity_t type = node->node.type; - ecs_expr_val_t *result = flecs_expr_value_from( + ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); if (type == ecs_id(ecs_entity_t)) { diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 588c147d79..51f868b346 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -11,7 +11,7 @@ static void flecs_expr_value_visit_free( ecs_script_t *script, - ecs_expr_val_t *node) + ecs_expr_value_node_t *node) { if (node->ptr != &node->storage) { ecs_value_free(script->world, node->node.type, node->ptr); @@ -97,8 +97,8 @@ void flecs_script_expr_visit_free( switch(node->kind) { case EcsExprValue: flecs_expr_value_visit_free( - script, (ecs_expr_val_t*)node); - flecs_free_t(a, ecs_expr_val_t, node); + script, (ecs_expr_value_node_t*)node); + flecs_free_t(a, ecs_expr_value_node_t, node); break; case EcsExprInitializer: case EcsExprEmptyInitializer: diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 5794b21086..bde5001906 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -23,7 +23,7 @@ int flecs_expr_node_to_str( static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, - const ecs_expr_val_t *node) + const ecs_expr_value_node_t *node) { return ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); @@ -204,7 +204,7 @@ int flecs_expr_node_to_str( switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, - (const ecs_expr_val_t*)node)) + (const ecs_expr_value_node_t*)node)) { goto error; } diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 6e149ee857..7b4504a86e 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -7887,21 +7887,14 @@ typedef struct Strings { char *b; } Strings; -static int strings_ctor_invoked = 0; -static int strings_dtor_invoked = 0; -static int strings_move_invoked = 0; -static int strings_copy_invoked = 0; - ECS_CTOR(Strings, ptr, { ptr->a = NULL; ptr->b = NULL; - strings_ctor_invoked ++; }) ECS_DTOR(Strings, ptr, { ecs_os_free(ptr->a); ecs_os_free(ptr->b); - strings_dtor_invoked ++; }) ECS_MOVE(Strings, dst, src, { @@ -7911,7 +7904,6 @@ ECS_MOVE(Strings, dst, src, { dst->b = src->b; src->a = NULL; dst->a = NULL; - strings_move_invoked ++; }) ECS_COPY(Strings, dst, src, { @@ -7919,7 +7911,6 @@ ECS_COPY(Strings, dst, src, { ecs_os_free(dst->b); dst->a = ecs_os_strdup(src->a); dst->b = ecs_os_strdup(src->b); - strings_copy_invoked ++; }) void Eval_partial_assign_nontrivial(void) { @@ -7948,9 +7939,6 @@ void Eval_partial_assign_nontrivial(void) { }); test_assert(s != 0); - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 2); - ecs_entity_t foo = ecs_lookup(world, "foo"); test_assert(foo != 0); @@ -7960,11 +7948,6 @@ void Eval_partial_assign_nontrivial(void) { test_str(p->b, "bar"); ecs_fini(world); - - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 3); - test_int(strings_move_invoked, 0); - test_int(strings_copy_invoked, 0); } void Eval_partial_assign_with(void) { @@ -8036,11 +8019,6 @@ void Eval_partial_assign_nontrivial_with(void) { }); test_assert(s != 0); - test_int(strings_ctor_invoked, 4); - test_int(strings_dtor_invoked, 2); - test_int(strings_move_invoked, 0); - test_int(strings_copy_invoked, 4); - { ecs_entity_t e = ecs_lookup(world, "foo"); test_assert(e != 0); @@ -8061,11 +8039,6 @@ void Eval_partial_assign_nontrivial_with(void) { } ecs_fini(world); - - test_int(strings_ctor_invoked, 4); - test_int(strings_dtor_invoked, 4); - test_int(strings_move_invoked, 0); - test_int(strings_copy_invoked, 4); } typedef struct LargeArray { @@ -8142,11 +8115,6 @@ void Eval_non_trivial_var_component(void) { }); test_assert(s != 0); - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 1); - test_int(strings_copy_invoked, 2); - test_int(strings_move_invoked, 0); - { ecs_entity_t e = ecs_lookup(world, "foo"); test_assert(e != 0); @@ -8167,11 +8135,6 @@ void Eval_non_trivial_var_component(void) { } ecs_fini(world); - - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 3); - test_int(strings_copy_invoked, 2); - test_int(strings_move_invoked, 0); } void Eval_non_trivial_var_with(void) { @@ -8208,11 +8171,6 @@ void Eval_non_trivial_var_with(void) { }); test_assert(s != 0); - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 1); - test_int(strings_copy_invoked, 2); - test_int(strings_move_invoked, 0); - { ecs_entity_t e = ecs_lookup(world, "foo"); test_assert(e != 0); @@ -8233,11 +8191,6 @@ void Eval_non_trivial_var_with(void) { } ecs_fini(world); - - test_int(strings_ctor_invoked, 3); - test_int(strings_dtor_invoked, 3); - test_int(strings_copy_invoked, 2); - test_int(strings_move_invoked, 0); } void Eval_update_template_w_tag(void) { From 9855d8e2c7373ce95fd35ea31fa533c761689646 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 04:44:20 +0000 Subject: [PATCH 31/83] Implement reusable script runtime object --- distr/flecs.c | 226 ++++++++++++++++------------ distr/flecs.h | 31 +++- include/flecs/addons/script.h | 31 +++- src/addons/script/expr/visit_eval.c | 30 ++-- src/addons/script/script.c | 31 +++- src/addons/script/script.h | 10 ++ src/addons/script/template.c | 21 +-- src/addons/script/visit_eval.c | 125 +++++++-------- src/addons/script/visit_eval.h | 9 +- 9 files changed, 324 insertions(+), 190 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d64d2f3aa3..5e20337c50 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5357,19 +5357,14 @@ int32_t ecs_script_node_line_number_( typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; - ecs_allocator_t *allocator; + ecs_script_runtime_t *r; ecs_script_template_t *template; ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; - ecs_vec_t using; - ecs_vec_t with; - ecs_vec_t with_type_info; - ecs_vec_t annot; ecs_entity_t with_relationship; int32_t with_relationship_sp; ecs_script_vars_t *vars; - ecs_stack_t stack; } ecs_script_eval_visitor_t; void flecs_script_eval_error_( @@ -5412,7 +5407,7 @@ void flecs_script_template_fini( ecs_script_template_t *template); void flecs_script_eval_visit_init( - ecs_script_impl_t *script, + const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v); void flecs_script_eval_visit_fini( @@ -5425,6 +5420,16 @@ int flecs_script_eval_node( #endif +struct ecs_script_runtime_t { + ecs_allocator_t allocator; + ecs_expr_stack_t expr_stack; + ecs_stack_t stack; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t with_type_info; + ecs_vec_t annot; +}; + struct ecs_script_template_t { /* Template handle */ ecs_entity_t entity; @@ -57400,8 +57405,37 @@ ecs_entity_t ecs_script_init( return 0; } +ecs_script_runtime_t* ecs_script_runtime_new(void) +{ + ecs_script_runtime_t *r = ecs_os_calloc_t(ecs_script_runtime_t); + flecs_expr_stack_init(&r->expr_stack); + flecs_allocator_init(&r->allocator); + flecs_stack_init(&r->stack); + ecs_vec_init_t(&r->allocator, &r->using, ecs_entity_t, 0); + ecs_vec_init_t(&r->allocator, &r->with, ecs_value_t, 0); + ecs_vec_init_t(&r->allocator, &r->with_type_info, ecs_type_info_t*, 0); + ecs_vec_init_t(&r->allocator, &r->annot, ecs_script_annot_t*, 0); + return r; +} + +void ecs_script_runtime_free( + ecs_script_runtime_t *r) +{ + flecs_expr_stack_fini(&r->expr_stack); + ecs_vec_fini_t(&r->allocator, &r->annot, ecs_script_annot_t*); + ecs_vec_fini_t(&r->allocator, &r->with, ecs_value_t); + ecs_vec_fini_t(&r->allocator, &r->with_type_info, ecs_type_info_t*); + ecs_vec_fini_t(&r->allocator, &r->using, ecs_entity_t); + flecs_allocator_fini(&r->allocator); + flecs_stack_fini(&r->stack); + ecs_os_free(r); +} + static -int EcsScript_serialize(const ecs_serializer_t *ser, const void *ptr) { +int EcsScript_serialize( + const ecs_serializer_t *ser, + const void *ptr) +{ const EcsScript *data = ptr; if (data->script) { ser->member(ser, "name"); @@ -58037,8 +58071,8 @@ void flecs_script_template_on_set( ecs_script_eval_visitor_t v; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); - ecs_vec_t prev_using = v.using; - v.using = template->using_; + ecs_vec_t prev_using = v.r->using; + v.r->using = template->using_; ecs_script_scope_t *scope = template->node->scope; @@ -58060,7 +58094,7 @@ void flecs_script_template_on_set( /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( - NULL, &v.stack, v.allocator); + NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ /* Populate properties from template members */ @@ -58109,7 +58143,7 @@ void flecs_script_template_on_set( data = ECS_OFFSET(data, ti->size); } - v.using = prev_using; + v.r->using = prev_using; flecs_script_eval_visit_fini(&v); } @@ -58141,7 +58175,8 @@ int flecs_script_template_eval_prop( } var->value.type = type; - var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_alloc( + &v->r->stack, ti->size, ti->alignment); var->type_info = ti; if (flecs_script_eval_expr(v, &node->expr, &var->value)) { @@ -58222,7 +58257,7 @@ int flecs_script_template_preprocess( ecs_visit_action_t prev_visit = v->base.visit; v->template = template; v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; - v->vars = flecs_script_vars_push(v->vars, &v->stack, v->allocator); + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; @@ -58237,13 +58272,13 @@ int flecs_script_template_hoist_using( { if (v->module) { ecs_vec_append_t( - v->allocator, &template->using_, ecs_entity_t)[0] = v->module; + &v->r->allocator, &template->using_, ecs_entity_t)[0] = v->module; } - int i, count = ecs_vec_count(&v->using); + int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { - ecs_vec_append_t(v->allocator, &template->using_, ecs_entity_t)[0] = - ecs_vec_get_t(&v->using, ecs_entity_t, i)[0]; + ecs_vec_append_t(&v->r->allocator, &template->using_, ecs_entity_t)[0] = + ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } return 0; @@ -59403,20 +59438,20 @@ ecs_value_t* flecs_script_with_append( ecs_script_eval_visitor_t *v, const ecs_type_info_t *ti) { - if (ecs_vec_count(&v->with)) { - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_remove_last(&v->with); + ecs_vec_remove_last(&v->r->with); } - ecs_vec_append_t(a, &v->with_type_info, const ecs_type_info_t*)[0] = ti; + ecs_vec_append_t(a, &v->r->with_type_info, const ecs_type_info_t*)[0] = ti; - ecs_vec_append_t(a, &v->with, ecs_value_t); - ecs_value_t *last = ecs_vec_append_t(a, &v->with, ecs_value_t); + ecs_vec_append_t(a, &v->r->with, ecs_value_t); + ecs_value_t *last = ecs_vec_append_t(a, &v->r->with, ecs_value_t); ecs_os_memset_t(last, 0, ecs_value_t); - return ecs_vec_get_t(&v->with, ecs_value_t, ecs_vec_count(&v->with) - 2); + return ecs_vec_get_t(&v->r->with, ecs_value_t, ecs_vec_count(&v->r->with) - 2); } static @@ -59425,34 +59460,34 @@ void flecs_script_with_set_count( ecs_script_eval_visitor_t *v, int32_t count) { - int32_t i = count, until = ecs_vec_count(&v->with) - 1; + int32_t i = count, until = ecs_vec_count(&v->r->with) - 1; for (; i < until; i ++) { - ecs_value_t *val = ecs_vec_get_t(&v->with, ecs_value_t, i); + ecs_value_t *val = ecs_vec_get_t(&v->r->with, ecs_value_t, i); ecs_type_info_t *ti = ecs_vec_get_t( - &v->with_type_info, ecs_type_info_t*, i)[0]; + &v->r->with_type_info, ecs_type_info_t*, i)[0]; if (ti && ti->hooks.dtor) { ti->hooks.dtor(val->ptr, 1, ti); } } if (count) { - ecs_value_t *last = ecs_vec_get_t(&v->with, ecs_value_t, count); + ecs_value_t *last = ecs_vec_get_t(&v->r->with, ecs_value_t, count); ecs_os_memset_t(last, 0, ecs_value_t); - ecs_vec_set_count_t(a, &v->with, ecs_value_t, count + 1); + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, count + 1); } else { - ecs_vec_set_count_t(a, &v->with, ecs_value_t, 0); + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, 0); } - ecs_vec_set_count_t(a, &v->with_type_info, ecs_type_info_t*, count); + ecs_vec_set_count_t(a, &v->r->with_type_info, ecs_type_info_t*, count); } static ecs_value_t* flecs_script_with_last( ecs_script_eval_visitor_t *v) { - int32_t count = ecs_vec_count(&v->with); + int32_t count = ecs_vec_count(&v->r->with); if (count) { - return ecs_vec_get_t(&v->with, ecs_value_t, count - 2); + return ecs_vec_get_t(&v->r->with, ecs_value_t, count - 2); } return NULL; } @@ -59461,12 +59496,12 @@ static int32_t flecs_script_with_count( ecs_script_eval_visitor_t *v) { - if (ecs_vec_count(&v->with)) { - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_vec_count(&v->with) - 1; + return ecs_vec_count(&v->r->with) - 1; } return 0; } @@ -59539,9 +59574,9 @@ ecs_entity_t flecs_script_find_entity( if (from) { return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { - int32_t i, using_count = ecs_vec_count(&v->using); + int32_t i, using_count = ecs_vec_count(&v->r->using); if (using_count) { - ecs_entity_t *using = ecs_vec_first(&v->using); + ecs_entity_t *using = ecs_vec_first(&v->r->using); for (i = using_count - 1; i >= 0; i --) { ecs_entity_t e = ecs_lookup_path_w_sep( v->world, using[i], path, NULL, NULL, false); @@ -59562,7 +59597,7 @@ ecs_entity_t flecs_script_create_entity( { ecs_value_t *with = NULL; if (flecs_script_with_count(v)) { - with = ecs_vec_first_t(&v->with, ecs_value_t); + with = ecs_vec_first_t(&v->r->with, ecs_value_t); } ecs_entity_desc_t desc = {0}; @@ -59713,7 +59748,8 @@ int flecs_script_eval_expr( .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars, - .type = value->type + .type = value->type, + .runtime = v->r }; if (!expr->type_info) { @@ -59742,7 +59778,7 @@ int flecs_script_eval_scope( { ecs_script_node_t *scope_parent = ecs_script_parent_node(v); ecs_entity_t prev_eval_parent = v->parent; - int32_t prev_using_count = ecs_vec_count(&v->using); + int32_t prev_using_count = ecs_vec_count(&v->r->using); for (int i = v->base.depth - 2; i >= 0; i --) { if (v->base.nodes[i]->kind == EcsAstScope) { @@ -59751,8 +59787,8 @@ int flecs_script_eval_scope( } } - ecs_allocator_t *a = v->allocator; - v->vars = flecs_script_vars_push(v->vars, &v->stack, a); + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); if (scope_parent && (scope_parent->kind == EcsAstEntity)) { if (!v->template) { @@ -59762,7 +59798,7 @@ int flecs_script_eval_scope( int result = ecs_script_visit_scope(v, node); - ecs_vec_set_count_t(a, &v->using, ecs_entity_t, prev_using_count); + ecs_vec_set_count_t(a, &v->r->using, ecs_entity_t, prev_using_count); v->vars = ecs_script_vars_pop(v->vars); v->parent = prev_eval_parent; @@ -59869,13 +59905,13 @@ int flecs_script_eval_entity( } } - int32_t i, count = ecs_vec_count(&v->annot); + int32_t i, count = ecs_vec_count(&v->r->annot); if (count) { - ecs_script_annot_t **annots = ecs_vec_first(&v->annot); + ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = 0; i < count ; i ++) { flecs_script_apply_annot(v, node->eval, annots[i]); } - ecs_vec_clear(&v->annot); + ecs_vec_clear(&v->r->annot); } if (ecs_script_visit_node(v, node->scope)) { @@ -60159,7 +60195,7 @@ int flecs_script_eval_with_var( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types *value = var->value; @@ -60179,7 +60215,7 @@ int flecs_script_eval_with_tag( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); value->type = node->id.eval; value->ptr = NULL; @@ -60200,7 +60236,7 @@ int flecs_script_eval_with_component( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); @@ -60213,7 +60249,7 @@ int flecs_script_eval_with_component( return -1; } - value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); value->type = ti->component; // Expression parser needs actual type if (ti->hooks.ctor) { @@ -60235,9 +60271,9 @@ int flecs_script_eval_with( ecs_script_eval_visitor_t *v, ecs_script_with_t *node) { - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; int32_t prev_with_count = flecs_script_with_count(v); - ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->r->stack); int result = 0; if (ecs_script_visit_scope(v, node->expressions)) { @@ -60259,7 +60295,7 @@ int flecs_script_eval_with( error: flecs_script_with_set_count(a, v, prev_with_count); - flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); + flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); return result; } @@ -60268,7 +60304,7 @@ int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; int32_t len = ecs_os_strlen(node->name); if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { @@ -60289,7 +60325,7 @@ int flecs_script_eval_using( int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_vec_append_t( - a, &v->using, ecs_entity_t)[0] = it.entities[i]; + a, &v->r->using, ecs_entity_t)[0] = it.entities[i]; } } @@ -60308,7 +60344,7 @@ int flecs_script_eval_using( } } - ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; + ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = from; } return 0; @@ -60361,7 +60397,7 @@ int flecs_script_eval_const( return -1; } - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; @@ -60390,7 +60426,7 @@ int flecs_script_eval_const( const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; @@ -60424,7 +60460,7 @@ int flecs_script_eval_pair_scope( return -1; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_entity_t prev_first = v->with_relationship; ecs_entity_t prev_second = 0; int32_t prev_with_relationship_sp = v->with_relationship_sp; @@ -60440,7 +60476,7 @@ int flecs_script_eval_pair_scope( } else { /* Get existing with element for current relationhip stack */ ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); + &v->r->with, ecs_value_t, v->with_relationship_sp); ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, ECS_INTERNAL_ERROR, NULL); prev_second = ECS_PAIR_SECOND(value->type); @@ -60454,7 +60490,7 @@ int flecs_script_eval_pair_scope( if (prev_second) { ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); + &v->r->with, ecs_value_t, v->with_relationship_sp); value->type = ecs_pair(first, prev_second); } else { flecs_script_with_set_count(a, v, v->with_relationship_sp); @@ -60512,8 +60548,8 @@ int flecs_script_eval_annot( return -1; } - ecs_allocator_t *a = v->allocator; - ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; + ecs_allocator_t *a = &v->r->allocator; + ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; return 0; } @@ -60582,41 +60618,31 @@ int flecs_script_eval_node( } void flecs_script_eval_visit_init( - ecs_script_impl_t *script, + const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v) { *v = (ecs_script_eval_visitor_t){ .base = { - .script = script, - .visit = (ecs_visit_action_t)flecs_script_eval_node - }, + .visit = (ecs_visit_action_t)flecs_script_eval_node, + .script = ECS_CONST_CAST(ecs_script_impl_t*, script) + }, .world = script->pub.world, - .allocator = &script->allocator + .r = ecs_script_runtime_new() }; - flecs_stack_init(&v->stack); - ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); - ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); - ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); - ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); - /* Always include flecs.meta */ - ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = + ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v) { - ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); - ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); - ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); - ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); - flecs_stack_fini(&v->stack); + ecs_script_runtime_free(v->r); } int ecs_script_eval( - ecs_script_t *script) + const ecs_script_t *script) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl(script); @@ -74915,16 +74941,24 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) { - ecs_expr_stack_t stack; - flecs_expr_stack_init(&stack); - flecs_expr_stack_push(&stack); + ecs_expr_stack_t *stack = NULL, stack_local; + if (desc && desc->runtime) { + stack = &desc->runtime->expr_stack; + ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); + } + if (!stack) { + stack = &stack_local; + flecs_expr_stack_init(stack); + } + + flecs_expr_stack_push(stack); - ecs_expr_value_t *val = flecs_expr_stack_result(&stack, node); + ecs_expr_value_t *val = flecs_expr_stack_result(stack, node); ecs_script_eval_ctx_t ctx = { .script = script, .world = script->world, - .stack = &stack, + .stack = stack, .desc = desc }; @@ -74958,12 +74992,16 @@ int flecs_script_expr_visit_eval( } } - flecs_expr_stack_pop(&stack); - flecs_expr_stack_fini(&stack); + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } return 0; error: - flecs_expr_stack_pop(&stack); - flecs_expr_stack_fini(&stack); + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } return -1; } diff --git a/distr/flecs.h b/distr/flecs.h index 9e2761e368..c910fd4906 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14257,6 +14257,9 @@ typedef struct ecs_script_t { const char *code; } ecs_script_t; +/* Runtime for executing scripts */ +typedef struct ecs_script_runtime_t ecs_script_runtime_t; + /** Script component. * This component is added to the entities of managed scripts and templates. */ @@ -14310,7 +14313,7 @@ ecs_script_t* ecs_script_parse( */ FLECS_API int ecs_script_eval( - ecs_script_t *script); + const ecs_script_t *script); /** Free script. * This operation frees a script object. @@ -14360,6 +14363,31 @@ int ecs_script_run_file( ecs_world_t *world, const char *filename); +/** Create runtime for script. + * A script runtime is a container for any data created during script + * evaluation. By default calling ecs_script_run() or ecs_script_eval() will + * create a runtime on the spot. A runtime can be created in advance and reused + * across multiple script evaluations to improve performance. + * + * When scripts are evaluated on multiple threads, each thread should have its + * own script runtime. + * + * A script runtime must be deleted with ecs_script_runtime_free(). + * + * @return A new script runtime. + */ +FLECS_API +ecs_script_runtime_t* ecs_script_runtime_new(void); + +/** Free script runtime. + * This operation frees a script runtime created by ecs_script_runtime_new(). + * + * @param runtime The runtime to free. + */ +FLECS_API +void ecs_script_runtime_free( + ecs_script_runtime_t *runtime); + /** Convert script AST to string. * This operation converts the script abstract syntax tree to a string, which * can be used to debug a script. @@ -14605,6 +14633,7 @@ typedef struct ecs_script_expr_run_desc_t { void *lookup_ctx; ecs_script_vars_t *vars; ecs_entity_t type; + ecs_script_runtime_t *runtime; } ecs_script_expr_run_desc_t; /** Run expression. diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 0cf6b4f603..55754d4d52 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -78,6 +78,9 @@ typedef struct ecs_script_t { const char *code; } ecs_script_t; +/* Runtime for executing scripts */ +typedef struct ecs_script_runtime_t ecs_script_runtime_t; + /** Script component. * This component is added to the entities of managed scripts and templates. */ @@ -131,7 +134,7 @@ ecs_script_t* ecs_script_parse( */ FLECS_API int ecs_script_eval( - ecs_script_t *script); + const ecs_script_t *script); /** Free script. * This operation frees a script object. @@ -181,6 +184,31 @@ int ecs_script_run_file( ecs_world_t *world, const char *filename); +/** Create runtime for script. + * A script runtime is a container for any data created during script + * evaluation. By default calling ecs_script_run() or ecs_script_eval() will + * create a runtime on the spot. A runtime can be created in advance and reused + * across multiple script evaluations to improve performance. + * + * When scripts are evaluated on multiple threads, each thread should have its + * own script runtime. + * + * A script runtime must be deleted with ecs_script_runtime_free(). + * + * @return A new script runtime. + */ +FLECS_API +ecs_script_runtime_t* ecs_script_runtime_new(void); + +/** Free script runtime. + * This operation frees a script runtime created by ecs_script_runtime_new(). + * + * @param runtime The runtime to free. + */ +FLECS_API +void ecs_script_runtime_free( + ecs_script_runtime_t *runtime); + /** Convert script AST to string. * This operation converts the script abstract syntax tree to a string, which * can be used to debug a script. @@ -426,6 +454,7 @@ typedef struct ecs_script_expr_run_desc_t { void *lookup_ctx; ecs_script_vars_t *vars; ecs_entity_t type; + ecs_script_runtime_t *runtime; } ecs_script_expr_run_desc_t; /** Run expression. diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 431e0c9311..287444cf01 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -514,16 +514,24 @@ int flecs_script_expr_visit_eval( const ecs_script_expr_run_desc_t *desc, ecs_value_t *out) { - ecs_expr_stack_t stack; - flecs_expr_stack_init(&stack); - flecs_expr_stack_push(&stack); + ecs_expr_stack_t *stack = NULL, stack_local; + if (desc && desc->runtime) { + stack = &desc->runtime->expr_stack; + ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); + } + if (!stack) { + stack = &stack_local; + flecs_expr_stack_init(stack); + } + + flecs_expr_stack_push(stack); - ecs_expr_value_t *val = flecs_expr_stack_result(&stack, node); + ecs_expr_value_t *val = flecs_expr_stack_result(stack, node); ecs_script_eval_ctx_t ctx = { .script = script, .world = script->world, - .stack = &stack, + .stack = stack, .desc = desc }; @@ -557,12 +565,16 @@ int flecs_script_expr_visit_eval( } } - flecs_expr_stack_pop(&stack); - flecs_expr_stack_fini(&stack); + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } return 0; error: - flecs_expr_stack_pop(&stack); - flecs_expr_stack_fini(&stack); + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } return -1; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index b891d74479..e84201baa3 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -233,8 +233,37 @@ ecs_entity_t ecs_script_init( return 0; } +ecs_script_runtime_t* ecs_script_runtime_new(void) +{ + ecs_script_runtime_t *r = ecs_os_calloc_t(ecs_script_runtime_t); + flecs_expr_stack_init(&r->expr_stack); + flecs_allocator_init(&r->allocator); + flecs_stack_init(&r->stack); + ecs_vec_init_t(&r->allocator, &r->using, ecs_entity_t, 0); + ecs_vec_init_t(&r->allocator, &r->with, ecs_value_t, 0); + ecs_vec_init_t(&r->allocator, &r->with_type_info, ecs_type_info_t*, 0); + ecs_vec_init_t(&r->allocator, &r->annot, ecs_script_annot_t*, 0); + return r; +} + +void ecs_script_runtime_free( + ecs_script_runtime_t *r) +{ + flecs_expr_stack_fini(&r->expr_stack); + ecs_vec_fini_t(&r->allocator, &r->annot, ecs_script_annot_t*); + ecs_vec_fini_t(&r->allocator, &r->with, ecs_value_t); + ecs_vec_fini_t(&r->allocator, &r->with_type_info, ecs_type_info_t*); + ecs_vec_fini_t(&r->allocator, &r->using, ecs_entity_t); + flecs_allocator_fini(&r->allocator); + flecs_stack_fini(&r->stack); + ecs_os_free(r); +} + static -int EcsScript_serialize(const ecs_serializer_t *ser, const void *ptr) { +int EcsScript_serialize( + const ecs_serializer_t *ser, + const void *ptr) +{ const EcsScript *data = ptr; if (data->script) { ser->member(ser, "name"); diff --git a/src/addons/script/script.h b/src/addons/script/script.h index bcfc92e9dd..94d92235db 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -57,6 +57,16 @@ typedef struct ecs_function_calldata_t { #include "visit.h" #include "visit_eval.h" +struct ecs_script_runtime_t { + ecs_allocator_t allocator; + ecs_expr_stack_t expr_stack; + ecs_stack_t stack; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t with_type_info; + ecs_vec_t annot; +}; + struct ecs_script_template_t { /* Template handle */ ecs_entity_t entity; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 90d94a1b8b..658d22fcb4 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -90,8 +90,8 @@ void flecs_script_template_on_set( ecs_script_eval_visitor_t v; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); - ecs_vec_t prev_using = v.using; - v.using = template->using_; + ecs_vec_t prev_using = v.r->using; + v.r->using = template->using_; ecs_script_scope_t *scope = template->node->scope; @@ -113,7 +113,7 @@ void flecs_script_template_on_set( /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( - NULL, &v.stack, v.allocator); + NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ /* Populate properties from template members */ @@ -162,7 +162,7 @@ void flecs_script_template_on_set( data = ECS_OFFSET(data, ti->size); } - v.using = prev_using; + v.r->using = prev_using; flecs_script_eval_visit_fini(&v); } @@ -194,7 +194,8 @@ int flecs_script_template_eval_prop( } var->value.type = type; - var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_alloc( + &v->r->stack, ti->size, ti->alignment); var->type_info = ti; if (flecs_script_eval_expr(v, &node->expr, &var->value)) { @@ -275,7 +276,7 @@ int flecs_script_template_preprocess( ecs_visit_action_t prev_visit = v->base.visit; v->template = template; v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; - v->vars = flecs_script_vars_push(v->vars, &v->stack, v->allocator); + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; @@ -290,13 +291,13 @@ int flecs_script_template_hoist_using( { if (v->module) { ecs_vec_append_t( - v->allocator, &template->using_, ecs_entity_t)[0] = v->module; + &v->r->allocator, &template->using_, ecs_entity_t)[0] = v->module; } - int i, count = ecs_vec_count(&v->using); + int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { - ecs_vec_append_t(v->allocator, &template->using_, ecs_entity_t)[0] = - ecs_vec_get_t(&v->using, ecs_entity_t, i)[0]; + ecs_vec_append_t(&v->r->allocator, &template->using_, ecs_entity_t)[0] = + ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } return 0; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 6ee4ed8e08..5e60929b02 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -35,20 +35,20 @@ ecs_value_t* flecs_script_with_append( ecs_script_eval_visitor_t *v, const ecs_type_info_t *ti) { - if (ecs_vec_count(&v->with)) { - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_remove_last(&v->with); + ecs_vec_remove_last(&v->r->with); } - ecs_vec_append_t(a, &v->with_type_info, const ecs_type_info_t*)[0] = ti; + ecs_vec_append_t(a, &v->r->with_type_info, const ecs_type_info_t*)[0] = ti; - ecs_vec_append_t(a, &v->with, ecs_value_t); - ecs_value_t *last = ecs_vec_append_t(a, &v->with, ecs_value_t); + ecs_vec_append_t(a, &v->r->with, ecs_value_t); + ecs_value_t *last = ecs_vec_append_t(a, &v->r->with, ecs_value_t); ecs_os_memset_t(last, 0, ecs_value_t); - return ecs_vec_get_t(&v->with, ecs_value_t, ecs_vec_count(&v->with) - 2); + return ecs_vec_get_t(&v->r->with, ecs_value_t, ecs_vec_count(&v->r->with) - 2); } static @@ -57,34 +57,34 @@ void flecs_script_with_set_count( ecs_script_eval_visitor_t *v, int32_t count) { - int32_t i = count, until = ecs_vec_count(&v->with) - 1; + int32_t i = count, until = ecs_vec_count(&v->r->with) - 1; for (; i < until; i ++) { - ecs_value_t *val = ecs_vec_get_t(&v->with, ecs_value_t, i); + ecs_value_t *val = ecs_vec_get_t(&v->r->with, ecs_value_t, i); ecs_type_info_t *ti = ecs_vec_get_t( - &v->with_type_info, ecs_type_info_t*, i)[0]; + &v->r->with_type_info, ecs_type_info_t*, i)[0]; if (ti && ti->hooks.dtor) { ti->hooks.dtor(val->ptr, 1, ti); } } if (count) { - ecs_value_t *last = ecs_vec_get_t(&v->with, ecs_value_t, count); + ecs_value_t *last = ecs_vec_get_t(&v->r->with, ecs_value_t, count); ecs_os_memset_t(last, 0, ecs_value_t); - ecs_vec_set_count_t(a, &v->with, ecs_value_t, count + 1); + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, count + 1); } else { - ecs_vec_set_count_t(a, &v->with, ecs_value_t, 0); + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, 0); } - ecs_vec_set_count_t(a, &v->with_type_info, ecs_type_info_t*, count); + ecs_vec_set_count_t(a, &v->r->with_type_info, ecs_type_info_t*, count); } static ecs_value_t* flecs_script_with_last( ecs_script_eval_visitor_t *v) { - int32_t count = ecs_vec_count(&v->with); + int32_t count = ecs_vec_count(&v->r->with); if (count) { - return ecs_vec_get_t(&v->with, ecs_value_t, count - 2); + return ecs_vec_get_t(&v->r->with, ecs_value_t, count - 2); } return NULL; } @@ -93,12 +93,12 @@ static int32_t flecs_script_with_count( ecs_script_eval_visitor_t *v) { - if (ecs_vec_count(&v->with)) { - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_vec_count(&v->with) - 1; + return ecs_vec_count(&v->r->with) - 1; } return 0; } @@ -171,9 +171,9 @@ ecs_entity_t flecs_script_find_entity( if (from) { return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { - int32_t i, using_count = ecs_vec_count(&v->using); + int32_t i, using_count = ecs_vec_count(&v->r->using); if (using_count) { - ecs_entity_t *using = ecs_vec_first(&v->using); + ecs_entity_t *using = ecs_vec_first(&v->r->using); for (i = using_count - 1; i >= 0; i --) { ecs_entity_t e = ecs_lookup_path_w_sep( v->world, using[i], path, NULL, NULL, false); @@ -194,7 +194,7 @@ ecs_entity_t flecs_script_create_entity( { ecs_value_t *with = NULL; if (flecs_script_with_count(v)) { - with = ecs_vec_first_t(&v->with, ecs_value_t); + with = ecs_vec_first_t(&v->r->with, ecs_value_t); } ecs_entity_desc_t desc = {0}; @@ -345,7 +345,8 @@ int flecs_script_eval_expr( .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars, - .type = value->type + .type = value->type, + .runtime = v->r }; if (!expr->type_info) { @@ -374,7 +375,7 @@ int flecs_script_eval_scope( { ecs_script_node_t *scope_parent = ecs_script_parent_node(v); ecs_entity_t prev_eval_parent = v->parent; - int32_t prev_using_count = ecs_vec_count(&v->using); + int32_t prev_using_count = ecs_vec_count(&v->r->using); for (int i = v->base.depth - 2; i >= 0; i --) { if (v->base.nodes[i]->kind == EcsAstScope) { @@ -383,8 +384,8 @@ int flecs_script_eval_scope( } } - ecs_allocator_t *a = v->allocator; - v->vars = flecs_script_vars_push(v->vars, &v->stack, a); + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); if (scope_parent && (scope_parent->kind == EcsAstEntity)) { if (!v->template) { @@ -394,7 +395,7 @@ int flecs_script_eval_scope( int result = ecs_script_visit_scope(v, node); - ecs_vec_set_count_t(a, &v->using, ecs_entity_t, prev_using_count); + ecs_vec_set_count_t(a, &v->r->using, ecs_entity_t, prev_using_count); v->vars = ecs_script_vars_pop(v->vars); v->parent = prev_eval_parent; @@ -501,13 +502,13 @@ int flecs_script_eval_entity( } } - int32_t i, count = ecs_vec_count(&v->annot); + int32_t i, count = ecs_vec_count(&v->r->annot); if (count) { - ecs_script_annot_t **annots = ecs_vec_first(&v->annot); + ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = 0; i < count ; i ++) { flecs_script_apply_annot(v, node->eval, annots[i]); } - ecs_vec_clear(&v->annot); + ecs_vec_clear(&v->r->annot); } if (ecs_script_visit_node(v, node->scope)) { @@ -791,7 +792,7 @@ int flecs_script_eval_with_var( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types *value = var->value; @@ -811,7 +812,7 @@ int flecs_script_eval_with_tag( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); value->type = node->id.eval; value->ptr = NULL; @@ -832,7 +833,7 @@ int flecs_script_eval_with_component( return 0; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); @@ -845,7 +846,7 @@ int flecs_script_eval_with_component( return -1; } - value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); value->type = ti->component; // Expression parser needs actual type if (ti->hooks.ctor) { @@ -867,9 +868,9 @@ int flecs_script_eval_with( ecs_script_eval_visitor_t *v, ecs_script_with_t *node) { - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; int32_t prev_with_count = flecs_script_with_count(v); - ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->r->stack); int result = 0; if (ecs_script_visit_scope(v, node->expressions)) { @@ -891,7 +892,7 @@ int flecs_script_eval_with( error: flecs_script_with_set_count(a, v, prev_with_count); - flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); + flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); return result; } @@ -900,7 +901,7 @@ int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; int32_t len = ecs_os_strlen(node->name); if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { @@ -921,7 +922,7 @@ int flecs_script_eval_using( int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_vec_append_t( - a, &v->using, ecs_entity_t)[0] = it.entities[i]; + a, &v->r->using, ecs_entity_t)[0] = it.entities[i]; } } @@ -940,7 +941,7 @@ int flecs_script_eval_using( } } - ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; + ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = from; } return 0; @@ -993,7 +994,7 @@ int flecs_script_eval_const( return -1; } - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; @@ -1022,7 +1023,7 @@ int flecs_script_eval_const( const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; @@ -1056,7 +1057,7 @@ int flecs_script_eval_pair_scope( return -1; } - ecs_allocator_t *a = v->allocator; + ecs_allocator_t *a = &v->r->allocator; ecs_entity_t prev_first = v->with_relationship; ecs_entity_t prev_second = 0; int32_t prev_with_relationship_sp = v->with_relationship_sp; @@ -1072,7 +1073,7 @@ int flecs_script_eval_pair_scope( } else { /* Get existing with element for current relationhip stack */ ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); + &v->r->with, ecs_value_t, v->with_relationship_sp); ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, ECS_INTERNAL_ERROR, NULL); prev_second = ECS_PAIR_SECOND(value->type); @@ -1086,7 +1087,7 @@ int flecs_script_eval_pair_scope( if (prev_second) { ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); + &v->r->with, ecs_value_t, v->with_relationship_sp); value->type = ecs_pair(first, prev_second); } else { flecs_script_with_set_count(a, v, v->with_relationship_sp); @@ -1144,8 +1145,8 @@ int flecs_script_eval_annot( return -1; } - ecs_allocator_t *a = v->allocator; - ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; + ecs_allocator_t *a = &v->r->allocator; + ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; return 0; } @@ -1214,41 +1215,31 @@ int flecs_script_eval_node( } void flecs_script_eval_visit_init( - ecs_script_impl_t *script, + const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v) { *v = (ecs_script_eval_visitor_t){ .base = { - .script = script, - .visit = (ecs_visit_action_t)flecs_script_eval_node - }, + .visit = (ecs_visit_action_t)flecs_script_eval_node, + .script = ECS_CONST_CAST(ecs_script_impl_t*, script) + }, .world = script->pub.world, - .allocator = &script->allocator + .r = ecs_script_runtime_new() }; - flecs_stack_init(&v->stack); - ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); - ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); - ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); - ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); - /* Always include flecs.meta */ - ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = + ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v) { - ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); - ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); - ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); - ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); - flecs_stack_fini(&v->stack); + ecs_script_runtime_free(v->r); } int ecs_script_eval( - ecs_script_t *script) + const ecs_script_t *script) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl(script); diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index a493be5708..e523904554 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -9,19 +9,14 @@ typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; - ecs_allocator_t *allocator; + ecs_script_runtime_t *r; ecs_script_template_t *template; ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; - ecs_vec_t using; - ecs_vec_t with; - ecs_vec_t with_type_info; - ecs_vec_t annot; ecs_entity_t with_relationship; int32_t with_relationship_sp; ecs_script_vars_t *vars; - ecs_stack_t stack; } ecs_script_eval_visitor_t; void flecs_script_eval_error_( @@ -64,7 +59,7 @@ void flecs_script_template_fini( ecs_script_template_t *template); void flecs_script_eval_visit_init( - ecs_script_impl_t *script, + const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v); void flecs_script_eval_visit_fini( From 30b077e36568e3e1b0657282063af87bde7ccbba Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 05:17:14 +0000 Subject: [PATCH 32/83] Fix more warnings --- distr/flecs.c | 117 +++++++++++++++++++++++++--- src/addons/script/expr/parser.c | 15 ++-- src/addons/script/expr/stack.c | 3 +- src/addons/script/expr/util.c | 76 ++++++++++++++++++ src/addons/script/expr/visit.h | 2 +- src/addons/script/expr/visit_fold.c | 12 ++- src/addons/script/expr/visit_type.c | 2 + src/addons/script/tokenizer.c | 3 + src/addons/script/visit_eval.c | 4 +- 9 files changed, 214 insertions(+), 20 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 5e20337c50..db9e79abed 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5123,7 +5123,7 @@ ecs_expr_cast_t* flecs_expr_cast( #define flecs_expr_visit_error(script, node, ...) \ ecs_parser_error( \ script->name, script->code, \ - ((ecs_expr_node_t*)node)->pos - script->code, \ + ((const ecs_expr_node_t*)node)->pos - script->code, \ __VA_ARGS__); int flecs_script_expr_visit_type( @@ -58494,6 +58494,8 @@ const char* flecs_script_token_kind_str( return "number "; case EcsTokNewline: return "newline"; + case EcsTokMember: + return "member"; case EcsTokEnd: return "end of script"; default: @@ -58549,6 +58551,7 @@ const char* flecs_script_token_str( case EcsTokString: return "string"; case EcsTokNumber: return "number"; case EcsTokNewline: return "newline"; + case EcsTokMember: return "member"; case EcsTokEnd: return "end of script"; default: return ""; @@ -60645,7 +60648,9 @@ int ecs_script_eval( const ecs_script_t *script) { ecs_script_eval_visitor_t v; - ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); flecs_script_eval_visit_init(impl, &v); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v); @@ -73776,7 +73781,6 @@ const char* flecs_script_parse_rhs( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, - ecs_expr_node_t *left, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out) { @@ -73909,9 +73913,13 @@ const char* flecs_script_parse_lhs( if (strchr(expr, '.') || strchr(expr, 'e')) { *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); } else if (expr[0] == '-') { - *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + char *end; + *out = (ecs_expr_node_t*)flecs_expr_int(parser, + strtoll(expr, &end, 10)); } else { - *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + char *end; + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, + strtoull(expr, &end, 10)); } break; } @@ -74017,7 +74025,7 @@ const char* flecs_script_parse_lhs( } /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs(parser, pos, tokenizer, *out, left_oper, out); + return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); error: return NULL; } @@ -74096,7 +74104,9 @@ int ecs_script_expr_eval( const ecs_script_expr_run_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, won't be writing to script */ + ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_expr_run_desc_t priv_desc = {0}; @@ -74185,7 +74195,6 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( - ecs_expr_stack_t *stack, ecs_expr_value_t *v) { const ecs_type_info_t *ti = v->type_info; @@ -74270,7 +74279,7 @@ void flecs_expr_stack_pop( } for (sp = end - 1; sp >= start; sp --) { - flecs_expr_value_free(stack, &stack->values[sp]); + flecs_expr_value_free(&stack->values[sp]); } flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); @@ -74340,18 +74349,65 @@ int flecs_value_move_to( return -1; } + int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator) { + (void)script; switch(operator) { case EcsTokNot: ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); *(bool*)out->ptr = !*(bool*)expr->ptr; break; + case EcsTokEnd: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokMatch: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } @@ -74418,6 +74474,35 @@ int flecs_value_binary( case EcsTokShiftRight: ECS_BINARY_INT_OP(left, right, out, >>); break; + case EcsTokEnd: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } @@ -75015,6 +75100,7 @@ int flecs_script_expr_visit_eval( #ifdef FLECS_SCRIPT +static void flecs_visit_fold_replace( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75025,6 +75111,7 @@ void flecs_visit_fold_replace( *node_ptr = with; } +static int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75073,6 +75160,7 @@ int flecs_expr_unary_visit_fold( return -1; } +static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75124,6 +75212,7 @@ int flecs_expr_binary_visit_fold( return -1; } +static int flecs_expr_cast_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75170,6 +75259,7 @@ int flecs_expr_cast_visit_fold( return -1; } +static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, @@ -75212,6 +75302,7 @@ int flecs_expr_initializer_pre_fold( return -1; } +static int flecs_expr_initializer_post_fold( ecs_script_t *script, ecs_expr_initializer_t *node, @@ -75249,6 +75340,7 @@ int flecs_expr_initializer_post_fold( return -1; } +static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75266,7 +75358,6 @@ int flecs_expr_initializer_visit_fold( * be folded into a literal. */ if (can_fold) { void *value = ecs_value_new(script->world, node->node.type); - ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; if (flecs_expr_initializer_post_fold(script, node, value)) { goto error; @@ -75284,6 +75375,7 @@ int flecs_expr_initializer_visit_fold( return -1; } +static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75322,6 +75414,7 @@ int flecs_expr_identifier_visit_fold( return -1; } +static int flecs_expr_function_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75338,6 +75431,7 @@ int flecs_expr_function_visit_fold( return -1; } +static int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -75354,6 +75448,7 @@ int flecs_expr_member_visit_fold( return -1; } +static int flecs_expr_element_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -76592,8 +76687,10 @@ int flecs_expr_member_visit_type( ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(cur); +#ifdef FLECS_DEBUG const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); +#endif node->offset = (uintptr_t)ecs_meta_get_ptr(cur); return 0; diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 8a23e79717..b062689a4f 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -198,7 +198,6 @@ const char* flecs_script_parse_rhs( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, - ecs_expr_node_t *left, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out) { @@ -331,9 +330,13 @@ const char* flecs_script_parse_lhs( if (strchr(expr, '.') || strchr(expr, 'e')) { *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); } else if (expr[0] == '-') { - *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + char *end; + *out = (ecs_expr_node_t*)flecs_expr_int(parser, + strtoll(expr, &end, 10)); } else { - *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + char *end; + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, + strtoull(expr, &end, 10)); } break; } @@ -439,7 +442,7 @@ const char* flecs_script_parse_lhs( } /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs(parser, pos, tokenizer, *out, left_oper, out); + return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); error: return NULL; } @@ -518,7 +521,9 @@ int ecs_script_expr_eval( const ecs_script_expr_run_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, won't be writing to script */ + ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_expr_run_desc_t priv_desc = {0}; diff --git a/src/addons/script/expr/stack.c b/src/addons/script/expr/stack.c index 65327696f0..8e1e173364 100644 --- a/src/addons/script/expr/stack.c +++ b/src/addons/script/expr/stack.c @@ -28,7 +28,6 @@ void flecs_expr_value_alloc( static void flecs_expr_value_free( - ecs_expr_stack_t *stack, ecs_expr_value_t *v) { const ecs_type_info_t *ti = v->type_info; @@ -113,7 +112,7 @@ void flecs_expr_stack_pop( } for (sp = end - 1; sp >= start; sp --) { - flecs_expr_value_free(stack, &stack->values[sp]); + flecs_expr_value_free(&stack->values[sp]); } flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 6cc68f0a93..ab0fb8ec94 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -62,18 +62,65 @@ int flecs_value_move_to( return -1; } + int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator) { + (void)script; switch(operator) { case EcsTokNot: ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); *(bool*)out->ptr = !*(bool*)expr->ptr; break; + case EcsTokEnd: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokMatch: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } @@ -140,6 +187,35 @@ int flecs_value_binary( case EcsTokShiftRight: ECS_BINARY_INT_OP(left, right, out, >>); break; + case EcsTokEnd: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokIdentifier: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index 7d1c468dff..ca6942303f 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -9,7 +9,7 @@ #define flecs_expr_visit_error(script, node, ...) \ ecs_parser_error( \ script->name, script->code, \ - ((ecs_expr_node_t*)node)->pos - script->code, \ + ((const ecs_expr_node_t*)node)->pos - script->code, \ __VA_ARGS__); int flecs_script_expr_visit_type( diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 79a760011c..353f4a339d 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -8,6 +8,7 @@ #ifdef FLECS_SCRIPT #include "../script.h" +static void flecs_visit_fold_replace( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -18,6 +19,7 @@ void flecs_visit_fold_replace( *node_ptr = with; } +static int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -66,6 +68,7 @@ int flecs_expr_unary_visit_fold( return -1; } +static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -117,6 +120,7 @@ int flecs_expr_binary_visit_fold( return -1; } +static int flecs_expr_cast_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -163,6 +167,7 @@ int flecs_expr_cast_visit_fold( return -1; } +static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, @@ -205,6 +210,7 @@ int flecs_expr_initializer_pre_fold( return -1; } +static int flecs_expr_initializer_post_fold( ecs_script_t *script, ecs_expr_initializer_t *node, @@ -242,6 +248,7 @@ int flecs_expr_initializer_post_fold( return -1; } +static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -259,7 +266,6 @@ int flecs_expr_initializer_visit_fold( * be folded into a literal. */ if (can_fold) { void *value = ecs_value_new(script->world, node->node.type); - ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; if (flecs_expr_initializer_post_fold(script, node, value)) { goto error; @@ -277,6 +283,7 @@ int flecs_expr_initializer_visit_fold( return -1; } +static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -315,6 +322,7 @@ int flecs_expr_identifier_visit_fold( return -1; } +static int flecs_expr_function_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -331,6 +339,7 @@ int flecs_expr_function_visit_fold( return -1; } +static int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -347,6 +356,7 @@ int flecs_expr_member_visit_fold( return -1; } +static int flecs_expr_element_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index f40535ff0d..0200eb138c 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -705,8 +705,10 @@ int flecs_expr_member_visit_type( ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(cur); +#ifdef FLECS_DEBUG const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); +#endif node->offset = (uintptr_t)ecs_meta_get_ptr(cur); return 0; diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 9a6e551418..9e1cec36be 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -81,6 +81,8 @@ const char* flecs_script_token_kind_str( return "number "; case EcsTokNewline: return "newline"; + case EcsTokMember: + return "member"; case EcsTokEnd: return "end of script"; default: @@ -136,6 +138,7 @@ const char* flecs_script_token_str( case EcsTokString: return "string"; case EcsTokNumber: return "number"; case EcsTokNewline: return "newline"; + case EcsTokMember: return "member"; case EcsTokEnd: return "end of script"; default: return ""; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 5e60929b02..5e6eba4888 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1242,7 +1242,9 @@ int ecs_script_eval( const ecs_script_t *script) { ecs_script_eval_visitor_t v; - ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); flecs_script_eval_visit_init(impl, &v); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v); From b39b1081eb1b289bc18124f181b114ebfc1b1441 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 06:58:03 +0000 Subject: [PATCH 33/83] Make expression folding optional --- distr/flecs.c | 140 ++++--- distr/flecs.h | 1 + include/flecs/addons/script.h | 1 + src/addons/script/expr/ast.h | 1 + src/addons/script/expr/parser.c | 10 +- src/addons/script/expr/visit_eval.c | 33 +- src/addons/script/expr/visit_fold.c | 43 +- src/addons/script/expr/visit_free.c | 10 + src/addons/script/expr/visit_type.c | 33 ++ src/addons/script/visit_to_str.c | 10 +- test/script/project.json | 8 + test/script/src/Deserialize.c | 538 ++++++++++++++++-------- test/script/src/Expr.c | 615 +++++++++++++++++----------- test/script/src/main.c | 22 +- 14 files changed, 946 insertions(+), 519 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index db9e79abed..0508b72ffa 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4998,6 +4998,7 @@ typedef struct ecs_expr_initializer_t { typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; + ecs_expr_node_t *expr; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { @@ -61242,8 +61243,14 @@ char* ecs_script_ast_to_str( { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (ecs_script_ast_to_buf(script, &buf)) { - goto error; + + if (flecs_script_impl(script)->expr) { + flecs_script_expr_to_str_buf( + script->world, flecs_script_impl(script)->expr, &buf); + } else { + if (ecs_script_ast_to_buf(script, &buf)) { + goto error; + } } return ecs_strbuf_get(&buf); @@ -74084,13 +74091,15 @@ ecs_script_t* ecs_script_expr_parse( goto error; } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + // printf("%s\n", ecs_script_ast_to_str(script)); - if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { - goto error; + if (!desc || !desc->disable_folding) { + if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { + goto error; + } } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + // printf("%s\n", ecs_script_ast_to_str(script)); return script; error: @@ -74579,11 +74588,9 @@ int flecs_expr_initializer_eval_static( goto error; } - ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; - /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ - ecs_entity_t type = elem_value->node.type; + ecs_entity_t type = elem->value->type; if (expr->owned) { if (ecs_value_move(ctx->world, type, @@ -74645,15 +74652,13 @@ int flecs_expr_initializer_eval_dynamic( goto error; } - ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; - if (elem->member) { ecs_meta_member(&cur, elem->member); } ecs_value_t v_elem_value = { - .ptr = elem_value->ptr, - .type = elem_value->node.type + .ptr = expr->value.ptr, + .type = expr->value.type }; if (ecs_meta_set_value(&cur, &v_elem_value)) { @@ -74753,6 +74758,15 @@ int flecs_expr_binary_visit_eval( return -1; } +static +int flecs_expr_identifier_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_identifier_t *node, + ecs_expr_value_t *out) +{ + return flecs_script_expr_visit_eval_priv(ctx, node->expr, out); +} + static int flecs_expr_variable_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -74905,10 +74919,15 @@ int flecs_expr_component_visit_eval( ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ - ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); + ecs_expr_node_t *index = node->index; + if (index->kind == EcsExprIdentifier) { + index = ((ecs_expr_identifier_t*)index)->expr; + } + + ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; - ecs_entity_t component = ((ecs_expr_value_node_t*)node->index)->storage.entity; + ecs_entity_t component = ((ecs_expr_value_node_t*)index)->storage.entity; ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, @@ -74970,6 +74989,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit_eval( + ctx, (ecs_expr_identifier_t*)node, out)) + { + goto error; + } break; case EcsExprVariable: if (flecs_expr_variable_visit_eval( @@ -75187,16 +75211,6 @@ int flecs_expr_binary_visit_fold( ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); - /* Handle bitmask separately since it's not done by switch */ - if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { - ecs_assert(node->left->type == node->node.type, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->right->type == node->node.type, - ECS_INTERNAL_ERROR, NULL); - result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; - goto done; - } - ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; @@ -75205,7 +75219,6 @@ int flecs_expr_binary_visit_fold( goto error; } -done: flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: @@ -75381,37 +75394,17 @@ int flecs_expr_identifier_visit_fold( ecs_expr_node_t **node_ptr, const ecs_script_expr_run_desc_t *desc) { - ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; - ecs_entity_t type = node->node.type; + (void)desc; - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); + ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; - if (type == ecs_id(ecs_entity_t)) { - result->storage.entity = desc->lookup_action( - script->world, node->value, desc->lookup_ctx); - if (!result->storage.entity) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; - } - result->ptr = &result->storage.entity; - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&cur, node->value)) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); - goto error; - } - result->ptr = &result->storage.u64; + ecs_expr_node_t *expr = node->expr; + if (expr) { + node->expr = NULL; + flecs_visit_fold_replace(script, node_ptr, expr); } - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - return 0; -error: - return -1; } static @@ -75584,6 +75577,14 @@ void flecs_expr_binary_visit_free( flecs_script_expr_visit_free(script, node->right); } +static +void flecs_expr_identifier_visit_free( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + static void flecs_expr_function_visit_free( ecs_script_t *script, @@ -75650,6 +75651,8 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_binary_t, node); break; case EcsExprIdentifier: + flecs_expr_identifier_visit_free( + script, (ecs_expr_identifier_t*)node); flecs_free_t(a, ecs_expr_identifier_t, node); break; case EcsExprVariable: @@ -76499,6 +76502,10 @@ int flecs_expr_binary_visit_type( goto error; } + if (ecs_get(script->world, result_type, EcsBitmask) != NULL) { + operand_type = ecs_id(ecs_u64_t); + } + if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); @@ -76531,7 +76538,36 @@ int flecs_expr_identifier_visit_type( *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); } + ecs_entity_t type = node->node.type; + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + + if (type == ecs_id(ecs_entity_t)) { + result->storage.entity = desc->lookup_action( + script->world, node->value, desc->lookup_ctx); + if (!result->storage.entity) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + result->ptr = &result->storage.entity; + } else { + ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( + script->world, type, &result->storage.u64); + if (ecs_meta_set_string(&tmp_cur, node->value)) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } + result->ptr = &result->storage.u64; + } + + node->expr = (ecs_expr_node_t*)result; + return 0; +error: + return -1; } static diff --git a/distr/flecs.h b/distr/flecs.h index c910fd4906..94e32e4e24 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14633,6 +14633,7 @@ typedef struct ecs_script_expr_run_desc_t { void *lookup_ctx; ecs_script_vars_t *vars; ecs_entity_t type; + bool disable_folding; ecs_script_runtime_t *runtime; } ecs_script_expr_run_desc_t; diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 55754d4d52..2513b8c7c6 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -454,6 +454,7 @@ typedef struct ecs_script_expr_run_desc_t { void *lookup_ctx; ecs_script_vars_t *vars; ecs_entity_t type; + bool disable_folding; ecs_script_runtime_t *runtime; } ecs_script_expr_run_desc_t; diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index e94f8b2d53..058a3f7989 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -53,6 +53,7 @@ typedef struct ecs_expr_initializer_t { typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; + ecs_expr_node_t *expr; } ecs_expr_identifier_t; typedef struct ecs_expr_variable_t { diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index b062689a4f..9bf58822d2 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -501,13 +501,15 @@ ecs_script_t* ecs_script_expr_parse( goto error; } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + // printf("%s\n", ecs_script_ast_to_str(script)); - if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { - goto error; + if (!desc || !desc->disable_folding) { + if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { + goto error; + } } - // printf("%s\n", ecs_script_expr_to_str(world, out)); + // printf("%s\n", ecs_script_ast_to_str(script)); return script; error: diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 287444cf01..49c414e5d0 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -67,11 +67,9 @@ int flecs_expr_initializer_eval_static( goto error; } - ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; - /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ - ecs_entity_t type = elem_value->node.type; + ecs_entity_t type = elem->value->type; if (expr->owned) { if (ecs_value_move(ctx->world, type, @@ -133,15 +131,13 @@ int flecs_expr_initializer_eval_dynamic( goto error; } - ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; - if (elem->member) { ecs_meta_member(&cur, elem->member); } ecs_value_t v_elem_value = { - .ptr = elem_value->ptr, - .type = elem_value->node.type + .ptr = expr->value.ptr, + .type = expr->value.type }; if (ecs_meta_set_value(&cur, &v_elem_value)) { @@ -241,6 +237,15 @@ int flecs_expr_binary_visit_eval( return -1; } +static +int flecs_expr_identifier_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_identifier_t *node, + ecs_expr_value_t *out) +{ + return flecs_script_expr_visit_eval_priv(ctx, node->expr, out); +} + static int flecs_expr_variable_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -393,10 +398,15 @@ int flecs_expr_component_visit_eval( ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ - ecs_assert(node->index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); + ecs_expr_node_t *index = node->index; + if (index->kind == EcsExprIdentifier) { + index = ((ecs_expr_identifier_t*)index)->expr; + } + + ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; - ecs_entity_t component = ((ecs_expr_value_node_t*)node->index)->storage.entity; + ecs_entity_t component = ((ecs_expr_value_node_t*)index)->storage.entity; ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, @@ -458,6 +468,11 @@ int flecs_script_expr_visit_eval_priv( } break; case EcsExprIdentifier: + if (flecs_expr_identifier_visit_eval( + ctx, (ecs_expr_identifier_t*)node, out)) + { + goto error; + } break; case EcsExprVariable: if (flecs_expr_variable_visit_eval( diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 353f4a339d..c0128ff729 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -95,16 +95,6 @@ int flecs_expr_binary_visit_fold( ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); - /* Handle bitmask separately since it's not done by switch */ - if (ecs_get(script->world, node->node.type, EcsBitmask) != NULL) { - ecs_assert(node->left->type == node->node.type, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->right->type == node->node.type, - ECS_INTERNAL_ERROR, NULL); - result->storage.u32 = *(uint32_t*)left->ptr | *(uint32_t*)right->ptr; - goto done; - } - ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; @@ -113,7 +103,6 @@ int flecs_expr_binary_visit_fold( goto error; } -done: flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: @@ -289,37 +278,17 @@ int flecs_expr_identifier_visit_fold( ecs_expr_node_t **node_ptr, const ecs_script_expr_run_desc_t *desc) { + (void)desc; + ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; - ecs_entity_t type = node->node.type; - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); - - if (type == ecs_id(ecs_entity_t)) { - result->storage.entity = desc->lookup_action( - script->world, node->value, desc->lookup_ctx); - if (!result->storage.entity) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; - } - result->ptr = &result->storage.entity; - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&cur, node->value)) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); - goto error; - } - result->ptr = &result->storage.u64; + ecs_expr_node_t *expr = node->expr; + if (expr) { + node->expr = NULL; + flecs_visit_fold_replace(script, node_ptr, expr); } - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - return 0; -error: - return -1; } static diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 51f868b346..62c1dc16cf 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -51,6 +51,14 @@ void flecs_expr_binary_visit_free( flecs_script_expr_visit_free(script, node->right); } +static +void flecs_expr_identifier_visit_free( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + flecs_script_expr_visit_free(script, node->expr); +} + static void flecs_expr_function_visit_free( ecs_script_t *script, @@ -117,6 +125,8 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_binary_t, node); break; case EcsExprIdentifier: + flecs_expr_identifier_visit_free( + script, (ecs_expr_identifier_t*)node); flecs_free_t(a, ecs_expr_identifier_t, node); break; case EcsExprVariable: diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 0200eb138c..392c5507e3 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -517,6 +517,10 @@ int flecs_expr_binary_visit_type( goto error; } + if (ecs_get(script->world, result_type, EcsBitmask) != NULL) { + operand_type = ecs_id(ecs_u64_t); + } + if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); @@ -549,7 +553,36 @@ int flecs_expr_identifier_visit_type( *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); } + ecs_entity_t type = node->node.type; + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + + if (type == ecs_id(ecs_entity_t)) { + result->storage.entity = desc->lookup_action( + script->world, node->value, desc->lookup_ctx); + if (!result->storage.entity) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + result->ptr = &result->storage.entity; + } else { + ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( + script->world, type, &result->storage.u64); + if (ecs_meta_set_string(&tmp_cur, node->value)) { + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } + result->ptr = &result->storage.u64; + } + + node->expr = (ecs_expr_node_t*)result; + return 0; +error: + return -1; } static diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 689bab4837..cc245b1926 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -406,8 +406,14 @@ char* ecs_script_ast_to_str( { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (ecs_script_ast_to_buf(script, &buf)) { - goto error; + + if (flecs_script_impl(script)->expr) { + flecs_script_expr_to_str_buf( + script->world, flecs_script_impl(script)->expr, &buf); + } else { + if (ecs_script_ast_to_buf(script, &buf)) { + goto error; + } } return ecs_strbuf_get(&buf); diff --git a/test/script/project.json b/test/script/project.json index 4a780b6812..4ce011a8d5 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -376,6 +376,10 @@ ] }, { "id": "Expr", + "params": { + "folding": ["enabled", "disabled"] + }, + "setup": true, "testcases": [ "add_2_int_literals", "add_2_int_literals_twice", @@ -631,6 +635,10 @@ ] }, { "id": "Deserialize", + "params": { + "folding": ["enabled", "disabled"] + }, + "setup": true, "testcases": [ "bool", "byte", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index edf2296a50..52ac054661 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -1,11 +1,27 @@ #include +static ecs_query_cache_kind_t disable_folding = false; + +void Deserialize_setup(void) { + const char *folding_param = test_param("folding"); + if (folding_param) { + if (!strcmp(folding_param, "disabled")) { + disable_folding = true; + } else if (!strcmp(folding_param, "enabled")) { + // already set to default + } else { + printf("unexpected value for folding '%s'\n", folding_param); + } + } +} + void Deserialize_bool(void) { ecs_world_t *world = ecs_init(); bool value = false; - - const char *ptr = ecs_script_expr_run(world, "true", &ecs_value_ptr(ecs_bool_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "true", &ecs_value_ptr(ecs_bool_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -18,8 +34,9 @@ void Deserialize_byte(void) { ecs_world_t *world = ecs_init(); ecs_byte_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_byte_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_byte_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -32,8 +49,9 @@ void Deserialize_char(void) { ecs_world_t *world = ecs_init(); ecs_char_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_char_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_char_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -46,8 +64,9 @@ void Deserialize_char_literal(void) { ecs_world_t *world = ecs_init(); ecs_char_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "\"a\"", &ecs_value_ptr(ecs_char_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "\"a\"", &ecs_value_ptr(ecs_char_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 'a'); @@ -59,8 +78,9 @@ void Deserialize_i8(void) { ecs_world_t *world = ecs_init(); ecs_i8_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_i8_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -72,8 +92,9 @@ void Deserialize_i16(void) { ecs_world_t *world = ecs_init(); ecs_i16_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_i16_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_i16_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -85,8 +106,9 @@ void Deserialize_i32(void) { ecs_world_t *world = ecs_init(); ecs_i32_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_i32_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_i32_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -98,8 +120,9 @@ void Deserialize_i64(void) { ecs_world_t *world = ecs_init(); ecs_i64_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_i64_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_i64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -111,8 +134,9 @@ void Deserialize_iptr(void) { ecs_world_t *world = ecs_init(); ecs_iptr_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_iptr_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_iptr_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -124,8 +148,9 @@ void Deserialize_u8(void) { ecs_world_t *world = ecs_init(); ecs_u8_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_u8_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_u8_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -137,8 +162,9 @@ void Deserialize_u16(void) { ecs_world_t *world = ecs_init(); ecs_u16_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_u16_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_u16_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -150,8 +176,9 @@ void Deserialize_u32(void) { ecs_world_t *world = ecs_init(); ecs_u32_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_u32_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_u32_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -164,24 +191,27 @@ void Deserialize_u64(void) { { ecs_u64_t value = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "0", &ecs_value_ptr(ecs_u64_t, &value), NULL); + world, "0", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 0); } { ecs_u64_t value = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "10", &ecs_value_ptr(ecs_u64_t, &value), NULL); + world, "10", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); } { ecs_u64_t value = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "2366700781656087864", &ecs_value_ptr(ecs_u64_t, &value), NULL); + world, "2366700781656087864", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 2366700781656087864); } @@ -193,8 +223,9 @@ void Deserialize_uptr(void) { ecs_world_t *world = ecs_init(); ecs_uptr_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10", &ecs_value_ptr(ecs_uptr_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10", &ecs_value_ptr(ecs_uptr_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -206,8 +237,9 @@ void Deserialize_float(void) { ecs_world_t *world = ecs_init(); ecs_f32_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10.5", &ecs_value_ptr(ecs_f32_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10.5", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr != NULL); test_flt(value, 10.5); @@ -219,8 +251,9 @@ void Deserialize_double(void) { ecs_world_t *world = ecs_init(); ecs_f64_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "10.5", &ecs_value_ptr(ecs_f64_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "10.5", &ecs_value_ptr(ecs_f64_t, &value), &desc); test_assert(ptr != NULL); test_flt(value, 10.5); @@ -232,8 +265,9 @@ void Deserialize_negative_int(void) { ecs_world_t *world = ecs_init(); ecs_i8_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "-10", &ecs_value_ptr(ecs_i8_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "-10", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr != NULL); test_int(value, -10); @@ -245,8 +279,9 @@ void Deserialize_negative_float(void) { ecs_world_t *world = ecs_init(); ecs_f32_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "-10.5", &ecs_value_ptr(ecs_f32_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "-10.5", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr != NULL); test_flt(value, -10.5); @@ -260,8 +295,9 @@ void Deserialize_invalid_i8(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_i8_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -273,8 +309,9 @@ void Deserialize_invalid_i16(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_i16_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_i16_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -286,8 +323,9 @@ void Deserialize_invalid_i32(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_i32_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_i32_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -299,8 +337,9 @@ void Deserialize_invalid_i64(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_i64_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_i64_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -312,8 +351,9 @@ void Deserialize_invalid_iptr(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_iptr_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_iptr_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -325,8 +365,9 @@ void Deserialize_invalid_u8(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_u8_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_u8_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -338,8 +379,9 @@ void Deserialize_invalid_u16(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_u16_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_u16_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -351,8 +393,9 @@ void Deserialize_invalid_u32(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_u32_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_u32_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -364,8 +407,9 @@ void Deserialize_invalid_u64(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_u64_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -377,8 +421,9 @@ void Deserialize_invalid_uptr(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_uptr_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_uptr_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -390,8 +435,9 @@ void Deserialize_invalid_float(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_f32_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -403,8 +449,9 @@ void Deserialize_invalid_double(void) { uint64_t value = 0; ecs_log_set_level(-4); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( - world, "a", &ecs_value_ptr(ecs_f64_t, &value), NULL); + world, "a", &ecs_value_ptr(ecs_f64_t, &value), &desc); test_assert(ptr == NULL); ecs_fini(world); @@ -414,8 +461,9 @@ void Deserialize_string(void) { ecs_world_t *world = ecs_init(); ecs_string_t value = 0; - - const char *ptr = ecs_script_expr_run(world, "\"Hello World\"", &ecs_value_ptr(ecs_string_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "\"Hello World\"", &ecs_value_ptr(ecs_string_t, &value), &desc); test_assert(ptr != NULL); test_str(value, "Hello World"); @@ -429,11 +477,14 @@ void Deserialize_entity(void) { ecs_world_t *world = ecs_init(); ecs_entity_t value = 0; - const char *ptr = ecs_script_expr_run(world, "flecs.core", &ecs_value_ptr(ecs_entity_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "flecs.core", &ecs_value_ptr(ecs_entity_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, EcsFlecsCore); - ptr = ecs_script_expr_run(world, "0", &ecs_value_ptr(ecs_entity_t, &value), NULL); + ptr = ecs_script_expr_run(world, + "0", &ecs_value_ptr(ecs_entity_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, 0); @@ -444,11 +495,14 @@ void Deserialize_id(void) { ecs_world_t *world = ecs_init(); ecs_id_t value = 0; - const char *ptr = ecs_script_expr_run(world, "flecs.core", &ecs_value_ptr(ecs_id_t, &value), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "flecs.core", &ecs_value_ptr(ecs_id_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, EcsFlecsCore); - ptr = ecs_script_expr_run(world, "0", &ecs_value_ptr(ecs_id_t, &value), NULL); + ptr = ecs_script_expr_run(world, + "0", &ecs_value_ptr(ecs_id_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, 0); @@ -473,21 +527,30 @@ void Deserialize_enum(void) { { T value = 0; - const char *ptr = ecs_script_expr_run(world, "Red", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Red", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value, Red); } { T value = 0; - const char *ptr = ecs_script_expr_run(world, "Blue", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Blue", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value, Blue); } { T value = 0; - const char *ptr = ecs_script_expr_run(world, "Green", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Green", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value, Green); } @@ -495,7 +558,10 @@ void Deserialize_enum(void) { { ecs_log_set_level(-4); T value = 0; - const char *ptr = ecs_script_expr_run(world, "Black", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Black", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); } @@ -519,42 +585,60 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "Lettuce", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Lettuce", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, Lettuce); } { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "Lettuce|Bacon", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Lettuce|Bacon", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, Lettuce|Bacon); } { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "Lettuce|Bacon|Tomato|Cheese", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Lettuce|Bacon|Tomato|Cheese", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, Lettuce|Bacon|Tomato|Cheese); } { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "Lettuce | Bacon | Tomato | Cheese", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Lettuce | Bacon | Tomato | Cheese", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, Lettuce|Bacon|Tomato|Cheese); } { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "BLT", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "BLT", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, Lettuce|Bacon|Tomato); } { uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "0", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "0", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value, 0); } @@ -562,7 +646,10 @@ void Deserialize_bitmask(void) { { ecs_log_set_level(-4); uint32_t value = 0; - const char *ptr = ecs_script_expr_run(world, "Foo", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "Foo", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); } @@ -599,21 +686,30 @@ void Deserialize_struct_enum(void) { { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v: Red}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v: Red}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value.v, Red); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v: Blue}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v: Blue}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value.v, Blue); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v: Green}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v: Green}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_int(value.v, Green); } @@ -621,7 +717,10 @@ void Deserialize_struct_enum(void) { { ecs_log_set_level(-4); T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v: Black}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v: Black}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); } @@ -659,35 +758,50 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:Lettuce}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:Lettuce}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value.v, Lettuce); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:Lettuce|Bacon}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:Lettuce|Bacon}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value.v, Lettuce|Bacon); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:Lettuce|Bacon|Tomato|Cheese}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:Lettuce|Bacon|Tomato|Cheese}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value.v, Lettuce|Bacon|Tomato|Cheese); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:BLT}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:BLT}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value.v, Lettuce|Bacon|Tomato); } { T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:0}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:0}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_uint(value.v, 0); } @@ -695,7 +809,10 @@ void Deserialize_struct_bitmask(void) { { ecs_log_set_level(-4); T value = {0}; - const char *ptr = ecs_script_expr_run(world, "{v:Foo\"}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{v:Foo\"}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); } @@ -717,8 +834,10 @@ void Deserialize_struct_i32(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{10}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{10}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -742,8 +861,10 @@ void Deserialize_struct_i32_neg(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{-10}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{-10}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -769,8 +890,10 @@ void Deserialize_struct_i32_i32(void) { }); T value = {0, 0}; - - const char *ptr = ecs_script_expr_run(world, "{10, 20}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{10, 20}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -795,8 +918,10 @@ void Deserialize_struct_string(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{\"Hello World\"}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{\"Hello World\"}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -822,9 +947,10 @@ void Deserialize_struct_string_from_name(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{flecs.core.name()}", - &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{flecs.core.name()}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -850,9 +976,10 @@ void Deserialize_struct_string_from_path(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{flecs.core.path()}", - &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{flecs.core.path()}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -884,9 +1011,9 @@ void Deserialize_struct_string_from_var(void) { ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_string_t); *(char**)foo->value.ptr = "Hello World"; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - - const char *ptr = ecs_script_expr_run(world, "{$foo}", + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{$foo}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -916,8 +1043,10 @@ void Deserialize_struct_entity(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{flecs.core}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{flecs.core}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -941,8 +1070,10 @@ void Deserialize_struct_id(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{flecs.core}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{flecs.core}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -977,8 +1108,10 @@ void Deserialize_struct_nested_i32(void) { }); T value = {{0}}; - - const char *ptr = ecs_script_expr_run(world, "{{10}}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{{10}}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1015,8 +1148,10 @@ void Deserialize_struct_nested_i32_i32(void) { }); T value = {{0}}; - - const char *ptr = ecs_script_expr_run(world, "{{10, 20}}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{{10, 20}}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1056,8 +1191,10 @@ void Deserialize_struct_2_nested_i32_i32(void) { }); T value = {{0}}; - - const char *ptr = ecs_script_expr_run(world, "{{10, 20}, {30, 40}}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{{10, 20}, {30, 40}}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1084,8 +1221,10 @@ void Deserialize_struct_member_i32(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{x: 10}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{x: 10}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1109,8 +1248,10 @@ void Deserialize_struct_member_i32_neg(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{x: -10}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{x: -10}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1136,8 +1277,10 @@ void Deserialize_struct_member_i32_i32(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{x: 10, y: 20}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{x: 10, y: 20}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1173,8 +1316,10 @@ void Deserialize_struct_member_nested_i32(void) { }); T value = {{0}}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: {x: 10}}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: {x: 10}}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1211,8 +1356,10 @@ void Deserialize_struct_member_nested_i32_i32(void) { }); T value = {{0}}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: {x: 10, y: 20}}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: {x: 10, y: 20}}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1258,8 +1405,8 @@ void Deserialize_struct_member_2_nested_i32_i32(void) { LINE " n_1: {x: 10, y: 20}," LINE " n_2: {x: 30, y: 40}" LINE "}"; - - const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1296,10 +1443,10 @@ void Deserialize_struct_from_var(void) { var->x = 10; var->y = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - { - const char *ptr = ecs_script_expr_run(world, "$foo", + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "$foo", &(ecs_value_t){ecs_id(T), &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1334,10 +1481,11 @@ void Deserialize_struct_member_from_var(void) { T *var = foo->value.ptr; var->value = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ $foo }", + const char *ptr = ecs_script_expr_run(world, + "{ $foo }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1347,7 +1495,8 @@ void Deserialize_struct_member_from_var(void) { value.value = 0; { - const char *ptr = ecs_script_expr_run(world, "{ value: $foo }", + const char *ptr = ecs_script_expr_run(world, + "{ value: $foo }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1381,10 +1530,11 @@ void Deserialize_struct_member_auto_var(void) { T *var = foo->value.ptr; var->value = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ value: $ }", + const char *ptr = ecs_script_expr_run(world, + "{ value: $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1421,10 +1571,11 @@ void Deserialize_struct_member_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ x: $, y: $ }", + const char *ptr = ecs_script_expr_run(world, + "{ x: $, y: $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1471,10 +1622,11 @@ void Deserialize_struct_nested_member_auto_var(void) { ecs_script_var_t *foo = ecs_script_vars_define(vars, "x", ecs_i32_t); *(int32_t*)foo->value.ptr = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ stop: { x: $ }}", + const char *ptr = ecs_script_expr_run(world, + "{ stop: { x: $ }}", &(ecs_value_t){ecs_id(Line), &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1525,10 +1677,11 @@ void Deserialize_struct_nested_member_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ stop: { x: $, y: $ }}", + const char *ptr = ecs_script_expr_run(world, + "{ stop: { x: $, y: $ }}", &(ecs_value_t){ecs_id(Line), &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1568,10 +1721,11 @@ void Deserialize_struct_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, "{ $, $ }", + const char *ptr = ecs_script_expr_run(world, + "{ $, $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1620,8 +1774,8 @@ void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { LINE " n_2: {x: 30, y: 40}," LINE " n_1: {x: 10, y: 20}" LINE "}"; - - const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1648,8 +1802,10 @@ void Deserialize_struct_i32_array_3(void) { }); T value = {0}; - - const char *ptr = ecs_script_expr_run(world, "{x: [10, 20, 30]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{x: [10, 20, 30]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1686,8 +1842,10 @@ void Deserialize_struct_struct_i32_array_3(void) { }); T value = {{{0}}}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: [{x: 10}, {x: 20}, {x: 30}]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: [{x: 10}, {x: 20}, {x: 30}]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1726,8 +1884,10 @@ void Deserialize_struct_struct_i32_i32_array_3(void) { }); T value = {{{0}}}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}, {x: 50, y: 60}]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}, {x: 50, y: 60}]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1764,8 +1924,10 @@ void Deserialize_struct_w_array_type_i32_i32(void) { }); T value = {{ 0 }}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: [10, 20]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: [10, 20]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1800,8 +1962,10 @@ void Deserialize_struct_w_2_array_type_i32_i32(void) { }); T value = {{ 0 }}; - - const char *ptr = ecs_script_expr_run(world, "{n_1: [10, 20], n_2: [30, 40]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: [10, 20], n_2: [30, 40]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1855,8 +2019,10 @@ void Deserialize_struct_w_array_type_struct(void) { test_assert(t != 0); T value; - - const char *ptr = ecs_script_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}]}", &(ecs_value_t){t, &value}, NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}]}", + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1912,10 +2078,10 @@ void Deserialize_struct_w_2_array_type_struct(void) { test_assert(t != 0); T value; - + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}], n_2: [{x: 50, y: 60}, {x: 70, y: 80}]}", - &(ecs_value_t){t, &value}, NULL); + &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1946,9 +2112,10 @@ void Deserialize_array_i32_2(void) { test_assert(ecs_id(Ints) != 0); Ints value = {0, 0}; - + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, - "[10, 20]", &(ecs_value_t){ecs_id(Ints), &value}, NULL); + "[10, 20]", + &(ecs_value_t){ecs_id(Ints), &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1972,9 +2139,10 @@ void Deserialize_array_string_2(void) { test_assert(ecs_id(Strings) != 0); Strings value = {0, 0}; - + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, - "[\"Hello\", \"World\"]", &(ecs_value_t){ecs_id(Strings), &value}, NULL); + "[\"Hello\", \"World\"]", + &(ecs_value_t){ecs_id(Strings), &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1991,7 +2159,9 @@ void Deserialize_discover_type_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "10", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(uint64_t*)v.ptr, 10); @@ -2004,7 +2174,9 @@ void Deserialize_discover_type_negative_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "-10", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(int64_t*)v.ptr, -10); @@ -2017,7 +2189,9 @@ void Deserialize_discover_type_float(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "10.5", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10.5); @@ -2030,7 +2204,9 @@ void Deserialize_discover_type_negative_float(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "-10.5", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, -10.5); @@ -2043,7 +2219,9 @@ void Deserialize_discover_type_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "\"foo\"", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "\"foo\"", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(ecs_string_t*)v.ptr, "foo"); @@ -2056,7 +2234,9 @@ void Deserialize_discover_type_multiline_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "`foo\nbar`", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "`foo\nbar`", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(ecs_string_t*)v.ptr, "foo\nbar"); @@ -2071,7 +2251,9 @@ void Deserialize_discover_type_entity(void) { ecs_entity_t foo = ecs_entity(world, { .name = "foo" }); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "foo", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "foo", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, foo); @@ -2084,14 +2266,18 @@ void Deserialize_discover_type_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "true", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(ecs_bool_t*)v.ptr, true); ecs_value_free(world, v.type, v.ptr); ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "false", &v, NULL) != NULL); + + test_assert(ecs_script_expr_run(world, + "false", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(ecs_bool_t*)v.ptr, false); @@ -2106,8 +2292,12 @@ void Deserialize_discover_type_unknown(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - test_assert(ecs_script_expr_run(world, "{10}", &v, NULL) == NULL); - test_assert(ecs_script_expr_run(world, "[10]", &v, NULL) == NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "{10}", &v, &desc) == NULL); + + test_assert(ecs_script_expr_run(world, + "[10]", &v, &desc) == NULL); ecs_fini(world); } @@ -2118,7 +2308,9 @@ void Deserialize_discover_type_invalid(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - test_assert(ecs_script_expr_run(world, "-", &v, NULL) == NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, + "-", &v, &desc) == NULL); ecs_fini(world); } @@ -2160,8 +2352,9 @@ void Deserialize_opaque_struct(void) { }); OpaqueStruct v; - - const char *ptr = ecs_script_expr_run(world, "{10, 20}", &ecs_value_ptr(OpaqueStruct, &v), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{10, 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2190,8 +2383,9 @@ void Deserialize_opaque_struct_w_member(void) { }); OpaqueStruct v; - - const char *ptr = ecs_script_expr_run(world, "{x: 10, y: 20}", &ecs_value_ptr(OpaqueStruct, &v), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{x: 10, y: 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2220,8 +2414,9 @@ void Deserialize_opaque_struct_w_member_reverse(void) { }); OpaqueStruct v; - - const char *ptr = ecs_script_expr_run(world, "{y: 10, x: 20}", &ecs_value_ptr(OpaqueStruct, &v), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{y: 10, x: 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2264,8 +2459,9 @@ void Deserialize_struct_w_opaque_member(void) { }); Struct_w_opaque v = {{0, NULL}}; - - const char *ptr = ecs_script_expr_run(world, "{str: \"foobar\"}", &ecs_value_ptr(Struct_w_opaque, &v), NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_script_expr_run(world, + "{str: \"foobar\"}", &ecs_value_ptr(Struct_w_opaque, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 1fd889ad36..cb2f0d9b76 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1,10 +1,26 @@ #include +static ecs_query_cache_kind_t disable_folding = false; + +void Expr_setup(void) { + const char *folding_param = test_param("folding"); + if (folding_param) { + if (!strcmp(folding_param, "disabled")) { + disable_folding = true; + } else if (!strcmp(folding_param, "enabled")) { + // already set to default + } else { + printf("unexpected value for folding '%s'\n", folding_param); + } + } +} + void Expr_add_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); @@ -17,12 +33,13 @@ void Expr_add_2_int_literals_twice(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); - test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); @@ -35,7 +52,8 @@ void Expr_sub_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "20 - 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "20 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(int64_t*)v.ptr, 20 - 10); @@ -48,7 +66,8 @@ void Expr_mul_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "20 * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "20 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_u64_t*)v.ptr, 20 * 10); @@ -61,7 +80,8 @@ void Expr_div_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 / 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10 / 2); @@ -74,7 +94,8 @@ void Expr_add_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); @@ -87,12 +108,13 @@ void Expr_add_3_int_literals_twice(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); @@ -105,7 +127,8 @@ void Expr_sub_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "30 - 10 - 5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "30 - 10 - 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, 30 - 10 - 5); @@ -118,7 +141,8 @@ void Expr_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 * 5 * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 * 5 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 2 * 5 * 10); @@ -131,7 +155,8 @@ void Expr_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "40 / 5 / 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "40 / 5 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 40 / 5 / 2); @@ -145,18 +170,18 @@ void Expr_int_to_bool(void) { bool b = false; ecs_value_t v = { .type = ecs_id(ecs_bool_t), .ptr = &b }; - - test_assert(ecs_script_expr_run(world, "10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "256", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "256", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, true); @@ -169,13 +194,13 @@ void Expr_bool_to_int(void) { int32_t i = 0; ecs_value_t v = { .type = ecs_id(ecs_i32_t), .ptr = &i }; - - test_assert(ecs_script_expr_run(world, "true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_i32_t*)v.ptr, 1); - test_assert(ecs_script_expr_run(world, "false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_i32_t*)v.ptr, 0); @@ -188,13 +213,13 @@ void Expr_bool_to_uint(void) { uint32_t i = 0; ecs_value_t v = { .type = ecs_id(ecs_u32_t), .ptr = &i }; - - test_assert(ecs_script_expr_run(world, "true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_u32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_u32_t*)v.ptr, 1); - test_assert(ecs_script_expr_run(world, "false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_u32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_u32_t*)v.ptr, 0); @@ -206,7 +231,8 @@ void Expr_add_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 20 * 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 * 2); @@ -219,7 +245,8 @@ void Expr_sub_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "50 - 10 * 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "50 - 10 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(int64_t*)v.ptr, 50 - 10 * 2); @@ -232,7 +259,8 @@ void Expr_div_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 / 5 * 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 / 5 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10 / 5 * 2); @@ -245,7 +273,8 @@ void Expr_add_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 30 / 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 30 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 10 + 30 / 2); @@ -258,7 +287,8 @@ void Expr_sub_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "30 - 10 / 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "30 - 10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 30 - 10 / 2); @@ -271,7 +301,8 @@ void Expr_mul_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "20 * 10 / 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "20 * 10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 20 * 10 / 2); @@ -284,7 +315,8 @@ void Expr_mul_add_mul_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 * 4 + 6 * 8 + 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 * 4 + 6 * 8 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 2 * 4 + 6 * 8 + 10); @@ -297,7 +329,8 @@ void Expr_mul_sub_mul_sub_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 * 4 - 6 * 8 - 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 * 4 - 6 * 8 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, 2 * 4 - 6 * 8 - 10); @@ -310,7 +343,8 @@ void Expr_mul_div_mul_div_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 * 4 / 6 * 8 / 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 * 4 / 6 * 8 / 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 * 4.0 / 6.0 * 8.0 / 10.0); @@ -323,7 +357,8 @@ void Expr_div_add_div_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 / 4 + 6 / 8 + 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 / 4 + 6 / 8 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 + 6.0 / 8.0 + 10.0); @@ -336,7 +371,8 @@ void Expr_div_sub_div_sub_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 - 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 - 6.0 / 8.0 - 10.0); @@ -349,7 +385,8 @@ void Expr_div_mul_div_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 / 4 * 6 / 8 * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 / 4 * 6 / 8 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 * 6.0 / 8.0 * 10.0); @@ -362,7 +399,8 @@ void Expr_div_sub_div_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 - 6.0 / 8.0 * 10.0); @@ -375,7 +413,8 @@ void Expr_add_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 + 20.0", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 + 20.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10.5 + 20.0); @@ -388,7 +427,8 @@ void Expr_sub_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "20.5 - 10.0", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "20.5 - 10.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 20.5 - 10.0); @@ -401,7 +441,8 @@ void Expr_mul_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "20.5 * 10.0", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "20.5 * 10.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 20.5 * 10.0); @@ -414,7 +455,8 @@ void Expr_div_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 / 2.0", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 / 2.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10.5 / 2.0); @@ -427,7 +469,8 @@ void Expr_add_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10 + -20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-10 + -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 + -20); @@ -440,7 +483,8 @@ void Expr_sub_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10 - -20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-10 - -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 - -20); @@ -453,7 +497,8 @@ void Expr_mul_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10 * -20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-10 * -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 * -20); @@ -466,7 +511,8 @@ void Expr_div_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-10 / -20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-10 / -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, -10.0 / -20.0); @@ -479,7 +525,8 @@ void Expr_mul_lparen_add_add_rparen_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 * (20 + 30)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30)); @@ -492,7 +539,8 @@ void Expr_mul_lparen_add_add_add_rparen_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30 + 40)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 * (20 + 30 + 40)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30 + 40)); @@ -505,7 +553,8 @@ void Expr_mul_lparen_add_add_rparen_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30) + 40", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 * (20 + 30) + 40", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30) + 40); @@ -518,7 +567,8 @@ void Expr_lparen_add_add_rparen_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "(20 + 30) * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "(20 + 30) * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (20 + 30) * 10); @@ -531,7 +581,8 @@ void Expr_lparen_add_add_add_rparen_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "(20 + 30 + 40) * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "(20 + 30 + 40) * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (20 + 30 + 40) * 10); @@ -544,7 +595,8 @@ void Expr_double_paren_add_add(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "((20 + 30))", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "((20 + 30))", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, ((20 + 30))); @@ -557,7 +609,8 @@ void Expr_double_paren_literal(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "((20))", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "((20))", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, ((20))); @@ -570,7 +623,8 @@ void Expr_lparen_add_add_rparen_mul_lparen_add_add_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "(10 + 20) * (20 + 30)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "(10 + 20) * (20 + 30)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 20) * (20 + 30)); @@ -587,7 +641,8 @@ void Expr_float_result_add_2_int_literals(void) { .type = ecs_id(ecs_f32_t), .ptr = &value }; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); test_flt(value, 10 + 20); @@ -611,9 +666,10 @@ void Expr_struct_result_implicit_members(void) { }); Position v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{5 + 5, 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -639,9 +695,10 @@ void Expr_struct_result_explicit_members(void) { }); Position v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{x: 5 + 5, y: 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -668,9 +725,10 @@ void Expr_struct_result_explicit_members_reverse(void) { }); Position v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{y: 5 + 5, x: 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -708,10 +766,11 @@ void Expr_struct_result_nested_implicit_members(void) { }); Line v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{{5 + 5, 10 + 10}, {10 + 20, 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -753,11 +812,12 @@ void Expr_struct_result_nested_explicit_members(void) { }); Line v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{start: {x: 5 + 5, y: 10 + 10}, stop: {x: 10 + 20, y: 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -799,11 +859,12 @@ void Expr_struct_result_nested_explicit_members_reverse(void) { }); Line v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{stop: {x: 5 + 5, y: 10 + 10}, start: {x: 10 + 20, y: 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -845,11 +906,12 @@ void Expr_struct_result_nested_explicit_dotmembers(void) { }); Line v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{stop.x: 5 + 5, start.x: 10 + 10, stop.y: 10 + 20, start.y: 20 + 20}", &(ecs_value_t){ .type = line, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -875,9 +937,10 @@ void Expr_struct_result_add_2_int_literals(void) { }); Mass v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{10 + 20}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -902,9 +965,10 @@ void Expr_struct_result_add_2_2_fields_int_literals(void) { }); Mass v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{10 + 20, 20 + 30}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -928,9 +992,10 @@ void Expr_struct_result_add_3_int_literals(void) { }); Mass v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{10 + 20 + 30}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -953,9 +1018,10 @@ void Expr_struct_result_lparen_int_rparen(void) { }); Mass v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{(10)}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -974,7 +1040,7 @@ void Expr_add_to_var(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "$foo + 20", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -995,7 +1061,7 @@ void Expr_add_var_to(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "20 + $foo", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -1025,7 +1091,7 @@ void Expr_var_member(void) { vars, "foo", PositionI); *(PositionI*)var->value.ptr = (PositionI){10, 20}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo.x", &v, &desc); @@ -1082,7 +1148,7 @@ void Expr_var_member_member(void) { vars, "foo", Line); *(Line*)var->value.ptr = (Line){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo.start.x", &v, &desc); @@ -1140,7 +1206,7 @@ void Expr_var_element(void) { ((int*)var->value.ptr)[0] = 10; ((int*)var->value.ptr)[1] = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo[0]", &v, &desc); @@ -1186,7 +1252,7 @@ void Expr_var_element_element(void) { Ints value[] = {{10, 20}, {30, 40}, {50, 60}}; ecs_os_memcpy(var->value.ptr, value, sizeof(value)); - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo[0][0]", &v, &desc); @@ -1254,7 +1320,7 @@ void Expr_var_member_element(void) { vars, "foo", Points); *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); @@ -1317,7 +1383,7 @@ void Expr_var_member_element_inline(void) { vars, "foo", Points); *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); @@ -1386,7 +1452,7 @@ void Expr_var_element_member(void) { ((Point*)var->value.ptr)[0] = (Point){10, 20}; ((Point*)var->value.ptr)[1] = (Point){30, 40}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; const char *ptr = ecs_script_expr_run(world, "$foo[0].x", &v, &desc); @@ -1432,22 +1498,23 @@ void Expr_bool_cond_and_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true && true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true && false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1460,22 +1527,23 @@ void Expr_bool_cond_or_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true || true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true || false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1488,22 +1556,23 @@ void Expr_int_cond_and_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 && 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 && 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1516,22 +1585,23 @@ void Expr_int_cond_or_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 || 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 || 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1544,22 +1614,23 @@ void Expr_bool_cond_and_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true && 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true && 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1572,22 +1643,23 @@ void Expr_int_cond_and_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 && true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 && false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1600,22 +1672,23 @@ void Expr_bool_cond_or_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true || 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true || 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1628,22 +1701,23 @@ void Expr_int_cond_or_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 || true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 || false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1656,22 +1730,23 @@ void Expr_cond_eq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true == true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true == false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false == true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false == false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1684,22 +1759,23 @@ void Expr_cond_eq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 == 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 == 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 == 20", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 == 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 == 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 == 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1712,22 +1788,23 @@ void Expr_cond_neq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true != true", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true != false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false != true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false != false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1740,22 +1817,23 @@ void Expr_cond_neq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 != 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 != 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 != 20", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 != 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 != 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 != 0", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "0 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1794,17 +1872,18 @@ void Expr_cond_eq_cond_and(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true == true && false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true == true && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true == true && false); - test_assert(ecs_script_expr_run(world, "true && false == false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true && false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true && false == false); - test_assert(ecs_script_expr_run(world, "true && true == true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true && true == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true && true == true); @@ -1817,12 +1896,13 @@ void Expr_cond_eq_cond_or(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true == true || false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true == true || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true == true || false); - test_assert(ecs_script_expr_run(world, "true || false == false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true || false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true || false == false); @@ -1835,22 +1915,23 @@ void Expr_cond_gt_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true > false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true > true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false > true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false > false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1863,22 +1944,23 @@ void Expr_cond_gt_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 > 5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 > 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 > 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 > 5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1891,22 +1973,23 @@ void Expr_cond_gt_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 > 5.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10.5 > 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 > 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 > 5.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1919,22 +2002,23 @@ void Expr_cond_gteq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true >= false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true >= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true >= true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false >= true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false >= false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false >= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1947,22 +2031,23 @@ void Expr_cond_gteq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 >= 5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 >= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 >= 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 >= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 >= 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 >= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 >= 5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 >= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1975,22 +2060,23 @@ void Expr_cond_gteq_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 >= 5.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 >= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10.5 >= 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10.5 >= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 >= 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 >= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 >= 5.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 >= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2003,22 +2089,23 @@ void Expr_cond_lt_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true < false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true < false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true < true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true < true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false < true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false < true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false < false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false < false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2031,22 +2118,23 @@ void Expr_cond_lt_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 < 5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 < 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 < 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 < 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 < 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 < 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 < 5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 < 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2059,22 +2147,23 @@ void Expr_cond_lt_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 < 5.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 < 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10.5 < 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10.5 < 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 < 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 < 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 < 5.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 < 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2087,22 +2176,23 @@ void Expr_cond_lteq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "true <= false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "true <= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true <= true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "true <= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false <= true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false <= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false <= false", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "false <= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2115,22 +2205,23 @@ void Expr_cond_lteq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 <= 5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 <= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 <= 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10 <= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 <= 10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 <= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 <= 5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5 <= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2143,22 +2234,23 @@ void Expr_cond_lteq_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10.5 <= 5.5", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10.5 <= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10.5 <= 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "10.5 <= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 <= 10.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 <= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 <= 5.5", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "5.5 <= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2171,7 +2263,8 @@ void Expr_min_lparen_int_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-(10)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10); @@ -2184,7 +2277,8 @@ void Expr_min_lparen_int_add_int_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "-(10 + 20)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-(10 + 20)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -30); @@ -2220,7 +2314,8 @@ void Expr_min_lparen_int_rparen_to_i64(void) { ecs_i64_t vi = 0; ecs_value_t v = { .type = ecs_id(ecs_i64_t), .ptr = &vi }; - test_assert(ecs_script_expr_run(world, "-(10)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(vi, -10); @@ -2233,7 +2328,8 @@ void Expr_min_lparen_int_rparen_to_i32(void) { ecs_i32_t vi = 0; ecs_value_t v = { .type = ecs_id(ecs_i32_t), .ptr = &vi }; - test_assert(ecs_script_expr_run(world, "-(10)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding };; + test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_int(vi, -10); @@ -2261,7 +2357,7 @@ void Expr_struct_w_min_var(void) { *(ecs_i64_t*)var->value.ptr = 10; Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{-$foo}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); @@ -2288,9 +2384,10 @@ void Expr_struct_w_min_lparen_int_rparen(void) { }); Mass v = {0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{-(10)}", &(ecs_value_t){ .type = t, .ptr = &v - }, NULL); + }, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); @@ -2319,7 +2416,7 @@ void Expr_struct_w_min_lparen_var_rparen(void) { *(ecs_u64_t*)var->value.ptr = 10; Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{-($foo)}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); @@ -2336,7 +2433,8 @@ void Expr_not_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "!false", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "!false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2344,7 +2442,7 @@ void Expr_not_bool(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!true", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "!true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2357,7 +2455,8 @@ void Expr_not_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "!0", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "!0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2365,7 +2464,7 @@ void Expr_not_int(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!10", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "!10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2378,7 +2477,8 @@ void Expr_not_paren_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "!(0)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "!(0)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2386,7 +2486,7 @@ void Expr_not_paren_int(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!(10)", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "!(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2399,7 +2499,8 @@ void Expr_not_paren_expr(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "!(10 - 10)", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "!(10 - 10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2407,7 +2508,7 @@ void Expr_not_paren_expr(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!(5 + 5)", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "!(5 + 5)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2427,7 +2528,7 @@ void Expr_not_var(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2452,7 +2553,8 @@ void Expr_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "1 << 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << 2); @@ -2465,7 +2567,8 @@ void Expr_shift_right_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "4 >> 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "4 >> 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 4 >> 2); @@ -2478,7 +2581,8 @@ void Expr_shift_left_int_add_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "1 << 2 + 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "1 << 2 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << (2 + 10)); @@ -2491,7 +2595,8 @@ void Expr_shift_left_int_mul_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "1 << 2 * 10", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "1 << 2 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << 2 * 10); @@ -2504,7 +2609,8 @@ void Expr_add_int_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 1 << 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << 2); @@ -2517,7 +2623,8 @@ void Expr_mul_int_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 * 1 << 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 * 1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2); @@ -2530,7 +2637,8 @@ void Expr_add_int_shift_left_int_add_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 + 1 << 2 + 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 + 1 << 2 + 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << (2 + 2)); @@ -2543,7 +2651,8 @@ void Expr_mul_int_shift_left_int_mul_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "10 * 1 << 2 * 2", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "10 * 1 << 2 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2 * 2); @@ -2559,7 +2668,8 @@ void Expr_entity_expr(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "foo", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, foo); @@ -2578,7 +2688,8 @@ void Expr_entity_path_expr(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, foo); @@ -2597,7 +2708,8 @@ void Expr_entity_parent_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo.parent()", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo.parent()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, parent); @@ -2616,7 +2728,8 @@ void Expr_entity_name_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo.name()", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo.name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "foo"); @@ -2636,7 +2749,8 @@ void Expr_entity_doc_name_func(void) { ecs_doc_set_name(world, foo, "FooDoc"); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo.doc_name()", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo.doc_name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "FooDoc"); @@ -2655,7 +2769,8 @@ void Expr_entity_chain_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo.parent().name()", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo.parent().name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "parent"); @@ -2681,7 +2796,7 @@ void Expr_var_parent_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; test_assert(ecs_script_expr_run(world, "$foo.parent()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); @@ -2703,7 +2818,8 @@ void Expr_entity_path_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "parent.foo.path()", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.foo.path()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "parent.foo"); @@ -2729,7 +2845,7 @@ void Expr_var_name_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; test_assert(ecs_script_expr_run(world, "$foo.name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); @@ -2759,7 +2875,7 @@ void Expr_var_doc_name_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; test_assert(ecs_script_expr_run(world, "$foo.doc_name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); @@ -2788,7 +2904,7 @@ void Expr_var_chain_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; test_assert(ecs_script_expr_run(world, "$foo.parent().name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); @@ -3539,7 +3655,8 @@ void Expr_component_expr(void) { ecs_set(world, e, Position, {10, 20}); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[Position]", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[Position]", &v, &desc) != NULL); test_assert(v.type == ecs_id(Position)); test_assert(v.ptr != NULL); @@ -3566,7 +3683,8 @@ void Expr_component_member_expr(void) { ecs_set(world, e, Position, {10, 20}); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[Position].x", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[Position].x", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); { @@ -3576,7 +3694,8 @@ void Expr_component_member_expr(void) { } v = (ecs_value_t){0}; - test_assert(ecs_script_expr_run(world, "e[Position].y", &v, NULL) != NULL); + + test_assert(ecs_script_expr_run(world, "e[Position].y", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); { @@ -3606,7 +3725,8 @@ void Expr_component_expr_string(void) { ecs_set(world, e, String, { "Hello World" }); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[String]", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[String]", &v, &desc) != NULL); test_assert(v.type == ecs_id(String)); test_assert(v.ptr != NULL); { @@ -3640,7 +3760,8 @@ void Expr_component_member_expr_string(void) { ecs_set(world, e, String, { "Hello World" }); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[String].value", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[String].value", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3667,7 +3788,8 @@ void Expr_component_elem_expr(void) { ecs_set(world, e, Strings, { "Hello", "World" }); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[Strings][0]", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[Strings][0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3678,7 +3800,7 @@ void Expr_component_elem_expr(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[Strings][1]", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "e[Strings][1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3713,7 +3835,8 @@ void Expr_component_elem_expr_string(void) { ecs_set(world, e, String, {{ "Hello", "World" }}); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3724,7 +3847,7 @@ void Expr_component_elem_expr_string(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3754,7 +3877,8 @@ void Expr_component_inline_elem_expr_string(void) { ecs_set(world, e, String, {{ "Hello", "World" }}); ecs_value_t v = {0}; - test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, NULL) != NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3765,7 +3889,7 @@ void Expr_component_inline_elem_expr_string(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, NULL) != NULL); + test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -3786,7 +3910,7 @@ void Expr_var_expr(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); @@ -3815,7 +3939,7 @@ void Expr_var_member_expr(void) { *(PositionI*)foo->value.ptr = (PositionI){10, 20}; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { const char *ptr = ecs_script_expr_run( @@ -3857,7 +3981,7 @@ void Expr_var_elem_expr(void) { (*var)[1] = 20; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { const char *ptr = ecs_script_expr_run( @@ -3889,7 +4013,7 @@ void Expr_var_expr_string(void) { *(char**)foo->value.ptr = "Hello World"; char* v = NULL; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( world, "$foo", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); @@ -3928,7 +4052,7 @@ void Expr_var_member_expr_string(void) { var->y = "World"; char *v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { const char *ptr = ecs_script_expr_run( @@ -3979,7 +4103,7 @@ void Expr_var_elem_expr_string(void) { (*var)[1] = "World"; char *v = NULL; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { const char *ptr = ecs_script_expr_run( @@ -4033,7 +4157,7 @@ void Expr_var_inline_elem_expr_string(void) { var->value[1] = "World"; char *v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { const char *ptr = ecs_script_expr_run( @@ -4084,7 +4208,7 @@ void Expr_var_expr_desc_w_no_vars(void) { int32_t v = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .vars = NULL }; + ecs_script_expr_run_desc_t desc = { .vars = NULL, .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr == NULL); @@ -4101,7 +4225,7 @@ void Expr_parse_eval(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); test_assert(s != NULL); @@ -4125,7 +4249,7 @@ void Expr_parse_eval_multiple_times(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); test_assert(s != NULL); @@ -4150,7 +4274,8 @@ void Expr_parse_error(void) { ecs_world_t *world = ecs_init(); ecs_log_set_level(-4); - ecs_script_t *s = ecs_script_expr_parse(world, "10 +", NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + ecs_script_t *s = ecs_script_expr_parse(world, "10 +", &desc); test_assert(s == NULL); ecs_fini(world); @@ -4173,7 +4298,8 @@ void Expr_parse_eval_error(void) { ecs_add(world, e, Position); ecs_log_set_level(-4); - ecs_script_t *s = ecs_script_expr_parse(world, "e[Position]", NULL); + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + ecs_script_t *s = ecs_script_expr_parse(world, "e[Position]", &desc); test_assert(s != NULL); ecs_remove(world, e, Position); @@ -4191,8 +4317,9 @@ void Expr_remainder_after_number(void) { ecs_world_t *world = ecs_init(); int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "10 foo", - &ecs_value_ptr(ecs_i32_t, &v), NULL); + &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_int(v, 10); @@ -4204,8 +4331,9 @@ void Expr_remainder_after_string(void) { ecs_world_t *world = ecs_init(); char *v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "\"bar\" foo", - &ecs_value_ptr(ecs_string_t, &v), NULL); + &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_str(v, "bar"); @@ -4219,8 +4347,9 @@ void Expr_remainder_after_unary(void) { ecs_world_t *world = ecs_init(); bool v = false; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "!false foo", - &ecs_value_ptr(ecs_bool_t, &v), NULL); + &ecs_value_ptr(ecs_bool_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_bool(v, true); @@ -4232,8 +4361,9 @@ void Expr_remainder_after_binary(void) { ecs_world_t *world = ecs_init(); int32_t v = false; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "10 + 20 foo", - &ecs_value_ptr(ecs_i32_t, &v), NULL); + &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_int(v, 30); @@ -4245,8 +4375,9 @@ void Expr_remainder_after_parens(void) { ecs_world_t *world = ecs_init(); int32_t v = false; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "(10 + 20) foo", - &ecs_value_ptr(ecs_i32_t, &v), NULL); + &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_int(v, 30); @@ -4268,8 +4399,9 @@ void Expr_remainder_after_initializer(void) { }); Position v = {0, 0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "{10, 20} foo", - &ecs_value_ptr(Position, &v), NULL); + &ecs_value_ptr(Position, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_int(v.x, 10); @@ -4292,8 +4424,9 @@ void Expr_remainder_after_collection_initializer(void) { }); Ints v = {0, 0}; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "[10, 20] foo", - &ecs_value_ptr(Ints, &v), NULL); + &ecs_value_ptr(Ints, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); test_int(v[0], 10); @@ -4306,8 +4439,9 @@ void Expr_space_at_start(void) { ecs_world_t *world = ecs_init(); int32_t v = false; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, " 10 + 20", - &ecs_value_ptr(ecs_i32_t, &v), NULL); + &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); test_int(v, 30); @@ -4319,8 +4453,9 @@ void Expr_newline_at_start(void) { ecs_world_t *world = ecs_init(); int32_t v = false; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_script_expr_run(world, "\n10 + 20", - &ecs_value_ptr(ecs_i32_t, &v), NULL); + &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); test_int(v, 30); diff --git a/test/script/src/main.c b/test/script/src/main.c index e7fe097ee0..ceb973cb95 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -367,6 +367,7 @@ void Error_template_in_template(void); void Error_unterminated_binary(void); // Testsuite 'Expr' +void Expr_setup(void); void Expr_add_2_int_literals(void); void Expr_add_2_int_literals_twice(void); void Expr_sub_2_int_literals(void); @@ -616,6 +617,7 @@ void Serialize_escape_string_w_2_trailing_newlines(void); void Serialize_escape_string_w_delim(void); // Testsuite 'Deserialize' +void Deserialize_setup(void); void Deserialize_bool(void); void Deserialize_byte(void); void Deserialize_char(void); @@ -3448,6 +3450,14 @@ bake_test_case Deserialize_testcases[] = { } }; +const char* Expr_folding_param[] = {"enabled", "disabled"}; +bake_test_param Expr_params[] = { + {"folding", (char**)Expr_folding_param, 2} +}; +const char* Deserialize_folding_param[] = {"enabled", "disabled"}; +bake_test_param Deserialize_params[] = { + {"folding", (char**)Deserialize_folding_param, 2} +}; static bake_test_suite suites[] = { { @@ -3473,10 +3483,12 @@ static bake_test_suite suites[] = { }, { "Expr", - NULL, + Expr_setup, NULL, 174, - Expr_testcases + Expr_testcases, + 1, + Expr_params }, { "Vars", @@ -3494,10 +3506,12 @@ static bake_test_suite suites[] = { }, { "Deserialize", - NULL, + Deserialize_setup, NULL, 86, - Deserialize_testcases + Deserialize_testcases, + 1, + Deserialize_params } }; From 503acf3e3f24827dec4d7ed22a818edd6f9ae4c2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 08:33:55 +0000 Subject: [PATCH 34/83] Fix query parser regression --- distr/flecs.c | 44 +++++++++++++++++++------------- src/addons/script/query_parser.c | 39 +++++++++++++++------------- src/addons/script/script.h | 1 + src/addons/script/tokenizer.c | 4 +++ 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 0508b72ffa..2b9792dc83 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4635,6 +4635,7 @@ struct ecs_script_parser_t { char *token_cur; char *token_keep; bool significant_newline; + bool merge_variable_members; /* For term parser */ ecs_term_t *term; @@ -56569,25 +56570,27 @@ const char* flecs_term_parse_arg( // Position(src| // ^ - LookAhead_1('|', - pos = lookahead; - pos = flecs_term_parse_trav(parser, ref, pos); - if (!pos) { - goto error; - } - - // Position(src|up IsA - // ^ - LookAhead_1(EcsTokIdentifier, + { + LookAhead_1('|', pos = lookahead; - parser->term->trav = ecs_lookup( - parser->script->pub.world, Token(1)); - if (!parser->term->trav) { - Error( - "unresolved trav identifier '%s'", Token(1)); + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; } + + // Position(src|up IsA + // ^ + LookAhead_1(EcsTokIdentifier, + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->script->pub.world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved trav identifier '%s'", Token(1)); + } + ) ) - ) + } break; } @@ -56869,7 +56872,7 @@ const char* flecs_query_term_parse( Parse( case '[': return flecs_term_parse_inout(parser, pos); - case EcsTokTermIdentifier: + case EcsTokTermIdentifier: return flecs_term_parse_flags(parser, Token(0), pos); case '(': return flecs_term_parse_pair(parser, pos); @@ -56912,7 +56915,8 @@ int flecs_terms_parse( ecs_script_parser_t parser = { .script = flecs_script_impl(script), - .pos = script->code + .pos = script->code, + .merge_variable_members = true }; parser.token_cur = flecs_script_impl(script)->token_buffer; @@ -58629,6 +58633,10 @@ const char* flecs_script_identifier( bool is_var = pos[0] == '$'; char *outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } + do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || diff --git a/src/addons/script/query_parser.c b/src/addons/script/query_parser.c index a250d0adf0..7cda7a6159 100644 --- a/src/addons/script/query_parser.c +++ b/src/addons/script/query_parser.c @@ -177,25 +177,27 @@ const char* flecs_term_parse_arg( // Position(src| // ^ - LookAhead_1('|', - pos = lookahead; - pos = flecs_term_parse_trav(parser, ref, pos); - if (!pos) { - goto error; - } - - // Position(src|up IsA - // ^ - LookAhead_1(EcsTokIdentifier, + { + LookAhead_1('|', pos = lookahead; - parser->term->trav = ecs_lookup( - parser->script->pub.world, Token(1)); - if (!parser->term->trav) { - Error( - "unresolved trav identifier '%s'", Token(1)); + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; } + + // Position(src|up IsA + // ^ + LookAhead_1(EcsTokIdentifier, + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->script->pub.world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved trav identifier '%s'", Token(1)); + } + ) ) - ) + } break; } @@ -477,7 +479,7 @@ const char* flecs_query_term_parse( Parse( case '[': return flecs_term_parse_inout(parser, pos); - case EcsTokTermIdentifier: + case EcsTokTermIdentifier: return flecs_term_parse_flags(parser, Token(0), pos); case '(': return flecs_term_parse_pair(parser, pos); @@ -520,7 +522,8 @@ int flecs_terms_parse( ecs_script_parser_t parser = { .script = flecs_script_impl(script), - .pos = script->code + .pos = script->code, + .merge_variable_members = true }; parser.token_cur = flecs_script_impl(script)->token_buffer; diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 94d92235db..07df60afe5 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -39,6 +39,7 @@ struct ecs_script_parser_t { char *token_cur; char *token_keep; bool significant_newline; + bool merge_variable_members; /* For term parser */ ecs_term_t *term; diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 9e1cec36be..dbc2ef0e0c 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -215,6 +215,10 @@ const char* flecs_script_identifier( bool is_var = pos[0] == '$'; char *outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } + do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || From 5a0b04d6b52f1355b28d8f8ef494eb90bb456188 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 5 Dec 2024 08:37:08 +0000 Subject: [PATCH 35/83] Fix more warnings --- distr/flecs.c | 21 ++++++++++++--------- distr/flecs.h | 18 +++++++++--------- include/flecs/addons/script.h | 18 +++++++++--------- meson.build | 11 ++++++++++- src/addons/meta/cursor.c | 1 - src/addons/script/expr/ast.c | 2 +- src/addons/script/expr/parser.c | 4 ++-- src/addons/script/expr/stack.h | 2 +- src/addons/script/expr/visit_eval.c | 5 +++++ src/addons/script/expr/visit_to_str.c | 1 - src/addons/script/expr/visit_type.c | 1 - src/addons/script/parser.h | 3 ++- src/addons/script/tokenizer.c | 1 - src/entity.c | 1 + 14 files changed, 52 insertions(+), 37 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 2b9792dc83..19d4b0f8c2 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4896,7 +4896,7 @@ typedef union ecs_expr_small_value_t { uintptr_t uptr; double f32; double f64; - const char *string; + char *string; ecs_entity_t entity; ecs_id_t id; @@ -7313,6 +7313,7 @@ int flecs_traverse_add( ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: + flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } @@ -48061,7 +48062,6 @@ int flecs_meta_cursor_push_type( if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); - ecs_abort(ECS_INTERNAL_ERROR, NULL); ecs_os_free(str); return -1; } @@ -55322,7 +55322,8 @@ char* ecs_script_string_interpolate( /* Definitions for parser functions */ #define ParserBegin\ - ecs_script_tokenizer_t _tokenizer = {{0}};\ + ecs_script_tokenizer_t _tokenizer;\ + ecs_os_zeromem(&_tokenizer);\ _tokenizer.tokens = _tokenizer.stack.tokens;\ ecs_script_tokenizer_t *tokenizer = &_tokenizer; @@ -58823,7 +58824,6 @@ const char* flecs_script_multiline_string( const char *end = pos + 1; while ((ch = end[0]) && (ch != '`')) { if (ch == '\\' && end[1] == '`') { - ch = '`'; end ++; } end ++; @@ -73493,7 +73493,7 @@ ecs_expr_value_node_t* flecs_expr_string( { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); - result->storage.string = value; + result->storage.string = ECS_CONST_CAST(char*, value); result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); return result; @@ -73689,7 +73689,7 @@ const char* flecs_script_parse_initializer( LookAhead( case ')': case '}': { - if (lookahead_token.kind != until) { + if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } node->node.kind = EcsExprEmptyInitializer; @@ -73728,7 +73728,7 @@ const char* flecs_script_parse_initializer( case '\n': case ')': case '}': { - if (lookahead_token.kind != until) { + if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } EndOfRule; @@ -74781,6 +74781,11 @@ int flecs_expr_variable_visit_eval( ecs_expr_variable_t *node, ecs_expr_value_t *out) { + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + const ecs_script_var_t *var = ecs_script_vars_lookup( ctx->desc->vars, node->name); if (!var) { @@ -75968,7 +75973,6 @@ int flecs_expr_node_to_str( break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); - break; } if (node->type) { @@ -76906,7 +76910,6 @@ int flecs_script_expr_visit_type_priv( case EcsExprComponent: /* Component expressions are derived by type visitor */ ecs_abort(ECS_INTERNAL_ERROR, NULL); - break; } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); diff --git a/distr/flecs.h b/distr/flecs.h index 94e32e4e24..64c422c43a 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14624,17 +14624,17 @@ void ecs_script_vars_from_iter( /** Used with ecs_script_expr_run(). */ typedef struct ecs_script_expr_run_desc_t { - const char *name; - const char *expr; - ecs_entity_t (*lookup_action)( + const char *name; /**< Script name */ + const char *expr; /**< Full expression string */ + ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ const ecs_world_t*, const char *value, void *ctx); - void *lookup_ctx; - ecs_script_vars_t *vars; - ecs_entity_t type; - bool disable_folding; - ecs_script_runtime_t *runtime; + void *lookup_ctx; /**< Context passed to lookup function */ + 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) */ } ecs_script_expr_run_desc_t; /** Run expression. @@ -14670,7 +14670,7 @@ const char* ecs_script_expr_run( FLECS_API ecs_script_t* ecs_script_expr_parse( ecs_world_t *world, - const char *ptr, + const char *expr, const ecs_script_expr_run_desc_t *desc); /** Evaluate expression. diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 2513b8c7c6..0c3822f044 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -445,17 +445,17 @@ void ecs_script_vars_from_iter( /** Used with ecs_script_expr_run(). */ typedef struct ecs_script_expr_run_desc_t { - const char *name; - const char *expr; - ecs_entity_t (*lookup_action)( + const char *name; /**< Script name */ + const char *expr; /**< Full expression string */ + ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ const ecs_world_t*, const char *value, void *ctx); - void *lookup_ctx; - ecs_script_vars_t *vars; - ecs_entity_t type; - bool disable_folding; - ecs_script_runtime_t *runtime; + void *lookup_ctx; /**< Context passed to lookup function */ + 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) */ } ecs_script_expr_run_desc_t; /** Run expression. @@ -491,7 +491,7 @@ const char* ecs_script_expr_run( FLECS_API ecs_script_t* ecs_script_expr_parse( ecs_world_t *world, - const char *ptr, + const char *expr, const ecs_script_expr_run_desc_t *desc); /** Evaluate expression. diff --git a/meson.build b/meson.build index de74cfd483..a550ccb000 100644 --- a/meson.build +++ b/meson.build @@ -59,7 +59,7 @@ flecs_src = files( 'src/addons/rest.c', 'src/addons/script/template.c', 'src/addons/script/ast.c', - 'src/addons/script/expr.c', + 'src/addons/script/builtin_functions.c', 'src/addons/script/interpolate.c', 'src/addons/script/parser.c', 'src/addons/script/query_parser.c', @@ -71,6 +71,15 @@ flecs_src = files( 'src/addons/script/visit_free.c', 'src/addons/script/visit_to_str.c', 'src/addons/script/visit.c', + 'src/addons/script/expr/ast.c', + 'src/addons/script/expr/parser.c', + 'src/addons/script/expr/stack.c', + 'src/addons/script/expr/util.c', + 'src/addons/script/expr/visit_eval.c', + 'src/addons/script/expr/visit_fold.c', + 'src/addons/script/expr/visit_free.c', + 'src/addons/script/expr/visit_to_str.c', + 'src/addons/script/expr/visit_type.c', 'src/addons/system/system.c', 'src/addons/timer.c', 'src/addons/units.c', diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 5b5c39895c..e0e3f74292 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -181,7 +181,6 @@ int flecs_meta_cursor_push_type( if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); - ecs_abort(ECS_INTERNAL_ERROR, NULL); ecs_os_free(str); return -1; } diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 251ad3de33..29f58ffec4 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -94,7 +94,7 @@ ecs_expr_value_node_t* flecs_expr_string( { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); - result->storage.string = value; + result->storage.string = ECS_CONST_CAST(char*, value); result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); return result; diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 9bf58822d2..5cbe856dcc 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -91,7 +91,7 @@ const char* flecs_script_parse_initializer( LookAhead( case ')': case '}': { - if (lookahead_token.kind != until) { + if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } node->node.kind = EcsExprEmptyInitializer; @@ -130,7 +130,7 @@ const char* flecs_script_parse_initializer( case '\n': case ')': case '}': { - if (lookahead_token.kind != until) { + if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } EndOfRule; diff --git a/src/addons/script/expr/stack.h b/src/addons/script/expr/stack.h index e181fda6b8..cd9dff30bc 100644 --- a/src/addons/script/expr/stack.h +++ b/src/addons/script/expr/stack.h @@ -27,7 +27,7 @@ typedef union ecs_expr_small_value_t { uintptr_t uptr; double f32; double f64; - const char *string; + char *string; ecs_entity_t entity; ecs_id_t id; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 49c414e5d0..bd77d2b951 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -252,6 +252,11 @@ int flecs_expr_variable_visit_eval( ecs_expr_variable_t *node, ecs_expr_value_t *out) { + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + const ecs_script_var_t *var = ecs_script_vars_lookup( ctx->desc->vars, node->name); if (!var) { diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index bde5001906..cdd7e5fd81 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -276,7 +276,6 @@ int flecs_expr_node_to_str( break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); - break; } if (node->type) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 392c5507e3..f855a15183 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -913,7 +913,6 @@ int flecs_script_expr_visit_type_priv( case EcsExprComponent: /* Component expressions are derived by type visitor */ ecs_abort(ECS_INTERNAL_ERROR, NULL); - break; } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index 67829ccd74..3ae62849c1 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -37,7 +37,8 @@ /* Definitions for parser functions */ #define ParserBegin\ - ecs_script_tokenizer_t _tokenizer = {{0}};\ + ecs_script_tokenizer_t _tokenizer;\ + ecs_os_zeromem(&_tokenizer);\ _tokenizer.tokens = _tokenizer.stack.tokens;\ ecs_script_tokenizer_t *tokenizer = &_tokenizer; diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index dbc2ef0e0c..a671431177 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -405,7 +405,6 @@ const char* flecs_script_multiline_string( const char *end = pos + 1; while ((ch = end[0]) && (ch != '`')) { if (ch == '\\' && end[1] == '`') { - ch = '`'; end ++; } end ++; diff --git a/src/entity.c b/src/entity.c index 99a20115a4..01e2fc84e1 100644 --- a/src/entity.c +++ b/src/entity.c @@ -1838,6 +1838,7 @@ int flecs_traverse_add( ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: + flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } From b40290c02b298f95e71f5b043396bfe6f683a583 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 6 Dec 2024 03:09:05 +0000 Subject: [PATCH 36/83] Fix script memory mgmt issues, extend allocator with more debug info --- distr/flecs.c | 134 ++++++++++++++---- distr/flecs.h | 54 +++++-- include/flecs.h | 2 +- include/flecs/datastructures/allocator.h | 15 +- .../flecs/datastructures/block_allocator.h | 22 +++ include/flecs/datastructures/map.h | 4 +- include/flecs/datastructures/vec.h | 11 +- src/addons/script/ast.c | 3 +- src/addons/script/expr/ast.c | 3 +- src/addons/script/expr/parser.c | 4 +- src/addons/script/expr/visit_free.c | 6 +- src/addons/script/parser.c | 4 +- src/addons/script/template.c | 10 +- src/datastructures/block_allocator.c | 80 +++++++++-- src/datastructures/vec.c | 21 ++- src/value.c | 3 +- 16 files changed, 306 insertions(+), 70 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 19d4b0f8c2..b19e3c390e 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -18015,7 +18015,8 @@ void* ecs_value_new_w_type_info( ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; - void *result = flecs_alloc(&world->allocator, ti->size); + void *result = flecs_alloc_w_dbg_info( + &world->allocator, ti->size, ti->name); if (ecs_value_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; @@ -29452,6 +29453,10 @@ void flecs_ballocator_init( ba->data_size = size; #ifdef FLECS_SANITIZE ba->alloc_count = 0; + if (size != 24) { /* Prevent stack overflow as map uses block allocator */ + ba->outstanding = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ba->outstanding, NULL); + } size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); @@ -29476,8 +29481,27 @@ void flecs_ballocator_fini( ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE - ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, - "(size = %u)", (uint32_t)ba->data_size); + if (ba->alloc_count != 0) { + ecs_err("Leak detected! (size %u, remaining = %d)", + (uint32_t)ba->data_size, ba->alloc_count); + if (ba->outstanding) { + ecs_map_iter_t it = ecs_map_iter(ba->outstanding); + while (ecs_map_next(&it)) { + uint64_t key = ecs_map_key(&it); + char *type_name = ecs_map_ptr(&it); + if (type_name) { + printf(" - %p (%s)\n", (void*)key, type_name); + } else { + printf(" - %p (unknown type)\n", (void*)key); + } + } + } + ecs_abort(ECS_LEAK_DETECTED, NULL); + } + if (ba->outstanding) { + ecs_map_fini(ba->outstanding); + ecs_os_free(ba->outstanding); + } #endif ecs_block_allocator_block_t *block; @@ -29499,8 +29523,16 @@ void flecs_ballocator_free( } void* flecs_balloc( - ecs_block_allocator_t *ba) + ecs_block_allocator_t *ba) +{ + return flecs_balloc_w_dbg_info(ba, NULL); +} + +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) { + (void)type_name; void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); @@ -29518,8 +29550,12 @@ void* flecs_balloc( #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); + if (ba->outstanding) { + uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); + *(const char**)v = type_name; + } ba->alloc_count ++; - *(int64_t*)result = ba->chunk_size; + *(int64_t*)result = (uintptr_t)ba; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif @@ -29533,13 +29569,20 @@ void* flecs_balloc( void* flecs_bcalloc( ecs_block_allocator_t *ba) +{ + return flecs_bcalloc_w_dbg_info(ba, NULL); +} + +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) { #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; - void *result = flecs_balloc(ba); + void *result = flecs_balloc_w_dbg_info(ba, type_name); ecs_os_memset(result, 0, ba->data_size); return result; #endif @@ -29575,27 +29618,32 @@ void flecs_bfree_w_dbg_info( #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); - if (*(int64_t*)memory != ba->chunk_size) { + ecs_block_allocator_t *actual_ba = *(ecs_block_allocator_t**)memory; + if (actual_ba != ba) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", - memory, *(int64_t*)memory, ba->chunk_size, type_name); + memory, actual_ba->data_size, ba->data_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", - memory, *(int64_t*)memory, ba->chunk_size); + memory, actual_ba->data_size, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } + if (ba->outstanding) { + ecs_map_remove(ba->outstanding, (uintptr_t)memory); + } + ba->alloc_count --; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, + "corrupted allocator (size = %d)", ba->chunk_size); #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; - ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, - "corrupted allocator (size = %d)", ba->chunk_size); #endif } @@ -29603,6 +29651,15 @@ void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) +{ + return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); +} + +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name) { void *result; #ifdef FLECS_USE_OS_ALLOC @@ -29613,7 +29670,7 @@ void* flecs_brealloc( return memory; } - result = flecs_balloc(dst); + result = flecs_balloc_w_dbg_info(dst, type_name); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { @@ -29621,7 +29678,7 @@ void* flecs_brealloc( } ecs_os_memcpy(result, memory, size); } - flecs_bfree(src, memory); + flecs_bfree_w_dbg_info(src, memory, type_name); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { @@ -32504,12 +32561,24 @@ void ecs_vec_init( ecs_size_t size, int32_t elem_count) { + ecs_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); +} + +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count, + const char *type_name) +{ + (void)type_name; ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { - v->array = flecs_alloc(allocator, size * elem_count); + v->array = flecs_alloc_w_dbg_info( + allocator, size * elem_count, type_name); } else { v->array = ecs_os_malloc(size * elem_count); } @@ -32517,6 +32586,7 @@ void ecs_vec_init( v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; + v->type_name = type_name; #endif } @@ -32662,8 +32732,14 @@ void ecs_vec_set_size( } if (elem_count != v->size) { if (allocator) { +#ifdef FLECS_SANITIZE + v->array = flecs_realloc_w_dbg_info( + allocator, size * elem_count, size * v->size, v->array, + v->type_name); +#else v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); +#endif } else { v->array = ecs_os_realloc(v->array, size * elem_count); } @@ -54700,7 +54776,8 @@ void* flecs_ast_new_( { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; - ecs_script_node_t *result = flecs_calloc(a, size); + ecs_script_node_t *result = flecs_calloc_w_dbg_info( + a, size, "ecs_script_node_t"); result->kind = kind; result->pos = parser->pos; return result; @@ -56367,8 +56444,8 @@ ecs_script_t* ecs_script_parse( * ensures that AST nodes don't need to do separate allocations for the data * they contain. */ impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); parser.token_cur = impl->token_buffer; /* Start parsing code */ @@ -58191,7 +58268,8 @@ int flecs_script_template_eval_prop( ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, &template->prop_defaults, ecs_script_var_t); - value->value.ptr = flecs_calloc(&v->base.script->allocator, ti->size); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); value->value.type = type; value->type_info = ti; ecs_value_copy_w_type_info( @@ -58276,14 +58354,14 @@ int flecs_script_template_hoist_using( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { + ecs_allocator_t *a = &v->base.script->allocator; if (v->module) { - ecs_vec_append_t( - &v->r->allocator, &template->using_, ecs_entity_t)[0] = v->module; + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; } int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { - ecs_vec_append_t(&v->r->allocator, &template->using_, ecs_entity_t)[0] = + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } @@ -58343,6 +58421,7 @@ void flecs_script_template_fini( } ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); + ecs_vec_fini_t(a, &template->using_, ecs_entity_t); ecs_script_vars_fini(template->vars); flecs_free_t(a, ecs_script_template_t, template); @@ -73418,7 +73497,8 @@ void* flecs_expr_ast_new_( { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; - ecs_expr_node_t *result = flecs_calloc(a, size); + ecs_expr_node_t *result = flecs_calloc_w_dbg_info(a, size, + "ecs_expr_node_t"); result->kind = kind; result->pos = parser->pos; return result; @@ -74084,8 +74164,8 @@ ecs_script_t* ecs_script_expr_parse( }; impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); parser.token_cur = impl->token_buffer; const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); @@ -75569,8 +75649,8 @@ void flecs_expr_initializer_visit_free( flecs_script_expr_visit_free(script, elem->value); } - ecs_vec_fini_t(&script->world->allocator, &node->elements, - ecs_expr_initializer_element_t); + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; + ecs_vec_fini_t(a, &node->elements, ecs_expr_initializer_element_t); } static @@ -75639,7 +75719,7 @@ void flecs_script_expr_visit_free( return; } - ecs_allocator_t *a = &script->world->allocator; + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; switch(node->kind) { case EcsExprValue: diff --git a/distr/flecs.h b/distr/flecs.h index 64c422c43a..68197aa1ee 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -264,7 +264,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ @@ -1097,6 +1097,7 @@ typedef struct ecs_vec_t { int32_t size; #ifdef FLECS_SANITIZE ecs_size_t elem_size; + const char *type_name; #endif } ecs_vec_t; @@ -1107,8 +1108,16 @@ void ecs_vec_init( ecs_size_t size, int32_t elem_count); +FLECS_API +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const char *type_name); + #define ecs_vec_init_t(allocator, vec, T, elem_count) \ - ecs_vec_init(allocator, vec, ECS_SIZEOF(T), elem_count) + ecs_vec_init_w_dbg_info(allocator, vec, ECS_SIZEOF(T), elem_count, "vec<"#T">") FLECS_API void ecs_vec_init_if( @@ -1518,6 +1527,8 @@ void* ecs_sparse_get( #define FLECS_BLOCK_ALLOCATOR_H +typedef struct ecs_map_t ecs_map_t; + typedef struct ecs_block_allocator_block_t { void *memory; struct ecs_block_allocator_block_t *next; @@ -1535,7 +1546,10 @@ typedef struct ecs_block_allocator_t { int32_t data_size; int32_t chunks_per_block; int32_t block_size; +#ifdef FLECS_SANITIZE int32_t alloc_count; + ecs_map_t *outstanding; +#endif } ecs_block_allocator_t; FLECS_API @@ -1569,10 +1583,20 @@ FLECS_API void* flecs_balloc( ecs_block_allocator_t *allocator); +FLECS_API +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + FLECS_API void* flecs_bcalloc( ecs_block_allocator_t *allocator); +FLECS_API +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + FLECS_API void flecs_bfree( ecs_block_allocator_t *allocator, @@ -1590,6 +1614,13 @@ void* flecs_brealloc( ecs_block_allocator_t *src, void *memory); +FLECS_API +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name); + FLECS_API void* flecs_bdup( ecs_block_allocator_t *ba, @@ -1719,7 +1750,7 @@ typedef struct ecs_bucket_t { ecs_bucket_entry_t *first; } ecs_bucket_t; -typedef struct ecs_map_t { +struct ecs_map_t { uint8_t bucket_shift; bool shared_allocator; ecs_bucket_t *buckets; @@ -1727,7 +1758,7 @@ typedef struct ecs_map_t { int32_t count; struct ecs_block_allocator_t *entry_allocator; struct ecs_allocator_t *allocator; -} ecs_map_t; +}; typedef struct ecs_map_iter_t { const ecs_map_t *map; @@ -2016,12 +2047,14 @@ void* flecs_dup( #define flecs_allocator(obj) (&obj->allocators.dyn) #define flecs_alloc(a, size) flecs_balloc(flecs_allocator_get(a, size)) -#define flecs_alloc_t(a, T) flecs_alloc(a, ECS_SIZEOF(T)) -#define flecs_alloc_n(a, T, count) flecs_alloc(a, ECS_SIZEOF(T) * (count)) +#define flecs_alloc_w_dbg_info(a, size, type_name) flecs_balloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_alloc_t(a, T) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_alloc_n(a, T, count) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) #define flecs_calloc(a, size) flecs_bcalloc(flecs_allocator_get(a, size)) -#define flecs_calloc_t(a, T) flecs_calloc(a, ECS_SIZEOF(T)) -#define flecs_calloc_n(a, T, count) flecs_calloc(a, ECS_SIZEOF(T) * (count)) +#define flecs_calloc_w_dbg_info(a, size, type_name) flecs_bcalloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_calloc_t(a, T) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_calloc_n(a, T, count) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) #define flecs_free(a, size, ptr)\ flecs_bfree((ptr) ? flecs_allocator_get(a, size) : NULL, ptr) @@ -2035,6 +2068,11 @@ void* flecs_dup( flecs_brealloc(flecs_allocator_get(a, size_dst),\ flecs_allocator_get(a, size_src),\ ptr) +#define flecs_realloc_w_dbg_info(a, size_dst, size_src, ptr, type_name)\ + flecs_brealloc_w_dbg_info(flecs_allocator_get(a, size_dst),\ + flecs_allocator_get(a, size_src),\ + ptr,\ + type_name) #define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ flecs_realloc(a, ECS_SIZEOF(T) * (count_dst), ECS_SIZEOF(T) * (count_src), ptr) diff --git a/include/flecs.h b/include/flecs.h index 38d2eae2a1..8dde6591f1 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -262,7 +262,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs/datastructures/allocator.h b/include/flecs/datastructures/allocator.h index 0e16df97a3..889bbca4f1 100644 --- a/include/flecs/datastructures/allocator.h +++ b/include/flecs/datastructures/allocator.h @@ -50,12 +50,14 @@ void* flecs_dup( #define flecs_allocator(obj) (&obj->allocators.dyn) #define flecs_alloc(a, size) flecs_balloc(flecs_allocator_get(a, size)) -#define flecs_alloc_t(a, T) flecs_alloc(a, ECS_SIZEOF(T)) -#define flecs_alloc_n(a, T, count) flecs_alloc(a, ECS_SIZEOF(T) * (count)) +#define flecs_alloc_w_dbg_info(a, size, type_name) flecs_balloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_alloc_t(a, T) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_alloc_n(a, T, count) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) #define flecs_calloc(a, size) flecs_bcalloc(flecs_allocator_get(a, size)) -#define flecs_calloc_t(a, T) flecs_calloc(a, ECS_SIZEOF(T)) -#define flecs_calloc_n(a, T, count) flecs_calloc(a, ECS_SIZEOF(T) * (count)) +#define flecs_calloc_w_dbg_info(a, size, type_name) flecs_bcalloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_calloc_t(a, T) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_calloc_n(a, T, count) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) #define flecs_free(a, size, ptr)\ flecs_bfree((ptr) ? flecs_allocator_get(a, size) : NULL, ptr) @@ -69,6 +71,11 @@ void* flecs_dup( flecs_brealloc(flecs_allocator_get(a, size_dst),\ flecs_allocator_get(a, size_src),\ ptr) +#define flecs_realloc_w_dbg_info(a, size_dst, size_src, ptr, type_name)\ + flecs_brealloc_w_dbg_info(flecs_allocator_get(a, size_dst),\ + flecs_allocator_get(a, size_src),\ + ptr,\ + type_name) #define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ flecs_realloc(a, ECS_SIZEOF(T) * (count_dst), ECS_SIZEOF(T) * (count_src), ptr) diff --git a/include/flecs/datastructures/block_allocator.h b/include/flecs/datastructures/block_allocator.h index ff3a7298e6..1deb75f311 100644 --- a/include/flecs/datastructures/block_allocator.h +++ b/include/flecs/datastructures/block_allocator.h @@ -8,6 +8,8 @@ #include "../private/api_defines.h" +typedef struct ecs_map_t ecs_map_t; + typedef struct ecs_block_allocator_block_t { void *memory; struct ecs_block_allocator_block_t *next; @@ -25,7 +27,10 @@ typedef struct ecs_block_allocator_t { int32_t data_size; int32_t chunks_per_block; int32_t block_size; +#ifdef FLECS_SANITIZE int32_t alloc_count; + ecs_map_t *outstanding; +#endif } ecs_block_allocator_t; FLECS_API @@ -59,10 +64,20 @@ FLECS_API void* flecs_balloc( ecs_block_allocator_t *allocator); +FLECS_API +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + FLECS_API void* flecs_bcalloc( ecs_block_allocator_t *allocator); +FLECS_API +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + FLECS_API void flecs_bfree( ecs_block_allocator_t *allocator, @@ -80,6 +95,13 @@ void* flecs_brealloc( ecs_block_allocator_t *src, void *memory); +FLECS_API +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name); + FLECS_API void* flecs_bdup( ecs_block_allocator_t *ba, diff --git a/include/flecs/datastructures/map.h b/include/flecs/datastructures/map.h index 969125f677..eb4fe94842 100644 --- a/include/flecs/datastructures/map.h +++ b/include/flecs/datastructures/map.h @@ -27,7 +27,7 @@ typedef struct ecs_bucket_t { ecs_bucket_entry_t *first; } ecs_bucket_t; -typedef struct ecs_map_t { +struct ecs_map_t { uint8_t bucket_shift; bool shared_allocator; ecs_bucket_t *buckets; @@ -35,7 +35,7 @@ typedef struct ecs_map_t { int32_t count; struct ecs_block_allocator_t *entry_allocator; struct ecs_allocator_t *allocator; -} ecs_map_t; +}; typedef struct ecs_map_iter_t { const ecs_map_t *map; diff --git a/include/flecs/datastructures/vec.h b/include/flecs/datastructures/vec.h index 555d7ccb2a..a98f629104 100644 --- a/include/flecs/datastructures/vec.h +++ b/include/flecs/datastructures/vec.h @@ -19,6 +19,7 @@ typedef struct ecs_vec_t { int32_t size; #ifdef FLECS_SANITIZE ecs_size_t elem_size; + const char *type_name; #endif } ecs_vec_t; @@ -29,8 +30,16 @@ void ecs_vec_init( ecs_size_t size, int32_t elem_count); +FLECS_API +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const char *type_name); + #define ecs_vec_init_t(allocator, vec, T, elem_count) \ - ecs_vec_init(allocator, vec, ECS_SIZEOF(T), elem_count) + ecs_vec_init_w_dbg_info(allocator, vec, ECS_SIZEOF(T), elem_count, "vec<"#T">") FLECS_API void ecs_vec_init_if( diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index 941043b11d..2b91e03533 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -23,7 +23,8 @@ void* flecs_ast_new_( { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; - ecs_script_node_t *result = flecs_calloc(a, size); + ecs_script_node_t *result = flecs_calloc_w_dbg_info( + a, size, "ecs_script_node_t"); result->kind = kind; result->pos = parser->pos; return result; diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 29f58ffec4..1882fe5f4a 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -19,7 +19,8 @@ void* flecs_expr_ast_new_( { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; - ecs_expr_node_t *result = flecs_calloc(a, size); + ecs_expr_node_t *result = flecs_calloc_w_dbg_info(a, size, + "ecs_expr_node_t"); result->kind = kind; result->pos = parser->pos; return result; diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 5cbe856dcc..e447d0375d 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -486,8 +486,8 @@ ecs_script_t* ecs_script_expr_parse( }; impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); parser.token_cur = impl->token_buffer; const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 62c1dc16cf..5a9ae8d4f0 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -30,8 +30,8 @@ void flecs_expr_initializer_visit_free( flecs_script_expr_visit_free(script, elem->value); } - ecs_vec_fini_t(&script->world->allocator, &node->elements, - ecs_expr_initializer_element_t); + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; + ecs_vec_fini_t(a, &node->elements, ecs_expr_initializer_element_t); } static @@ -100,7 +100,7 @@ void flecs_script_expr_visit_free( return; } - ecs_allocator_t *a = &script->world->allocator; + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; switch(node->kind) { case EcsExprValue: diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 6fe840c9cd..ebeae27638 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -827,8 +827,8 @@ ecs_script_t* ecs_script_parse( * ensures that AST nodes don't need to do separate allocations for the data * they contain. */ impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); parser.token_cur = impl->token_buffer; /* Start parsing code */ diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 658d22fcb4..8e56ee6d35 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -204,7 +204,8 @@ int flecs_script_template_eval_prop( ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, &template->prop_defaults, ecs_script_var_t); - value->value.ptr = flecs_calloc(&v->base.script->allocator, ti->size); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); value->value.type = type; value->type_info = ti; ecs_value_copy_w_type_info( @@ -289,14 +290,14 @@ int flecs_script_template_hoist_using( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { + ecs_allocator_t *a = &v->base.script->allocator; if (v->module) { - ecs_vec_append_t( - &v->r->allocator, &template->using_, ecs_entity_t)[0] = v->module; + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; } int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { - ecs_vec_append_t(&v->r->allocator, &template->using_, ecs_entity_t)[0] = + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } @@ -356,6 +357,7 @@ void flecs_script_template_fini( } ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); + ecs_vec_fini_t(a, &template->using_, ecs_entity_t); ecs_script_vars_fini(template->vars); flecs_free_t(a, ecs_script_template_t, template); diff --git a/src/datastructures/block_allocator.c b/src/datastructures/block_allocator.c index c71c06959a..019862f909 100644 --- a/src/datastructures/block_allocator.c +++ b/src/datastructures/block_allocator.c @@ -67,6 +67,10 @@ void flecs_ballocator_init( ba->data_size = size; #ifdef FLECS_SANITIZE ba->alloc_count = 0; + if (size != 24) { /* Prevent stack overflow as map uses block allocator */ + ba->outstanding = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ba->outstanding, NULL); + } size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); @@ -91,8 +95,27 @@ void flecs_ballocator_fini( ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE - ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, - "(size = %u)", (uint32_t)ba->data_size); + if (ba->alloc_count != 0) { + ecs_err("Leak detected! (size %u, remaining = %d)", + (uint32_t)ba->data_size, ba->alloc_count); + if (ba->outstanding) { + ecs_map_iter_t it = ecs_map_iter(ba->outstanding); + while (ecs_map_next(&it)) { + uint64_t key = ecs_map_key(&it); + char *type_name = ecs_map_ptr(&it); + if (type_name) { + printf(" - %p (%s)\n", (void*)key, type_name); + } else { + printf(" - %p (unknown type)\n", (void*)key); + } + } + } + ecs_abort(ECS_LEAK_DETECTED, NULL); + } + if (ba->outstanding) { + ecs_map_fini(ba->outstanding); + ecs_os_free(ba->outstanding); + } #endif ecs_block_allocator_block_t *block; @@ -114,8 +137,16 @@ void flecs_ballocator_free( } void* flecs_balloc( - ecs_block_allocator_t *ba) + ecs_block_allocator_t *ba) +{ + return flecs_balloc_w_dbg_info(ba, NULL); +} + +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) { + (void)type_name; void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); @@ -133,8 +164,12 @@ void* flecs_balloc( #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); + if (ba->outstanding) { + uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); + *(const char**)v = type_name; + } ba->alloc_count ++; - *(int64_t*)result = ba->chunk_size; + *(int64_t*)result = (uintptr_t)ba; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif @@ -148,13 +183,20 @@ void* flecs_balloc( void* flecs_bcalloc( ecs_block_allocator_t *ba) +{ + return flecs_bcalloc_w_dbg_info(ba, NULL); +} + +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) { #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; - void *result = flecs_balloc(ba); + void *result = flecs_balloc_w_dbg_info(ba, type_name); ecs_os_memset(result, 0, ba->data_size); return result; #endif @@ -190,27 +232,32 @@ void flecs_bfree_w_dbg_info( #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); - if (*(int64_t*)memory != ba->chunk_size) { + ecs_block_allocator_t *actual_ba = *(ecs_block_allocator_t**)memory; + if (actual_ba != ba) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", - memory, *(int64_t*)memory, ba->chunk_size, type_name); + memory, actual_ba->data_size, ba->data_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", - memory, *(int64_t*)memory, ba->chunk_size); + memory, actual_ba->data_size, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } + if (ba->outstanding) { + ecs_map_remove(ba->outstanding, (uintptr_t)memory); + } + ba->alloc_count --; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, + "corrupted allocator (size = %d)", ba->chunk_size); #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; - ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, - "corrupted allocator (size = %d)", ba->chunk_size); #endif } @@ -218,6 +265,15 @@ void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) +{ + return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); +} + +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name) { void *result; #ifdef FLECS_USE_OS_ALLOC @@ -228,7 +284,7 @@ void* flecs_brealloc( return memory; } - result = flecs_balloc(dst); + result = flecs_balloc_w_dbg_info(dst, type_name); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { @@ -236,7 +292,7 @@ void* flecs_brealloc( } ecs_os_memcpy(result, memory, size); } - flecs_bfree(src, memory); + flecs_bfree_w_dbg_info(src, memory, type_name); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { diff --git a/src/datastructures/vec.c b/src/datastructures/vec.c index 9de0024e41..10dec4dc24 100644 --- a/src/datastructures/vec.c +++ b/src/datastructures/vec.c @@ -11,12 +11,24 @@ void ecs_vec_init( ecs_size_t size, int32_t elem_count) { + ecs_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); +} + +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count, + const char *type_name) +{ + (void)type_name; ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { - v->array = flecs_alloc(allocator, size * elem_count); + v->array = flecs_alloc_w_dbg_info( + allocator, size * elem_count, type_name); } else { v->array = ecs_os_malloc(size * elem_count); } @@ -24,6 +36,7 @@ void ecs_vec_init( v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; + v->type_name = type_name; #endif } @@ -169,8 +182,14 @@ void ecs_vec_set_size( } if (elem_count != v->size) { if (allocator) { +#ifdef FLECS_SANITIZE + v->array = flecs_realloc_w_dbg_info( + allocator, size * elem_count, size * v->size, v->array, + v->type_name); +#else v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); +#endif } else { v->array = ecs_os_realloc(v->array, size * elem_count); } diff --git a/src/value.c b/src/value.c index 324b6d9cf7..c70ea1971a 100644 --- a/src/value.c +++ b/src/value.c @@ -47,7 +47,8 @@ void* ecs_value_new_w_type_info( ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; - void *result = flecs_alloc(&world->allocator, ti->size); + void *result = flecs_alloc_w_dbg_info( + &world->allocator, ti->size, ti->name); if (ecs_value_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; From b3de49c0b9520209242e960f86f9d3436265e336 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 6 Dec 2024 03:15:43 +0000 Subject: [PATCH 37/83] Unquarantine expression error checking tests --- distr/flecs.c | 12 ++++++++---- src/datastructures/block_allocator.c | 12 ++++++++---- test/script/src/Error.c | 15 +++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index b19e3c390e..f652a8007b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -29577,6 +29577,8 @@ void* flecs_bcalloc_w_dbg_info( ecs_block_allocator_t *ba, const char *type_name) { + (void)type_name; + #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); @@ -29618,16 +29620,16 @@ void flecs_bfree_w_dbg_info( #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); - ecs_block_allocator_t *actual_ba = *(ecs_block_allocator_t**)memory; - if (actual_ba != ba) { + ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; + if (actual != ba) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", - memory, actual_ba->data_size, ba->data_size, type_name); + memory, actual->data_size, ba->data_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", - memory, actual_ba->data_size, ba->chunk_size); + memory, actual->data_size, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } @@ -29661,6 +29663,8 @@ void* flecs_brealloc_w_dbg_info( void *memory, const char *type_name) { + (void)type_name; + void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; diff --git a/src/datastructures/block_allocator.c b/src/datastructures/block_allocator.c index 019862f909..bbf1e1df11 100644 --- a/src/datastructures/block_allocator.c +++ b/src/datastructures/block_allocator.c @@ -191,6 +191,8 @@ void* flecs_bcalloc_w_dbg_info( ecs_block_allocator_t *ba, const char *type_name) { + (void)type_name; + #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); @@ -232,16 +234,16 @@ void flecs_bfree_w_dbg_info( #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); - ecs_block_allocator_t *actual_ba = *(ecs_block_allocator_t**)memory; - if (actual_ba != ba) { + ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; + if (actual != ba) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", - memory, actual_ba->data_size, ba->data_size, type_name); + memory, actual->data_size, ba->data_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", - memory, actual_ba->data_size, ba->chunk_size); + memory, actual->data_size, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } @@ -275,6 +277,8 @@ void* flecs_brealloc_w_dbg_info( void *memory, const char *type_name) { + (void)type_name; + void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 1f3b3425a9..3d88ac8587 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -660,8 +660,6 @@ void Error_entity_w_anonymous_tag(void) { } void Error_member_expr_without_value_end_of_scope(void) { - test_quarantine("Mon Aug 5"); // address when porting expressions over to new parser - ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -674,14 +672,13 @@ void Error_member_expr_without_value_end_of_scope(void) { } }); + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, "Position(x:)") != 0); ecs_fini(world); } void Error_member_expr_without_value_comma(void) { - test_quarantine("Mon Aug 5"); // address when porting expressions over to new parser - ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -694,14 +691,13 @@ void Error_member_expr_without_value_comma(void) { } }); + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, "Position(x:,0)") != 0); ecs_fini(world); } void Error_member_expr_without_value_newline(void) { - test_quarantine("Mon Aug 5"); // address when porting expressions over to new parser - ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -714,14 +710,13 @@ void Error_member_expr_without_value_newline(void) { } }); + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, "Position(x:\n)") != 0); ecs_fini(world); } void Error_2_member_expr_without_value(void) { - test_quarantine("Mon Aug 5"); // address when porting expressions over to new parser - ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -734,6 +729,7 @@ void Error_2_member_expr_without_value(void) { } }); + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, "Position(x:y:)") != 0); ecs_fini(world); @@ -778,8 +774,6 @@ void Error_expr_junk_after_unary_minus(void) { } void Error_expr_comma_after_nothing(void) { - test_quarantine("Mon Aug 5"); // address when porting expressions over to new parser - ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -792,6 +786,7 @@ void Error_expr_comma_after_nothing(void) { } }); + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, "Position(,)") != 0); ecs_fini(world); From 97eb5f18fd8550500d5ba0d0ceb37ffc58303df6 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 6 Dec 2024 08:55:55 +0000 Subject: [PATCH 38/83] Implement support for function arguments --- distr/flecs.c | 477 ++++++++++++++++++++++---- distr/flecs.h | 103 +++++- include/flecs/addons/script.h | 103 +++++- meson.build | 1 + src/addons/script/builtin_functions.c | 32 +- src/addons/script/expr/ast.h | 2 + src/addons/script/expr/parser.c | 21 +- src/addons/script/expr/visit_eval.c | 81 ++++- src/addons/script/expr/visit_fold.c | 28 ++ src/addons/script/expr/visit_free.c | 2 + src/addons/script/expr/visit_to_str.c | 12 +- src/addons/script/expr/visit_type.c | 110 +++++- src/addons/script/function.c | 167 +++++++++ src/addons/script/script.c | 20 +- src/addons/script/script.h | 3 + test/script/include/script.h | 2 + test/script/project.json | 11 + test/script/src/Expr.c | 450 ++++++++++++++++++++++++ test/script/src/main.c | 57 ++- 19 files changed, 1537 insertions(+), 145 deletions(-) create mode 100644 src/addons/script/function.c diff --git a/distr/flecs.c b/distr/flecs.c index f652a8007b..71a2b50724 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4963,6 +4963,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprIdentifier, EcsExprVariable, EcsExprFunction, + EcsExprMethod, EcsExprMember, EcsExprElement, EcsExprComponent, @@ -5030,6 +5031,7 @@ typedef struct ecs_expr_member_t { typedef struct ecs_expr_function_t { ecs_expr_node_t node; ecs_expr_node_t *left; + ecs_expr_initializer_t *args; const char *function_name; ecs_function_calldata_t calldata; } ecs_expr_function_t; @@ -5487,6 +5489,9 @@ const char* flecs_term_parse( void flecs_script_register_builtin_functions( ecs_world_t *world); +void flecs_script_function_import( + ecs_world_t *world); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H @@ -55118,13 +55123,11 @@ static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_entity(world, { + ecs_entity_t name = ecs_script_method(world, { .name = "doc_name", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name }); ecs_doc_set_brief(world, name, "Returns entity doc name"); @@ -55144,35 +55147,29 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_entity(world, { + ecs_entity_t name = ecs_script_method(world, { .name = "name", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name }); ecs_doc_set_brief(world, name, "Returns entity name"); - ecs_entity_t path = ecs_entity(world, { + ecs_entity_t path = ecs_script_method(world, { .name = "path", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path }); ecs_doc_set_brief(world, path, "Returns entity path"); - ecs_entity_t parent = ecs_entity(world, { + ecs_entity_t parent = ecs_script_method(world, { .name = "parent", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - })) + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent }); ecs_doc_set_brief(world, parent, "Returns entity parent"); @@ -55182,6 +55179,172 @@ void flecs_script_register_builtin_functions( #endif +/** + * @file addons/script/function.c + * @brief Script function API. + */ + + +#ifdef FLECS_SCRIPT + +static +void ecs_script_params_free(ecs_vec_t *params) { + ecs_script_parameter_t *array = ecs_vec_first(params); + int32_t i, count = ecs_vec_count(params); + for (i = 0; i < count; i ++) { + /* Safe, component owns string */ + ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); + } + ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); +} + +static +ECS_MOVE(EcsScriptFunction, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptFunction, ptr, { + ecs_script_params_free(&ptr->params); +}) + +static +ECS_MOVE(EcsScriptMethod, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptMethod, ptr, { + ecs_script_params_free(&ptr->params); +}) + +ecs_entity_t ecs_script_function_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; + + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } + + ecs_modified(world, result, EcsScriptFunction); + + return result; +} + +ecs_entity_t ecs_script_method_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; + + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } + + ecs_modified(world, result, EcsScriptMethod); + + return result; +} + +void flecs_script_function_import( + ecs_world_t *world) +{ + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_set_hooks(world, EcsScriptFunction, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptFunction), + .move = ecs_move(EcsScriptFunction), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ecs_set_hooks(world, EcsScriptMethod, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptMethod), + .move = ecs_move(EcsScriptMethod), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + flecs_script_register_builtin_functions(world); +} + +#endif + /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -57560,10 +57723,6 @@ void FlecsScriptImport( ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); - ecs_set_name_prefix(world, "EcsScript"); - ECS_COMPONENT_DEFINE(world, EcsScriptFunction); - ECS_COMPONENT_DEFINE(world, EcsScriptMethod); - ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), @@ -57587,25 +57746,11 @@ void FlecsScriptImport( .type.serialize = EcsScript_serialize }); - ecs_struct(world, { - .entity = ecs_id(EcsScriptFunction), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } - } - }); - - ecs_struct(world, { - .entity = ecs_id(EcsScriptMethod), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } - } - }); - ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); - flecs_script_register_builtin_functions(world); + flecs_script_function_import(world); } #endif @@ -73955,13 +74100,22 @@ const char* flecs_script_parse_rhs( } case EcsTokParenOpen: { - Parse_1(EcsTokParenClose, { - ecs_expr_function_t *result = flecs_expr_function(parser); - result->left = *out; - *out = (ecs_expr_node_t*)result; - break; - }); + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + + pos = flecs_script_parse_initializer( + parser, pos, ')', &result->args); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + + if (pos[0] != ')') { + Error("expected end of argument list"); + } + + pos ++; break; } @@ -74915,6 +75069,31 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_function_args_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + ecs_value_t *args) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_expr_value_t *expr = flecs_expr_stack_result( + ctx->stack, elem->value); + + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + goto error; + } + + args[i] = expr->value; + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -74923,6 +75102,41 @@ int flecs_expr_function_visit_eval( { flecs_expr_stack_push(ctx->stack); + ecs_function_ctx_t call_ctx = { + .world = ctx->world, + .function = node->calldata.function, + .ctx = node->calldata.ctx + }; + + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_value_t *argv = NULL; + int32_t argc = ecs_vec_count(&node->args->elements); + if (argc) { + argv = ecs_os_alloca_n(ecs_value_t, argc); + if (flecs_expr_function_args_visit_eval(ctx, node->args, argv)) { + goto error; + } + } + + node->calldata.callback(&call_ctx, argc, argv, &out->value); + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + +static +int flecs_expr_method_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_function_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + if (node->left) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { @@ -74938,7 +75152,19 @@ int flecs_expr_function_visit_eval( ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - node->calldata.callback(&call_ctx, 1, &expr->value, &out->value); + int32_t argc = ecs_vec_count(&node->args->elements); + ecs_value_t *argv = ecs_os_alloca_n(ecs_value_t, argc + 1); + argv[0] = expr->value; + + if (argc) { + if (flecs_expr_function_args_visit_eval( + ctx, node->args, &argv[1])) + { + goto error; + } + } + + node->calldata.callback(&call_ctx, argc, argv, &out->value); out->owned = true; } @@ -75106,6 +75332,13 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprMethod: + if (flecs_expr_method_visit_eval( + ctx, (ecs_expr_function_t*)node, out)) + { + goto error; + } + break; case EcsExprMember: if (flecs_expr_member_visit_eval( ctx, (ecs_expr_member_t*)node, out)) @@ -75504,6 +75737,27 @@ int flecs_expr_identifier_visit_fold( return 0; } +static +int flecs_expr_arguments_visit_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + goto error; + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_fold( ecs_script_t *script, @@ -75516,6 +75770,12 @@ int flecs_expr_function_visit_fold( goto error; } + if (node->args) { + if (flecs_expr_arguments_visit_fold(script, node->args, desc)) { + goto error; + } + } + return 0; error: return -1; @@ -75594,6 +75854,7 @@ int flecs_script_expr_visit_fold( case EcsExprVariable: break; case EcsExprFunction: + case EcsExprMethod: if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { goto error; } @@ -75688,6 +75949,7 @@ void flecs_expr_function_visit_free( ecs_expr_function_t *node) { flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)node->args); } static @@ -75756,6 +76018,7 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_variable_t, node); break; case EcsExprFunction: + case EcsExprMethod: flecs_expr_function_visit_free( script, (ecs_expr_function_t*)node); flecs_free_t(a, ecs_expr_function_t, node); @@ -75932,8 +76195,15 @@ int flecs_expr_function_to_str( ecs_strbuf_appendlit(v->buf, "."); } - ecs_strbuf_append(v->buf, "%s%s()%s", - ECS_CYAN, node->function_name, ECS_NORMAL); + ecs_strbuf_append(v->buf, "%s(", node->function_name); + + if (node->args) { + if (flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args)) { + return -1; + } + } + + ecs_strbuf_append(v->buf, ")"); return 0; } @@ -76027,6 +76297,7 @@ int flecs_expr_node_to_str( } break; case EcsExprFunction: + case EcsExprMethod: if (flecs_expr_function_to_str(v, (const ecs_expr_function_t*)node)) { @@ -76690,6 +76961,45 @@ int flecs_expr_variable_visit_type( return -1; } +static +int flecs_expr_arguments_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + const ecs_vec_t *param_vec) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + + if (count != ecs_vec_count(param_vec)) { + flecs_expr_visit_error(script, node, "expected %d arguments, got %d", + ecs_vec_count(param_vec), count); + goto error; + } + + ecs_script_parameter_t *params = ecs_vec_first(param_vec); + + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, params[i].type, NULL); + + if (flecs_script_expr_visit_type_priv(script, elem->value, &cur, desc)){ + goto error; + } + + if (elem->value->type != params[i].type) { + elem->value = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, params[i].type); + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_type( ecs_script_t *script, @@ -76698,23 +77008,29 @@ int flecs_expr_function_visit_type( const ecs_script_expr_run_desc_t *desc) { bool is_method = false; + char *last_elem = NULL; + const char *func_identifier = NULL; if (node->left->kind == EcsExprIdentifier) { /* If identifier contains '.' separator(s), this is a method call, * otherwise it's a regular function. */ ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; - char *last_elem = strrchr(ident->value, '.'); + func_identifier = ident->value; + + last_elem = strrchr(func_identifier, '.'); if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { node->function_name = last_elem + 1; last_elem[0] = '\0'; is_method = true; + } else { + node->function_name = ident->value; } } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; - char *last_elem = strrchr(member->member_name, '.'); + last_elem = strrchr(member->member_name, '.'); if (!last_elem) { node->left = member->left; node->function_name = member->member_name; @@ -76736,13 +77052,23 @@ int flecs_expr_function_visit_type( goto error; } - /* If this is a method, lookup function entity in scope of type */ ecs_world_t *world = script->world; + const ecs_vec_t *params = NULL; + + /* If this is a method, lookup function entity in scope of type */ if (is_method) { ecs_entity_t func = ecs_lookup_from( world, node->left->type, node->function_name); if (!func) { - char *type_str = ecs_get_path(script->world, node->left->type); + /* If identifier could be a function (not a method) try that */ + if (func_identifier) { + is_method = false; + last_elem[0] = '.'; + node->function_name = func_identifier; + goto try_function; + } + + char *type_str = ecs_get_path(world, node->left->type); flecs_expr_visit_error(script, node, "unresolved method identifier '%s' for type '%s'", node->function_name, type_str); @@ -76750,8 +77076,9 @@ int flecs_expr_function_visit_type( goto error; } - const EcsScriptMethod *method = ecs_get(world, func, EcsScriptMethod); - if (!method) { + const EcsScriptMethod *func_data = ecs_get( + world, func, EcsScriptMethod); + if (!func_data) { char *path = ecs_get_path(world, func); flecs_expr_visit_error(script, node, "entity '%s' is not a valid method", path); @@ -76759,10 +77086,43 @@ int flecs_expr_function_visit_type( goto error; } - node->node.type = method->return_type; + node->node.kind = EcsExprMethod; + node->node.type = func_data->return_type; node->calldata.function = func; - node->calldata.callback = method->callback; - node->calldata.ctx = method->ctx; + node->calldata.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + params = &func_data->params; + } + +try_function: + if (!is_method) { + ecs_entity_t func = ecs_lookup(world, node->function_name); + if (!func) { + flecs_expr_visit_error(script, node, + "unresolved function identifier '%s'", + node->function_name); + goto error; + } + + const EcsScriptFunction *func_data = ecs_get( + world, func, EcsScriptFunction); + if (!func_data) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.type = func_data->return_type; + node->calldata.function = func; + node->calldata.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + params = &func_data->params; + } + + if (flecs_expr_arguments_visit_type(script, node->args, desc, params)) { + goto error; } return 0; @@ -76991,8 +77351,9 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprCast: break; + case EcsExprMethod: case EcsExprComponent: - /* Component expressions are derived by type visitor */ + /* Expressions are derived by type visitor */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } diff --git a/distr/flecs.h b/distr/flecs.h index 68197aa1ee..0a391be2fd 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14248,11 +14248,18 @@ void FlecsUnitsImport( extern "C" { #endif +#define FLECS_SCRIPT_FUNCTION_ARGS_MAX (16) + FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptMethod); +/* Script template. */ typedef struct ecs_script_template_t ecs_script_template_t; /** Script variable. */ @@ -14274,20 +14281,6 @@ typedef struct ecs_script_vars_t { ecs_allocator_t *allocator; } ecs_script_vars_t; -/** Script function context */ -typedef struct ecs_function_ctx_t { - ecs_world_t *world; - ecs_entity_t function; - void *ctx; -} ecs_function_ctx_t; - -/** Script function callback */ -typedef void(*ecs_function_callback_t)( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result); - /** Script object. */ typedef struct ecs_script_t { ecs_world_t *world; @@ -14306,11 +14299,32 @@ typedef struct EcsScript { ecs_script_template_t *template_; /* Only set for template scripts */ } EcsScript; +/** Script function context. */ +typedef struct ecs_function_ctx_t { + ecs_world_t *world; + ecs_entity_t function; + void *ctx; +} ecs_function_ctx_t; + +/** Script function callback. */ +typedef void(*ecs_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result); + +/** Function argument type. */ +typedef struct ecs_script_parameter_t { + const char *name; + ecs_entity_t type; +} ecs_script_parameter_t; + /** Function component. * This component describes a function that can be called from a script. */ typedef struct EcsScriptFunction { ecs_entity_t return_type; + ecs_vec_t params; /* vec */ ecs_function_callback_t callback; void *ctx; } EcsScriptFunction; @@ -14322,6 +14336,7 @@ typedef struct EcsScriptFunction { */ typedef struct EcsScriptMethod { ecs_entity_t return_type; + ecs_vec_t params; /* vec */ ecs_function_callback_t callback; void *ctx; } EcsScriptMethod; @@ -14750,6 +14765,66 @@ char* ecs_script_string_interpolate( const ecs_script_vars_t *vars); +/* Functions */ + +/** Used with ecs_script_function_init and ecs_script_method_init */ +typedef struct ecs_script_function_desc_t { + /** Function name. */ + const char *name; + + /** Parent of function. For methods the parent is the type for which the + * method will be registered. */ + ecs_entity_t parent; + + /** Function parameters. */ + ecs_script_parameter_t params[FLECS_SCRIPT_FUNCTION_ARGS_MAX]; + + /** Function return type. */ + ecs_entity_t return_type; + + /** Function implementation. */ + ecs_function_callback_t callback; + + /** Context passed to function implementation. */ + void *ctx; +} ecs_script_function_desc_t; + +/** Create new function. + * This operation creates a new function that can be called from a script. + * + * @param world The world. + * @param desc Function init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_script_function_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc); + +#define ecs_script_function(world, ...)\ + ecs_script_function_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) + +/** Create new method. + * This operation creates a new method that can be called from a script. A + * method is like a function, except that it can be called on every instance of + * a type. + * + * Methods automatically receive the instance on which the method is invoked as + * first argument. + * + * @param world Method The world. + * @param desc Method init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_script_method_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc); + +#define ecs_script_method(world, ...)\ + ecs_script_method_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) + + /* Value serialization */ /** Serialize value into expression string. diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 0c3822f044..08a244a4ea 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -31,11 +31,18 @@ extern "C" { #endif +#define FLECS_SCRIPT_FUNCTION_ARGS_MAX (16) + FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptMethod); +/* Script template. */ typedef struct ecs_script_template_t ecs_script_template_t; /** Script variable. */ @@ -57,20 +64,6 @@ typedef struct ecs_script_vars_t { ecs_allocator_t *allocator; } ecs_script_vars_t; -/** Script function context */ -typedef struct ecs_function_ctx_t { - ecs_world_t *world; - ecs_entity_t function; - void *ctx; -} ecs_function_ctx_t; - -/** Script function callback */ -typedef void(*ecs_function_callback_t)( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result); - /** Script object. */ typedef struct ecs_script_t { ecs_world_t *world; @@ -89,11 +82,32 @@ typedef struct EcsScript { ecs_script_template_t *template_; /* Only set for template scripts */ } EcsScript; +/** Script function context. */ +typedef struct ecs_function_ctx_t { + ecs_world_t *world; + ecs_entity_t function; + void *ctx; +} ecs_function_ctx_t; + +/** Script function callback. */ +typedef void(*ecs_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result); + +/** Function argument type. */ +typedef struct ecs_script_parameter_t { + const char *name; + ecs_entity_t type; +} ecs_script_parameter_t; + /** Function component. * This component describes a function that can be called from a script. */ typedef struct EcsScriptFunction { ecs_entity_t return_type; + ecs_vec_t params; /* vec */ ecs_function_callback_t callback; void *ctx; } EcsScriptFunction; @@ -105,6 +119,7 @@ typedef struct EcsScriptFunction { */ typedef struct EcsScriptMethod { ecs_entity_t return_type; + ecs_vec_t params; /* vec */ ecs_function_callback_t callback; void *ctx; } EcsScriptMethod; @@ -533,6 +548,66 @@ char* ecs_script_string_interpolate( const ecs_script_vars_t *vars); +/* Functions */ + +/** Used with ecs_script_function_init and ecs_script_method_init */ +typedef struct ecs_script_function_desc_t { + /** Function name. */ + const char *name; + + /** Parent of function. For methods the parent is the type for which the + * method will be registered. */ + ecs_entity_t parent; + + /** Function parameters. */ + ecs_script_parameter_t params[FLECS_SCRIPT_FUNCTION_ARGS_MAX]; + + /** Function return type. */ + ecs_entity_t return_type; + + /** Function implementation. */ + ecs_function_callback_t callback; + + /** Context passed to function implementation. */ + void *ctx; +} ecs_script_function_desc_t; + +/** Create new function. + * This operation creates a new function that can be called from a script. + * + * @param world The world. + * @param desc Function init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_script_function_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc); + +#define ecs_script_function(world, ...)\ + ecs_script_function_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) + +/** Create new method. + * This operation creates a new method that can be called from a script. A + * method is like a function, except that it can be called on every instance of + * a type. + * + * Methods automatically receive the instance on which the method is invoked as + * first argument. + * + * @param world Method The world. + * @param desc Method init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_script_method_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc); + +#define ecs_script_method(world, ...)\ + ecs_script_method_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) + + /* Value serialization */ /** Serialize value into expression string. diff --git a/meson.build b/meson.build index a550ccb000..893a5c0819 100644 --- a/meson.build +++ b/meson.build @@ -60,6 +60,7 @@ flecs_src = files( 'src/addons/script/template.c', 'src/addons/script/ast.c', 'src/addons/script/builtin_functions.c', + 'src/addons/script/function.c', 'src/addons/script/interpolate.c', 'src/addons/script/parser.c', 'src/addons/script/query_parser.c', diff --git a/src/addons/script/builtin_functions.c b/src/addons/script/builtin_functions.c index 53099ab14d..af4f0c18bf 100644 --- a/src/addons/script/builtin_functions.c +++ b/src/addons/script/builtin_functions.c @@ -62,13 +62,11 @@ static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_entity(world, { + ecs_entity_t name = ecs_script_method(world, { .name = "doc_name", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name }); ecs_doc_set_brief(world, name, "Returns entity doc name"); @@ -88,35 +86,29 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_entity(world, { + ecs_entity_t name = ecs_script_method(world, { .name = "name", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name }); ecs_doc_set_brief(world, name, "Returns entity name"); - ecs_entity_t path = ecs_entity(world, { + ecs_entity_t path = ecs_script_method(world, { .name = "path", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - })) + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path }); ecs_doc_set_brief(world, path, "Returns entity path"); - ecs_entity_t parent = ecs_entity(world, { + ecs_entity_t parent = ecs_script_method(world, { .name = "parent", .parent = ecs_id(ecs_entity_t), - .set = ecs_values(ecs_value(EcsScriptMethod, { - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - })) + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent }); ecs_doc_set_brief(world, parent, "Returns entity parent"); diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 058a3f7989..49a1833d7a 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -17,6 +17,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprIdentifier, EcsExprVariable, EcsExprFunction, + EcsExprMethod, EcsExprMember, EcsExprElement, EcsExprComponent, @@ -84,6 +85,7 @@ typedef struct ecs_expr_member_t { typedef struct ecs_expr_function_t { ecs_expr_node_t node; ecs_expr_node_t *left; + ecs_expr_initializer_t *args; const char *function_name; ecs_function_calldata_t calldata; } ecs_expr_function_t; diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index e447d0375d..ba63b79aaf 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -273,13 +273,22 @@ const char* flecs_script_parse_rhs( } case EcsTokParenOpen: { - Parse_1(EcsTokParenClose, { - ecs_expr_function_t *result = flecs_expr_function(parser); - result->left = *out; - *out = (ecs_expr_node_t*)result; - break; - }); + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + + pos = flecs_script_parse_initializer( + parser, pos, ')', &result->args); + if (!pos) { + goto error; + } + + *out = (ecs_expr_node_t*)result; + + if (pos[0] != ')') { + Error("expected end of argument list"); + } + pos ++; break; } diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index bd77d2b951..b509e13f7d 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -302,6 +302,31 @@ int flecs_expr_cast_visit_eval( return -1; } +static +int flecs_expr_function_args_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + ecs_value_t *args) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_expr_value_t *expr = flecs_expr_stack_result( + ctx->stack, elem->value); + + if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + goto error; + } + + args[i] = expr->value; + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -310,6 +335,41 @@ int flecs_expr_function_visit_eval( { flecs_expr_stack_push(ctx->stack); + ecs_function_ctx_t call_ctx = { + .world = ctx->world, + .function = node->calldata.function, + .ctx = node->calldata.ctx + }; + + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_value_t *argv = NULL; + int32_t argc = ecs_vec_count(&node->args->elements); + if (argc) { + argv = ecs_os_alloca_n(ecs_value_t, argc); + if (flecs_expr_function_args_visit_eval(ctx, node->args, argv)) { + goto error; + } + } + + node->calldata.callback(&call_ctx, argc, argv, &out->value); + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + +static +int flecs_expr_method_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_function_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + if (node->left) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { @@ -325,7 +385,19 @@ int flecs_expr_function_visit_eval( ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - node->calldata.callback(&call_ctx, 1, &expr->value, &out->value); + int32_t argc = ecs_vec_count(&node->args->elements); + ecs_value_t *argv = ecs_os_alloca_n(ecs_value_t, argc + 1); + argv[0] = expr->value; + + if (argc) { + if (flecs_expr_function_args_visit_eval( + ctx, node->args, &argv[1])) + { + goto error; + } + } + + node->calldata.callback(&call_ctx, argc, argv, &out->value); out->owned = true; } @@ -493,6 +565,13 @@ int flecs_script_expr_visit_eval_priv( goto error; } break; + case EcsExprMethod: + if (flecs_expr_method_visit_eval( + ctx, (ecs_expr_function_t*)node, out)) + { + goto error; + } + break; case EcsExprMember: if (flecs_expr_member_visit_eval( ctx, (ecs_expr_member_t*)node, out)) diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index c0128ff729..78510bc5b9 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -291,6 +291,27 @@ int flecs_expr_identifier_visit_fold( return 0; } +static +int flecs_expr_arguments_visit_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + goto error; + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_fold( ecs_script_t *script, @@ -303,6 +324,12 @@ int flecs_expr_function_visit_fold( goto error; } + if (node->args) { + if (flecs_expr_arguments_visit_fold(script, node->args, desc)) { + goto error; + } + } + return 0; error: return -1; @@ -381,6 +408,7 @@ int flecs_script_expr_visit_fold( case EcsExprVariable: break; case EcsExprFunction: + case EcsExprMethod: if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { goto error; } diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 5a9ae8d4f0..24f9553c48 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -65,6 +65,7 @@ void flecs_expr_function_visit_free( ecs_expr_function_t *node) { flecs_script_expr_visit_free(script, node->left); + flecs_script_expr_visit_free(script, (ecs_expr_node_t*)node->args); } static @@ -133,6 +134,7 @@ void flecs_script_expr_visit_free( flecs_free_t(a, ecs_expr_variable_t, node); break; case EcsExprFunction: + case EcsExprMethod: flecs_expr_function_visit_free( script, (ecs_expr_function_t*)node); flecs_free_t(a, ecs_expr_function_t, node); diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index cdd7e5fd81..61d683daf9 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -151,8 +151,15 @@ int flecs_expr_function_to_str( ecs_strbuf_appendlit(v->buf, "."); } - ecs_strbuf_append(v->buf, "%s%s()%s", - ECS_CYAN, node->function_name, ECS_NORMAL); + ecs_strbuf_append(v->buf, "%s(", node->function_name); + + if (node->args) { + if (flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args)) { + return -1; + } + } + + ecs_strbuf_append(v->buf, ")"); return 0; } @@ -246,6 +253,7 @@ int flecs_expr_node_to_str( } break; case EcsExprFunction: + case EcsExprMethod: if (flecs_expr_function_to_str(v, (const ecs_expr_function_t*)node)) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index f855a15183..f22b7ea467 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -609,6 +609,45 @@ int flecs_expr_variable_visit_type( return -1; } +static +int flecs_expr_arguments_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_script_expr_run_desc_t *desc, + const ecs_vec_t *param_vec) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + + if (count != ecs_vec_count(param_vec)) { + flecs_expr_visit_error(script, node, "expected %d arguments, got %d", + ecs_vec_count(param_vec), count); + goto error; + } + + ecs_script_parameter_t *params = ecs_vec_first(param_vec); + + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, params[i].type, NULL); + + if (flecs_script_expr_visit_type_priv(script, elem->value, &cur, desc)){ + goto error; + } + + if (elem->value->type != params[i].type) { + elem->value = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, params[i].type); + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_function_visit_type( ecs_script_t *script, @@ -617,23 +656,29 @@ int flecs_expr_function_visit_type( const ecs_script_expr_run_desc_t *desc) { bool is_method = false; + char *last_elem = NULL; + const char *func_identifier = NULL; if (node->left->kind == EcsExprIdentifier) { /* If identifier contains '.' separator(s), this is a method call, * otherwise it's a regular function. */ ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; - char *last_elem = strrchr(ident->value, '.'); + func_identifier = ident->value; + + last_elem = strrchr(func_identifier, '.'); if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { node->function_name = last_elem + 1; last_elem[0] = '\0'; is_method = true; + } else { + node->function_name = ident->value; } } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; - char *last_elem = strrchr(member->member_name, '.'); + last_elem = strrchr(member->member_name, '.'); if (!last_elem) { node->left = member->left; node->function_name = member->member_name; @@ -655,13 +700,23 @@ int flecs_expr_function_visit_type( goto error; } - /* If this is a method, lookup function entity in scope of type */ ecs_world_t *world = script->world; + const ecs_vec_t *params = NULL; + + /* If this is a method, lookup function entity in scope of type */ if (is_method) { ecs_entity_t func = ecs_lookup_from( world, node->left->type, node->function_name); if (!func) { - char *type_str = ecs_get_path(script->world, node->left->type); + /* If identifier could be a function (not a method) try that */ + if (func_identifier) { + is_method = false; + last_elem[0] = '.'; + node->function_name = func_identifier; + goto try_function; + } + + char *type_str = ecs_get_path(world, node->left->type); flecs_expr_visit_error(script, node, "unresolved method identifier '%s' for type '%s'", node->function_name, type_str); @@ -669,8 +724,37 @@ int flecs_expr_function_visit_type( goto error; } - const EcsScriptMethod *method = ecs_get(world, func, EcsScriptMethod); - if (!method) { + const EcsScriptMethod *func_data = ecs_get( + world, func, EcsScriptMethod); + if (!func_data) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.kind = EcsExprMethod; + node->node.type = func_data->return_type; + node->calldata.function = func; + node->calldata.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + params = &func_data->params; + } + +try_function: + if (!is_method) { + ecs_entity_t func = ecs_lookup(world, node->function_name); + if (!func) { + flecs_expr_visit_error(script, node, + "unresolved function identifier '%s'", + node->function_name); + goto error; + } + + const EcsScriptFunction *func_data = ecs_get( + world, func, EcsScriptFunction); + if (!func_data) { char *path = ecs_get_path(world, func); flecs_expr_visit_error(script, node, "entity '%s' is not a valid method", path); @@ -678,10 +762,15 @@ int flecs_expr_function_visit_type( goto error; } - node->node.type = method->return_type; + node->node.type = func_data->return_type; node->calldata.function = func; - node->calldata.callback = method->callback; - node->calldata.ctx = method->ctx; + node->calldata.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + params = &func_data->params; + } + + if (flecs_expr_arguments_visit_type(script, node->args, desc, params)) { + goto error; } return 0; @@ -910,8 +999,9 @@ int flecs_script_expr_visit_type_priv( break; case EcsExprCast: break; + case EcsExprMethod: case EcsExprComponent: - /* Component expressions are derived by type visitor */ + /* Expressions are derived by type visitor */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } diff --git a/src/addons/script/function.c b/src/addons/script/function.c new file mode 100644 index 0000000000..f9530a55df --- /dev/null +++ b/src/addons/script/function.c @@ -0,0 +1,167 @@ +/** + * @file addons/script/function.c + * @brief Script function API. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +static +void ecs_script_params_free(ecs_vec_t *params) { + ecs_script_parameter_t *array = ecs_vec_first(params); + int32_t i, count = ecs_vec_count(params); + for (i = 0; i < count; i ++) { + /* Safe, component owns string */ + ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); + } + ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); +} + +static +ECS_MOVE(EcsScriptFunction, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptFunction, ptr, { + ecs_script_params_free(&ptr->params); +}) + +static +ECS_MOVE(EcsScriptMethod, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptMethod, ptr, { + ecs_script_params_free(&ptr->params); +}) + +ecs_entity_t ecs_script_function_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; + + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } + + ecs_modified(world, result, EcsScriptFunction); + + return result; +} + +ecs_entity_t ecs_script_method_init( + ecs_world_t *world, + const ecs_script_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; + + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } + + ecs_modified(world, result, EcsScriptMethod); + + return result; +} + +void flecs_script_function_import( + ecs_world_t *world) +{ + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_set_hooks(world, EcsScriptFunction, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptFunction), + .move = ecs_move(EcsScriptFunction), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ecs_set_hooks(world, EcsScriptMethod, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptMethod), + .move = ecs_move(EcsScriptMethod), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + flecs_script_register_builtin_functions(world); +} + +#endif diff --git a/src/addons/script/script.c b/src/addons/script/script.c index e84201baa3..012b1b6b5b 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -301,10 +301,6 @@ void FlecsScriptImport( ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); - ecs_set_name_prefix(world, "EcsScript"); - ECS_COMPONENT_DEFINE(world, EcsScriptFunction); - ECS_COMPONENT_DEFINE(world, EcsScriptMethod); - ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), @@ -328,25 +324,11 @@ void FlecsScriptImport( .type.serialize = EcsScript_serialize }); - ecs_struct(world, { - .entity = ecs_id(EcsScriptFunction), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } - } - }); - - ecs_struct(world, { - .entity = ecs_id(EcsScriptMethod), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } - } - }); - ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); - flecs_script_register_builtin_functions(world); + flecs_script_function_import(world); } #endif diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 07df60afe5..1515ab70e7 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -123,5 +123,8 @@ const char* flecs_term_parse( void flecs_script_register_builtin_functions( ecs_world_t *world); +void flecs_script_function_import( + ecs_world_t *world); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H diff --git a/test/script/include/script.h b/test/script/include/script.h index dc5ab35105..5a26c2e99c 100644 --- a/test/script/include/script.h +++ b/test/script/include/script.h @@ -67,6 +67,8 @@ typedef struct { float x, y, z; } Vec3; +void install_test_abort(void); + #ifdef __cplusplus } #endif diff --git a/test/script/project.json b/test/script/project.json index 4ce011a8d5..f2ef2c15d0 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -504,6 +504,17 @@ "var_name_func", "var_doc_name_func", "var_chain_func", + "root_func_void_return", + "root_func", + "scoped_func", + "root_func_w_1_arg", + "root_func_w_1_arg_expr", + "root_func_w_2_args", + "root_func_w_enum_arg", + "root_func_w_struct_arg", + "root_func_w_array_arg", + "root_func_mismatching_args", + "method_w_1_arg", "interpolate_string_w_i32_var", "interpolate_string_w_string_var", "interpolate_string_w_entity_var", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index cb2f0d9b76..9c05d44f37 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -2916,6 +2916,456 @@ void Expr_var_chain_func(void) { ecs_fini(world); } +static ecs_entity_t func_function = 0; +static void *func_ctx = NULL; + +static +void func_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + *(ecs_i32_t*)result->ptr = 10; + func_function = ctx->function; + func_ctx = ctx->ctx; +} + +static +void func_1_arg_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 1); + test_assert(argv != NULL); + + *(ecs_i32_t*)result->ptr = *(int32_t*)argv[0].ptr; + func_function = ctx->function; + func_ctx = ctx->ctx; +} + +static +void func_2_args_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 2); + test_assert(argv != NULL); + + *(ecs_i32_t*)result->ptr = + *(int32_t*)argv[0].ptr + + *(int32_t*)argv[1].ptr; + + func_function = ctx->function; + func_ctx = ctx->ctx; +} + +static +void i64_method_1_arg_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 1); + test_assert(argv != NULL); + + *(ecs_i64_t*)result->ptr = + *(int64_t*)argv[0].ptr + + *(int64_t*)argv[1].ptr; + func_function = ctx->function; + func_ctx = ctx->ctx; +} + + +static +void func_1_position_arg_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 1); + test_assert(argv != NULL); + + Position *p = argv[0].ptr; + + *(ecs_i32_t*)result->ptr = p->x + p->y; + func_function = ctx->function; + func_ctx = ctx->ctx; +} + +static +void func_1_array_arg_callback( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 1); + test_assert(argv != NULL); + + typedef int32_t Ints[2]; + + Ints *p = argv[0].ptr; + + *(ecs_i32_t*)result->ptr = (*p)[0] + (*p)[1]; + func_function = ctx->function; + func_ctx = ctx->ctx; +} + +void Expr_root_func_void_return(void) { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + ecs_script_function(world, { + .name = "func", + .callback = func_callback + }); +} + +void Expr_root_func(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .callback = func_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func()", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 10); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_scoped_func(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .parent = ecs_entity(world, { .name = "parent" }), + .return_type = ecs_id(ecs_i32_t), + .callback = func_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "parent.func()", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 10); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_1_arg(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(ecs_i32_t) } + }, + .callback = func_1_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func(10)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 10); + + test_assert(ecs_script_expr_run(world, "func(20)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 20); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_1_arg_expr(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(ecs_i32_t) } + }, + .callback = func_1_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func(10 + 20)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 30); + + test_assert(ecs_script_expr_run(world, "func(20 + 30)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 50); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_2_args(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(ecs_i32_t) }, + { "b", ecs_id(ecs_i32_t) } + }, + .callback = func_2_args_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func(10, 20)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 30); + + test_assert(ecs_script_expr_run(world, "func(20, 30)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 50); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_enum_arg(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t color = ecs_enum(world, { + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = color, + .params = { + { "a", color } + }, + .callback = func_1_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func(Red)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 0); + + test_assert(ecs_script_expr_run(world, "func(Green)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 1); + + test_assert(ecs_script_expr_run(world, "func(Blue)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 2); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_struct_arg(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + { .name = "x", .type = ecs_id(ecs_f32_t) }, + { .name = "y", .type = ecs_id(ecs_f32_t) } + } + }); + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(Position) } + }, + .callback = func_1_position_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func({10, 20})", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 30); + + test_assert(ecs_script_expr_run(world, "func({30, 40})", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 70); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_w_array_arg(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + typedef int32_t Ints[2]; + + ECS_COMPONENT(world, Ints); + + ecs_array(world, { + .entity = ecs_id(Ints), + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_entity_t func = ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(Ints) } + }, + .callback = func_1_array_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "func([10, 20])", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 30); + + test_assert(ecs_script_expr_run(world, "func([30, 40])", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 70); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + +void Expr_root_func_mismatching_args(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_script_function(world, { + .name = "func", + .return_type = ecs_id(ecs_i32_t), + .params = { + { "a", ecs_id(ecs_i32_t) }, + { "b", ecs_id(ecs_i32_t) } + }, + .callback = func_2_args_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + + ecs_log_set_level(-4); + + test_assert(ecs_script_expr_run(world, "func(10)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) == NULL); + + test_assert(ecs_script_expr_run(world, "func(10, 20, 30)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) == NULL); + + ecs_fini(world); +} + +void Expr_method_w_1_arg(void) { + ecs_world_t *world = ecs_init(); + + int ctx; + + ecs_entity_t func = ecs_script_method(world, { + .name = "add", + .parent = ecs_id(ecs_i64_t), + .return_type = ecs_id(ecs_i64_t), + .params = { + { "a", ecs_id(ecs_i64_t) }, + }, + .callback = i64_method_1_arg_callback, + .ctx = &ctx + }); + + int32_t v = 0; + ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_script_expr_run(world, "(10).add(20)", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 30); + + test_assert(ecs_script_expr_run(world, "(20).add(30))", + &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); + test_int(v, 50); + + test_assert(func_function == func); + test_assert(func_ctx == &ctx); + + ecs_fini(world); +} + void Expr_interpolate_string_w_i32_var(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index ceb973cb95..ad776e4361 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -491,6 +491,17 @@ void Expr_var_parent_func(void); void Expr_var_name_func(void); void Expr_var_doc_name_func(void); void Expr_var_chain_func(void); +void Expr_root_func_void_return(void); +void Expr_root_func(void); +void Expr_scoped_func(void); +void Expr_root_func_w_1_arg(void); +void Expr_root_func_w_1_arg_expr(void); +void Expr_root_func_w_2_args(void); +void Expr_root_func_w_enum_arg(void); +void Expr_root_func_w_struct_arg(void); +void Expr_root_func_w_array_arg(void); +void Expr_root_func_mismatching_args(void); +void Expr_method_w_1_arg(void); void Expr_interpolate_string_w_i32_var(void); void Expr_interpolate_string_w_string_var(void); void Expr_interpolate_string_w_entity_var(void); @@ -2615,6 +2626,50 @@ bake_test_case Expr_testcases[] = { "var_chain_func", Expr_var_chain_func }, + { + "root_func_void_return", + Expr_root_func_void_return + }, + { + "root_func", + Expr_root_func + }, + { + "scoped_func", + Expr_scoped_func + }, + { + "root_func_w_1_arg", + Expr_root_func_w_1_arg + }, + { + "root_func_w_1_arg_expr", + Expr_root_func_w_1_arg_expr + }, + { + "root_func_w_2_args", + Expr_root_func_w_2_args + }, + { + "root_func_w_enum_arg", + Expr_root_func_w_enum_arg + }, + { + "root_func_w_struct_arg", + Expr_root_func_w_struct_arg + }, + { + "root_func_w_array_arg", + Expr_root_func_w_array_arg + }, + { + "root_func_mismatching_args", + Expr_root_func_mismatching_args + }, + { + "method_w_1_arg", + Expr_method_w_1_arg + }, { "interpolate_string_w_i32_var", Expr_interpolate_string_w_i32_var @@ -3485,7 +3540,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 174, + 185, Expr_testcases, 1, Expr_params From 47488bb58ac292604235d1e9ec84dce68d0de8da Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 7 Dec 2024 00:52:14 +0000 Subject: [PATCH 39/83] Implement flecs script math functions --- distr/flecs.c | 418 +++++++++++++----- distr/flecs.h | 119 +++-- include/flecs.h | 63 +-- include/flecs/addons/script_math.h | 46 ++ include/flecs/private/addons.h | 10 + meson.build | 3 +- project.json | 2 +- src/addons/script/expr/visit_type.c | 3 +- ...uiltin_functions.c => functions_builtin.c} | 2 +- src/addons/script/functions_math.c | 180 ++++++++ test/custom_builds/c/script/include/script.h | 16 + .../c/script/include/script/bake_config.h | 24 + test/custom_builds/c/script/project.json | 14 + test/custom_builds/c/script/src/main.c | 12 + .../c/script_math/include/script_math.h | 16 + .../include/script_math/bake_config.h | 24 + test/custom_builds/c/script_math/project.json | 14 + test/custom_builds/c/script_math/src/main.c | 37 ++ test/script/project.json | 5 +- test/script/src/Eval.c | 129 ++++++ test/script/src/main.c | 17 +- 21 files changed, 967 insertions(+), 187 deletions(-) create mode 100644 include/flecs/addons/script_math.h rename src/addons/script/{builtin_functions.c => functions_builtin.c} (98%) create mode 100644 src/addons/script/functions_math.c create mode 100644 test/custom_builds/c/script/include/script.h create mode 100644 test/custom_builds/c/script/include/script/bake_config.h create mode 100644 test/custom_builds/c/script/project.json create mode 100644 test/custom_builds/c/script/src/main.c create mode 100644 test/custom_builds/c/script_math/include/script_math.h create mode 100644 test/custom_builds/c/script_math/include/script_math/bake_config.h create mode 100644 test/custom_builds/c/script_math/project.json create mode 100644 test/custom_builds/c/script_math/src/main.c diff --git a/distr/flecs.c b/distr/flecs.c index 71a2b50724..e75d1271c4 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -55061,124 +55061,6 @@ ecs_script_if_t* flecs_script_insert_if( #endif -/** - * @file addons/script/builtin_functions.c - * @brief Builtin script functions. - */ - - -#ifdef FLECS_SCRIPT - -static -void flecs_meta_entity_name( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); -} - -static -void flecs_meta_entity_path( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_get_path(ctx->world, entity); -} - -static -void flecs_meta_entity_parent( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); -} - -#ifdef FLECS_DOC - -static -void flecs_meta_entity_doc_name( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); -} - -static -void flecs_script_register_builtin_doc_functions( - ecs_world_t *world) -{ - ecs_entity_t name = ecs_script_method(world, { - .name = "doc_name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - }); - - ecs_doc_set_brief(world, name, "Returns entity doc name"); -} - -#else - -static -void flecs_script_register_builtin_doc_functions( - ecs_world_t *world) -{ - (void)world; -} - -#endif - -void flecs_script_register_builtin_functions( - ecs_world_t *world) -{ - ecs_entity_t name = ecs_script_method(world, { - .name = "name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - }); - - ecs_doc_set_brief(world, name, "Returns entity name"); - - ecs_entity_t path = ecs_script_method(world, { - .name = "path", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - }); - - ecs_doc_set_brief(world, path, "Returns entity path"); - - ecs_entity_t parent = ecs_script_method(world, { - .name = "parent", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - }); - - ecs_doc_set_brief(world, parent, "Returns entity parent"); - - flecs_script_register_builtin_doc_functions(world); -} - -#endif - /** * @file addons/script/function.c * @brief Script function API. @@ -55345,6 +55227,303 @@ void flecs_script_function_import( #endif +/** + * @file addons/script/builtin_functions.c + * @brief Flecs functions for flecs script. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_meta_entity_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); +} + +static +void flecs_meta_entity_path( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_path(ctx->world, entity); +} + +static +void flecs_meta_entity_parent( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); +} + +#ifdef FLECS_DOC + +static +void flecs_meta_entity_doc_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); +} + +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_script_method(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + }); + + ecs_doc_set_brief(world, name, "Returns entity doc name"); +} + +#else + +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + (void)world; +} + +#endif + +void flecs_script_register_builtin_functions( + ecs_world_t *world) +{ + ecs_entity_t name = ecs_script_method(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + }); + + ecs_doc_set_brief(world, name, "Returns entity name"); + + ecs_entity_t path = ecs_script_method(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + }); + + ecs_doc_set_brief(world, path, "Returns entity path"); + + ecs_entity_t parent = ecs_script_method(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + }); + + ecs_doc_set_brief(world, parent, "Returns entity parent"); + + flecs_script_register_builtin_doc_functions(world); +} + +#endif + +/** + * @file addons/script/functions_math.c + * @brief Math functions for flecs script. + */ + + +#ifdef FLECS_SCRIPT_MATH +#include + +#define FLECS_MATH_FUNC_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + double y = *(double*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_I32(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_f64_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_i32_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +/* Trigonometric functions */ +FLECS_MATH_FUNC_F64(cos, cos(x)) +FLECS_MATH_FUNC_F64(sin, sin(x)) +FLECS_MATH_FUNC_F64(tan, tan(x)) +FLECS_MATH_FUNC_F64(acos, acos(x)) +FLECS_MATH_FUNC_F64(asin, asin(x)) +FLECS_MATH_FUNC_F64(atan, atan(x)) +FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) + +/* Hyperbolic functions */ +FLECS_MATH_FUNC_F64(cosh, cosh(x)) +FLECS_MATH_FUNC_F64(sinh, sinh(x)) +FLECS_MATH_FUNC_F64(tanh, tanh(x)) +FLECS_MATH_FUNC_F64(acosh, acosh(x)) +FLECS_MATH_FUNC_F64(asinh, asinh(x)) +FLECS_MATH_FUNC_F64(atanh, atanh(x)) + +/* Exponential and logarithmic functions */ +FLECS_MATH_FUNC_F64(exp, exp(x)) +FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) +FLECS_MATH_FUNC_F64(log, log(x)) +FLECS_MATH_FUNC_F64(log10, log10(x)) +FLECS_MATH_FUNC_F64(exp2, exp2(x)) +FLECS_MATH_FUNC_F64(log2, log2(x)) + +/* Power functions */ +FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) +FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) +FLECS_MATH_FUNC_F64(sqr, x * x) + +/* Rounding functions */ +FLECS_MATH_FUNC_F64(ceil, ceil(x)) +FLECS_MATH_FUNC_F64(floor, floor(x)) +FLECS_MATH_FUNC_F64(round, round(x)) + +FLECS_API +void FlecsScriptMathImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScriptMath); + + ECS_IMPORT(world, FlecsScript); + + /* Trigonometric functions */ + FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); + FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); + FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); + FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); + FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); + FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); + FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); + + /* Hyperbolic functions */ + FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); + FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); + + /* Exponential and logarithmic functions */ + FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significand and exponent"); + FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); + FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); + FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); + FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); + + /* Power functions */ + FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); + FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); + FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); + + /* Rounding functions */ + FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); + FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); + FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); +} + +#endif + /** * @file addons/script/interpolate.c * @brief String interpolation. @@ -77096,7 +77275,8 @@ int flecs_expr_function_visit_type( try_function: if (!is_method) { - ecs_entity_t func = ecs_lookup(world, node->function_name); + ecs_entity_t func = desc->lookup_action( + world, node->function_name, desc->lookup_ctx); if (!func) { flecs_expr_visit_error(script, node, "unresolved function identifier '%s'", diff --git a/distr/flecs.h b/distr/flecs.h index 0a391be2fd..ddb60782d2 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -146,6 +146,16 @@ */ // #define FLECS_CPP_NO_AUTO_REGISTRATION +/** @def FLECS_CPP_NO_AUTO_REGISTRATION + * When set, the C++ API will require that components are registered before they + * are used. This is useful in multithreaded applications, where components need + * to be registered beforehand, and to catch issues in projects where component + * registration is mandatory. Disabling automatic component registration also + * slightly improves performance. + * The C API is not affected by this feature. + */ +// #define FLECS_CPP_NO_AUTO_REGISTRATION + /** \def FLECS_CUSTOM_BUILD * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add @@ -175,38 +185,29 @@ */ // #define FLECS_CUSTOM_BUILD -/** @def FLECS_CPP_NO_AUTO_REGISTRATION - * When set, the C++ API will require that components are registered before they - * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component - * registration is mandatory. Disabling automatic component registration also - * slightly improves performance. - * The C API is not affected by this feature. - */ -// #define FLECS_CPP_NO_AUTO_REGISTRATION - #ifndef FLECS_CUSTOM_BUILD -// #define FLECS_C /**< C API convenience macros, always enabled */ -#define FLECS_CPP /**< C++ API */ -#define FLECS_MODULE /**< Module support */ -#define FLECS_SCRIPT /**< ECS data definition format */ -#define FLECS_STATS /**< Track runtime statistics */ -#define FLECS_METRICS /**< Expose component data as statistics */ -#define FLECS_ALERTS /**< Monitor conditions for errors */ -#define FLECS_SYSTEM /**< System support */ -#define FLECS_PIPELINE /**< Pipeline support */ -#define FLECS_TIMER /**< Timer support */ -#define FLECS_META /**< Reflection support */ -#define FLECS_UNITS /**< Builtin standard units */ -#define FLECS_JSON /**< Parsing JSON to/from component values */ -#define FLECS_DOC /**< Document entities & components */ -#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ -#define FLECS_APP /**< Application addon */ -#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ -#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ -#define FLECS_REST /**< REST API for querying application data */ -// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ -// #define FLECS_PERF_TRACE /**< Enable performance tracing (disabled by default) */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ +#define FLECS_APP /**< Application addon */ +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_DOC /**< Document entities & components */ +// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_META /**< Reflection support */ +#define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +// #define FLECS_PERF_TRACE /**< Enable performance tracing (disabled by default) */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_REST /**< REST API for querying application data */ +#define FLECS_SCRIPT /**< Flecs entity notation language */ +// #define FLECS_SCRIPT_MATH /**< Math functions for flecs script (may require linking with libm) */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_STATS /**< Track runtime statistics */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_UNITS /**< Builtin standard units */ #endif // ifndef FLECS_CUSTOM_BUILD /** @def FLECS_LOW_FOOTPRINT @@ -10395,6 +10396,9 @@ int ecs_value_move_ctor( #ifdef FLECS_NO_SCRIPT #undef FLECS_SCRIPT #endif +#ifdef FLECS_NO_SCRIPT_MATH +#undef FLECS_SCRIPT_MATH +#endif #ifdef FLECS_NO_STATS #undef FLECS_STATS #endif @@ -14211,6 +14215,59 @@ void FlecsUnitsImport( #endif +#ifdef FLECS_SCRIPT_MATH +#ifdef FLECS_NO_SCRIPT_MATH +#error "FLECS_NO_SCRIPT_MATH failed: SCRIPT_MATH is required by other addons" +#endif +/** + * @file addons/script_math.h + * @brief Math functions for flecs script. + */ + +#ifdef FLECS_SCRIPT_MATH + +#ifndef FLECS_SCRIPT +#define FLECS_SCRIPT +#endif + +/** + * @defgroup c_addons_script_math Script Math + * @ingroup c_addons + * Math functions for flecs script. + * @{ + */ + +#ifndef FLECS_SCRIPT_MATH_H +#define FLECS_SCRIPT_MATH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Script math import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsScriptMath) + * @endcode + * + * @param world The world. + */ +FLECS_API +void FlecsScriptMathImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + #ifdef FLECS_SCRIPT #ifdef FLECS_NO_SCRIPT #error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" diff --git a/include/flecs.h b/include/flecs.h index 8dde6591f1..027abc2392 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -144,6 +144,16 @@ */ // #define FLECS_CPP_NO_AUTO_REGISTRATION +/** @def FLECS_CPP_NO_AUTO_REGISTRATION + * When set, the C++ API will require that components are registered before they + * are used. This is useful in multithreaded applications, where components need + * to be registered beforehand, and to catch issues in projects where component + * registration is mandatory. Disabling automatic component registration also + * slightly improves performance. + * The C API is not affected by this feature. + */ +// #define FLECS_CPP_NO_AUTO_REGISTRATION + /** \def FLECS_CUSTOM_BUILD * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add @@ -173,38 +183,29 @@ */ // #define FLECS_CUSTOM_BUILD -/** @def FLECS_CPP_NO_AUTO_REGISTRATION - * When set, the C++ API will require that components are registered before they - * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component - * registration is mandatory. Disabling automatic component registration also - * slightly improves performance. - * The C API is not affected by this feature. - */ -// #define FLECS_CPP_NO_AUTO_REGISTRATION - #ifndef FLECS_CUSTOM_BUILD -// #define FLECS_C /**< C API convenience macros, always enabled */ -#define FLECS_CPP /**< C++ API */ -#define FLECS_MODULE /**< Module support */ -#define FLECS_SCRIPT /**< ECS data definition format */ -#define FLECS_STATS /**< Track runtime statistics */ -#define FLECS_METRICS /**< Expose component data as statistics */ -#define FLECS_ALERTS /**< Monitor conditions for errors */ -#define FLECS_SYSTEM /**< System support */ -#define FLECS_PIPELINE /**< Pipeline support */ -#define FLECS_TIMER /**< Timer support */ -#define FLECS_META /**< Reflection support */ -#define FLECS_UNITS /**< Builtin standard units */ -#define FLECS_JSON /**< Parsing JSON to/from component values */ -#define FLECS_DOC /**< Document entities & components */ -#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ -#define FLECS_APP /**< Application addon */ -#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ -#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ -#define FLECS_REST /**< REST API for querying application data */ -// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ -// #define FLECS_PERF_TRACE /**< Enable performance tracing (disabled by default) */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ +#define FLECS_APP /**< Application addon */ +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_DOC /**< Document entities & components */ +// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_META /**< Reflection support */ +#define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +// #define FLECS_PERF_TRACE /**< Enable performance tracing (disabled by default) */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_REST /**< REST API for querying application data */ +#define FLECS_SCRIPT /**< Flecs entity notation language */ +// #define FLECS_SCRIPT_MATH /**< Math functions for flecs script (may require linking with libm) */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_STATS /**< Track runtime statistics */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_UNITS /**< Builtin standard units */ #endif // ifndef FLECS_CUSTOM_BUILD /** @def FLECS_LOW_FOOTPRINT diff --git a/include/flecs/addons/script_math.h b/include/flecs/addons/script_math.h new file mode 100644 index 0000000000..918576cb00 --- /dev/null +++ b/include/flecs/addons/script_math.h @@ -0,0 +1,46 @@ +/** + * @file addons/script_math.h + * @brief Math functions for flecs script. + */ + +#ifdef FLECS_SCRIPT_MATH + +#ifndef FLECS_SCRIPT +#define FLECS_SCRIPT +#endif + +/** + * @defgroup c_addons_script_math Script Math + * @ingroup c_addons + * Math functions for flecs script. + * @{ + */ + +#ifndef FLECS_SCRIPT_MATH_H +#define FLECS_SCRIPT_MATH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Script math import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsScriptMath) + * @endcode + * + * @param world The world. + */ +FLECS_API +void FlecsScriptMathImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif diff --git a/include/flecs/private/addons.h b/include/flecs/private/addons.h index 6f2775bef6..17945b5a2c 100644 --- a/include/flecs/private/addons.h +++ b/include/flecs/private/addons.h @@ -18,6 +18,9 @@ #ifdef FLECS_NO_SCRIPT #undef FLECS_SCRIPT #endif +#ifdef FLECS_NO_SCRIPT_MATH +#undef FLECS_SCRIPT_MATH +#endif #ifdef FLECS_NO_STATS #undef FLECS_STATS #endif @@ -161,6 +164,13 @@ #include "../addons/units.h" #endif +#ifdef FLECS_SCRIPT_MATH +#ifdef FLECS_NO_SCRIPT_MATH +#error "FLECS_NO_SCRIPT_MATH failed: SCRIPT_MATH is required by other addons" +#endif +#include "../addons/script_math.h" +#endif + #ifdef FLECS_SCRIPT #ifdef FLECS_NO_SCRIPT #error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" diff --git a/meson.build b/meson.build index 893a5c0819..932bf7e6d0 100644 --- a/meson.build +++ b/meson.build @@ -59,8 +59,9 @@ flecs_src = files( 'src/addons/rest.c', 'src/addons/script/template.c', 'src/addons/script/ast.c', - 'src/addons/script/builtin_functions.c', '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', diff --git a/project.json b/project.json index 85a1c2e79b..53bb4643d2 100644 --- a/project.json +++ b/project.json @@ -21,7 +21,7 @@ }, "lang.c": { "${os linux}": { - "lib": ["rt", "pthread"], + "lib": ["rt", "pthread", "m"], "${cfg debug}": { "export-symbols": true }, diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index f22b7ea467..8b642bde5c 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -744,7 +744,8 @@ int flecs_expr_function_visit_type( try_function: if (!is_method) { - ecs_entity_t func = ecs_lookup(world, node->function_name); + ecs_entity_t func = desc->lookup_action( + world, node->function_name, desc->lookup_ctx); if (!func) { flecs_expr_visit_error(script, node, "unresolved function identifier '%s'", diff --git a/src/addons/script/builtin_functions.c b/src/addons/script/functions_builtin.c similarity index 98% rename from src/addons/script/builtin_functions.c rename to src/addons/script/functions_builtin.c index af4f0c18bf..a855d8a227 100644 --- a/src/addons/script/builtin_functions.c +++ b/src/addons/script/functions_builtin.c @@ -1,6 +1,6 @@ /** * @file addons/script/builtin_functions.c - * @brief Builtin script functions. + * @brief Flecs functions for flecs script. */ #include "flecs.h" diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c new file mode 100644 index 0000000000..0a12186eb5 --- /dev/null +++ b/src/addons/script/functions_math.c @@ -0,0 +1,180 @@ +/** + * @file addons/script/functions_math.c + * @brief Math functions for flecs script. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT_MATH +#include "script.h" +#include + +#define FLECS_MATH_FUNC_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + double y = *(double*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_I32(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_f64_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ + {\ + ecs_entity_t f = ecs_script_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_i32_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +/* Trigonometric functions */ +FLECS_MATH_FUNC_F64(cos, cos(x)) +FLECS_MATH_FUNC_F64(sin, sin(x)) +FLECS_MATH_FUNC_F64(tan, tan(x)) +FLECS_MATH_FUNC_F64(acos, acos(x)) +FLECS_MATH_FUNC_F64(asin, asin(x)) +FLECS_MATH_FUNC_F64(atan, atan(x)) +FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) + +/* Hyperbolic functions */ +FLECS_MATH_FUNC_F64(cosh, cosh(x)) +FLECS_MATH_FUNC_F64(sinh, sinh(x)) +FLECS_MATH_FUNC_F64(tanh, tanh(x)) +FLECS_MATH_FUNC_F64(acosh, acosh(x)) +FLECS_MATH_FUNC_F64(asinh, asinh(x)) +FLECS_MATH_FUNC_F64(atanh, atanh(x)) + +/* Exponential and logarithmic functions */ +FLECS_MATH_FUNC_F64(exp, exp(x)) +FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) +FLECS_MATH_FUNC_F64(log, log(x)) +FLECS_MATH_FUNC_F64(log10, log10(x)) +FLECS_MATH_FUNC_F64(exp2, exp2(x)) +FLECS_MATH_FUNC_F64(log2, log2(x)) + +/* Power functions */ +FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) +FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) +FLECS_MATH_FUNC_F64(sqr, x * x) + +/* Rounding functions */ +FLECS_MATH_FUNC_F64(ceil, ceil(x)) +FLECS_MATH_FUNC_F64(floor, floor(x)) +FLECS_MATH_FUNC_F64(round, round(x)) + +FLECS_API +void FlecsScriptMathImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScriptMath); + + ECS_IMPORT(world, FlecsScript); + + /* Trigonometric functions */ + FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); + FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); + FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); + FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); + FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); + FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); + FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); + + /* Hyperbolic functions */ + FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); + FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); + + /* Exponential and logarithmic functions */ + FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significand and exponent"); + FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); + FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); + FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); + FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); + + /* Power functions */ + FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); + FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); + FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); + + /* Rounding functions */ + FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); + FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); + FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); +} + +#endif diff --git a/test/custom_builds/c/script/include/script.h b/test/custom_builds/c/script/include/script.h new file mode 100644 index 0000000000..41a9a03523 --- /dev/null +++ b/test/custom_builds/c/script/include/script.h @@ -0,0 +1,16 @@ +#ifndef SCRIPT_H +#define SCRIPT_H + +/* This generated file contains includes for project dependencies */ +#include "script/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/test/custom_builds/c/script/include/script/bake_config.h b/test/custom_builds/c/script/include/script/bake_config.h new file mode 100644 index 0000000000..3969c3821a --- /dev/null +++ b/test/custom_builds/c/script/include/script/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef SCRIPT_BAKE_CONFIG_H +#define SCRIPT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include "../../deps/flecs.h" + +#endif + diff --git a/test/custom_builds/c/script/project.json b/test/custom_builds/c/script/project.json new file mode 100644 index 0000000000..16c8f4c4ea --- /dev/null +++ b/test/custom_builds/c/script/project.json @@ -0,0 +1,14 @@ +{ + "id": "script", + "type": "application", + "value": { + "public": false, + "use": [ + "flecs" + ], + "standalone": true + }, + "lang.c": { + "defines": ["FLECS_CUSTOM_BUILD", "FLECS_SCRIPT"] + } +} \ No newline at end of file diff --git a/test/custom_builds/c/script/src/main.c b/test/custom_builds/c/script/src/main.c new file mode 100644 index 0000000000..6a208e7bec --- /dev/null +++ b/test/custom_builds/c/script/src/main.c @@ -0,0 +1,12 @@ +#include +#include + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_mini(); + (void)argc; + (void)argv; + + ECS_IMPORT(world, FlecsScript); + + return ecs_fini(world); +} diff --git a/test/custom_builds/c/script_math/include/script_math.h b/test/custom_builds/c/script_math/include/script_math.h new file mode 100644 index 0000000000..86cc5c01fe --- /dev/null +++ b/test/custom_builds/c/script_math/include/script_math.h @@ -0,0 +1,16 @@ +#ifndef SCRIPT_MATH_H +#define SCRIPT_MATH_H + +/* This generated file contains includes for project dependencies */ +#include "script_math/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/test/custom_builds/c/script_math/include/script_math/bake_config.h b/test/custom_builds/c/script_math/include/script_math/bake_config.h new file mode 100644 index 0000000000..ff5e590429 --- /dev/null +++ b/test/custom_builds/c/script_math/include/script_math/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef SCRIPT_MATH_BAKE_CONFIG_H +#define SCRIPT_MATH_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include "../../deps/flecs.h" + +#endif + diff --git a/test/custom_builds/c/script_math/project.json b/test/custom_builds/c/script_math/project.json new file mode 100644 index 0000000000..3605da0d4b --- /dev/null +++ b/test/custom_builds/c/script_math/project.json @@ -0,0 +1,14 @@ +{ + "id": "script_math", + "type": "application", + "value": { + "public": false, + "use": [ + "flecs" + ], + "standalone": true + }, + "lang.c": { + "defines": ["FLECS_CUSTOM_BUILD", "FLECS_SCRIPT_MATH"] + } +} \ No newline at end of file diff --git a/test/custom_builds/c/script_math/src/main.c b/test/custom_builds/c/script_math/src/main.c new file mode 100644 index 0000000000..61e49bd1bb --- /dev/null +++ b/test/custom_builds/c/script_math/src/main.c @@ -0,0 +1,37 @@ +#include +#include + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_IMPORT(world, FlecsScriptMath); + + int32_t v = 0; + (void)v; + + assert(ecs_script_expr_run(world, "flecs.script.math.sqr(10)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 100); + + assert(ecs_script_expr_run(world, "flecs.script.math.sqrt(100)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 10); + + assert(ecs_script_expr_run(world, "flecs.script.math.pow(5, 2)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 25); + + assert(ecs_script_expr_run(world, "flecs.script.math.ceil(1.6)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 2); + + assert(ecs_script_expr_run(world, "flecs.script.math.floor(1.6)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 1); + + assert(ecs_script_expr_run(world, "flecs.script.math.round(1.6)", + &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); + assert(v == 2); + + return ecs_fini(world); +} diff --git a/test/script/project.json b/test/script/project.json index f2ef2c15d0..3cb5f0894f 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -264,7 +264,10 @@ "partial_assign_with_large_array", "non_trivial_var_component", "non_trivial_var_with", - "update_template_w_tag" + "update_template_w_tag", + "assign_call_func", + "assign_call_scoped_func", + "assign_call_scoped_func_w_using" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 7b4504a86e..7148aaf39d 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -8228,3 +8228,132 @@ void Eval_update_template_w_tag(void) { ecs_fini(world); } + +static +void func_sqr( + const ecs_function_ctx_t *ctx, + int argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + int32_t v = *(int32_t*)argv[0].ptr; + *(int32_t*)result->ptr = v * v; +} + +void Eval_assign_call_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_function(world, { + .name = "sqr", + .return_type = ecs_id(ecs_i32_t), + .params = {{ "x", ecs_id(ecs_i32_t) }}, + .callback = func_sqr, + }); + + const char *expr = + HEAD "Foo = Position: {sqr(2), sqr(3)}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + + test_assert(foo != 0); + + test_assert(ecs_has(world, foo, Position)); + + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + + test_int(ptr->x, 4); + test_int(ptr->y, 9); + + ecs_fini(world); +} + +void Eval_assign_call_scoped_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + + ecs_script_function(world, { + .name = "sqr", + .parent = parent, + .return_type = ecs_id(ecs_i32_t), + .params = {{ "x", ecs_id(ecs_i32_t) }}, + .callback = func_sqr, + }); + + const char *expr = + HEAD "Foo = Position: {parent.sqr(2), parent.sqr(3)}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + + test_assert(foo != 0); + + test_assert(ecs_has(world, foo, Position)); + + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + + test_int(ptr->x, 4); + test_int(ptr->y, 9); + + ecs_fini(world); +} + +void Eval_assign_call_scoped_func_w_using(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + + ecs_script_function(world, { + .name = "sqr", + .parent = parent, + .return_type = ecs_id(ecs_i32_t), + .params = {{ "x", ecs_id(ecs_i32_t) }}, + .callback = func_sqr, + }); + + const char *expr = + HEAD "using parent" + LINE "Foo = Position: {sqr(2), sqr(3)}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + + test_assert(foo != 0); + + test_assert(ecs_has(world, foo, Position)); + + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + + test_int(ptr->x, 4); + test_int(ptr->y, 9); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index ad776e4361..51d71ad0d8 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -261,6 +261,9 @@ void Eval_partial_assign_with_large_array(void); void Eval_non_trivial_var_component(void); void Eval_non_trivial_var_with(void); void Eval_update_template_w_tag(void); +void Eval_assign_call_func(void); +void Eval_assign_call_scoped_func(void); +void Eval_assign_call_scoped_func_w_using(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -1724,6 +1727,18 @@ bake_test_case Eval_testcases[] = { { "update_template_w_tag", Eval_update_template_w_tag + }, + { + "assign_call_func", + Eval_assign_call_func + }, + { + "assign_call_scoped_func", + Eval_assign_call_scoped_func + }, + { + "assign_call_scoped_func_w_using", + Eval_assign_call_scoped_func_w_using } }; @@ -3519,7 +3534,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 252, + 255, Eval_testcases }, { From b4e6acc89f3d0de7e01c5c948287b983176f9386 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 7 Dec 2024 04:39:16 +0000 Subject: [PATCH 40/83] Add section on expressions to Flecs Script manual --- docs/FlecsScript.md | 184 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 3 deletions(-) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 16f904eca0..6abeddfc65 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -27,7 +27,7 @@ struct Position { y = f32 } -Prefab SpaceShip { +prefab SpaceShip { MaxSpeed: {value: 100} cockpit { @@ -242,7 +242,7 @@ prefab SpaceShip { Scripts can natively specify inheritance relationships between entities, which is useful in particular for prefabs. Example: ```c -Prefab SpaceShip { +prefab SpaceShip { MaxSpeed: {value: 100} } @@ -572,7 +572,7 @@ my_spaceship { } ``` -## Comma operator +### Comma operator The comma operator can be used as a shortcut to create multiple entities in a scope. Example: ```c @@ -609,6 +609,184 @@ enum Color { } ``` +## Expressions +Scripts can contain expressions, which allow for computing values from inputs such as component values, template properties and variables. Here are some examples of valid Flecs script expressions: + +```c +const x = 10 + 20 * 30 +const x = 10 * (20 + 30) +const x = $var * 10 +const x = pow($var, 2) +const x = e.parent().name() +const x = Position: {10, 20} +const x = Position: {x: 10, y: 20} +``` + +The following sections describe the different features of expressions. + +### Operators +The following operators are supported in expressions, in order of precedence: + +| **Symbol** | **Description** | **Example** | +|------------|------------------------|------------------------| +| `!` | Logical NOT | `!10` | +| `*` | Multiplication | `10 * 20` | +| `/` | Division | `10 / 20` | +| `%` | Modulus | `10 % 3` | +| `+` | Addition | `10 + 20` | +| `-` | Subtraction/negative | `10 - 20`, `-(10, 20)` | +| `<<` | Bitwise left shift | `10 << 1` | +| `>>` | Bitwise right shift | `10 >> 1` | +| `>` | Greater than | `10 > 20` | +| `>=` | Greater than or equal | `10 >= 20` | +| `<` | Less than | `10 < 20` | +| `<=` | Less than or equal | `10 <= 20` | +| `==` | Equality | `10 == 20` | +| `!=` | Not equal | `10 != 20` | +| `&` | Bitwise AND | `2 & 6` | +| `\|` | Bitwise OR | `2 | 4` | +| `&&` | Logical AND | `true && false` | +| `\|\|` | Logical OR | `true || false` | + +### Values +The following table lists the different kinds of values that are supported in expressions: + +| **Value kind** | **Type** | **Example** | +|---------------------------|---------------|-----------------------------------| +| **Integer** | `i64` | `42`, `-100`, `0`, `0x1A` | +| **Floating Point** | `f64` | `3.14`, `-2.718`, `1e6`, `0.0` | +| **String** | `string` | `"Hello, World!"`, `"123"`, `""` | +| **Multiline string** | `string` | \``Hello World`\` | +| **Entity** | `entity` | `spaceship`, `spaceship.pilot` | +| **Enum/Bitmask values** | from lvalue | `Red`, `Blue`, `Lettuce \| Bacon` | +| **Composites** | from lvalue | `{x: 10, y: 20}`, `{10, 20}` | +| **Collections** | from lvalue | `[1, 2, 3]` | + +#### Initializers +Initializers are values that are used to initialize composite and collection members. Composite values are initialized by initializers that are delimited by `{}`, while collection initializers are delimited by `[]`. Furthermore, composite initializers can specify which member of the composite value should be initialized. Here are some examples of initializer expressions: + +```c +{} +{10, 20} +{x: 10, y: 20} +{{10, 20}, {30, 40}} +{start: {x: 10, y: 20}, stop: {x: 10, y: 20}} +[10, 20, 30] +[{10, 20}, {30, 40}, {50, 60}] +``` + +Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: + +```c +const x = Position: {10, 20} +``` + +while this is an invalid usage of an initializer: + +```c +// Invalid, unknown type for initializer +const x = {10, 20} +``` + +When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element: + +```c +// Normal notation +Tree: {color: $color, height: $height} + + +// Shorthand notation +Tree: {color: $, height: $} +``` + +### Types +The type of an expression is determined by the kind of expression, its operands and the context in which the expression is evaluated. The words "type" and "component" can be used interchangeably, as every type in Flecs is a component, and every component is a type. For component types to be used with scripts, they have to be described using the meta reflection addon. + +The following sections go over the different kinds of expressions and how their types are derived. + +#### Unary expressions +Unary expressions have a single operand, with the operator preceding it. The following table shows the different unary operators with the expression type: + +| **Operator** | **Expression Type** | +|--------------|-------------------------| +| `!` | `bool` | +| `-` | Same as operand. | + +#### Binary expressions +Binary expressions have two operands. The following table shows the different binary operators with the expression type. The operand type is the type to which the operands must be castable for it to be a valid expression. + +| **Symbol** | **Expression type** | **Operand type** | +|------------|----------------------|----------------------| +| `*` | other (see below) | Numbers | +| `/` | `f64` | Numbers | +| `+` | other (see below) | Numbers | +| `-` | other (see below) | Numbers | +| `<<` | other (see below) | Integers | +| `>>` | other (see below) | Integers | +| `>` | `bool` | Numbers | +| `>=` | `bool` | Numbers | +| `<` | `bool` | Numbers | +| `<=` | `bool` | Numbers | +| `==` | `bool` | Values | +| `!=` | `bool` | Values | +| `&` | other (see below) | Integers | +| `\|` | other (see below) | Integers | +| `&&` | `bool` | `bool` | +| `\|\|` | `bool` | `bool` | + +For the operators where the expression type is listed as "other" the type is derived by going through these steps: +- If the types of the operands are equal, the expression type will be the operand type. +- If the types are different: + - Convert the operand types to their largest storage variant (`i8` becomes `i64`, `f32` becomes `f64`, `u16` becomes `u64`). + - The type of the expression becomes the most expressive of the two. + - Expressiveness is determined as `f64` > `i64` > `u64`. + +#### Lvalues +Lvalues are the left side of assignments. There are two kinds of assignments possible in Flecs script: +- Variable initialization +- Initializer initialization + +The type of an expression can be influenced by the type of the lvalue it is assigned to. For example, if the lvalue is a variable of type `Position`, the assigned initializer will also be of type `Position`: + +```c +const p = Position: {10, 20} +``` + +Similarly, when an initializer is used inside of an initializer, it obtains the type of the initializer element. In the following example the outer initializer is of type `Line`, while the inner initializers are of type `Point`: + +```c +const l = Line: {{10, 20}, {30, 40}} +``` + +Another notable example where this matters is for enum and bitmask constants. Consider the following example: + +```c +const c = Color: Red +``` + +Here, `Red` is a resolvable identifier, even though the fully qualified identifier is `Color.Red`. However, because the type of the lvalue is of enum type `Color`, the expression `Red` will be resolved in the scope of `Color`. + +### Functions +Expressions can call functions. Functions in Flecs script can have arguments of any type, and must return a value. The following snippet shows examples of function calls: + +```c +const x = sqrt(100) +const x = pow(100, 2) +const x = add({10, 20}, {30, 40}) +``` + +Currently functions can only be defined outside of scripts by the Flecs Script API. Flecs comes with a set of builtin and math functions. Math functions are defined by the script math addon, which must be explicitly enabled by defining `FLECS_SCRIPT_MATH`. + +### Methods +Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: + +```c +const x = v.length() +const x = v1.add(v2) +``` + +Just like functions, methods can currently only be defined outside of scripts by using the Flecs Script API. + ## Templates Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: From d942964214ac26d96eab43fd2ac21cb2963fed1f Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 02:21:00 +0000 Subject: [PATCH 41/83] Add flecs script examples --- examples/c/script/expr_run/include/expr_run.h | 16 ++++ .../expr_run/include/expr_run/bake_config.h | 24 +++++ examples/c/script/expr_run/project.json | 10 +++ examples/c/script/expr_run/src/main.c | 32 +++++++ .../script/expr_w_vars/include/expr_w_vars.h | 16 ++++ .../include/expr_w_vars/bake_config.h | 24 +++++ examples/c/script/expr_w_vars/project.json | 10 +++ examples/c/script/expr_w_vars/src/main.c | 64 ++++++++++++++ .../script_managed/include/script_managed.h | 16 ++++ .../include/script_managed/bake_config.h | 24 +++++ examples/c/script/script_managed/project.json | 10 +++ examples/c/script/script_managed/src/main.c | 87 +++++++++++++++++++ .../c/script/script_run/include/script_run.h | 16 ++++ .../include/script_run/bake_config.h | 24 +++++ examples/c/script/script_run/project.json | 10 +++ examples/c/script/script_run/src/main.c | 45 ++++++++++ 16 files changed, 428 insertions(+) create mode 100644 examples/c/script/expr_run/include/expr_run.h create mode 100644 examples/c/script/expr_run/include/expr_run/bake_config.h create mode 100644 examples/c/script/expr_run/project.json create mode 100644 examples/c/script/expr_run/src/main.c create mode 100644 examples/c/script/expr_w_vars/include/expr_w_vars.h create mode 100644 examples/c/script/expr_w_vars/include/expr_w_vars/bake_config.h create mode 100644 examples/c/script/expr_w_vars/project.json create mode 100644 examples/c/script/expr_w_vars/src/main.c create mode 100644 examples/c/script/script_managed/include/script_managed.h create mode 100644 examples/c/script/script_managed/include/script_managed/bake_config.h create mode 100644 examples/c/script/script_managed/project.json create mode 100644 examples/c/script/script_managed/src/main.c create mode 100644 examples/c/script/script_run/include/script_run.h create mode 100644 examples/c/script/script_run/include/script_run/bake_config.h create mode 100644 examples/c/script/script_run/project.json create mode 100644 examples/c/script/script_run/src/main.c diff --git a/examples/c/script/expr_run/include/expr_run.h b/examples/c/script/expr_run/include/expr_run.h new file mode 100644 index 0000000000..398686777f --- /dev/null +++ b/examples/c/script/expr_run/include/expr_run.h @@ -0,0 +1,16 @@ +#ifndef EXPR_RUN_H +#define EXPR_RUN_H + +/* This generated file contains includes for project dependencies */ +#include "expr_run/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/expr_run/include/expr_run/bake_config.h b/examples/c/script/expr_run/include/expr_run/bake_config.h new file mode 100644 index 0000000000..ce0178a55c --- /dev/null +++ b/examples/c/script/expr_run/include/expr_run/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef EXPR_RUN_BAKE_CONFIG_H +#define EXPR_RUN_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/expr_run/project.json b/examples/c/script/expr_run/project.json new file mode 100644 index 0000000000..e0e2174b45 --- /dev/null +++ b/examples/c/script/expr_run/project.json @@ -0,0 +1,10 @@ +{ + "id": "expr_run", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/examples/c/script/expr_run/src/main.c b/examples/c/script/expr_run/src/main.c new file mode 100644 index 0000000000..5f8ffc635f --- /dev/null +++ b/examples/c/script/expr_run/src/main.c @@ -0,0 +1,32 @@ +#include +#include + +// This example shows how to run an expression once. To see how to parse an +// expression and run it multiple times, see the expr_w_vars example. + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + // Declare value that will hold the expression result. Specify that we want + // the returned value to be of type i32. If the expression type doesn't + // match the specified type, it will be casted. + int32_t result = 0; + ecs_value_t result_value = { + .type = ecs_id(ecs_i32_t), + .ptr = &result + }; + + // Run the expression. If the operation is successful it returns a pointer + // to the character after the last parsed token. + if (ecs_script_expr_run(world, "10 + 20", &result_value, NULL) == NULL) { + printf("expression failed to run\n"); + return -1; + } + + printf("result = %d\n", result); + + return ecs_fini(world); + + // Output + // result = 30 +} diff --git a/examples/c/script/expr_w_vars/include/expr_w_vars.h b/examples/c/script/expr_w_vars/include/expr_w_vars.h new file mode 100644 index 0000000000..e37c707b1b --- /dev/null +++ b/examples/c/script/expr_w_vars/include/expr_w_vars.h @@ -0,0 +1,16 @@ +#ifndef EXPR_W_VARS_H +#define EXPR_W_VARS_H + +/* This generated file contains includes for project dependencies */ +#include "expr_w_vars/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/expr_w_vars/include/expr_w_vars/bake_config.h b/examples/c/script/expr_w_vars/include/expr_w_vars/bake_config.h new file mode 100644 index 0000000000..c118d032e3 --- /dev/null +++ b/examples/c/script/expr_w_vars/include/expr_w_vars/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef EXPR_W_VARS_BAKE_CONFIG_H +#define EXPR_W_VARS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/expr_w_vars/project.json b/examples/c/script/expr_w_vars/project.json new file mode 100644 index 0000000000..040a57401b --- /dev/null +++ b/examples/c/script/expr_w_vars/project.json @@ -0,0 +1,10 @@ +{ + "id": "expr_w_vars", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/examples/c/script/expr_w_vars/src/main.c b/examples/c/script/expr_w_vars/src/main.c new file mode 100644 index 0000000000..8e16f324c5 --- /dev/null +++ b/examples/c/script/expr_w_vars/src/main.c @@ -0,0 +1,64 @@ +#include +#include + +// This example shows how to run an expression that uses variables. Variables +// make it possible to change the inputs to an expression after the expression +// has been parsed. This example also shows how to parse an expression and +// evaluate it multiple times. + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + // Declare variables that we'll use in the expression + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *x = ecs_script_vars_define(vars, "x", ecs_i32_t); + ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); + + // Assign values to variables + *(int32_t*)x->value.ptr = 10; + *(int32_t*)y->value.ptr = 20; + + // Declare value that will hold the expression result. Specify that we want + // the returned value to be of type i32. If the expression type doesn't + // match the specified type, it will be casted. + int32_t result = 0; + ecs_value_t result_value = { + .type = ecs_id(ecs_i32_t), + .ptr = &result + }; + + // Parse expression + ecs_script_expr_run_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_expr_parse(world, "$x + $y", &desc); + if (!s) { + printf("failed to parse expression\n"); + return -1; + } + + // Evaluate expression with variables + if (ecs_script_expr_eval(s, &result_value, &desc)) { + printf("failed to evaluate expression\n"); + return -1; + } + + printf("result = %d\n", result); + + // Change variable and reevaluate expression + *(int32_t*)y->value.ptr = 30; + if (ecs_script_expr_eval(s, &result_value, &desc)) { + printf("failed to evaluate expression\n"); + return -1; + } + + printf("result = %d\n", result); + + // Free resources + ecs_script_vars_fini(vars); + ecs_script_free(s); + + return ecs_fini(world); + + // Output + // result = 30 + // result = 40 +} diff --git a/examples/c/script/script_managed/include/script_managed.h b/examples/c/script/script_managed/include/script_managed.h new file mode 100644 index 0000000000..5d0f09545b --- /dev/null +++ b/examples/c/script/script_managed/include/script_managed.h @@ -0,0 +1,16 @@ +#ifndef SCRIPT_MANAGED_H +#define SCRIPT_MANAGED_H + +/* This generated file contains includes for project dependencies */ +#include "script_managed/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/script_managed/include/script_managed/bake_config.h b/examples/c/script/script_managed/include/script_managed/bake_config.h new file mode 100644 index 0000000000..73194e451b --- /dev/null +++ b/examples/c/script/script_managed/include/script_managed/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef SCRIPT_MANAGED_BAKE_CONFIG_H +#define SCRIPT_MANAGED_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/script_managed/project.json b/examples/c/script/script_managed/project.json new file mode 100644 index 0000000000..6035c0c793 --- /dev/null +++ b/examples/c/script/script_managed/project.json @@ -0,0 +1,10 @@ +{ + "id": "script_managed", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/examples/c/script/script_managed/src/main.c b/examples/c/script/script_managed/src/main.c new file mode 100644 index 0000000000..6a120ed5a4 --- /dev/null +++ b/examples/c/script/script_managed/src/main.c @@ -0,0 +1,87 @@ +#include +#include + +/* This example shows how to create a managed script. A managed script remains + * in memory after it is ran, and makes it possible to inspect and modify the + * script. Managed scripts can be inspected and modified from the explorer. + * + * Updating a script will delete any entities or components that are no longer + * described by the script. + */ + +void print_planets(ecs_world_t *world) { + ecs_entity_t earth = ecs_lookup(world, "Sun"); + ecs_iter_t it = ecs_children(world, earth); + while (ecs_children_next(&it)) { + for (int i = 0; i < it.count; i ++) { + printf(" - %s\n", ecs_get_name(world, it.entities[i])); + } + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ecs_entity_t script = ecs_script(world, { + .code = + "Sun {\n" + " Mercury {}\n" + " Venus {}\n" + " Earth {}\n" + " Mars {}\n" + " Jupiter {}\n" + " Saturn {}\n" + " Neptune {}\n" + " Uranus {}\n" + " Pluto {}\n" + "}\n" + }); + + if (!script) { + printf("script failed to run\n"); + } + + printf("Before updating script:\n"); + print_planets(world); + + ecs_script_update(world, script, 0, + "Sun {\n" + " Mercury {}\n" + " Venus {}\n" + " Earth {}\n" + " Mars {}\n" + " Jupiter {}\n" + " Saturn {}\n" + " Neptune {}\n" + " Uranus {}\n" + // Pluto {}\n" not a planet :( + "}\n" + ); + + printf("\nAfter updating script:\n"); + print_planets(world); + + return ecs_fini(world); + + // Output + // Before updating script: + // - Mercury + // - Venus + // - Earth + // - Mars + // - Jupiter + // - Saturn + // - Neptune + // - Uranus + // - Pluto + // + // After updating script: + // - Mercury + // - Venus + // - Earth + // - Mars + // - Jupiter + // - Saturn + // - Neptune + // - Uranus +} diff --git a/examples/c/script/script_run/include/script_run.h b/examples/c/script/script_run/include/script_run.h new file mode 100644 index 0000000000..058b2da432 --- /dev/null +++ b/examples/c/script/script_run/include/script_run.h @@ -0,0 +1,16 @@ +#ifndef SCRIPT_RUN_H +#define SCRIPT_RUN_H + +/* This generated file contains includes for project dependencies */ +#include "script_run/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/script_run/include/script_run/bake_config.h b/examples/c/script/script_run/include/script_run/bake_config.h new file mode 100644 index 0000000000..63dcdd5c66 --- /dev/null +++ b/examples/c/script/script_run/include/script_run/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef SCRIPT_RUN_BAKE_CONFIG_H +#define SCRIPT_RUN_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/script_run/project.json b/examples/c/script/script_run/project.json new file mode 100644 index 0000000000..c91aa46b51 --- /dev/null +++ b/examples/c/script/script_run/project.json @@ -0,0 +1,10 @@ +{ + "id": "script_run", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/examples/c/script/script_run/src/main.c b/examples/c/script/script_run/src/main.c new file mode 100644 index 0000000000..72afca9789 --- /dev/null +++ b/examples/c/script/script_run/src/main.c @@ -0,0 +1,45 @@ +#include + +/* This example shows how to run a script once. To see how to parse a script and + * run it multiple times, see the script_parse_eval example. */ + +const char *script = + "Sun {\n" + " Mercury {}\n" + " Venus {}\n" + " Earth {}\n" + " Mars {}\n" + " Jupiter {}\n" + " Saturn {}\n" + " Neptune {}\n" + " Uranus {}\n" + "}\n" + ; + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + if (ecs_script_run(world, "My script", script)) { + printf("script failed to run\n"); + } + + ecs_entity_t earth = ecs_lookup(world, "Sun"); + ecs_iter_t it = ecs_children(world, earth); + while (ecs_children_next(&it)) { + for (int i = 0; i < it.count; i ++) { + printf(" - %s\n", ecs_get_name(world, it.entities[i])); + } + } + + return ecs_fini(world); + + // Output + // - Mercury + // - Venus + // - Earth + // - Mars + // - Jupiter + // - Saturn + // - Neptune + // - Uranus +} From 9917e11a65ac37a1cefd655ffb4abc6b2443cd92 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 03:10:05 +0000 Subject: [PATCH 42/83] Implement ability to specify variables and runtime to ecs_script_eval --- distr/flecs.c | 56 +++++++---- distr/flecs.h | 22 ++++- include/flecs/addons/script.h | 22 ++++- src/addons/script/parser.c | 5 +- src/addons/script/script.c | 8 +- src/addons/script/template.c | 4 +- src/addons/script/visit_eval.c | 33 +++++-- src/addons/script/visit_eval.h | 6 +- test/script/project.json | 4 +- test/script/src/Eval.c | 168 ++++++++++++++++++++++++++------- test/script/src/main.c | 12 ++- 11 files changed, 268 insertions(+), 72 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index e75d1271c4..2c5fe6fb18 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5412,10 +5412,12 @@ void flecs_script_template_fini( void flecs_script_eval_visit_init( const ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v); + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v); + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); int flecs_script_eval_node( ecs_script_eval_visitor_t *v, @@ -56765,8 +56767,11 @@ component_expr_collection: { ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, - const char *code) + const char *code, + const ecs_script_eval_desc_t *desc) { + (void)desc; /* Will be used in future to expand type checking features */ + if (!code) { code = ""; } @@ -57676,14 +57681,14 @@ int ecs_script_run( const char *name, const char *code) { - ecs_script_t *script = ecs_script_parse(world, name, code); + ecs_script_t *script = ecs_script_parse(world, name, code, NULL); if (!script) { goto error; } ecs_entity_t prev_scope = ecs_set_scope(world, 0); - if (ecs_script_eval(script)) { + if (ecs_script_eval(script, NULL)) { goto error_free; } @@ -57754,7 +57759,7 @@ int ecs_script_update( ecs_script_free(s->script); } - s->script = ecs_script_parse(world, name, code); + s->script = ecs_script_parse(world, name, code, NULL); if (!s->script) { return -1; } @@ -57773,7 +57778,7 @@ int ecs_script_update( ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); - if (ecs_script_eval(s->script)) { + if (ecs_script_eval(s->script, NULL)) { ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } @@ -58481,7 +58486,7 @@ void flecs_script_template_on_set( void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -58555,7 +58560,7 @@ void flecs_script_template_on_set( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v); + flecs_script_eval_visit_fini(&v, NULL); } static @@ -61038,7 +61043,8 @@ int flecs_script_eval_node( void flecs_script_eval_visit_init( const ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v) + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) { *v = (ecs_script_eval_visitor_t){ .base = { @@ -61046,30 +61052,48 @@ void flecs_script_eval_visit_init( .script = ECS_CONST_CAST(ecs_script_impl_t*, script) }, .world = script->pub.world, - .r = ecs_script_runtime_new() + .r = desc ? desc->runtime : NULL }; + if (!v->r) { + v->r = ecs_script_runtime_new(); + } + + if (desc && desc->vars) { + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); + v->vars->parent = desc->vars; + } + /* Always include flecs.meta */ ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v) + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) { - ecs_script_runtime_free(v->r); + if (desc && desc->vars) { + v->vars = ecs_script_vars_pop(v->vars); + } + + if (!desc || (v->r != desc->runtime)) { + ecs_script_runtime_free(v->r); + } } int ecs_script_eval( - const ecs_script_t *script) + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v); + flecs_script_eval_visit_init(impl, &v, desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v); + flecs_script_eval_visit_fini(&v, desc); return result; } diff --git a/distr/flecs.h b/distr/flecs.h index ddb60782d2..fec6663a22 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14400,30 +14400,48 @@ typedef struct EcsScriptMethod { /* Parsing & running scripts */ +/** Used with ecs_script_parse() and ecs_script_eval() */ +typedef struct ecs_script_eval_desc_t { + ecs_script_vars_t *vars; /**< Variables used by script */ + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ +} ecs_script_eval_desc_t; + /** Parse script. * This operation parses a script and returns a script object upon success. To * run the script, call ecs_script_eval(). * + * If the script uses outside variables, an ecs_script_vars_t object must be + * provided in the vars member of the desc object that defines all variables + * with the correct types. + * * @param world The world. * @param name Name of the script (typically a file/module name). * @param code The script code. + * @param desc Parameters for script runtime. * @return Script object if success, NULL if failed. */ FLECS_API ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, - const char *code); + const char *code, + const ecs_script_eval_desc_t *desc); /** Evaluate script. * This operation evaluates (runs) a parsed script. * + * If variables were provided to ecs_script_parse(), an application may pass + * a different ecs_script_vars_t object to ecs_script_eval(), as long as the + * object has all referenced variables and they are of the same type. + * * @param script The script. + * @param desc Parameters for script runtime. * @return Zero if success, non-zero if failed. */ FLECS_API int ecs_script_eval( - const ecs_script_t *script); + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); /** Free script. * This operation frees a script object. diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 08a244a4ea..7f73a87b0d 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -126,30 +126,48 @@ typedef struct EcsScriptMethod { /* Parsing & running scripts */ +/** Used with ecs_script_parse() and ecs_script_eval() */ +typedef struct ecs_script_eval_desc_t { + ecs_script_vars_t *vars; /**< Variables used by script */ + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ +} ecs_script_eval_desc_t; + /** Parse script. * This operation parses a script and returns a script object upon success. To * run the script, call ecs_script_eval(). * + * If the script uses outside variables, an ecs_script_vars_t object must be + * provided in the vars member of the desc object that defines all variables + * with the correct types. + * * @param world The world. * @param name Name of the script (typically a file/module name). * @param code The script code. + * @param desc Parameters for script runtime. * @return Script object if success, NULL if failed. */ FLECS_API ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, - const char *code); + const char *code, + const ecs_script_eval_desc_t *desc); /** Evaluate script. * This operation evaluates (runs) a parsed script. * + * If variables were provided to ecs_script_parse(), an application may pass + * a different ecs_script_vars_t object to ecs_script_eval(), as long as the + * object has all referenced variables and they are of the same type. + * * @param script The script. + * @param desc Parameters for script runtime. * @return Zero if success, non-zero if failed. */ FLECS_API int ecs_script_eval( - const ecs_script_t *script); + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); /** Free script. * This operation frees a script object. diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index ebeae27638..533cea40c7 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -802,8 +802,11 @@ component_expr_collection: { ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, - const char *code) + const char *code, + const ecs_script_eval_desc_t *desc) { + (void)desc; /* Will be used in future to expand type checking features */ + if (!code) { code = ""; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 012b1b6b5b..1f22a0e4f1 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -75,14 +75,14 @@ int ecs_script_run( const char *name, const char *code) { - ecs_script_t *script = ecs_script_parse(world, name, code); + ecs_script_t *script = ecs_script_parse(world, name, code, NULL); if (!script) { goto error; } ecs_entity_t prev_scope = ecs_set_scope(world, 0); - if (ecs_script_eval(script)) { + if (ecs_script_eval(script, NULL)) { goto error_free; } @@ -153,7 +153,7 @@ int ecs_script_update( ecs_script_free(s->script); } - s->script = ecs_script_parse(world, name, code); + s->script = ecs_script_parse(world, name, code, NULL); if (!s->script) { return -1; } @@ -172,7 +172,7 @@ int ecs_script_update( ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); - if (ecs_script_eval(s->script)) { + if (ecs_script_eval(s->script, NULL)) { ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 8e56ee6d35..5c69db7a9e 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -89,7 +89,7 @@ void flecs_script_template_on_set( void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -163,7 +163,7 @@ void flecs_script_template_on_set( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v); + flecs_script_eval_visit_fini(&v, NULL); } static diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 5e6eba4888..f9ae3bd3bf 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1216,7 +1216,8 @@ int flecs_script_eval_node( void flecs_script_eval_visit_init( const ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v) + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) { *v = (ecs_script_eval_visitor_t){ .base = { @@ -1224,30 +1225,48 @@ void flecs_script_eval_visit_init( .script = ECS_CONST_CAST(ecs_script_impl_t*, script) }, .world = script->pub.world, - .r = ecs_script_runtime_new() + .r = desc ? desc->runtime : NULL }; + if (!v->r) { + v->r = ecs_script_runtime_new(); + } + + if (desc && desc->vars) { + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); + v->vars->parent = desc->vars; + } + /* Always include flecs.meta */ ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v) + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) { - ecs_script_runtime_free(v->r); + if (desc && desc->vars) { + v->vars = ecs_script_vars_pop(v->vars); + } + + if (!desc || (v->r != desc->runtime)) { + ecs_script_runtime_free(v->r); + } } int ecs_script_eval( - const ecs_script_t *script) + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v); + flecs_script_eval_visit_init(impl, &v, desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v); + flecs_script_eval_visit_fini(&v, desc); return result; } diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index e523904554..9eb4aecf37 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -60,10 +60,12 @@ void flecs_script_template_fini( void flecs_script_eval_visit_init( const ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v); + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v); + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); int flecs_script_eval_node( ecs_script_eval_visitor_t *v, diff --git a/test/script/project.json b/test/script/project.json index 3cb5f0894f..325109daf0 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -267,7 +267,9 @@ "update_template_w_tag", "assign_call_func", "assign_call_scoped_func", - "assign_call_scoped_func_w_using" + "assign_call_scoped_func_w_using", + "eval_w_vars", + "eval_w_runtime" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 7148aaf39d..d8682406fe 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -1665,7 +1665,7 @@ void Eval_inherit_w_colon_w_scope(void) { void Eval_assign_component_w_value(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -1752,7 +1752,7 @@ void Eval_assign_tag_in_assign_scope_core_name(void) { void Eval_assign_component_value_in_assign_scope(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -1784,7 +1784,7 @@ void Eval_assign_component_value_in_assign_scope(void) { void Eval_assign_2_component_values_in_assign_scope(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -1792,7 +1792,7 @@ void Eval_assign_2_component_values_in_assign_scope(void) { } }); - ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Velocity"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2754,7 +2754,7 @@ void Eval_2_using_in_different_scope(void) { void Eval_scope_after_assign(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2787,7 +2787,7 @@ void Eval_scope_after_assign(void) { void Eval_assign_after_inherit(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2880,7 +2880,7 @@ void Eval_multiple_pairs_single_line(void) { void Eval_multiple_assignments_single_line(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2888,7 +2888,7 @@ void Eval_multiple_assignments_single_line(void) { } }); - ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Velocity"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2924,7 +2924,7 @@ void Eval_multiple_assignments_single_line(void) { void Eval_multiple_vars_single_line(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -2932,7 +2932,7 @@ void Eval_multiple_vars_single_line(void) { } }); - ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Velocity"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3043,7 +3043,7 @@ void Eval_assign_tag_to_parent(void) { void Eval_assign_component_to_parent(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3176,7 +3176,7 @@ void Eval_default_child_component(void) { void Eval_default_child_component_w_assign(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3346,7 +3346,7 @@ void Eval_enum_type_w_default_child_component(void) { void Eval_default_type_from_with(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3384,7 +3384,7 @@ void Eval_default_type_from_with(void) { void Eval_default_type_from_nested_with(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3392,7 +3392,7 @@ void Eval_default_type_from_nested_with(void) { } }); - ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Velocity"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3448,7 +3448,7 @@ void Eval_default_type_from_nested_with(void) { void Eval_default_type_from_with_in_entity_scope_w_default_type(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3456,7 +3456,7 @@ void Eval_default_type_from_with_in_entity_scope_w_default_type(void) { } }); - ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Velocity"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -3523,7 +3523,7 @@ void Eval_default_type_from_with_in_entity_scope_w_default_type(void) { void Eval_default_type_from_entity_scope_in_with(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -4033,7 +4033,7 @@ typedef struct String { void Eval_multiline_string(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(String) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(String) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "String"}), .members = { {"value", ecs_id(ecs_string_t)} @@ -6837,7 +6837,7 @@ void Eval_inherit_w_kind_scope(void) { void Eval_inherit_w_kind_value(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -6870,7 +6870,7 @@ void Eval_inherit_w_kind_value(void) { void Eval_inherit_w_kind_value_scope(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -6982,7 +6982,7 @@ void Eval_auto_override_tag(void) { void Eval_auto_override_component(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7036,7 +7036,7 @@ void Eval_auto_override_pair(void) { void Eval_auto_override_pair_component(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7092,7 +7092,7 @@ void Eval_lowercase_prefab_kind(void) { void Eval_assign_component_to_const(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7245,7 +7245,7 @@ void Eval_prefab_w_slot_variant(void) { void Eval_const_w_component_expr(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7279,7 +7279,7 @@ void Eval_const_w_component_expr(void) { void Eval_const_w_component_expr_in_scope(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7318,7 +7318,7 @@ void Eval_const_w_component_expr_in_scope(void) { void Eval_const_w_component_expr_in_module(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7359,7 +7359,7 @@ void Eval_const_w_component_in_scope_expr_in_scope(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); test_assert(parent != 0); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position", .parent = parent }), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7398,7 +7398,7 @@ void Eval_const_w_component_in_scope_expr_in_module(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); test_assert(parent != 0); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position", .parent = parent }), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7436,7 +7436,7 @@ void Eval_const_w_component_and_entity_in_scope_expr_in_scope(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); test_assert(parent != 0); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position", .parent = parent }), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -7475,7 +7475,7 @@ void Eval_const_w_component_and_entity_in_scope_expr_in_module(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); test_assert(parent != 0); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position", .parent = parent }), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -8243,7 +8243,7 @@ void func_sqr( void Eval_assign_call_func(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -8280,7 +8280,7 @@ void Eval_assign_call_func(void) { void Eval_assign_call_scoped_func(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -8320,7 +8320,7 @@ void Eval_assign_call_scoped_func(void) { void Eval_assign_call_scoped_func_w_using(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){ + ecs_entity_t ecs_id(Position) = ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, @@ -8357,3 +8357,103 @@ void Eval_assign_call_scoped_func_w_using(void) { ecs_fini(world); } + +void Eval_eval_w_vars(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(int32_t*)foo->value.ptr = 10; + + ecs_script_eval_desc_t desc = { .vars = vars }; + + const char *expr = + LINE "e = Position: {$foo, $foo * 2}"; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + } + + *(int32_t*)foo->value.ptr = 20; + + { + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_runtime(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_runtime_t *rt = ecs_script_runtime_new(); + ecs_script_eval_desc_t desc = { .runtime = rt }; + + const char *expr = + LINE "e = Position: {10, 20}"; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + Position *p = ecs_get_mut(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + p->x = 0; + p->y = 0; + } + + { + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + } + + ecs_script_runtime_free(rt); + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 51d71ad0d8..8bcd2f8b22 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -264,6 +264,8 @@ void Eval_update_template_w_tag(void); void Eval_assign_call_func(void); void Eval_assign_call_scoped_func(void); void Eval_assign_call_scoped_func_w_using(void); +void Eval_eval_w_vars(void); +void Eval_eval_w_runtime(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -1739,6 +1741,14 @@ bake_test_case Eval_testcases[] = { { "assign_call_scoped_func_w_using", Eval_assign_call_scoped_func_w_using + }, + { + "eval_w_vars", + Eval_eval_w_vars + }, + { + "eval_w_runtime", + Eval_eval_w_runtime } }; @@ -3534,7 +3544,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 255, + 257, Eval_testcases }, { From d32da97147d90aa3c9f8120a671a6dcdeb656e6e Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 03:21:31 +0000 Subject: [PATCH 43/83] Improve naming of script API --- distr/flecs.c | 280 +++--- distr/flecs.h | 44 +- examples/c/script/expr_run/src/main.c | 2 +- examples/c/script/expr_w_vars/src/main.c | 8 +- include/flecs/addons/script.h | 44 +- src/addons/script/expr/expr.h | 2 +- src/addons/script/expr/parser.c | 30 +- src/addons/script/expr/visit.h | 14 +- src/addons/script/expr/visit_eval.c | 38 +- src/addons/script/expr/visit_fold.c | 46 +- src/addons/script/expr/visit_free.c | 24 +- src/addons/script/expr/visit_to_str.c | 2 +- src/addons/script/expr/visit_type.c | 58 +- src/addons/script/function.c | 10 +- src/addons/script/functions_builtin.c | 8 +- src/addons/script/functions_math.c | 6 +- src/addons/script/interpolate.c | 4 +- src/addons/script/parser.h | 2 +- src/addons/script/script.c | 4 +- src/addons/script/script.h | 2 +- src/addons/script/visit_eval.c | 8 +- src/addons/script/visit_free.c | 8 +- src/addons/script/visit_to_str.c | 14 +- test/custom_builds/c/script_math/src/main.c | 12 +- test/script/src/Deserialize.c | 430 ++++----- test/script/src/Error.c | 2 +- test/script/src/Eval.c | 6 +- test/script/src/Expr.c | 922 ++++++++++---------- test/script/src/Vars.c | 28 +- 29 files changed, 1029 insertions(+), 1029 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 2c5fe6fb18..5569b54c1d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5130,23 +5130,23 @@ ecs_expr_cast_t* flecs_expr_cast( ((const ecs_expr_node_t*)node)->pos - script->code, \ __VA_ARGS__); -int flecs_script_expr_visit_type( +int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); -int flecs_script_expr_visit_fold( +int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); -int flecs_script_expr_visit_eval( +int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, ecs_value_t *out); -void flecs_script_expr_visit_free( +void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); @@ -5188,7 +5188,7 @@ const char* flecs_script_parse_initializer( char until, ecs_expr_initializer_t **node_out); -void flecs_script_expr_to_str_buf( +void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf); @@ -5491,7 +5491,7 @@ const char* flecs_term_parse( void flecs_script_register_builtin_functions( ecs_world_t *world); -void flecs_script_function_import( +void flecs_function_import( ecs_world_t *world); #endif // FLECS_SCRIPT @@ -55106,9 +55106,9 @@ ECS_DTOR(EcsScriptMethod, ptr, { ecs_script_params_free(&ptr->params); }) -ecs_entity_t ecs_script_function_init( +ecs_entity_t ecs_function_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc) + const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); @@ -55147,9 +55147,9 @@ ecs_entity_t ecs_script_function_init( return result; } -ecs_entity_t ecs_script_method_init( +ecs_entity_t ecs_method_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc) + const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); @@ -55189,7 +55189,7 @@ ecs_entity_t ecs_script_method_init( return result; } -void flecs_script_function_import( +void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); @@ -55291,7 +55291,7 @@ static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_script_method(world, { + ecs_entity_t name = ecs_method(world, { .name = "doc_name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -55315,7 +55315,7 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_script_method(world, { + ecs_entity_t name = ecs_method(world, { .name = "name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -55324,7 +55324,7 @@ void flecs_script_register_builtin_functions( ecs_doc_set_brief(world, name, "Returns entity name"); - ecs_entity_t path = ecs_script_method(world, { + ecs_entity_t path = ecs_method(world, { .name = "path", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -55333,7 +55333,7 @@ void flecs_script_register_builtin_functions( ecs_doc_set_brief(world, path, "Returns entity path"); - ecs_entity_t parent = ecs_script_method(world, { + ecs_entity_t parent = ecs_method(world, { .name = "parent", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_entity_t), @@ -55405,7 +55405,7 @@ void flecs_script_register_builtin_functions( #define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ @@ -55417,7 +55417,7 @@ void flecs_script_register_builtin_functions( #define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ @@ -55432,7 +55432,7 @@ void flecs_script_register_builtin_functions( #define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ @@ -55671,12 +55671,12 @@ char* ecs_script_string_interpolate( goto error; } - ecs_script_expr_run_desc_t expr_desc = { + ecs_expr_eval_desc_t expr_desc = { .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) }; ecs_value_t expr_result = {0}; - if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { + if (!ecs_expr_run(world, token, &expr_result, &expr_desc)) { goto error; } @@ -55804,7 +55804,7 @@ char* ecs_script_string_interpolate( if (!(pos = flecs_script_parse_initializer(\ parser, pos, until, &_initializer))) \ {\ - flecs_script_expr_visit_free(\ + flecs_expr_visit_free(\ &parser->script->pub, (ecs_expr_node_t*)_initializer);\ goto error;\ }\ @@ -57723,7 +57723,7 @@ void ecs_script_free( ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { flecs_script_visit_free(script); - flecs_script_expr_visit_free(script, impl->expr); + flecs_expr_visit_free(script, impl->expr); flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); @@ -57934,7 +57934,7 @@ void FlecsScriptImport( ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); - flecs_script_function_import(world); + flecs_function_import(world); } #endif @@ -60167,7 +60167,7 @@ int flecs_script_eval_expr( ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; - ecs_script_expr_run_desc_t desc = { + ecs_expr_eval_desc_t desc = { .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, @@ -60177,16 +60177,16 @@ int flecs_script_eval_expr( }; if (!expr->type_info) { - if (flecs_script_expr_visit_type(script, expr, &desc)) { + if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, expr_ptr, &desc)) { + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } } - if (flecs_script_expr_visit_eval(script, *expr_ptr, &desc, value)) { + if (flecs_expr_visit_eval(script, *expr_ptr, &desc, value)) { goto error; } @@ -61157,7 +61157,7 @@ void flecs_script_if_free( { flecs_script_scope_free(v, node->if_true); flecs_script_scope_free(v, node->if_false); - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -61165,7 +61165,7 @@ void flecs_script_component_free( ecs_script_visit_t *v, ecs_script_component_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -61173,7 +61173,7 @@ void flecs_script_default_component_free( ecs_script_visit_t *v, ecs_script_default_component_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -61181,7 +61181,7 @@ void flecs_script_var_node_free( ecs_script_visit_t *v, ecs_script_var_node_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -61356,12 +61356,12 @@ void flecs_script_id_to_str( } static -void flecs_script_expr_to_str( +void flecs_expr_to_str( ecs_script_str_visitor_t *v, const ecs_expr_node_t *expr) { if (expr) { - flecs_script_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); + flecs_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -61422,7 +61422,7 @@ void flecs_script_component_to_str( flecs_script_id_to_str(v, &node->id); if (node->expr) { flecs_scriptbuf_appendstr(v, ": "); - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } @@ -61434,7 +61434,7 @@ void flecs_script_default_component_to_str( { flecs_scriptbuf_node(v, &node->node); if (node->expr) { - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } @@ -61519,7 +61519,7 @@ void flecs_script_var_node_to_str( flecs_scriptbuf_append(v, "%s = ", node->name); } - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, "\n"); } @@ -61562,7 +61562,7 @@ void flecs_script_if_to_str( ecs_script_if_t *node) { flecs_scriptbuf_node(v, &node->node); - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; @@ -61684,7 +61684,7 @@ char* ecs_script_ast_to_str( ecs_strbuf_t buf = ECS_STRBUF_INIT; if (flecs_script_impl(script)->expr) { - flecs_script_expr_to_str_buf( + flecs_expr_to_str_buf( script->world, flecs_script_impl(script)->expr, &buf); } else { if (ecs_script_ast_to_buf(script, &buf)) { @@ -74078,7 +74078,7 @@ void flecs_script_parser_expr_free( ecs_script_parser_t *parser, ecs_expr_node_t *node) { - flecs_script_expr_visit_free(&parser->script->pub, node); + flecs_expr_visit_free(&parser->script->pub, node); } static @@ -74501,12 +74501,12 @@ const char* flecs_script_parse_expr( ParserEnd; } -ecs_script_t* ecs_script_expr_parse( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -74536,14 +74536,14 @@ ecs_script_t* ecs_script_expr_parse( impl->next_token = ptr; - if (flecs_script_expr_visit_type(script, impl->expr, &priv_desc)) { + if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_ast_to_str(script)); if (!desc || !desc->disable_folding) { - if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { + if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } } @@ -74556,10 +74556,10 @@ ecs_script_t* ecs_script_expr_parse( return NULL; } -int ecs_script_expr_eval( +int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = flecs_script_impl( @@ -74567,7 +74567,7 @@ int ecs_script_expr_eval( ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -74576,7 +74576,7 @@ int ecs_script_expr_eval( priv_desc.lookup_action = flecs_script_default_lookup; } - if (flecs_script_expr_visit_eval(script, impl->expr, &priv_desc, value)) { + if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { goto error; } @@ -74586,13 +74586,13 @@ int ecs_script_expr_eval( } FLECS_API -const char* ecs_script_expr_run( +const char* ecs_expr_run( ecs_world_t *world, const char *expr, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -74604,12 +74604,12 @@ const char* ecs_script_expr_run( "type of value parameter does not match desc->type"); } - ecs_script_t *s = ecs_script_expr_parse(world, expr, &priv_desc); + ecs_script_t *s = ecs_expr_parse(world, expr, &priv_desc); if (!s) { goto error; } - if (ecs_script_expr_eval(s, value, &priv_desc)) { + if (ecs_expr_eval(s, value, &priv_desc)) { ecs_script_free(s); goto error; } @@ -74981,12 +74981,12 @@ int flecs_value_binary( typedef struct ecs_script_eval_ctx_t { const ecs_script_t *script; ecs_world_t *world; - const ecs_script_expr_run_desc_t *desc; + const ecs_expr_eval_desc_t *desc; ecs_expr_stack_t *stack; } ecs_script_eval_ctx_t; static -int flecs_script_expr_visit_eval_priv( +int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out); @@ -75033,7 +75033,7 @@ int flecs_expr_initializer_eval_static( } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -75097,7 +75097,7 @@ int flecs_expr_initializer_eval_dynamic( } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -75158,7 +75158,7 @@ int flecs_expr_unary_visit_eval( flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); - if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } @@ -75185,12 +75185,12 @@ int flecs_expr_binary_visit_eval( /* Evaluate left & right expressions */ ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); - if (flecs_script_expr_visit_eval_priv(ctx, node->right, right)) { + if (flecs_expr_visit_eval_priv(ctx, node->right, right)) { goto error; } @@ -75213,7 +75213,7 @@ int flecs_expr_identifier_visit_eval( ecs_expr_identifier_t *node, ecs_expr_value_t *out) { - return flecs_script_expr_visit_eval_priv(ctx, node->expr, out); + return flecs_expr_visit_eval_priv(ctx, node->expr, out); } static @@ -75255,7 +75255,7 @@ int flecs_expr_cast_visit_eval( /* Evaluate expression to cast */ ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); - if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } @@ -75285,7 +75285,7 @@ int flecs_expr_function_args_visit_eval( ecs_expr_value_t *expr = flecs_expr_stack_result( ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -75342,7 +75342,7 @@ int flecs_expr_method_visit_eval( if (node->left) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } @@ -75387,7 +75387,7 @@ int flecs_expr_member_visit_eval( flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } @@ -75409,12 +75409,12 @@ int flecs_expr_element_visit_eval( ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); - if (flecs_script_expr_visit_eval_priv(ctx, node->index, index)) { + if (flecs_expr_visit_eval_priv(ctx, node->index, index)) { goto error; } @@ -75436,7 +75436,7 @@ int flecs_expr_component_visit_eval( ecs_expr_value_t *out) { ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } @@ -75476,7 +75476,7 @@ int flecs_expr_component_visit_eval( } static -int flecs_script_expr_visit_eval_priv( +int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out) @@ -75577,10 +75577,10 @@ int flecs_script_expr_visit_eval_priv( return -1; } -int flecs_script_expr_visit_eval( +int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, ecs_value_t *out) { ecs_expr_stack_t *stack = NULL, stack_local; @@ -75604,7 +75604,7 @@ int flecs_script_expr_visit_eval( .desc = desc }; - if (flecs_script_expr_visit_eval_priv(&ctx, node, val)) { + if (flecs_expr_visit_eval_priv(&ctx, node, val)) { goto error; } @@ -75664,7 +75664,7 @@ void flecs_visit_fold_replace( ecs_expr_node_t *with) { ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); - flecs_script_expr_visit_free(script, *node_ptr); + flecs_expr_visit_free(script, *node_ptr); *node_ptr = with; } @@ -75672,7 +75672,7 @@ static int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; @@ -75682,7 +75682,7 @@ int flecs_expr_unary_visit_fold( goto error; } - if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { + if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -75721,15 +75721,15 @@ static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->right, desc)) { + if (flecs_expr_visit_fold(script, &node->right, desc)) { goto error; } @@ -75762,11 +75762,11 @@ static int flecs_expr_cast_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { + if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -75809,7 +75809,7 @@ static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, bool *can_fold) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); @@ -75829,7 +75829,7 @@ int flecs_expr_initializer_pre_fold( continue; } - if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } @@ -75890,7 +75890,7 @@ static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { bool can_fold = true; @@ -75925,7 +75925,7 @@ static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { (void)desc; @@ -75944,14 +75944,14 @@ static int flecs_expr_arguments_visit_fold( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; - if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } } @@ -75965,11 +75965,11 @@ static int flecs_expr_function_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } @@ -75988,11 +75988,11 @@ static int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } @@ -76005,15 +76005,15 @@ static int flecs_expr_element_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->index, desc)) { + if (flecs_expr_visit_fold(script, &node->index, desc)) { goto error; } @@ -76022,10 +76022,10 @@ int flecs_expr_element_visit_fold( return -1; } -int flecs_script_expr_visit_fold( +int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; @@ -76114,7 +76114,7 @@ void flecs_expr_initializer_visit_free( int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; - flecs_script_expr_visit_free(script, elem->value); + flecs_expr_visit_free(script, elem->value); } ecs_allocator_t *a = &flecs_script_impl(script)->allocator; @@ -76126,7 +76126,7 @@ void flecs_expr_unary_visit_free( ecs_script_t *script, ecs_expr_unary_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } static @@ -76134,8 +76134,8 @@ void flecs_expr_binary_visit_free( ecs_script_t *script, ecs_expr_binary_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, node->right); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->right); } static @@ -76143,7 +76143,7 @@ void flecs_expr_identifier_visit_free( ecs_script_t *script, ecs_expr_identifier_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } static @@ -76151,8 +76151,8 @@ void flecs_expr_function_visit_free( ecs_script_t *script, ecs_expr_function_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)node->args); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, (ecs_expr_node_t*)node->args); } static @@ -76160,7 +76160,7 @@ void flecs_expr_member_visit_free( ecs_script_t *script, ecs_expr_member_t *node) { - flecs_script_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->left); } static @@ -76168,8 +76168,8 @@ void flecs_expr_element_visit_free( ecs_script_t *script, ecs_expr_element_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, node->index); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->index); } static @@ -76177,10 +76177,10 @@ void flecs_expr_cast_visit_free( ecs_script_t *script, ecs_expr_cast_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } -void flecs_script_expr_visit_free( +void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node) { @@ -76542,7 +76542,7 @@ int flecs_expr_node_to_str( return -1; } -void flecs_script_expr_to_str_buf( +void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf) @@ -76564,11 +76564,11 @@ void flecs_script_expr_to_str_buf( #ifdef FLECS_SCRIPT static -int flecs_script_expr_visit_type_priv( +int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); static bool flecs_expr_operator_is_equality( @@ -76920,7 +76920,7 @@ int flecs_expr_initializer_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { if (!cur || !cur->valid) { flecs_expr_visit_error(script, node, "missing type for initializer"); @@ -76986,7 +76986,7 @@ int flecs_expr_initializer_visit_type( ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; - if (flecs_script_expr_visit_type_priv( + if (flecs_expr_visit_type_priv( script, elem->value, &elem_cur, desc)) { goto error; @@ -77018,9 +77018,9 @@ int flecs_expr_unary_visit_type( ecs_script_t *script, ecs_expr_unary_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->expr, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { goto error; } @@ -77042,7 +77042,7 @@ int flecs_expr_binary_visit_type( ecs_script_t *script, ecs_expr_binary_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { /* Operands must be of this type or casted to it */ ecs_entity_t operand_type = 0; @@ -77050,11 +77050,11 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } - if (flecs_script_expr_visit_type_priv(script, node->right, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->right, cur, desc)) { goto error; } @@ -77098,7 +77098,7 @@ int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { (void)desc; if (cur->valid) { @@ -77117,7 +77117,7 @@ int flecs_expr_identifier_visit_type( result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (!result->storage.entity) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); goto error; @@ -77127,7 +77127,7 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); if (ecs_meta_set_string(&tmp_cur, node->value)) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; @@ -77145,7 +77145,7 @@ int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( desc->vars, node->name); @@ -77168,7 +77168,7 @@ static int flecs_expr_arguments_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, const ecs_vec_t *param_vec) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); @@ -77188,7 +77188,7 @@ int flecs_expr_arguments_visit_type( ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, params[i].type, NULL); - if (flecs_script_expr_visit_type_priv(script, elem->value, &cur, desc)){ + if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ goto error; } @@ -77208,7 +77208,7 @@ int flecs_expr_function_visit_type( ecs_script_t *script, ecs_expr_function_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { bool is_method = false; char *last_elem = NULL; @@ -77239,7 +77239,7 @@ int flecs_expr_function_visit_type( node->function_name = member->member_name; member->left = NULL; /* Prevent cleanup */ - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)member); + flecs_expr_visit_free(script, (ecs_expr_node_t*)member); } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; @@ -77251,7 +77251,7 @@ int flecs_expr_function_visit_type( * function return type is what's going to be assigned. */ ecs_os_zeromem(cur); - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -77339,9 +77339,9 @@ int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -77399,14 +77399,14 @@ int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } ecs_meta_cursor_t index_cur = {0}; - if (flecs_script_expr_visit_type_priv( + if (flecs_expr_visit_type_priv( script, node->index, &index_cur, desc)) { goto error; @@ -77483,11 +77483,11 @@ not_a_collection: { } static -int flecs_script_expr_visit_type_priv( +int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); @@ -77573,10 +77573,10 @@ int flecs_script_expr_visit_type_priv( return -1; } -int flecs_script_expr_visit_type( +int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; @@ -77589,11 +77589,11 @@ int flecs_script_expr_visit_type( if (desc->type) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, desc->type, NULL); - return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + return flecs_expr_visit_type_priv(script, node, &cur, desc); } else { ecs_meta_cursor_t cur; ecs_os_zeromem(&cur); - return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + return flecs_expr_visit_type_priv(script, node, &cur, desc); } } diff --git a/distr/flecs.h b/distr/flecs.h index fec6663a22..16b99070d3 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14750,8 +14750,8 @@ void ecs_script_vars_from_iter( /* Standalone expression evaluation */ -/** Used with ecs_script_expr_run(). */ -typedef struct ecs_script_expr_run_desc_t { +/** Used with ecs_expr_run(). */ +typedef struct ecs_expr_eval_desc_t { const char *name; /**< Script name */ const char *expr; /**< Full expression string */ ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ @@ -14763,7 +14763,7 @@ typedef struct ecs_script_expr_run_desc_t { 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) */ -} ecs_script_expr_run_desc_t; +} ecs_expr_eval_desc_t; /** Run expression. * This operation runs an expression and stores the result in the provided @@ -14780,15 +14780,15 @@ typedef struct ecs_script_expr_run_desc_t { * @return Pointer to the character after the last one read, or NULL if failed. */ FLECS_API -const char* ecs_script_expr_run( +const char* ecs_expr_run( ecs_world_t *world, const char *ptr, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Parse expression. * This operation parses an expression and returns an object that can be - * evaluated multiple times with ecs_script_expr_eval(). + * evaluated multiple times with ecs_expr_eval(). * * @param world The world. * @param expr The expression string. @@ -14796,13 +14796,13 @@ const char* ecs_script_expr_run( * @return A script object if parsing is successful, NULL if parsing failed. */ FLECS_API -ecs_script_t* ecs_script_expr_parse( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Evaluate expression. - * This operation evaluates an expression parsed with ecs_script_expr_parse() + * This operation evaluates an expression parsed with ecs_expr_parse() * and stores the result in the provided value. If the value contains a type * that is different from the type of the expression, the expression will be * cast to the value. @@ -14816,10 +14816,10 @@ ecs_script_t* ecs_script_expr_parse( * @return Zero if successful, non-zero if failed. */ FLECS_API -int ecs_script_expr_eval( +int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Evaluate interpolated expressions in string. * This operation evaluates expressions in a string, and replaces them with @@ -14842,8 +14842,8 @@ char* ecs_script_string_interpolate( /* Functions */ -/** Used with ecs_script_function_init and ecs_script_method_init */ -typedef struct ecs_script_function_desc_t { +/** Used with ecs_function_init and ecs_method_init */ +typedef struct ecs_function_desc_t { /** Function name. */ const char *name; @@ -14862,7 +14862,7 @@ typedef struct ecs_script_function_desc_t { /** Context passed to function implementation. */ void *ctx; -} ecs_script_function_desc_t; +} ecs_function_desc_t; /** Create new function. * This operation creates a new function that can be called from a script. @@ -14872,12 +14872,12 @@ typedef struct ecs_script_function_desc_t { * @return The function, or 0 if failed. */ FLECS_API -ecs_entity_t ecs_script_function_init( +ecs_entity_t ecs_function_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc); + const ecs_function_desc_t *desc); -#define ecs_script_function(world, ...)\ - ecs_script_function_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) +#define ecs_function(world, ...)\ + ecs_function_init(world, &(ecs_function_desc_t)__VA_ARGS__) /** Create new method. * This operation creates a new method that can be called from a script. A @@ -14892,12 +14892,12 @@ ecs_entity_t ecs_script_function_init( * @return The function, or 0 if failed. */ FLECS_API -ecs_entity_t ecs_script_method_init( +ecs_entity_t ecs_method_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc); + const ecs_function_desc_t *desc); -#define ecs_script_method(world, ...)\ - ecs_script_method_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) +#define ecs_method(world, ...)\ + ecs_method_init(world, &(ecs_function_desc_t)__VA_ARGS__) /* Value serialization */ diff --git a/examples/c/script/expr_run/src/main.c b/examples/c/script/expr_run/src/main.c index 5f8ffc635f..5ba5de9b04 100644 --- a/examples/c/script/expr_run/src/main.c +++ b/examples/c/script/expr_run/src/main.c @@ -18,7 +18,7 @@ int main(int argc, char *argv[]) { // Run the expression. If the operation is successful it returns a pointer // to the character after the last parsed token. - if (ecs_script_expr_run(world, "10 + 20", &result_value, NULL) == NULL) { + if (ecs_expr_run(world, "10 + 20", &result_value, NULL) == NULL) { printf("expression failed to run\n"); return -1; } diff --git a/examples/c/script/expr_w_vars/src/main.c b/examples/c/script/expr_w_vars/src/main.c index 8e16f324c5..546285a9ac 100644 --- a/examples/c/script/expr_w_vars/src/main.c +++ b/examples/c/script/expr_w_vars/src/main.c @@ -28,15 +28,15 @@ int main(int argc, char *argv[]) { }; // Parse expression - ecs_script_expr_run_desc_t desc = { .vars = vars }; - ecs_script_t *s = ecs_script_expr_parse(world, "$x + $y", &desc); + ecs_expr_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_expr_parse(world, "$x + $y", &desc); if (!s) { printf("failed to parse expression\n"); return -1; } // Evaluate expression with variables - if (ecs_script_expr_eval(s, &result_value, &desc)) { + if (ecs_expr_eval(s, &result_value, &desc)) { printf("failed to evaluate expression\n"); return -1; } @@ -45,7 +45,7 @@ int main(int argc, char *argv[]) { // Change variable and reevaluate expression *(int32_t*)y->value.ptr = 30; - if (ecs_script_expr_eval(s, &result_value, &desc)) { + if (ecs_expr_eval(s, &result_value, &desc)) { printf("failed to evaluate expression\n"); return -1; } diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 7f73a87b0d..6897dde31b 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -476,8 +476,8 @@ void ecs_script_vars_from_iter( /* Standalone expression evaluation */ -/** Used with ecs_script_expr_run(). */ -typedef struct ecs_script_expr_run_desc_t { +/** Used with ecs_expr_run(). */ +typedef struct ecs_expr_eval_desc_t { const char *name; /**< Script name */ const char *expr; /**< Full expression string */ ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ @@ -489,7 +489,7 @@ typedef struct ecs_script_expr_run_desc_t { 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) */ -} ecs_script_expr_run_desc_t; +} ecs_expr_eval_desc_t; /** Run expression. * This operation runs an expression and stores the result in the provided @@ -506,15 +506,15 @@ typedef struct ecs_script_expr_run_desc_t { * @return Pointer to the character after the last one read, or NULL if failed. */ FLECS_API -const char* ecs_script_expr_run( +const char* ecs_expr_run( ecs_world_t *world, const char *ptr, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Parse expression. * This operation parses an expression and returns an object that can be - * evaluated multiple times with ecs_script_expr_eval(). + * evaluated multiple times with ecs_expr_eval(). * * @param world The world. * @param expr The expression string. @@ -522,13 +522,13 @@ const char* ecs_script_expr_run( * @return A script object if parsing is successful, NULL if parsing failed. */ FLECS_API -ecs_script_t* ecs_script_expr_parse( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Evaluate expression. - * This operation evaluates an expression parsed with ecs_script_expr_parse() + * This operation evaluates an expression parsed with ecs_expr_parse() * and stores the result in the provided value. If the value contains a type * that is different from the type of the expression, the expression will be * cast to the value. @@ -542,10 +542,10 @@ ecs_script_t* ecs_script_expr_parse( * @return Zero if successful, non-zero if failed. */ FLECS_API -int ecs_script_expr_eval( +int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); /** Evaluate interpolated expressions in string. * This operation evaluates expressions in a string, and replaces them with @@ -568,8 +568,8 @@ char* ecs_script_string_interpolate( /* Functions */ -/** Used with ecs_script_function_init and ecs_script_method_init */ -typedef struct ecs_script_function_desc_t { +/** Used with ecs_function_init and ecs_method_init */ +typedef struct ecs_function_desc_t { /** Function name. */ const char *name; @@ -588,7 +588,7 @@ typedef struct ecs_script_function_desc_t { /** Context passed to function implementation. */ void *ctx; -} ecs_script_function_desc_t; +} ecs_function_desc_t; /** Create new function. * This operation creates a new function that can be called from a script. @@ -598,12 +598,12 @@ typedef struct ecs_script_function_desc_t { * @return The function, or 0 if failed. */ FLECS_API -ecs_entity_t ecs_script_function_init( +ecs_entity_t ecs_function_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc); + const ecs_function_desc_t *desc); -#define ecs_script_function(world, ...)\ - ecs_script_function_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) +#define ecs_function(world, ...)\ + ecs_function_init(world, &(ecs_function_desc_t)__VA_ARGS__) /** Create new method. * This operation creates a new method that can be called from a script. A @@ -618,12 +618,12 @@ ecs_entity_t ecs_script_function_init( * @return The function, or 0 if failed. */ FLECS_API -ecs_entity_t ecs_script_method_init( +ecs_entity_t ecs_method_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc); + const ecs_function_desc_t *desc); -#define ecs_script_method(world, ...)\ - ecs_script_method_init(world, &(ecs_script_function_desc_t)__VA_ARGS__) +#define ecs_method(world, ...)\ + ecs_method_init(world, &(ecs_function_desc_t)__VA_ARGS__) /* Value serialization */ diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index cf23c6b487..5adc38ad24 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -45,7 +45,7 @@ const char* flecs_script_parse_initializer( char until, ecs_expr_initializer_t **node_out); -void flecs_script_expr_to_str_buf( +void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf); diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index ba63b79aaf..ab7f1172c6 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -48,7 +48,7 @@ void flecs_script_parser_expr_free( ecs_script_parser_t *parser, ecs_expr_node_t *node) { - flecs_script_expr_visit_free(&parser->script->pub, node); + flecs_expr_visit_free(&parser->script->pub, node); } static @@ -471,12 +471,12 @@ const char* flecs_script_parse_expr( ParserEnd; } -ecs_script_t* ecs_script_expr_parse( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -506,14 +506,14 @@ ecs_script_t* ecs_script_expr_parse( impl->next_token = ptr; - if (flecs_script_expr_visit_type(script, impl->expr, &priv_desc)) { + if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; } // printf("%s\n", ecs_script_ast_to_str(script)); if (!desc || !desc->disable_folding) { - if (flecs_script_expr_visit_fold(script, &impl->expr, &priv_desc)) { + if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } } @@ -526,10 +526,10 @@ ecs_script_t* ecs_script_expr_parse( return NULL; } -int ecs_script_expr_eval( +int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = flecs_script_impl( @@ -537,7 +537,7 @@ int ecs_script_expr_eval( ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -546,7 +546,7 @@ int ecs_script_expr_eval( priv_desc.lookup_action = flecs_script_default_lookup; } - if (flecs_script_expr_visit_eval(script, impl->expr, &priv_desc, value)) { + if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { goto error; } @@ -556,13 +556,13 @@ int ecs_script_expr_eval( } FLECS_API -const char* ecs_script_expr_run( +const char* ecs_expr_run( ecs_world_t *world, const char *expr, ecs_value_t *value, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - ecs_script_expr_run_desc_t priv_desc = {0}; + ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } @@ -574,12 +574,12 @@ const char* ecs_script_expr_run( "type of value parameter does not match desc->type"); } - ecs_script_t *s = ecs_script_expr_parse(world, expr, &priv_desc); + ecs_script_t *s = ecs_expr_parse(world, expr, &priv_desc); if (!s) { goto error; } - if (ecs_script_expr_eval(s, value, &priv_desc)) { + if (ecs_expr_eval(s, value, &priv_desc)) { ecs_script_free(s); goto error; } diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index ca6942303f..8874d0c387 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -12,23 +12,23 @@ ((const ecs_expr_node_t*)node)->pos - script->code, \ __VA_ARGS__); -int flecs_script_expr_visit_type( +int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); -int flecs_script_expr_visit_fold( +int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); -int flecs_script_expr_visit_eval( +int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, ecs_value_t *out); -void flecs_script_expr_visit_free( +void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index b509e13f7d..1aa83be061 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -11,12 +11,12 @@ typedef struct ecs_script_eval_ctx_t { const ecs_script_t *script; ecs_world_t *world; - const ecs_script_expr_run_desc_t *desc; + const ecs_expr_eval_desc_t *desc; ecs_expr_stack_t *stack; } ecs_script_eval_ctx_t; static -int flecs_script_expr_visit_eval_priv( +int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out); @@ -63,7 +63,7 @@ int flecs_expr_initializer_eval_static( } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -127,7 +127,7 @@ int flecs_expr_initializer_eval_dynamic( } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -188,7 +188,7 @@ int flecs_expr_unary_visit_eval( flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); - if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } @@ -215,12 +215,12 @@ int flecs_expr_binary_visit_eval( /* Evaluate left & right expressions */ ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); - if (flecs_script_expr_visit_eval_priv(ctx, node->right, right)) { + if (flecs_expr_visit_eval_priv(ctx, node->right, right)) { goto error; } @@ -243,7 +243,7 @@ int flecs_expr_identifier_visit_eval( ecs_expr_identifier_t *node, ecs_expr_value_t *out) { - return flecs_script_expr_visit_eval_priv(ctx, node->expr, out); + return flecs_expr_visit_eval_priv(ctx, node->expr, out); } static @@ -285,7 +285,7 @@ int flecs_expr_cast_visit_eval( /* Evaluate expression to cast */ ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); - if (flecs_script_expr_visit_eval_priv(ctx, node->expr, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } @@ -315,7 +315,7 @@ int flecs_expr_function_args_visit_eval( ecs_expr_value_t *expr = flecs_expr_stack_result( ctx->stack, elem->value); - if (flecs_script_expr_visit_eval_priv(ctx, elem->value, expr)) { + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } @@ -372,7 +372,7 @@ int flecs_expr_method_visit_eval( if (node->left) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } @@ -417,7 +417,7 @@ int flecs_expr_member_visit_eval( flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } @@ -439,12 +439,12 @@ int flecs_expr_element_visit_eval( ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, expr)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); - if (flecs_script_expr_visit_eval_priv(ctx, node->index, index)) { + if (flecs_expr_visit_eval_priv(ctx, node->index, index)) { goto error; } @@ -466,7 +466,7 @@ int flecs_expr_component_visit_eval( ecs_expr_value_t *out) { ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); - if (flecs_script_expr_visit_eval_priv(ctx, node->left, left)) { + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } @@ -506,7 +506,7 @@ int flecs_expr_component_visit_eval( } static -int flecs_script_expr_visit_eval_priv( +int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out) @@ -607,10 +607,10 @@ int flecs_script_expr_visit_eval_priv( return -1; } -int flecs_script_expr_visit_eval( +int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, ecs_value_t *out) { ecs_expr_stack_t *stack = NULL, stack_local; @@ -634,7 +634,7 @@ int flecs_script_expr_visit_eval( .desc = desc }; - if (flecs_script_expr_visit_eval_priv(&ctx, node, val)) { + if (flecs_expr_visit_eval_priv(&ctx, node, val)) { goto error; } diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 78510bc5b9..dfc37833eb 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -15,7 +15,7 @@ void flecs_visit_fold_replace( ecs_expr_node_t *with) { ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); - flecs_script_expr_visit_free(script, *node_ptr); + flecs_expr_visit_free(script, *node_ptr); *node_ptr = with; } @@ -23,7 +23,7 @@ static int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; @@ -33,7 +33,7 @@ int flecs_expr_unary_visit_fold( goto error; } - if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { + if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -72,15 +72,15 @@ static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->right, desc)) { + if (flecs_expr_visit_fold(script, &node->right, desc)) { goto error; } @@ -113,11 +113,11 @@ static int flecs_expr_cast_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->expr, desc)) { + if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } @@ -160,7 +160,7 @@ static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, bool *can_fold) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); @@ -180,7 +180,7 @@ int flecs_expr_initializer_pre_fold( continue; } - if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } @@ -241,7 +241,7 @@ static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { bool can_fold = true; @@ -276,7 +276,7 @@ static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { (void)desc; @@ -295,14 +295,14 @@ static int flecs_expr_arguments_visit_fold( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; - if (flecs_script_expr_visit_fold(script, &elem->value, desc)) { + if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } } @@ -316,11 +316,11 @@ static int flecs_expr_function_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } @@ -339,11 +339,11 @@ static int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } @@ -356,15 +356,15 @@ static int flecs_expr_element_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; - if (flecs_script_expr_visit_fold(script, &node->left, desc)) { + if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, &node->index, desc)) { + if (flecs_expr_visit_fold(script, &node->index, desc)) { goto error; } @@ -373,10 +373,10 @@ int flecs_expr_element_visit_fold( return -1; } -int flecs_script_expr_visit_fold( +int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 24f9553c48..32b6a9de94 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -27,7 +27,7 @@ void flecs_expr_initializer_visit_free( int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; - flecs_script_expr_visit_free(script, elem->value); + flecs_expr_visit_free(script, elem->value); } ecs_allocator_t *a = &flecs_script_impl(script)->allocator; @@ -39,7 +39,7 @@ void flecs_expr_unary_visit_free( ecs_script_t *script, ecs_expr_unary_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } static @@ -47,8 +47,8 @@ void flecs_expr_binary_visit_free( ecs_script_t *script, ecs_expr_binary_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, node->right); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->right); } static @@ -56,7 +56,7 @@ void flecs_expr_identifier_visit_free( ecs_script_t *script, ecs_expr_identifier_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } static @@ -64,8 +64,8 @@ void flecs_expr_function_visit_free( ecs_script_t *script, ecs_expr_function_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)node->args); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, (ecs_expr_node_t*)node->args); } static @@ -73,7 +73,7 @@ void flecs_expr_member_visit_free( ecs_script_t *script, ecs_expr_member_t *node) { - flecs_script_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->left); } static @@ -81,8 +81,8 @@ void flecs_expr_element_visit_free( ecs_script_t *script, ecs_expr_element_t *node) { - flecs_script_expr_visit_free(script, node->left); - flecs_script_expr_visit_free(script, node->index); + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->index); } static @@ -90,10 +90,10 @@ void flecs_expr_cast_visit_free( ecs_script_t *script, ecs_expr_cast_t *node) { - flecs_script_expr_visit_free(script, node->expr); + flecs_expr_visit_free(script, node->expr); } -void flecs_script_expr_visit_free( +void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node) { diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 61d683daf9..3666a4f435 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -295,7 +295,7 @@ int flecs_expr_node_to_str( return -1; } -void flecs_script_expr_to_str_buf( +void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf) diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 8b642bde5c..fc4389dc5e 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -9,11 +9,11 @@ #include "../script.h" static -int flecs_script_expr_visit_type_priv( +int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc); + const ecs_expr_eval_desc_t *desc); static bool flecs_expr_operator_is_equality( @@ -365,7 +365,7 @@ int flecs_expr_initializer_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { if (!cur || !cur->valid) { flecs_expr_visit_error(script, node, "missing type for initializer"); @@ -431,7 +431,7 @@ int flecs_expr_initializer_visit_type( ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; - if (flecs_script_expr_visit_type_priv( + if (flecs_expr_visit_type_priv( script, elem->value, &elem_cur, desc)) { goto error; @@ -463,9 +463,9 @@ int flecs_expr_unary_visit_type( ecs_script_t *script, ecs_expr_unary_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->expr, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { goto error; } @@ -487,7 +487,7 @@ int flecs_expr_binary_visit_type( ecs_script_t *script, ecs_expr_binary_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { /* Operands must be of this type or casted to it */ ecs_entity_t operand_type = 0; @@ -495,11 +495,11 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } - if (flecs_script_expr_visit_type_priv(script, node->right, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->right, cur, desc)) { goto error; } @@ -543,7 +543,7 @@ int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { (void)desc; if (cur->valid) { @@ -562,7 +562,7 @@ int flecs_expr_identifier_visit_type( result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (!result->storage.entity) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); goto error; @@ -572,7 +572,7 @@ int flecs_expr_identifier_visit_type( ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); if (ecs_meta_set_string(&tmp_cur, node->value)) { - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)result); + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; @@ -590,7 +590,7 @@ int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_script_var_t *var = ecs_script_vars_lookup( desc->vars, node->name); @@ -613,7 +613,7 @@ static int flecs_expr_arguments_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, - const ecs_script_expr_run_desc_t *desc, + const ecs_expr_eval_desc_t *desc, const ecs_vec_t *param_vec) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); @@ -633,7 +633,7 @@ int flecs_expr_arguments_visit_type( ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, params[i].type, NULL); - if (flecs_script_expr_visit_type_priv(script, elem->value, &cur, desc)){ + if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ goto error; } @@ -653,7 +653,7 @@ int flecs_expr_function_visit_type( ecs_script_t *script, ecs_expr_function_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { bool is_method = false; char *last_elem = NULL; @@ -684,7 +684,7 @@ int flecs_expr_function_visit_type( node->function_name = member->member_name; member->left = NULL; /* Prevent cleanup */ - flecs_script_expr_visit_free(script, (ecs_expr_node_t*)member); + flecs_expr_visit_free(script, (ecs_expr_node_t*)member); } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; @@ -696,7 +696,7 @@ int flecs_expr_function_visit_type( * function return type is what's going to be assigned. */ ecs_os_zeromem(cur); - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -784,9 +784,9 @@ int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } @@ -844,14 +844,14 @@ int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { - if (flecs_script_expr_visit_type_priv(script, node->left, cur, desc)) { + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } ecs_meta_cursor_t index_cur = {0}; - if (flecs_script_expr_visit_type_priv( + if (flecs_expr_visit_type_priv( script, node->index, &index_cur, desc)) { goto error; @@ -928,11 +928,11 @@ not_a_collection: { } static -int flecs_script_expr_visit_type_priv( +int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); @@ -1018,10 +1018,10 @@ int flecs_script_expr_visit_type_priv( return -1; } -int flecs_script_expr_visit_type( +int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, - const ecs_script_expr_run_desc_t *desc) + const ecs_expr_eval_desc_t *desc) { if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; @@ -1034,11 +1034,11 @@ int flecs_script_expr_visit_type( if (desc->type) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, desc->type, NULL); - return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + return flecs_expr_visit_type_priv(script, node, &cur, desc); } else { ecs_meta_cursor_t cur; ecs_os_zeromem(&cur); - return flecs_script_expr_visit_type_priv(script, node, &cur, desc); + return flecs_expr_visit_type_priv(script, node, &cur, desc); } } diff --git a/src/addons/script/function.c b/src/addons/script/function.c index f9530a55df..5041523519 100644 --- a/src/addons/script/function.c +++ b/src/addons/script/function.c @@ -43,9 +43,9 @@ ECS_DTOR(EcsScriptMethod, ptr, { ecs_script_params_free(&ptr->params); }) -ecs_entity_t ecs_script_function_init( +ecs_entity_t ecs_function_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc) + const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); @@ -84,9 +84,9 @@ ecs_entity_t ecs_script_function_init( return result; } -ecs_entity_t ecs_script_method_init( +ecs_entity_t ecs_method_init( ecs_world_t *world, - const ecs_script_function_desc_t *desc) + const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); @@ -126,7 +126,7 @@ ecs_entity_t ecs_script_method_init( return result; } -void flecs_script_function_import( +void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); diff --git a/src/addons/script/functions_builtin.c b/src/addons/script/functions_builtin.c index a855d8a227..94ae9c32fb 100644 --- a/src/addons/script/functions_builtin.c +++ b/src/addons/script/functions_builtin.c @@ -62,7 +62,7 @@ static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_script_method(world, { + ecs_entity_t name = ecs_method(world, { .name = "doc_name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -86,7 +86,7 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_script_method(world, { + ecs_entity_t name = ecs_method(world, { .name = "name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -95,7 +95,7 @@ void flecs_script_register_builtin_functions( ecs_doc_set_brief(world, name, "Returns entity name"); - ecs_entity_t path = ecs_script_method(world, { + ecs_entity_t path = ecs_method(world, { .name = "path", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), @@ -104,7 +104,7 @@ void flecs_script_register_builtin_functions( ecs_doc_set_brief(world, path, "Returns entity path"); - ecs_entity_t parent = ecs_script_method(world, { + ecs_entity_t parent = ecs_method(world, { .name = "parent", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_entity_t), diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index 0a12186eb5..e102390621 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -58,7 +58,7 @@ #define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ @@ -70,7 +70,7 @@ #define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ @@ -85,7 +85,7 @@ #define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ {\ - ecs_entity_t f = ecs_script_function(world, {\ + ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ diff --git a/src/addons/script/interpolate.c b/src/addons/script/interpolate.c index 641c969019..9833f5b689 100644 --- a/src/addons/script/interpolate.c +++ b/src/addons/script/interpolate.c @@ -145,12 +145,12 @@ char* ecs_script_string_interpolate( goto error; } - ecs_script_expr_run_desc_t expr_desc = { + ecs_expr_eval_desc_t expr_desc = { .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) }; ecs_value_t expr_result = {0}; - if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { + if (!ecs_expr_run(world, token, &expr_result, &expr_desc)) { goto error; } diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index 3ae62849c1..4bd25c3d3c 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -94,7 +94,7 @@ if (!(pos = flecs_script_parse_initializer(\ parser, pos, until, &_initializer))) \ {\ - flecs_script_expr_visit_free(\ + flecs_expr_visit_free(\ &parser->script->pub, (ecs_expr_node_t*)_initializer);\ goto error;\ }\ diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 1f22a0e4f1..ee7c49169d 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -117,7 +117,7 @@ void ecs_script_free( ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { flecs_script_visit_free(script); - flecs_script_expr_visit_free(script, impl->expr); + flecs_expr_visit_free(script, impl->expr); flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); @@ -328,7 +328,7 @@ void FlecsScriptImport( ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); - flecs_script_function_import(world); + flecs_function_import(world); } #endif diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 1515ab70e7..35be265b44 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -123,7 +123,7 @@ const char* flecs_term_parse( void flecs_script_register_builtin_functions( ecs_world_t *world); -void flecs_script_function_import( +void flecs_function_import( ecs_world_t *world); #endif // FLECS_SCRIPT diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index f9ae3bd3bf..f01f4063b4 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -340,7 +340,7 @@ int flecs_script_eval_expr( ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; - ecs_script_expr_run_desc_t desc = { + ecs_expr_eval_desc_t desc = { .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, @@ -350,16 +350,16 @@ int flecs_script_eval_expr( }; if (!expr->type_info) { - if (flecs_script_expr_visit_type(script, expr, &desc)) { + if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } - if (flecs_script_expr_visit_fold(script, expr_ptr, &desc)) { + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } } - if (flecs_script_expr_visit_eval(script, *expr_ptr, &desc, value)) { + if (flecs_expr_visit_eval(script, *expr_ptr, &desc, value)) { goto error; } diff --git a/src/addons/script/visit_free.c b/src/addons/script/visit_free.c index acca690dd9..06c4433cc7 100644 --- a/src/addons/script/visit_free.c +++ b/src/addons/script/visit_free.c @@ -58,7 +58,7 @@ void flecs_script_if_free( { flecs_script_scope_free(v, node->if_true); flecs_script_scope_free(v, node->if_false); - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -66,7 +66,7 @@ void flecs_script_component_free( ecs_script_visit_t *v, ecs_script_component_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -74,7 +74,7 @@ void flecs_script_default_component_free( ecs_script_visit_t *v, ecs_script_default_component_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static @@ -82,7 +82,7 @@ void flecs_script_var_node_free( ecs_script_visit_t *v, ecs_script_var_node_t *node) { - flecs_script_expr_visit_free(&v->script->pub, node->expr); + flecs_expr_visit_free(&v->script->pub, node->expr); } static diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index cc245b1926..90423b8dde 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -80,12 +80,12 @@ void flecs_script_id_to_str( } static -void flecs_script_expr_to_str( +void flecs_expr_to_str( ecs_script_str_visitor_t *v, const ecs_expr_node_t *expr) { if (expr) { - flecs_script_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); + flecs_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -146,7 +146,7 @@ void flecs_script_component_to_str( flecs_script_id_to_str(v, &node->id); if (node->expr) { flecs_scriptbuf_appendstr(v, ": "); - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } @@ -158,7 +158,7 @@ void flecs_script_default_component_to_str( { flecs_scriptbuf_node(v, &node->node); if (node->expr) { - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } @@ -243,7 +243,7 @@ void flecs_script_var_node_to_str( flecs_scriptbuf_append(v, "%s = ", node->name); } - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, "\n"); } @@ -286,7 +286,7 @@ void flecs_script_if_to_str( ecs_script_if_t *node) { flecs_scriptbuf_node(v, &node->node); - flecs_script_expr_to_str(v, node->expr); + flecs_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; @@ -408,7 +408,7 @@ char* ecs_script_ast_to_str( ecs_strbuf_t buf = ECS_STRBUF_INIT; if (flecs_script_impl(script)->expr) { - flecs_script_expr_to_str_buf( + flecs_expr_to_str_buf( script->world, flecs_script_impl(script)->expr, &buf); } else { if (ecs_script_ast_to_buf(script, &buf)) { diff --git a/test/custom_builds/c/script_math/src/main.c b/test/custom_builds/c/script_math/src/main.c index 61e49bd1bb..b05f5aee1e 100644 --- a/test/custom_builds/c/script_math/src/main.c +++ b/test/custom_builds/c/script_math/src/main.c @@ -9,27 +9,27 @@ int main(int argc, char *argv[]) { int32_t v = 0; (void)v; - assert(ecs_script_expr_run(world, "flecs.script.math.sqr(10)", + assert(ecs_expr_run(world, "flecs.script.math.sqr(10)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 100); - assert(ecs_script_expr_run(world, "flecs.script.math.sqrt(100)", + assert(ecs_expr_run(world, "flecs.script.math.sqrt(100)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 10); - assert(ecs_script_expr_run(world, "flecs.script.math.pow(5, 2)", + assert(ecs_expr_run(world, "flecs.script.math.pow(5, 2)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 25); - assert(ecs_script_expr_run(world, "flecs.script.math.ceil(1.6)", + assert(ecs_expr_run(world, "flecs.script.math.ceil(1.6)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 2); - assert(ecs_script_expr_run(world, "flecs.script.math.floor(1.6)", + assert(ecs_expr_run(world, "flecs.script.math.floor(1.6)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 1); - assert(ecs_script_expr_run(world, "flecs.script.math.round(1.6)", + assert(ecs_expr_run(world, "flecs.script.math.round(1.6)", &ecs_value_ptr(ecs_i32_t, &v), NULL) != NULL); assert(v == 2); diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 52ac054661..379d1d67ad 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -19,8 +19,8 @@ void Deserialize_bool(void) { ecs_world_t *world = ecs_init(); bool value = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "true", &ecs_value_ptr(ecs_bool_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -34,8 +34,8 @@ void Deserialize_byte(void) { ecs_world_t *world = ecs_init(); ecs_byte_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_byte_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -49,8 +49,8 @@ void Deserialize_char(void) { ecs_world_t *world = ecs_init(); ecs_char_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_char_t, &value), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -64,8 +64,8 @@ void Deserialize_char_literal(void) { ecs_world_t *world = ecs_init(); ecs_char_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "\"a\"", &ecs_value_ptr(ecs_char_t, &value), &desc); test_assert(ptr != NULL); @@ -78,8 +78,8 @@ void Deserialize_i8(void) { ecs_world_t *world = ecs_init(); ecs_i8_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr != NULL); @@ -92,8 +92,8 @@ void Deserialize_i16(void) { ecs_world_t *world = ecs_init(); ecs_i16_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_i16_t, &value), &desc); test_assert(ptr != NULL); @@ -106,8 +106,8 @@ void Deserialize_i32(void) { ecs_world_t *world = ecs_init(); ecs_i32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_i32_t, &value), &desc); test_assert(ptr != NULL); @@ -120,8 +120,8 @@ void Deserialize_i64(void) { ecs_world_t *world = ecs_init(); ecs_i64_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_i64_t, &value), &desc); test_assert(ptr != NULL); @@ -134,8 +134,8 @@ void Deserialize_iptr(void) { ecs_world_t *world = ecs_init(); ecs_iptr_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_iptr_t, &value), &desc); test_assert(ptr != NULL); @@ -148,8 +148,8 @@ void Deserialize_u8(void) { ecs_world_t *world = ecs_init(); ecs_u8_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_u8_t, &value), &desc); test_assert(ptr != NULL); @@ -162,8 +162,8 @@ void Deserialize_u16(void) { ecs_world_t *world = ecs_init(); ecs_u16_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_u16_t, &value), &desc); test_assert(ptr != NULL); @@ -176,8 +176,8 @@ void Deserialize_u32(void) { ecs_world_t *world = ecs_init(); ecs_u32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_u32_t, &value), &desc); test_assert(ptr != NULL); @@ -191,8 +191,8 @@ void Deserialize_u64(void) { { ecs_u64_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "0", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 0); @@ -200,8 +200,8 @@ void Deserialize_u64(void) { { ecs_u64_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "10", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 10); @@ -209,8 +209,8 @@ void Deserialize_u64(void) { { ecs_u64_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "2366700781656087864", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr != NULL); test_int(value, 2366700781656087864); @@ -223,8 +223,8 @@ void Deserialize_uptr(void) { ecs_world_t *world = ecs_init(); ecs_uptr_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10", &ecs_value_ptr(ecs_uptr_t, &value), &desc); test_assert(ptr != NULL); @@ -237,8 +237,8 @@ void Deserialize_float(void) { ecs_world_t *world = ecs_init(); ecs_f32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10.5", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr != NULL); @@ -251,8 +251,8 @@ void Deserialize_double(void) { ecs_world_t *world = ecs_init(); ecs_f64_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10.5", &ecs_value_ptr(ecs_f64_t, &value), &desc); test_assert(ptr != NULL); @@ -265,8 +265,8 @@ void Deserialize_negative_int(void) { ecs_world_t *world = ecs_init(); ecs_i8_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-10", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr != NULL); @@ -279,8 +279,8 @@ void Deserialize_negative_float(void) { ecs_world_t *world = ecs_init(); ecs_f32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-10.5", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr != NULL); @@ -295,8 +295,8 @@ void Deserialize_invalid_i8(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_i8_t, &value), &desc); test_assert(ptr == NULL); @@ -309,8 +309,8 @@ void Deserialize_invalid_i16(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_i16_t, &value), &desc); test_assert(ptr == NULL); @@ -323,8 +323,8 @@ void Deserialize_invalid_i32(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_i32_t, &value), &desc); test_assert(ptr == NULL); @@ -337,8 +337,8 @@ void Deserialize_invalid_i64(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_i64_t, &value), &desc); test_assert(ptr == NULL); @@ -351,8 +351,8 @@ void Deserialize_invalid_iptr(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_iptr_t, &value), &desc); test_assert(ptr == NULL); @@ -365,8 +365,8 @@ void Deserialize_invalid_u8(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_u8_t, &value), &desc); test_assert(ptr == NULL); @@ -379,8 +379,8 @@ void Deserialize_invalid_u16(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_u16_t, &value), &desc); test_assert(ptr == NULL); @@ -393,8 +393,8 @@ void Deserialize_invalid_u32(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_u32_t, &value), &desc); test_assert(ptr == NULL); @@ -407,8 +407,8 @@ void Deserialize_invalid_u64(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_u64_t, &value), &desc); test_assert(ptr == NULL); @@ -421,8 +421,8 @@ void Deserialize_invalid_uptr(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_uptr_t, &value), &desc); test_assert(ptr == NULL); @@ -435,8 +435,8 @@ void Deserialize_invalid_float(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_f32_t, &value), &desc); test_assert(ptr == NULL); @@ -449,8 +449,8 @@ void Deserialize_invalid_double(void) { uint64_t value = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "a", &ecs_value_ptr(ecs_f64_t, &value), &desc); test_assert(ptr == NULL); @@ -461,8 +461,8 @@ void Deserialize_string(void) { ecs_world_t *world = ecs_init(); ecs_string_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "\"Hello World\"", &ecs_value_ptr(ecs_string_t, &value), &desc); test_assert(ptr != NULL); @@ -477,13 +477,13 @@ void Deserialize_entity(void) { ecs_world_t *world = ecs_init(); ecs_entity_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "flecs.core", &ecs_value_ptr(ecs_entity_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, EcsFlecsCore); - ptr = ecs_script_expr_run(world, + ptr = ecs_expr_run(world, "0", &ecs_value_ptr(ecs_entity_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, 0); @@ -495,13 +495,13 @@ void Deserialize_id(void) { ecs_world_t *world = ecs_init(); ecs_id_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "flecs.core", &ecs_value_ptr(ecs_id_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, EcsFlecsCore); - ptr = ecs_script_expr_run(world, + ptr = ecs_expr_run(world, "0", &ecs_value_ptr(ecs_id_t, &value), &desc); test_assert(ptr != NULL); test_uint(value, 0); @@ -527,8 +527,8 @@ void Deserialize_enum(void) { { T value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Red", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -537,8 +537,8 @@ void Deserialize_enum(void) { { T value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Blue", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -547,8 +547,8 @@ void Deserialize_enum(void) { { T value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Green", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -558,8 +558,8 @@ void Deserialize_enum(void) { { ecs_log_set_level(-4); T value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Black", &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); @@ -585,8 +585,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Lettuce", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -595,8 +595,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Lettuce|Bacon", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -605,8 +605,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Lettuce|Bacon|Tomato|Cheese", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -615,8 +615,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Lettuce | Bacon | Tomato | Cheese", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -625,8 +625,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "BLT", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -635,8 +635,8 @@ void Deserialize_bitmask(void) { { uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "0", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -646,8 +646,8 @@ void Deserialize_bitmask(void) { { ecs_log_set_level(-4); uint32_t value = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "Foo", &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); @@ -686,8 +686,8 @@ void Deserialize_struct_enum(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v: Red}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -696,8 +696,8 @@ void Deserialize_struct_enum(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v: Blue}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -706,8 +706,8 @@ void Deserialize_struct_enum(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v: Green}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -717,8 +717,8 @@ void Deserialize_struct_enum(void) { { ecs_log_set_level(-4); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v: Black}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); @@ -758,8 +758,8 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:Lettuce}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -768,8 +768,8 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:Lettuce|Bacon}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -778,8 +778,8 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:Lettuce|Bacon|Tomato|Cheese}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -788,8 +788,8 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:BLT}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -798,8 +798,8 @@ void Deserialize_struct_bitmask(void) { { T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:0}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -809,8 +809,8 @@ void Deserialize_struct_bitmask(void) { { ecs_log_set_level(-4); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{v:Foo\"}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr == NULL); @@ -834,8 +834,8 @@ void Deserialize_struct_i32(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -861,8 +861,8 @@ void Deserialize_struct_i32_neg(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{-10}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -890,8 +890,8 @@ void Deserialize_struct_i32_i32(void) { }); T value = {0, 0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10, 20}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -918,8 +918,8 @@ void Deserialize_struct_string(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{\"Hello World\"}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -947,8 +947,8 @@ void Deserialize_struct_string_from_name(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{flecs.core.name()}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -976,8 +976,8 @@ void Deserialize_struct_string_from_path(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{flecs.core.path()}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1011,8 +1011,8 @@ void Deserialize_struct_string_from_var(void) { ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_string_t); *(char**)foo->value.ptr = "Hello World"; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{$foo}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1043,8 +1043,8 @@ void Deserialize_struct_entity(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{flecs.core}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1070,8 +1070,8 @@ void Deserialize_struct_id(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{flecs.core}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1108,8 +1108,8 @@ void Deserialize_struct_nested_i32(void) { }); T value = {{0}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{{10}}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1148,8 +1148,8 @@ void Deserialize_struct_nested_i32_i32(void) { }); T value = {{0}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{{10, 20}}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1191,8 +1191,8 @@ void Deserialize_struct_2_nested_i32_i32(void) { }); T value = {{0}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{{10, 20}, {30, 40}}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1221,8 +1221,8 @@ void Deserialize_struct_member_i32(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: 10}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1248,8 +1248,8 @@ void Deserialize_struct_member_i32_neg(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: -10}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1277,8 +1277,8 @@ void Deserialize_struct_member_i32_i32(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: 10, y: 20}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1316,8 +1316,8 @@ void Deserialize_struct_member_nested_i32(void) { }); T value = {{0}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: {x: 10}}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1356,8 +1356,8 @@ void Deserialize_struct_member_nested_i32_i32(void) { }); T value = {{0}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: {x: 10, y: 20}}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1405,8 +1405,8 @@ void Deserialize_struct_member_2_nested_i32_i32(void) { LINE " n_1: {x: 10, y: 20}," LINE " n_2: {x: 30, y: 40}" LINE "}"; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1444,8 +1444,8 @@ void Deserialize_struct_from_var(void) { var->y = 20; { - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "$foo", &(ecs_value_t){ecs_id(T), &value}, &desc); test_assert(ptr != NULL); @@ -1481,10 +1481,10 @@ void Deserialize_struct_member_from_var(void) { T *var = foo->value.ptr; var->value = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ $foo }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1495,7 +1495,7 @@ void Deserialize_struct_member_from_var(void) { value.value = 0; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ value: $foo }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1530,10 +1530,10 @@ void Deserialize_struct_member_auto_var(void) { T *var = foo->value.ptr; var->value = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ value: $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1571,10 +1571,10 @@ void Deserialize_struct_member_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ x: $, y: $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1622,10 +1622,10 @@ void Deserialize_struct_nested_member_auto_var(void) { ecs_script_var_t *foo = ecs_script_vars_define(vars, "x", ecs_i32_t); *(int32_t*)foo->value.ptr = 10; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ stop: { x: $ }}", &(ecs_value_t){ecs_id(Line), &value}, &desc); test_assert(ptr != NULL); @@ -1677,10 +1677,10 @@ void Deserialize_struct_nested_member_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ stop: { x: $, y: $ }}", &(ecs_value_t){ecs_id(Line), &value}, &desc); test_assert(ptr != NULL); @@ -1721,10 +1721,10 @@ void Deserialize_struct_auto_vars(void) { ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); *(int32_t*)y->value.ptr = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run(world, + const char *ptr = ecs_expr_run(world, "{ $, $ }", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1774,8 +1774,8 @@ void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { LINE " n_2: {x: 30, y: 40}," LINE " n_1: {x: 10, y: 20}" LINE "}"; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, expr, &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -1802,8 +1802,8 @@ void Deserialize_struct_i32_array_3(void) { }); T value = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: [10, 20, 30]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1842,8 +1842,8 @@ void Deserialize_struct_struct_i32_array_3(void) { }); T value = {{{0}}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [{x: 10}, {x: 20}, {x: 30}]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1884,8 +1884,8 @@ void Deserialize_struct_struct_i32_i32_array_3(void) { }); T value = {{{0}}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}, {x: 50, y: 60}]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1924,8 +1924,8 @@ void Deserialize_struct_w_array_type_i32_i32(void) { }); T value = {{ 0 }}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [10, 20]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -1962,8 +1962,8 @@ void Deserialize_struct_w_2_array_type_i32_i32(void) { }); T value = {{ 0 }}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [10, 20], n_2: [30, 40]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -2019,8 +2019,8 @@ void Deserialize_struct_w_array_type_struct(void) { test_assert(t != 0); T value; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -2078,8 +2078,8 @@ void Deserialize_struct_w_2_array_type_struct(void) { test_assert(t != 0); T value; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{n_1: [{x: 10, y: 20}, {x: 30, y: 40}], n_2: [{x: 50, y: 60}, {x: 70, y: 80}]}", &(ecs_value_t){t, &value}, &desc); test_assert(ptr != NULL); @@ -2112,8 +2112,8 @@ void Deserialize_array_i32_2(void) { test_assert(ecs_id(Ints) != 0); Ints value = {0, 0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "[10, 20]", &(ecs_value_t){ecs_id(Ints), &value}, &desc); test_assert(ptr != NULL); @@ -2139,8 +2139,8 @@ void Deserialize_array_string_2(void) { test_assert(ecs_id(Strings) != 0); Strings value = {0, 0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "[\"Hello\", \"World\"]", &(ecs_value_t){ecs_id(Strings), &value}, &desc); test_assert(ptr != NULL); @@ -2159,8 +2159,8 @@ void Deserialize_discover_type_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); @@ -2174,8 +2174,8 @@ void Deserialize_discover_type_negative_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); @@ -2189,8 +2189,8 @@ void Deserialize_discover_type_float(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); @@ -2204,8 +2204,8 @@ void Deserialize_discover_type_negative_float(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10.5", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); @@ -2219,8 +2219,8 @@ void Deserialize_discover_type_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "\"foo\"", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); @@ -2234,8 +2234,8 @@ void Deserialize_discover_type_multiline_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "`foo\nbar`", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); @@ -2251,8 +2251,8 @@ void Deserialize_discover_type_entity(void) { ecs_entity_t foo = ecs_entity(world, { .name = "foo" }); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "foo", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); @@ -2266,8 +2266,8 @@ void Deserialize_discover_type_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2276,7 +2276,7 @@ void Deserialize_discover_type_bool(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, + test_assert(ecs_expr_run(world, "false", &v, &desc) != NULL); test_uint(v.type, ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); @@ -2292,11 +2292,11 @@ void Deserialize_discover_type_unknown(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "{10}", &v, &desc) == NULL); - test_assert(ecs_script_expr_run(world, + test_assert(ecs_expr_run(world, "[10]", &v, &desc) == NULL); ecs_fini(world); @@ -2308,8 +2308,8 @@ void Deserialize_discover_type_invalid(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-", &v, &desc) == NULL); ecs_fini(world); @@ -2352,8 +2352,8 @@ void Deserialize_opaque_struct(void) { }); OpaqueStruct v; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10, 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2383,8 +2383,8 @@ void Deserialize_opaque_struct_w_member(void) { }); OpaqueStruct v; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: 10, y: 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2414,8 +2414,8 @@ void Deserialize_opaque_struct_w_member_reverse(void) { }); OpaqueStruct v; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{y: 10, x: 20}", &ecs_value_ptr(OpaqueStruct, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); @@ -2459,8 +2459,8 @@ void Deserialize_struct_w_opaque_member(void) { }); Struct_w_opaque v = {{0, NULL}}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{str: \"foobar\"}", &ecs_value_ptr(Struct_w_opaque, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == '\0'); diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 3d88ac8587..39734d1954 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1233,7 +1233,7 @@ void Error_unterminated_binary(void) { int32_t v = 0; ecs_log_set_level(-4); - const char *ptr = ecs_script_expr_run(world, "10 +", + const char *ptr = ecs_expr_run(world, "10 +", &ecs_value_ptr(ecs_i32_t, &v), NULL); test_assert(ptr == NULL); diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index d8682406fe..75d4992f84 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -8251,7 +8251,7 @@ void Eval_assign_call_func(void) { } }); - ecs_script_function(world, { + ecs_function(world, { .name = "sqr", .return_type = ecs_id(ecs_i32_t), .params = {{ "x", ecs_id(ecs_i32_t) }}, @@ -8290,7 +8290,7 @@ void Eval_assign_call_scoped_func(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); - ecs_script_function(world, { + ecs_function(world, { .name = "sqr", .parent = parent, .return_type = ecs_id(ecs_i32_t), @@ -8330,7 +8330,7 @@ void Eval_assign_call_scoped_func_w_using(void) { ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); - ecs_script_function(world, { + ecs_function(world, { .name = "sqr", .parent = parent, .return_type = ecs_id(ecs_i32_t), diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 9c05d44f37..7b974e571d 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -19,8 +19,8 @@ void Expr_add_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); @@ -33,13 +33,13 @@ void Expr_add_2_int_literals_twice(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); - test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20); @@ -52,8 +52,8 @@ void Expr_sub_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "20 - 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(int64_t*)v.ptr, 20 - 10); @@ -66,8 +66,8 @@ void Expr_mul_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "20 * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_u64_t*)v.ptr, 20 * 10); @@ -80,8 +80,8 @@ void Expr_div_2_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 / 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10 / 2); @@ -94,8 +94,8 @@ void Expr_add_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); @@ -108,13 +108,13 @@ void Expr_add_3_int_literals_twice(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); - test_assert(ecs_script_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 + 20 + 30", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 + 30); @@ -127,8 +127,8 @@ void Expr_sub_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "30 - 10 - 5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "30 - 10 - 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, 30 - 10 - 5); @@ -141,8 +141,8 @@ void Expr_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 * 5 * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 * 5 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 2 * 5 * 10); @@ -155,8 +155,8 @@ void Expr_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "40 / 5 / 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "40 / 5 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 40 / 5 / 2); @@ -170,18 +170,18 @@ void Expr_int_to_bool(void) { bool b = false; ecs_value_t v = { .type = ecs_id(ecs_bool_t), .ptr = &b }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "256", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "256", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_bool_t*)v.ptr, true); @@ -194,13 +194,13 @@ void Expr_bool_to_int(void) { int32_t i = 0; ecs_value_t v = { .type = ecs_id(ecs_i32_t), .ptr = &i }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_i32_t*)v.ptr, 1); - test_assert(ecs_script_expr_run(world, "false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_i32_t*)v.ptr, 0); @@ -213,13 +213,13 @@ void Expr_bool_to_uint(void) { uint32_t i = 0; ecs_value_t v = { .type = ecs_id(ecs_u32_t), .ptr = &i }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_u32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_u32_t*)v.ptr, 1); - test_assert(ecs_script_expr_run(world, "false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_u32_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_u32_t*)v.ptr, 0); @@ -231,8 +231,8 @@ void Expr_add_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20 * 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(uint64_t*)v.ptr, 10 + 20 * 2); @@ -245,8 +245,8 @@ void Expr_sub_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "50 - 10 * 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "50 - 10 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(int64_t*)v.ptr, 50 - 10 * 2); @@ -259,8 +259,8 @@ void Expr_div_mul_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 / 5 * 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 / 5 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10 / 5 * 2); @@ -273,8 +273,8 @@ void Expr_add_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 30 / 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 30 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 10 + 30 / 2); @@ -287,8 +287,8 @@ void Expr_sub_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "30 - 10 / 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "30 - 10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 30 - 10 / 2); @@ -301,8 +301,8 @@ void Expr_mul_div_3_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "20 * 10 / 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20 * 10 / 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_f64_t*)v.ptr, 20 * 10 / 2); @@ -315,8 +315,8 @@ void Expr_mul_add_mul_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 * 4 + 6 * 8 + 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 * 4 + 6 * 8 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 2 * 4 + 6 * 8 + 10); @@ -329,8 +329,8 @@ void Expr_mul_sub_mul_sub_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 * 4 - 6 * 8 - 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 * 4 - 6 * 8 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, 2 * 4 - 6 * 8 - 10); @@ -343,8 +343,8 @@ void Expr_mul_div_mul_div_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 * 4 / 6 * 8 / 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 * 4 / 6 * 8 / 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 * 4.0 / 6.0 * 8.0 / 10.0); @@ -357,8 +357,8 @@ void Expr_div_add_div_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 / 4 + 6 / 8 + 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 / 4 + 6 / 8 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 + 6.0 / 8.0 + 10.0); @@ -371,8 +371,8 @@ void Expr_div_sub_div_sub_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 - 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 / 4 - 6 / 8 - 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 - 6.0 / 8.0 - 10.0); @@ -385,8 +385,8 @@ void Expr_div_mul_div_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 / 4 * 6 / 8 * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 / 4 * 6 / 8 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 * 6.0 / 8.0 * 10.0); @@ -399,8 +399,8 @@ void Expr_div_sub_div_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "2 / 4 - 6 / 8 * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "2 / 4 - 6 / 8 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 2.0 / 4.0 - 6.0 / 8.0 * 10.0); @@ -413,8 +413,8 @@ void Expr_add_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 + 20.0", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 + 20.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10.5 + 20.0); @@ -427,8 +427,8 @@ void Expr_sub_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "20.5 - 10.0", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20.5 - 10.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 20.5 - 10.0); @@ -441,8 +441,8 @@ void Expr_mul_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "20.5 * 10.0", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20.5 * 10.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 20.5 * 10.0); @@ -455,8 +455,8 @@ void Expr_div_2_flt_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 / 2.0", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 / 2.0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, 10.5 / 2.0); @@ -469,8 +469,8 @@ void Expr_add_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-10 + -20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10 + -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 + -20); @@ -483,8 +483,8 @@ void Expr_sub_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-10 - -20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10 - -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 - -20); @@ -497,8 +497,8 @@ void Expr_mul_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-10 * -20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10 * -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10 * -20); @@ -511,8 +511,8 @@ void Expr_div_2_int_neg_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-10 / -20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-10 / -20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f64_t)); test_assert(v.ptr != NULL); test_flt(*(ecs_f64_t*)v.ptr, -10.0 / -20.0); @@ -525,8 +525,8 @@ void Expr_mul_lparen_add_add_rparen_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 * (20 + 30)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30)); @@ -539,8 +539,8 @@ void Expr_mul_lparen_add_add_add_rparen_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30 + 40)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 * (20 + 30 + 40)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30 + 40)); @@ -553,8 +553,8 @@ void Expr_mul_lparen_add_add_rparen_add_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 * (20 + 30) + 40", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 * (20 + 30) + 40", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * (20 + 30) + 40); @@ -567,8 +567,8 @@ void Expr_lparen_add_add_rparen_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "(20 + 30) * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "(20 + 30) * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (20 + 30) * 10); @@ -581,8 +581,8 @@ void Expr_lparen_add_add_add_rparen_mul_int_literals(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "(20 + 30 + 40) * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "(20 + 30 + 40) * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (20 + 30 + 40) * 10); @@ -595,8 +595,8 @@ void Expr_double_paren_add_add(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "((20 + 30))", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "((20 + 30))", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, ((20 + 30))); @@ -609,8 +609,8 @@ void Expr_double_paren_literal(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "((20))", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "((20))", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, ((20))); @@ -623,8 +623,8 @@ void Expr_lparen_add_add_rparen_mul_lparen_add_add_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "(10 + 20) * (20 + 30)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "(10 + 20) * (20 + 30)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 20) * (20 + 30)); @@ -641,8 +641,8 @@ void Expr_float_result_add_2_int_literals(void) { .type = ecs_id(ecs_f32_t), .ptr = &value }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 20", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); test_flt(value, 10 + 20); @@ -666,8 +666,8 @@ void Expr_struct_result_implicit_members(void) { }); Position v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{5 + 5, 10 + 10}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{5 + 5, 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -695,8 +695,8 @@ void Expr_struct_result_explicit_members(void) { }); Position v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{x: 5 + 5, y: 10 + 10}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{x: 5 + 5, y: 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -725,8 +725,8 @@ void Expr_struct_result_explicit_members_reverse(void) { }); Position v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{y: 5 + 5, x: 10 + 10}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{y: 5 + 5, x: 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -766,8 +766,8 @@ void Expr_struct_result_nested_implicit_members(void) { }); Line v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{{5 + 5, 10 + 10}, {10 + 20, 20 + 20}}", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{{5 + 5, 10 + 10}, {10 + 20, 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v }, &desc); @@ -812,8 +812,8 @@ void Expr_struct_result_nested_explicit_members(void) { }); Line v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{start: {x: 5 + 5, y: 10 + 10}, stop: {x: 10 + 20, y: 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v @@ -859,8 +859,8 @@ void Expr_struct_result_nested_explicit_members_reverse(void) { }); Line v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{stop: {x: 5 + 5, y: 10 + 10}, start: {x: 10 + 20, y: 20 + 20}}", &(ecs_value_t){ .type = line, .ptr = &v @@ -906,8 +906,8 @@ void Expr_struct_result_nested_explicit_dotmembers(void) { }); Line v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{stop.x: 5 + 5, start.x: 10 + 10, stop.y: 10 + 20, start.y: 20 + 20}", &(ecs_value_t){ .type = line, .ptr = &v @@ -937,8 +937,8 @@ void Expr_struct_result_add_2_int_literals(void) { }); Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{10 + 20}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10 + 20}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -965,8 +965,8 @@ void Expr_struct_result_add_2_2_fields_int_literals(void) { }); Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{10 + 20, 20 + 30}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10 + 20, 20 + 30}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -992,8 +992,8 @@ void Expr_struct_result_add_3_int_literals(void) { }); Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{10 + 20 + 30}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10 + 20 + 30}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -1018,8 +1018,8 @@ void Expr_struct_result_lparen_int_rparen(void) { }); Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{(10)}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{(10)}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -1040,8 +1040,8 @@ void Expr_add_to_var(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "$foo + 20", &v, &desc); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "$foo + 20", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_int(*(int32_t*)v.ptr, 10 + 20); @@ -1061,8 +1061,8 @@ void Expr_add_var_to(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "20 + $foo", &v, &desc); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "20 + $foo", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_int(*(int32_t*)v.ptr, 20 + 10); @@ -1091,10 +1091,10 @@ void Expr_var_member(void) { vars, "foo", PositionI); *(PositionI*)var->value.ptr = (PositionI){10, 20}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.x", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.x", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1103,7 +1103,7 @@ void Expr_var_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.y", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.y", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1148,10 +1148,10 @@ void Expr_var_member_member(void) { vars, "foo", Line); *(Line*)var->value.ptr = (Line){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.start.x", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.start.x", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1160,7 +1160,7 @@ void Expr_var_member_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.start.y", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.start.y", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1169,7 +1169,7 @@ void Expr_var_member_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.stop.x", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.stop.x", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1178,7 +1178,7 @@ void Expr_var_member_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.stop.y", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.stop.y", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1206,10 +1206,10 @@ void Expr_var_element(void) { ((int*)var->value.ptr)[0] = 10; ((int*)var->value.ptr)[1] = 20; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1218,7 +1218,7 @@ void Expr_var_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1252,10 +1252,10 @@ void Expr_var_element_element(void) { Ints value[] = {{10, 20}, {30, 40}, {50, 60}}; ecs_os_memcpy(var->value.ptr, value, sizeof(value)); - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[0][0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[0][0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1264,7 +1264,7 @@ void Expr_var_element_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[0][1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[0][1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1273,7 +1273,7 @@ void Expr_var_element_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[1][0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[1][0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1282,7 +1282,7 @@ void Expr_var_element_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[1][1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[1][1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1320,10 +1320,10 @@ void Expr_var_member_element(void) { vars, "foo", Points); *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.x[0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1332,7 +1332,7 @@ void Expr_var_member_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.x[1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.x[1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1341,7 +1341,7 @@ void Expr_var_member_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.y[0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.y[0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1350,7 +1350,7 @@ void Expr_var_member_element(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.y[1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.y[1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1383,10 +1383,10 @@ void Expr_var_member_element_inline(void) { vars, "foo", Points); *((Points*)var->value.ptr) = (Points){{10, 20}, {30, 40}}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.x[0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.x[0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1395,7 +1395,7 @@ void Expr_var_member_element_inline(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.x[1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.x[1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1404,7 +1404,7 @@ void Expr_var_member_element_inline(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.y[0]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.y[0]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1413,7 +1413,7 @@ void Expr_var_member_element_inline(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo.y[1]", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo.y[1]", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1452,10 +1452,10 @@ void Expr_var_element_member(void) { ((Point*)var->value.ptr)[0] = (Point){10, 20}; ((Point*)var->value.ptr)[1] = (Point){30, 40}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[0].x", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[0].x", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1464,7 +1464,7 @@ void Expr_var_element_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[0].y", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[0].y", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1473,7 +1473,7 @@ void Expr_var_element_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[1].x", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[1].x", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1482,7 +1482,7 @@ void Expr_var_element_member(void) { } { ecs_value_t v = {0}; - const char *ptr = ecs_script_expr_run(world, "$foo[1].y", &v, &desc); + const char *ptr = ecs_expr_run(world, "$foo[1].y", &v, &desc); test_assert(ptr != NULL); test_assert(!ptr[0]); test_uint(v.type, ecs_id(ecs_i32_t)); @@ -1498,23 +1498,23 @@ void Expr_bool_cond_and_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true && true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true && false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1527,23 +1527,23 @@ void Expr_bool_cond_or_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true || true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true || false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1556,23 +1556,23 @@ void Expr_int_cond_and_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 && 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 && 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1585,23 +1585,23 @@ void Expr_int_cond_or_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 || 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 || 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1614,23 +1614,23 @@ void Expr_bool_cond_and_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true && 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true && 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false && 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false && 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false && 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1643,23 +1643,23 @@ void Expr_int_cond_and_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 && true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 && false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 && true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 && false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1672,23 +1672,23 @@ void Expr_bool_cond_or_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true || 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true || 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false || 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false || 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false || 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1701,23 +1701,23 @@ void Expr_int_cond_or_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 || true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 || false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 || true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 || false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1730,23 +1730,23 @@ void Expr_cond_eq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true == true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true == false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false == true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false == false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1759,23 +1759,23 @@ void Expr_cond_eq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 == 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 == 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 == 20", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 == 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 == 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "0 == 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -1788,23 +1788,23 @@ void Expr_cond_neq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true != true", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true != false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false != true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false != false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1817,23 +1817,23 @@ void Expr_cond_neq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 != 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 != 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 != 20", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 != 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 != 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "0 != 0", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1848,9 +1848,9 @@ void Expr_cond_eq_bool_int(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - test_assert(ecs_script_expr_run(world, "true == 1", &v, NULL) == NULL); - test_assert(ecs_script_expr_run(world, "true == 0", &v, NULL) == NULL); - test_assert(ecs_script_expr_run(world, "false == 0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "true == 1", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "true == 0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "false == 0", &v, NULL) == NULL); ecs_fini(world); } @@ -1861,9 +1861,9 @@ void Expr_cond_eq_int_flt(void) { ecs_value_t v = {0}; ecs_log_set_level(-4); - test_assert(ecs_script_expr_run(world, "1 == 1.0", &v, NULL) == NULL); - test_assert(ecs_script_expr_run(world, "1 == 0.0", &v, NULL) == NULL); - test_assert(ecs_script_expr_run(world, "0 == 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 == 1.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 == 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "0 == 0.0", &v, NULL) == NULL); ecs_fini(world); } @@ -1872,18 +1872,18 @@ void Expr_cond_eq_cond_and(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true == true && false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true == true && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true == true && false); - test_assert(ecs_script_expr_run(world, "true && false == false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true && false == false); - test_assert(ecs_script_expr_run(world, "true && true == true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && true == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true && true == true); @@ -1896,13 +1896,13 @@ void Expr_cond_eq_cond_or(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true == true || false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true == true || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true == true || false); - test_assert(ecs_script_expr_run(world, "true || false == false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true || false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true || false == false); @@ -1915,23 +1915,23 @@ void Expr_cond_gt_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true > false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true > true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false > true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false > false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1944,23 +1944,23 @@ void Expr_cond_gt_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 > 5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 > 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 > 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 > 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -1973,23 +1973,23 @@ void Expr_cond_gt_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 > 5.5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10.5 > 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 > 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 > 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2002,23 +2002,23 @@ void Expr_cond_gteq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true >= false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true >= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "true >= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false >= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false >= false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false >= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2031,23 +2031,23 @@ void Expr_cond_gteq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 >= 5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 >= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10 >= 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 >= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 >= 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 >= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 >= 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 >= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2060,23 +2060,23 @@ void Expr_cond_gteq_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 >= 5.5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 >= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "10.5 >= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 >= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 >= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 >= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 >= 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 >= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2089,23 +2089,23 @@ void Expr_cond_lt_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true < false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true < false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true < true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true < true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "false < true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false < true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false < false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false < false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2118,23 +2118,23 @@ void Expr_cond_lt_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 < 5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 < 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 < 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 < 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5 < 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 < 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 < 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 < 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2147,23 +2147,23 @@ void Expr_cond_lt_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 < 5.5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 < 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10.5 < 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 < 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "5.5 < 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 < 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 < 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 < 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2176,23 +2176,23 @@ void Expr_cond_lteq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "true <= false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true <= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "true <= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true <= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false <= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false <= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "false <= false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false <= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2205,23 +2205,23 @@ void Expr_cond_lteq_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 <= 5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 <= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10 <= 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 <= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 <= 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 <= 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5 <= 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 <= 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2234,23 +2234,23 @@ void Expr_cond_lteq_flt(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10.5 <= 5.5", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 <= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_script_expr_run(world, "10.5 <= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 <= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 <= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 <= 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_script_expr_run(world, "5.5 <= 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 <= 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2263,8 +2263,8 @@ void Expr_min_lparen_int_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10); @@ -2277,8 +2277,8 @@ void Expr_min_lparen_int_add_int_rparen(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-(10 + 20)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-(10 + 20)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -30); @@ -2297,8 +2297,8 @@ void Expr_min_var(void) { *(ecs_u64_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - test_assert(ecs_script_expr_run(world, "-$foo", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars }; + test_assert(ecs_expr_run(world, "-$foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(*(ecs_i64_t*)v.ptr, -10); @@ -2314,8 +2314,8 @@ void Expr_min_lparen_int_rparen_to_i64(void) { ecs_i64_t vi = 0; ecs_value_t v = { .type = ecs_id(ecs_i64_t), .ptr = &vi }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_int(vi, -10); @@ -2328,8 +2328,8 @@ void Expr_min_lparen_int_rparen_to_i32(void) { ecs_i32_t vi = 0; ecs_value_t v = { .type = ecs_id(ecs_i32_t), .ptr = &vi }; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding };; - test_assert(ecs_script_expr_run(world, "-(10)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding };; + test_assert(ecs_expr_run(world, "-(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i32_t)); test_assert(v.ptr != NULL); test_int(vi, -10); @@ -2357,8 +2357,8 @@ void Expr_struct_w_min_var(void) { *(ecs_i64_t*)var->value.ptr = 10; Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{-$foo}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{-$foo}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -2384,8 +2384,8 @@ void Expr_struct_w_min_lparen_int_rparen(void) { }); Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{-(10)}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{-(10)}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -2416,8 +2416,8 @@ void Expr_struct_w_min_lparen_var_rparen(void) { *(ecs_u64_t*)var->value.ptr = 10; Mass v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{-($foo)}", &(ecs_value_t){ + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{-($foo)}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); test_assert(ptr != NULL); @@ -2433,8 +2433,8 @@ void Expr_not_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "!false", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "!false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2442,7 +2442,7 @@ void Expr_not_bool(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "!true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2455,8 +2455,8 @@ void Expr_not_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "!0", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "!0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2464,7 +2464,7 @@ void Expr_not_int(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "!10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2477,8 +2477,8 @@ void Expr_not_paren_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "!(0)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "!(0)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2486,7 +2486,7 @@ void Expr_not_paren_int(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!(10)", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "!(10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2499,8 +2499,8 @@ void Expr_not_paren_expr(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "!(10 - 10)", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "!(10 - 10)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); @@ -2508,7 +2508,7 @@ void Expr_not_paren_expr(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "!(5 + 5)", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "!(5 + 5)", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2528,8 +2528,8 @@ void Expr_not_var(void) { *(int32_t*)var->value.ptr = 10; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "!$foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_uint(*(bool*)v.ptr, false); @@ -2538,7 +2538,7 @@ void Expr_not_var(void) { ecs_os_zeromem(&v); *(int32_t*)var->value.ptr = 0; - test_assert(ecs_script_expr_run(world, "!$foo", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "!$foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_uint(*(bool*)v.ptr, true); @@ -2553,8 +2553,8 @@ void Expr_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "1 << 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << 2); @@ -2567,8 +2567,8 @@ void Expr_shift_right_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "4 >> 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "4 >> 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 4 >> 2); @@ -2581,8 +2581,8 @@ void Expr_shift_left_int_add_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "1 << 2 + 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "1 << 2 + 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << (2 + 10)); @@ -2595,8 +2595,8 @@ void Expr_shift_left_int_mul_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "1 << 2 * 10", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "1 << 2 * 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 1 << 2 * 10); @@ -2609,8 +2609,8 @@ void Expr_add_int_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 1 << 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << 2); @@ -2623,8 +2623,8 @@ void Expr_mul_int_shift_left_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 * 1 << 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 * 1 << 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2); @@ -2637,8 +2637,8 @@ void Expr_add_int_shift_left_int_add_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 + 1 << 2 + 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 + 1 << 2 + 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, (10 + 1) << (2 + 2)); @@ -2651,8 +2651,8 @@ void Expr_mul_int_shift_left_int_mul_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "10 * 1 << 2 * 2", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 * 1 << 2 * 2", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_i64_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_i64_t*)v.ptr, 10 * 1 << 2 * 2); @@ -2668,8 +2668,8 @@ void Expr_entity_expr(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "foo", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, foo); @@ -2688,8 +2688,8 @@ void Expr_entity_path_expr(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, foo); @@ -2708,8 +2708,8 @@ void Expr_entity_parent_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo.parent()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.parent()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, parent); @@ -2728,8 +2728,8 @@ void Expr_entity_name_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo.name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "foo"); @@ -2749,8 +2749,8 @@ void Expr_entity_doc_name_func(void) { ecs_doc_set_name(world, foo, "FooDoc"); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo.doc_name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "FooDoc"); @@ -2769,8 +2769,8 @@ void Expr_entity_chain_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo.parent().name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.parent().name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "parent"); @@ -2796,8 +2796,8 @@ void Expr_var_parent_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "$foo.parent()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "$foo.parent()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_entity_t)); test_assert(v.ptr != NULL); test_uint(*(ecs_entity_t*)v.ptr, parent); @@ -2818,8 +2818,8 @@ void Expr_entity_path_func(void) { test_assert(foo != 0); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.foo.path()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.path()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "parent.foo"); @@ -2845,8 +2845,8 @@ void Expr_var_name_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "$foo.name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "$foo.name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "child"); @@ -2875,8 +2875,8 @@ void Expr_var_doc_name_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "$foo.doc_name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "$foo.doc_name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "ChildDoc"); @@ -2904,8 +2904,8 @@ void Expr_var_chain_func(void) { *(ecs_entity_t*)var->value.ptr = child; ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "$foo.parent().name()", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "$foo.parent().name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); test_str(*(char**)v.ptr, "parent"); @@ -3030,7 +3030,7 @@ void Expr_root_func_void_return(void) { ecs_world_t *world = ecs_init(); test_expect_abort(); - ecs_script_function(world, { + ecs_function(world, { .name = "func", .callback = func_callback }); @@ -3041,7 +3041,7 @@ void Expr_root_func(void) { int ctx; - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .callback = func_callback, @@ -3049,8 +3049,8 @@ void Expr_root_func(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func()", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func()", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 10); @@ -3065,7 +3065,7 @@ void Expr_scoped_func(void) { int ctx; - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .parent = ecs_entity(world, { .name = "parent" }), .return_type = ecs_id(ecs_i32_t), @@ -3074,8 +3074,8 @@ void Expr_scoped_func(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "parent.func()", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.func()", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 10); @@ -3090,7 +3090,7 @@ void Expr_root_func_w_1_arg(void) { int ctx; - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3101,12 +3101,12 @@ void Expr_root_func_w_1_arg(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func(10)", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func(10)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 10); - test_assert(ecs_script_expr_run(world, "func(20)", + test_assert(ecs_expr_run(world, "func(20)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 20); @@ -3121,7 +3121,7 @@ void Expr_root_func_w_1_arg_expr(void) { int ctx; - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3132,12 +3132,12 @@ void Expr_root_func_w_1_arg_expr(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func(10 + 20)", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func(10 + 20)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 30); - test_assert(ecs_script_expr_run(world, "func(20 + 30)", + test_assert(ecs_expr_run(world, "func(20 + 30)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 50); @@ -3152,7 +3152,7 @@ void Expr_root_func_w_2_args(void) { int ctx; - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3164,12 +3164,12 @@ void Expr_root_func_w_2_args(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func(10, 20)", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func(10, 20)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 30); - test_assert(ecs_script_expr_run(world, "func(20, 30)", + test_assert(ecs_expr_run(world, "func(20, 30)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 50); @@ -3192,7 +3192,7 @@ void Expr_root_func_w_enum_arg(void) { } }); - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = color, .params = { @@ -3203,16 +3203,16 @@ void Expr_root_func_w_enum_arg(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func(Red)", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func(Red)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 0); - test_assert(ecs_script_expr_run(world, "func(Green)", + test_assert(ecs_expr_run(world, "func(Green)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 1); - test_assert(ecs_script_expr_run(world, "func(Blue)", + test_assert(ecs_expr_run(world, "func(Blue)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 2); @@ -3237,7 +3237,7 @@ void Expr_root_func_w_struct_arg(void) { } }); - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3248,12 +3248,12 @@ void Expr_root_func_w_struct_arg(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func({10, 20})", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func({10, 20})", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 30); - test_assert(ecs_script_expr_run(world, "func({30, 40})", + test_assert(ecs_expr_run(world, "func({30, 40})", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 70); @@ -3278,7 +3278,7 @@ void Expr_root_func_w_array_arg(void) { .count = 2 }); - ecs_entity_t func = ecs_script_function(world, { + ecs_entity_t func = ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3289,12 +3289,12 @@ void Expr_root_func_w_array_arg(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "func([10, 20])", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "func([10, 20])", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 30); - test_assert(ecs_script_expr_run(world, "func([30, 40])", + test_assert(ecs_expr_run(world, "func([30, 40])", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 70); @@ -3309,7 +3309,7 @@ void Expr_root_func_mismatching_args(void) { int ctx; - ecs_script_function(world, { + ecs_function(world, { .name = "func", .return_type = ecs_id(ecs_i32_t), .params = { @@ -3321,14 +3321,14 @@ void Expr_root_func_mismatching_args(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; ecs_log_set_level(-4); - test_assert(ecs_script_expr_run(world, "func(10)", + test_assert(ecs_expr_run(world, "func(10)", &ecs_value_ptr(ecs_i32_t, &v), &desc) == NULL); - test_assert(ecs_script_expr_run(world, "func(10, 20, 30)", + test_assert(ecs_expr_run(world, "func(10, 20, 30)", &ecs_value_ptr(ecs_i32_t, &v), &desc) == NULL); ecs_fini(world); @@ -3339,7 +3339,7 @@ void Expr_method_w_1_arg(void) { int ctx; - ecs_entity_t func = ecs_script_method(world, { + ecs_entity_t func = ecs_method(world, { .name = "add", .parent = ecs_id(ecs_i64_t), .return_type = ecs_id(ecs_i64_t), @@ -3351,12 +3351,12 @@ void Expr_method_w_1_arg(void) { }); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "(10).add(20)", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "(10).add(20)", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 30); - test_assert(ecs_script_expr_run(world, "(20).add(30))", + test_assert(ecs_expr_run(world, "(20).add(30))", &ecs_value_ptr(ecs_i32_t, &v), &desc) != NULL); test_int(v, 50); @@ -4105,8 +4105,8 @@ void Expr_component_expr(void) { ecs_set(world, e, Position, {10, 20}); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[Position]", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[Position]", &v, &desc) != NULL); test_assert(v.type == ecs_id(Position)); test_assert(v.ptr != NULL); @@ -4133,8 +4133,8 @@ void Expr_component_member_expr(void) { ecs_set(world, e, Position, {10, 20}); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[Position].x", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[Position].x", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); { @@ -4145,7 +4145,7 @@ void Expr_component_member_expr(void) { v = (ecs_value_t){0}; - test_assert(ecs_script_expr_run(world, "e[Position].y", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "e[Position].y", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_f32_t)); test_assert(v.ptr != NULL); { @@ -4175,8 +4175,8 @@ void Expr_component_expr_string(void) { ecs_set(world, e, String, { "Hello World" }); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[String]", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[String]", &v, &desc) != NULL); test_assert(v.type == ecs_id(String)); test_assert(v.ptr != NULL); { @@ -4210,8 +4210,8 @@ void Expr_component_member_expr_string(void) { ecs_set(world, e, String, { "Hello World" }); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[String].value", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[String].value", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4238,8 +4238,8 @@ void Expr_component_elem_expr(void) { ecs_set(world, e, Strings, { "Hello", "World" }); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[Strings][0]", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[Strings][0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4250,7 +4250,7 @@ void Expr_component_elem_expr(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[Strings][1]", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "e[Strings][1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4285,8 +4285,8 @@ void Expr_component_elem_expr_string(void) { ecs_set(world, e, String, {{ "Hello", "World" }}); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4297,7 +4297,7 @@ void Expr_component_elem_expr_string(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4327,8 +4327,8 @@ void Expr_component_inline_elem_expr_string(void) { ecs_set(world, e, String, {{ "Hello", "World" }}); ecs_value_t v = {0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_script_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e[String].value[0]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4339,7 +4339,7 @@ void Expr_component_inline_elem_expr_string(void) { ecs_os_zeromem(&v); - test_assert(ecs_script_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "e[String].value[1]", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); { @@ -4360,8 +4360,8 @@ void Expr_var_expr(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4389,10 +4389,10 @@ void Expr_var_member_expr(void) { *(PositionI*)foo->value.ptr = (PositionI){10, 20}; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.x", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4400,7 +4400,7 @@ void Expr_var_member_expr(void) { } { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.y", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4431,10 +4431,10 @@ void Expr_var_elem_expr(void) { (*var)[1] = 20; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo[0]", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4442,7 +4442,7 @@ void Expr_var_elem_expr(void) { } { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo[1]", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4463,8 +4463,8 @@ void Expr_var_expr_string(void) { *(char**)foo->value.ptr = "Hello World"; char* v = NULL; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4502,10 +4502,10 @@ void Expr_var_member_expr_string(void) { var->y = "World"; char *v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.x", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4516,7 +4516,7 @@ void Expr_var_member_expr_string(void) { v = NULL; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.y", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4553,10 +4553,10 @@ void Expr_var_elem_expr_string(void) { (*var)[1] = "World"; char *v = NULL; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo[0]", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4567,7 +4567,7 @@ void Expr_var_elem_expr_string(void) { v = NULL; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo[1]", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4607,10 +4607,10 @@ void Expr_var_inline_elem_expr_string(void) { var->value[1] = "World"; char *v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.value[0]", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4621,7 +4621,7 @@ void Expr_var_inline_elem_expr_string(void) { v = NULL; { - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo.value[1]", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4645,7 +4645,7 @@ void Expr_var_expr_no_desc(void) { int32_t v = 0; ecs_log_set_level(-4); - const char *ptr = ecs_script_expr_run( + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), NULL); test_assert(ptr == NULL); @@ -4658,8 +4658,8 @@ void Expr_var_expr_desc_w_no_vars(void) { int32_t v = 0; ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .vars = NULL, .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = NULL, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr == NULL); @@ -4675,12 +4675,12 @@ void Expr_parse_eval(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); + ecs_script_t *s = ecs_expr_parse(world, "$foo + 20", &desc); test_assert(s != NULL); - test_int(0, ecs_script_expr_eval(s, + test_int(0, ecs_expr_eval(s, &ecs_value_ptr(ecs_i32_t, &v), &desc)); test_int(v, 30); @@ -4699,18 +4699,18 @@ void Expr_parse_eval_multiple_times(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; - ecs_script_t *s = ecs_script_expr_parse(world, "$foo + 20", &desc); + ecs_script_t *s = ecs_expr_parse(world, "$foo + 20", &desc); test_assert(s != NULL); - test_int(0, ecs_script_expr_eval(s, + test_int(0, ecs_expr_eval(s, &ecs_value_ptr(ecs_i32_t, &v), &desc)); test_int(v, 30); *(int32_t*)foo->value.ptr = 30; - test_int(0, ecs_script_expr_eval(s, + test_int(0, ecs_expr_eval(s, &ecs_value_ptr(ecs_i32_t, &v), &desc)); test_int(v, 50); @@ -4724,8 +4724,8 @@ void Expr_parse_error(void) { ecs_world_t *world = ecs_init(); ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - ecs_script_t *s = ecs_script_expr_parse(world, "10 +", &desc); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + ecs_script_t *s = ecs_expr_parse(world, "10 +", &desc); test_assert(s == NULL); ecs_fini(world); @@ -4748,14 +4748,14 @@ void Expr_parse_eval_error(void) { ecs_add(world, e, Position); ecs_log_set_level(-4); - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - ecs_script_t *s = ecs_script_expr_parse(world, "e[Position]", &desc); + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + ecs_script_t *s = ecs_expr_parse(world, "e[Position]", &desc); test_assert(s != NULL); ecs_remove(world, e, Position); Position v; - test_assert(0 != ecs_script_expr_eval(s, + test_assert(0 != ecs_expr_eval(s, &ecs_value_ptr(Position, &v), NULL)); ecs_script_free(s); @@ -4767,8 +4767,8 @@ void Expr_remainder_after_number(void) { ecs_world_t *world = ecs_init(); int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "10 foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10 foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4781,8 +4781,8 @@ void Expr_remainder_after_string(void) { ecs_world_t *world = ecs_init(); char *v = 0; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "\"bar\" foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "\"bar\" foo", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4797,8 +4797,8 @@ void Expr_remainder_after_unary(void) { ecs_world_t *world = ecs_init(); bool v = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "!false foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "!false foo", &ecs_value_ptr(ecs_bool_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4811,8 +4811,8 @@ void Expr_remainder_after_binary(void) { ecs_world_t *world = ecs_init(); int32_t v = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "10 + 20 foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "10 + 20 foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4825,8 +4825,8 @@ void Expr_remainder_after_parens(void) { ecs_world_t *world = ecs_init(); int32_t v = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "(10 + 20) foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "(10 + 20) foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4849,8 +4849,8 @@ void Expr_remainder_after_initializer(void) { }); Position v = {0, 0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "{10, 20} foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10, 20} foo", &ecs_value_ptr(Position, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4874,8 +4874,8 @@ void Expr_remainder_after_collection_initializer(void) { }); Ints v = {0, 0}; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "[10, 20] foo", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "[10, 20] foo", &ecs_value_ptr(Ints, &v), &desc); test_assert(ptr != NULL); test_str(ptr, " foo"); @@ -4889,8 +4889,8 @@ void Expr_space_at_start(void) { ecs_world_t *world = ecs_init(); int32_t v = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, " 10 + 20", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, " 10 + 20", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -4903,8 +4903,8 @@ void Expr_newline_at_start(void) { ecs_world_t *world = ecs_init(); int32_t v = false; - ecs_script_expr_run_desc_t desc = { .disable_folding = disable_folding }; - const char *ptr = ecs_script_expr_run(world, "\n10 + 20", + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "\n10 + 20", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); diff --git a/test/script/src/Vars.c b/test/script/src/Vars.c index a1e7c69342..3f31b4cf84 100644 --- a/test/script/src/Vars.c +++ b/test/script/src/Vars.c @@ -152,8 +152,8 @@ void Vars_i32_expr_w_i32_var(void) { *(int32_t*)foo->value.ptr = 10; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -174,8 +174,8 @@ void Vars_i32_expr_w_f32_var(void) { *(float*)foo->value.ptr = 10.5; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -195,8 +195,8 @@ void Vars_i32_expr_w_string_var(void) { *(char**)foo->value.ptr = "10"; int32_t v = 0; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_i32_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -217,8 +217,8 @@ void Vars_string_expr_w_string_var(void) { *(char**)foo->value.ptr = "Hello World"; char* v = NULL; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run( + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run( world, "$foo", &ecs_value_ptr(ecs_string_t, &v), &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -250,8 +250,8 @@ void Vars_struct_expr_w_i32_vars(void) { *(int32_t*)bar->value.ptr = 20; PositionI v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run(world, "{$foo, $bar}", &(ecs_value_t){point, &v}, &desc); + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run(world, "{$foo, $bar}", &(ecs_value_t){point, &v}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -279,8 +279,8 @@ void Vars_struct_expr_w_struct_var(void) { *(PositionI*)foo->value.ptr = (PositionI){10, 20}; PositionI v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run(world, "$foo", &(ecs_value_t){point, &v}, &desc); + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run(world, "$foo", &(ecs_value_t){point, &v}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); @@ -317,8 +317,8 @@ void Vars_nested_struct_expr_w_struct_var(void) { *(PositionI*)bar->value.ptr = (PositionI){30, 40}; LineI v = {0}; - ecs_script_expr_run_desc_t desc = { .vars = vars }; - const char *ptr = ecs_script_expr_run(world, + ecs_expr_eval_desc_t desc = { .vars = vars }; + const char *ptr = ecs_expr_run(world, "{start: $foo, stop: $bar}", &(ecs_value_t){line, &v}, &desc); test_assert(ptr != NULL); test_assert(ptr[0] == 0); From 6979e3945a1cb7c22fd326d77bcb6d13234b062c Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 03:48:29 +0000 Subject: [PATCH 44/83] Add more script examples --- docs/FlecsScript.md | 2 +- .../c/script/functions/include/functions.h | 16 ++++ .../functions/include/functions/bake_config.h | 24 ++++++ examples/c/script/functions/project.json | 9 ++ examples/c/script/functions/src/main.c | 82 +++++++++++++++++++ .../script_w_vars/include/script_w_vars.h | 16 ++++ .../include/script_w_vars/bake_config.h | 24 ++++++ examples/c/script/script_w_vars/project.json | 9 ++ examples/c/script/script_w_vars/src/main.c | 77 +++++++++++++++++ 9 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 examples/c/script/functions/include/functions.h create mode 100644 examples/c/script/functions/include/functions/bake_config.h create mode 100644 examples/c/script/functions/project.json create mode 100644 examples/c/script/functions/src/main.c create mode 100644 examples/c/script/script_w_vars/include/script_w_vars.h create mode 100644 examples/c/script/script_w_vars/include/script_w_vars/bake_config.h create mode 100644 examples/c/script/script_w_vars/project.json create mode 100644 examples/c/script/script_w_vars/src/main.c diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 6abeddfc65..0dc3f84f2b 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -675,7 +675,7 @@ Initializers are values that are used to initialize composite and collection mem [{10, 20}, {30, 40}, {50, 60}] ``` -Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: +Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: ```c const x = Position: {10, 20} diff --git a/examples/c/script/functions/include/functions.h b/examples/c/script/functions/include/functions.h new file mode 100644 index 0000000000..285c2da825 --- /dev/null +++ b/examples/c/script/functions/include/functions.h @@ -0,0 +1,16 @@ +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +/* This generated file contains includes for project dependencies */ +#include "functions/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/functions/include/functions/bake_config.h b/examples/c/script/functions/include/functions/bake_config.h new file mode 100644 index 0000000000..f426646f00 --- /dev/null +++ b/examples/c/script/functions/include/functions/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef FUNCTIONS_BAKE_CONFIG_H +#define FUNCTIONS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/functions/project.json b/examples/c/script/functions/project.json new file mode 100644 index 0000000000..dde59cacd6 --- /dev/null +++ b/examples/c/script/functions/project.json @@ -0,0 +1,9 @@ +{ + "id": "functions", + "type": "application", + "value": { + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/examples/c/script/functions/src/main.c b/examples/c/script/functions/src/main.c new file mode 100644 index 0000000000..de11101dd1 --- /dev/null +++ b/examples/c/script/functions/src/main.c @@ -0,0 +1,82 @@ +#include +#include + +/* This example shows how to use user-defined functions in scripts. */ + +const char *code = + "e {\n" + " Position: { sum(10, 20), (30).add(40) }\n" + "}\n" + ; + +typedef struct { + int32_t x, y; +} Position; + +void sum( + const ecs_function_ctx_t *ctx, + int argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + + *(int64_t*)result->ptr = + *(int64_t*)argv[0].ptr + + *(int64_t*)argv[1].ptr; +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + + // Add reflection data to component so we can assign it in script + ecs_struct(world, { + .entity = ecs_id(Position), // Make sure to use existing id + .members = { + { .name = "x", .type = ecs_id(ecs_i32_t) }, + { .name = "y", .type = ecs_id(ecs_i32_t) } + } + }); + + // Define a sum function + ecs_function(world, { + .name = "sum", + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) }, + { .name = "b", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum + }); + + // Define an add method. A method is like a function, except that it can be + // called on type instances, and receives the instance as first parameter. + ecs_method(world, { + .name = "add", + .parent = ecs_id(ecs_i64_t), // Add method to i64 + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum + }); + + if (ecs_script_run(world, "My script", code)) { + printf("script failed to run\n"); + return -1; + } + + // Get entity and Position component + ecs_entity_t e = ecs_lookup(world, "e"); + const Position *p = ecs_get(world, e, Position); + + printf("{x: %d, y: %d}\n", p->x, p->y); + + return ecs_fini(world); + + // Output + // {30, 70} +} diff --git a/examples/c/script/script_w_vars/include/script_w_vars.h b/examples/c/script/script_w_vars/include/script_w_vars.h new file mode 100644 index 0000000000..59d741d451 --- /dev/null +++ b/examples/c/script/script_w_vars/include/script_w_vars.h @@ -0,0 +1,16 @@ +#ifndef SCRIPT_W_VARS_H +#define SCRIPT_W_VARS_H + +/* This generated file contains includes for project dependencies */ +#include "script_w_vars/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/script/script_w_vars/include/script_w_vars/bake_config.h b/examples/c/script/script_w_vars/include/script_w_vars/bake_config.h new file mode 100644 index 0000000000..87a815065e --- /dev/null +++ b/examples/c/script/script_w_vars/include/script_w_vars/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef SCRIPT_W_VARS_BAKE_CONFIG_H +#define SCRIPT_W_VARS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/script/script_w_vars/project.json b/examples/c/script/script_w_vars/project.json new file mode 100644 index 0000000000..e12fe18997 --- /dev/null +++ b/examples/c/script/script_w_vars/project.json @@ -0,0 +1,9 @@ +{ + "id": "script_w_vars", + "type": "application", + "value": { + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/examples/c/script/script_w_vars/src/main.c b/examples/c/script/script_w_vars/src/main.c new file mode 100644 index 0000000000..f352462513 --- /dev/null +++ b/examples/c/script/script_w_vars/src/main.c @@ -0,0 +1,77 @@ +#include +#include + +/* This example shows how to run a script that takes variables as input. The + * example also shows how to parse a script once and run it multiple times. */ + +const char *code = + "e {\n" + " Position: {$x * 10, $y * 10}\n" + "}\n" + ; + +typedef struct { + int32_t x, y; +} Position; + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + + // Add reflection data to component so we can assign it in script + ecs_struct(world, { + .entity = ecs_id(Position), // Make sure to use existing id + .members = { + { .name = "x", .type = ecs_id(ecs_i32_t) }, + { .name = "y", .type = ecs_id(ecs_i32_t) } + } + }); + + // Declare variables that we'll use in the expression + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *x = ecs_script_vars_define(vars, "x", ecs_i32_t); + ecs_script_var_t *y = ecs_script_vars_define(vars, "y", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + + // Parse the script + ecs_script_t *script = ecs_script_parse(world, "My script", code, &desc); + if (!script) { + printf("script failed to parse\n"); + return -1; + } + + // Assign values to variables and evaluate + *(int32_t*)x->value.ptr = 1; + *(int32_t*)y->value.ptr = 2; + if (ecs_script_eval(script, &desc) != 0) { + printf("script failed to run\n"); + return -1; + } + + // Get entity and Position component + ecs_entity_t e = ecs_lookup(world, "e"); + const Position *p = ecs_get(world, e, Position); + + printf("{x: %d, y: %d}\n", p->x, p->y); + + // Change values for variables and reevaluate + *(int32_t*)x->value.ptr = 1; + *(int32_t*)y->value.ptr = 2; + if (ecs_script_eval(script, &desc) != 0) { + printf("script failed to run\n"); + return -1; + } + + printf("{x: %d, y: %d}\n", p->x, p->y); + + ecs_script_vars_fini(vars); + ecs_script_free(script); + + return ecs_fini(world); + + // Output + // {10, 20} + // {20, 30} +} From 7540b5c741994ab364d3426fcb077a1d2147925c Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 15:39:49 -0800 Subject: [PATCH 45/83] Fix template instantiation issue while world is deferred --- distr/flecs.c | 233 ++++++++++++++++++++++++++------- src/addons/script/script.c | 4 +- src/addons/script/script.h | 21 +-- src/addons/script/template.c | 160 ++++++++++++++++++---- src/addons/script/template.h | 53 ++++++++ src/addons/script/visit_eval.h | 11 -- src/entity.c | 6 + test/script/project.json | 6 +- test/script/src/Template.c | 37 ++++++ test/script/src/main.c | 14 +- 10 files changed, 430 insertions(+), 115 deletions(-) create mode 100644 src/addons/script/template.h diff --git a/distr/flecs.c b/distr/flecs.c index 5569b54c1d..30c8cc37eb 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5399,17 +5399,6 @@ int flecs_script_eval_expr( ecs_expr_node_t **expr_ptr, ecs_value_t *value); -int flecs_script_eval_template( - ecs_script_eval_visitor_t *v, - ecs_script_template_node_t *template); - -ecs_script_template_t* flecs_script_template_init( - ecs_script_impl_t *script); - -void flecs_script_template_fini( - ecs_script_impl_t *script, - ecs_script_template_t *template); - void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, @@ -5425,16 +5414,15 @@ int flecs_script_eval_node( #endif +/** + * @file addons/script/template.h + * @brief Script template implementation. + */ -struct ecs_script_runtime_t { - ecs_allocator_t allocator; - ecs_expr_stack_t expr_stack; - ecs_stack_t stack; - ecs_vec_t using; - ecs_vec_t with; - ecs_vec_t with_type_info; - ecs_vec_t annot; -}; +#ifndef FLECS_SCRIPT_TEMPLATE_H +#define FLECS_SCRIPT_TEMPLATE_H + +extern ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); struct ecs_script_template_t { /* Template handle */ @@ -5456,6 +5444,41 @@ struct ecs_script_template_t { const ecs_type_info_t *type_info; }; +/* Event used for deferring template instantiation */ +typedef struct EcsTemplateSetEvent { + ecs_entity_t template_entity; + ecs_entity_t *entities; + void *data; + int32_t count; +} EcsTemplateSetEvent; + +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *template); + +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script); + +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template); + +void flecs_script_template_import( + ecs_world_t *world); + +#endif + + +struct ecs_script_runtime_t { + ecs_allocator_t allocator; + ecs_expr_stack_t expr_stack; + ecs_stack_t stack; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t with_type_info; + ecs_vec_t annot; +}; + ecs_script_t* flecs_script_new( ecs_world_t *world); @@ -6326,6 +6349,8 @@ ecs_record_t* flecs_new_entity( return record; } +static int commit_indent = 0; + static void flecs_move_entity( ecs_world_t *world, @@ -6463,6 +6488,8 @@ void flecs_commit( return; } + commit_indent += 2; + ecs_os_perf_trace_push("flecs.commit"); if (src_table) { @@ -6502,6 +6529,8 @@ void flecs_commit( ECS_OUT_OF_RANGE, 0); } + commit_indent -=2 ; + ecs_os_perf_trace_pop("flecs.commit"); error: @@ -57910,7 +57939,8 @@ void FlecsScriptImport( ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), - .dtor = ecs_dtor(EcsScript) + .dtor = ecs_dtor(EcsScript), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ECS_COMPONENT(world, ecs_script_t); @@ -57934,6 +57964,7 @@ void FlecsScriptImport( ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); + flecs_script_template_import(world); flecs_function_import(world); } @@ -58405,6 +58436,26 @@ char* ecs_ptr_to_str( #ifdef FLECS_SCRIPT +ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); + +static +ECS_MOVE(EcsTemplateSetEvent, dst, src, { + ecs_os_free(dst->entities); + ecs_os_free(dst->data); + dst->entities = src->entities; + dst->data = src->data; + dst->template_entity = src->template_entity; + dst->count = src->count; + src->entities = NULL; + src->data = NULL; +}) + +static +ECS_DTOR(EcsTemplateSetEvent, ptr, { + ecs_os_free(ptr->entities); + ecs_os_free(ptr->data); +}) + /* Template ctor to initialize with default property values */ static void flecs_script_template_ctor( @@ -58453,18 +58504,37 @@ void flecs_script_template_ctor( } } -/* Template on_set handler to update contents for new property values */ +/* Defer template instantiation if we're in deferred mode. */ static -void flecs_script_template_on_set( - ecs_iter_t *it) +void flecs_script_template_defer_on_set( + ecs_iter_t *it, + ecs_entity_t template_entity, + const ecs_type_info_t *ti, + void *data) +{ + EcsTemplateSetEvent evt; + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + evt.count = it->count; + evt.template_entity = template_entity; + + ecs_enqueue(it->world, &(ecs_event_desc_t){ + .event = ecs_id(EcsTemplateSetEvent), + .entity = EcsAny, + .param = &evt + }); +} + +static +void flecs_script_template_instantiate( + ecs_world_t *world, + ecs_entity_t template_entity, + const ecs_entity_t *entities, + void *data, + int32_t count) { - if (it->table->flags & EcsTableIsPrefab) { - /* Don't instantiate templates for prefabs */ - return; - } + ecs_assert(!ecs_is_deferred(world), ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = it->world; - ecs_entity_t template_entity = ecs_field_id(it, 0); ecs_record_t *r = ecs_record_find(world, template_entity); if (!r) { ecs_err("template entity is empty (should never happen)"); @@ -58483,8 +58553,6 @@ void flecs_script_template_on_set( ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const EcsStruct *st = ecs_record_get(world, r, EcsStruct); - void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); - ecs_script_eval_visitor_t v; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); ecs_vec_t prev_using = v.r->using; @@ -58504,9 +58572,9 @@ void flecs_script_template_on_set( v.entity = &instance_node; int32_t i, m; - for (i = 0; i < it->count; i ++) { - v.parent = it->entities[i]; - instance_node.eval = it->entities[i]; + for (i = 0; i < count; i ++) { + v.parent = entities[i]; + instance_node.eval = entities[i]; /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( @@ -58528,31 +58596,17 @@ void flecs_script_template_on_set( } /* Populate $this variable with instance entity */ - ecs_entity_t instance = it->entities[i]; + ecs_entity_t instance = entities[i]; ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); var->value.type = ecs_id(ecs_entity_t); var->value.ptr = &instance; - bool is_defer = ecs_is_deferred(world); - ecs_suspend_readonly_state_t srs; - ecs_world_t *real_world = NULL; - if (is_defer) { - ecs_assert(flecs_poly_is(world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - real_world = flecs_suspend_readonly(world, &srs); - ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); - } - ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; ecs_script_visit_scope(&v, scope); - if (is_defer) { - flecs_resume_readonly(real_world, &srs); - } - /* Pop variable scope */ ecs_script_vars_pop(vars); @@ -58563,6 +58617,65 @@ void flecs_script_template_on_set( flecs_script_eval_visit_fini(&v, NULL); } +static +void flecs_on_template_set_event( + ecs_iter_t *it) +{ + ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); + + EcsTemplateSetEvent *evt = it->param; + ecs_suspend_readonly_state_t srs; + ecs_world_t *world = it->real_world; + ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + + flecs_suspend_readonly(world, &srs); + + flecs_script_template_instantiate( + world, evt->template_entity, evt->entities, evt->data, evt->count); + + flecs_resume_readonly(world, &srs); +} + +/* Template on_set handler to update contents for new property values */ +static +void flecs_script_template_on_set( + ecs_iter_t *it) +{ + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate templates for prefabs */ + return; + } + + ecs_world_t *world = it->world; + ecs_entity_t template_entity = ecs_field_id(it, 0); + ecs_record_t *r = ecs_record_find(world, template_entity); + if (!r) { + ecs_err("template entity is empty (should never happen)"); + return; + } + + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("template is missing script component"); + return; + } + + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = template->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); + + if (ecs_is_deferred(it->world)) { + flecs_script_template_defer_on_set(it, template_entity, ti, data); + return; + } + + flecs_script_template_instantiate( + world, template_entity, it->entities, data, it->count); + return; +} + static int flecs_script_template_eval_prop( ecs_script_eval_visitor_t *v, @@ -58829,6 +58942,26 @@ int flecs_script_eval_template( return -1; } +void flecs_script_template_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); + + ecs_set_hooks(world, EcsTemplateSetEvent, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsTemplateSetEvent), + .dtor = ecs_dtor(EcsTemplateSetEvent), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), + .query.terms = {{ .id = EcsAny }}, + .events = { ecs_id(EcsTemplateSetEvent) }, + .callback = flecs_on_template_set_event + }); +} + #endif /** diff --git a/src/addons/script/script.c b/src/addons/script/script.c index ee7c49169d..a1163c5ca8 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -304,7 +304,8 @@ void FlecsScriptImport( ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), - .dtor = ecs_dtor(EcsScript) + .dtor = ecs_dtor(EcsScript), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ECS_COMPONENT(world, ecs_script_t); @@ -328,6 +329,7 @@ void FlecsScriptImport( ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); + flecs_script_template_import(world); flecs_function_import(world); } diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 35be265b44..b1d7d5bd13 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -57,6 +57,7 @@ typedef struct ecs_function_calldata_t { #include "expr/expr.h" #include "visit.h" #include "visit_eval.h" +#include "template.h" struct ecs_script_runtime_t { ecs_allocator_t allocator; @@ -68,26 +69,6 @@ struct ecs_script_runtime_t { ecs_vec_t annot; }; -struct ecs_script_template_t { - /* Template handle */ - ecs_entity_t entity; - - /* Template AST node */ - ecs_script_template_node_t *node; - - /* Hoisted using statements */ - ecs_vec_t using_; - - /* Hoisted variables */ - ecs_script_vars_t *vars; - - /* Default values for props */ - ecs_vec_t prop_defaults; - - /* Type info for template component */ - const ecs_type_info_t *type_info; -}; - ecs_script_t* flecs_script_new( ecs_world_t *world); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 5c69db7a9e..3d3a132716 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -8,6 +8,26 @@ #ifdef FLECS_SCRIPT #include "script.h" +ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); + +static +ECS_MOVE(EcsTemplateSetEvent, dst, src, { + ecs_os_free(dst->entities); + ecs_os_free(dst->data); + dst->entities = src->entities; + dst->data = src->data; + dst->template_entity = src->template_entity; + dst->count = src->count; + src->entities = NULL; + src->data = NULL; +}) + +static +ECS_DTOR(EcsTemplateSetEvent, ptr, { + ecs_os_free(ptr->entities); + ecs_os_free(ptr->data); +}) + /* Template ctor to initialize with default property values */ static void flecs_script_template_ctor( @@ -56,18 +76,37 @@ void flecs_script_template_ctor( } } -/* Template on_set handler to update contents for new property values */ +/* Defer template instantiation if we're in deferred mode. */ static -void flecs_script_template_on_set( - ecs_iter_t *it) +void flecs_script_template_defer_on_set( + ecs_iter_t *it, + ecs_entity_t template_entity, + const ecs_type_info_t *ti, + void *data) { - if (it->table->flags & EcsTableIsPrefab) { - /* Don't instantiate templates for prefabs */ - return; - } + EcsTemplateSetEvent evt; + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + evt.count = it->count; + evt.template_entity = template_entity; + + ecs_enqueue(it->world, &(ecs_event_desc_t){ + .event = ecs_id(EcsTemplateSetEvent), + .entity = EcsAny, + .param = &evt + }); +} + +static +void flecs_script_template_instantiate( + ecs_world_t *world, + ecs_entity_t template_entity, + const ecs_entity_t *entities, + void *data, + int32_t count) +{ + ecs_assert(!ecs_is_deferred(world), ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = it->world; - ecs_entity_t template_entity = ecs_field_id(it, 0); ecs_record_t *r = ecs_record_find(world, template_entity); if (!r) { ecs_err("template entity is empty (should never happen)"); @@ -86,8 +125,6 @@ void flecs_script_template_on_set( ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const EcsStruct *st = ecs_record_get(world, r, EcsStruct); - void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); - ecs_script_eval_visitor_t v; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); ecs_vec_t prev_using = v.r->using; @@ -107,9 +144,9 @@ void flecs_script_template_on_set( v.entity = &instance_node; int32_t i, m; - for (i = 0; i < it->count; i ++) { - v.parent = it->entities[i]; - instance_node.eval = it->entities[i]; + for (i = 0; i < count; i ++) { + v.parent = entities[i]; + instance_node.eval = entities[i]; /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( @@ -131,31 +168,17 @@ void flecs_script_template_on_set( } /* Populate $this variable with instance entity */ - ecs_entity_t instance = it->entities[i]; + ecs_entity_t instance = entities[i]; ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); var->value.type = ecs_id(ecs_entity_t); var->value.ptr = &instance; - bool is_defer = ecs_is_deferred(world); - ecs_suspend_readonly_state_t srs; - ecs_world_t *real_world = NULL; - if (is_defer) { - ecs_assert(flecs_poly_is(world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - real_world = flecs_suspend_readonly(world, &srs); - ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); - } - ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; ecs_script_visit_scope(&v, scope); - if (is_defer) { - flecs_resume_readonly(real_world, &srs); - } - /* Pop variable scope */ ecs_script_vars_pop(vars); @@ -166,6 +189,65 @@ void flecs_script_template_on_set( flecs_script_eval_visit_fini(&v, NULL); } +static +void flecs_on_template_set_event( + ecs_iter_t *it) +{ + ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); + + EcsTemplateSetEvent *evt = it->param; + ecs_suspend_readonly_state_t srs; + ecs_world_t *world = it->real_world; + ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + + flecs_suspend_readonly(world, &srs); + + flecs_script_template_instantiate( + world, evt->template_entity, evt->entities, evt->data, evt->count); + + flecs_resume_readonly(world, &srs); +} + +/* Template on_set handler to update contents for new property values */ +static +void flecs_script_template_on_set( + ecs_iter_t *it) +{ + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate templates for prefabs */ + return; + } + + ecs_world_t *world = it->world; + ecs_entity_t template_entity = ecs_field_id(it, 0); + ecs_record_t *r = ecs_record_find(world, template_entity); + if (!r) { + ecs_err("template entity is empty (should never happen)"); + return; + } + + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("template is missing script component"); + return; + } + + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = template->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); + + if (ecs_is_deferred(it->world)) { + flecs_script_template_defer_on_set(it, template_entity, ti, data); + return; + } + + flecs_script_template_instantiate( + world, template_entity, it->entities, data, it->count); + return; +} + static int flecs_script_template_eval_prop( ecs_script_eval_visitor_t *v, @@ -432,4 +514,24 @@ int flecs_script_eval_template( return -1; } +void flecs_script_template_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); + + ecs_set_hooks(world, EcsTemplateSetEvent, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsTemplateSetEvent), + .dtor = ecs_dtor(EcsTemplateSetEvent), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), + .query.terms = {{ .id = EcsAny }}, + .events = { ecs_id(EcsTemplateSetEvent) }, + .callback = flecs_on_template_set_event + }); +} + #endif diff --git a/src/addons/script/template.h b/src/addons/script/template.h new file mode 100644 index 0000000000..9a5a42bb08 --- /dev/null +++ b/src/addons/script/template.h @@ -0,0 +1,53 @@ +/** + * @file addons/script/template.h + * @brief Script template implementation. + */ + +#ifndef FLECS_SCRIPT_TEMPLATE_H +#define FLECS_SCRIPT_TEMPLATE_H + +extern ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); + +struct ecs_script_template_t { + /* Template handle */ + ecs_entity_t entity; + + /* Template AST node */ + ecs_script_template_node_t *node; + + /* Hoisted using statements */ + ecs_vec_t using_; + + /* Hoisted variables */ + ecs_script_vars_t *vars; + + /* Default values for props */ + ecs_vec_t prop_defaults; + + /* Type info for template component */ + const ecs_type_info_t *type_info; +}; + +/* Event used for deferring template instantiation */ +typedef struct EcsTemplateSetEvent { + ecs_entity_t template_entity; + ecs_entity_t *entities; + void *data; + int32_t count; +} EcsTemplateSetEvent; + +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *template); + +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script); + +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template); + +void flecs_script_template_import( + ecs_world_t *world); + +#endif diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index 9eb4aecf37..bf4df1111d 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -47,17 +47,6 @@ int flecs_script_eval_expr( ecs_expr_node_t **expr_ptr, ecs_value_t *value); -int flecs_script_eval_template( - ecs_script_eval_visitor_t *v, - ecs_script_template_node_t *template); - -ecs_script_template_t* flecs_script_template_init( - ecs_script_impl_t *script); - -void flecs_script_template_fini( - ecs_script_impl_t *script, - ecs_script_template_t *template); - void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, diff --git a/src/entity.c b/src/entity.c index 01e2fc84e1..b7e0635f7b 100644 --- a/src/entity.c +++ b/src/entity.c @@ -844,6 +844,8 @@ ecs_record_t* flecs_new_entity( return record; } +static int commit_indent = 0; + static void flecs_move_entity( ecs_world_t *world, @@ -981,6 +983,8 @@ void flecs_commit( return; } + commit_indent += 2; + ecs_os_perf_trace_push("flecs.commit"); if (src_table) { @@ -1020,6 +1024,8 @@ void flecs_commit( ECS_OUT_OF_RANGE, 0); } + commit_indent -=2 ; + ecs_os_perf_trace_pop("flecs.commit"); error: diff --git a/test/script/project.json b/test/script/project.json index 325109daf0..8defc6429d 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -309,7 +309,8 @@ "anonymous_template_instance_w_prop", "anonymous_template_instance_w_prop_no_scope", "with_after_template", - "with_in_scope_after_template" + "with_in_scope_after_template", + "prefab_w_template" ] }, { "id": "Error", @@ -377,7 +378,8 @@ "run_template_after_error", "update_template_after_error", "template_in_template", - "unterminated_binary" + "unterminated_binary", + "component_in_with_scope" ] }, { "id": "Expr", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index e44ae3dba3..9e0759ea71 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -1935,3 +1935,40 @@ void Template_with_in_scope_after_template(void) { ecs_fini(world); } + +void Template_prefab_w_template(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Bar); + + const char *expr = + HEAD "Tag {}" + LINE "" + LINE "template Foo {" + LINE " Tag" + LINE "}" + LINE "" + LINE "prefab Base {" + LINE " Foo: {}" + LINE "}" + LINE "" + LINE "e : Base"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + ecs_entity_t base = ecs_lookup(world, "Base"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t tag = ecs_lookup(world, "Tag"); + + test_assert(e != 0); + test_assert(base != 0); + test_assert(foo != 0); + test_assert(tag != 0); + + test_assert(ecs_has_id(world, e, tag)); + test_assert(ecs_has_id(world, e, foo)); + test_assert(ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 8bcd2f8b22..7d1658bf95 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -304,6 +304,7 @@ void Template_anonymous_template_instance_w_prop(void); void Template_anonymous_template_instance_w_prop_no_scope(void); void Template_with_after_template(void); void Template_with_in_scope_after_template(void); +void Template_prefab_w_template(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -370,6 +371,7 @@ void Error_run_template_after_error(void); void Error_update_template_after_error(void); void Error_template_in_template(void); void Error_unterminated_binary(void); +void Error_component_in_with_scope(void); // Testsuite 'Expr' void Expr_setup(void); @@ -1896,6 +1898,10 @@ bake_test_case Template_testcases[] = { { "with_in_scope_after_template", Template_with_in_scope_after_template + }, + { + "prefab_w_template", + Template_prefab_w_template } }; @@ -2155,6 +2161,10 @@ bake_test_case Error_testcases[] = { { "unterminated_binary", Error_unterminated_binary + }, + { + "component_in_with_scope", + Error_component_in_with_scope } }; @@ -3551,14 +3561,14 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 36, + 37, Template_testcases }, { "Error", NULL, NULL, - 64, + 65, Error_testcases }, { From ffe3e6d0b779532fb0e2ecd3c7556a1ef01acf12 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 19:23:25 -0800 Subject: [PATCH 46/83] Allow for infinitely nested observer events --- distr/flecs.c | 86 ++++++++++++++---------------------- src/addons/script/template.c | 5 +-- src/entity.c | 49 ++++++++++---------- src/private_types.h | 6 +-- src/stage.c | 20 +-------- src/stage.h | 6 --- test/core/project.json | 3 +- test/core/src/Commands.c | 50 +++++++++++++++++++++ test/core/src/Event.c | 2 + test/core/src/main.c | 7 ++- 10 files changed, 126 insertions(+), 108 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 30c8cc37eb..7d4039fa07 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -852,10 +852,10 @@ struct ecs_stage_t { /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; - /* Command queue stack, for nested execution */ + /* Command queue */ ecs_commands_t *cmd; - ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; - int32_t cmd_sp; + ecs_commands_t cmd_stack[2]; /* Two so we can flush one & populate the other */ + bool cmd_flushing; /* Ensures only one defer_end call flushes */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ @@ -2791,12 +2791,6 @@ void flecs_enqueue( ecs_stage_t *stage, ecs_event_desc_t *desc); -void flecs_commands_push( - ecs_stage_t *stage); - -void flecs_commands_pop( - ecs_stage_t *stage); - ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); @@ -9141,26 +9135,18 @@ void ecs_enable_id( ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, "add CanToggle trait to component"); + ecs_entity_t bs_id = id | ECS_TOGGLE; + ecs_add_id(world, entity, bs_id); + if (flecs_defer_enable(stage, entity, id, enable)) { return; } - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_entity_t bs_id = id | ECS_TOGGLE; - + ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; - int32_t index = -1; - if (table) { - index = ecs_table_get_type_index(world, table, bs_id); - } + int32_t index = ecs_table_get_type_index(world, table, bs_id); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - if (index == -1) { - ecs_add_id(world, entity, bs_id); - flecs_defer_end(world, stage); - ecs_enable_id(world, entity, id, enable); - return; - } - ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); @@ -10611,7 +10597,7 @@ bool flecs_defer_end( ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - if (!--stage->defer) { + if (!--stage->defer && !stage->cmd_flushing) { ecs_os_perf_trace_push("flecs.commands.merge"); /* Test whether we're flushing to another queue or whether we're @@ -10621,11 +10607,17 @@ bool flecs_defer_end( merge_to_world = world->stages[0]->defer == 0; } - ecs_stage_t *dst_stage = flecs_stage_from_world(&world); - ecs_commands_t *commands = stage->cmd; - ecs_vec_t *queue = &commands->queue; + do { + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; + + if (!ecs_vec_count(queue)) { + break; + } + + stage->cmd_flushing = true; - if (ecs_vec_count(queue)) { /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, @@ -10637,7 +10629,12 @@ bool flecs_defer_end( ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); - flecs_commands_push(stage); + + if (stage->cmd == &stage->cmd_stack[0]) { + stage->cmd = &stage->cmd_stack[1]; + } else { + stage->cmd = &stage->cmd_stack[0]; + } for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; @@ -10795,10 +10792,10 @@ bool flecs_defer_end( } } + stage->cmd_flushing = false; + flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); - flecs_commands_pop(stage); - flecs_table_diff_builder_fini(world, &diff); /* Internal callback for capturing commands, signal queue is done */ @@ -10806,7 +10803,7 @@ bool flecs_defer_end( world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } - } + } while (true); ecs_os_perf_trace_pop("flecs.commands.merge"); @@ -17685,22 +17682,6 @@ void flecs_commands_fini( flecs_sparse_fini(&cmd->entries); } -void flecs_commands_push( - ecs_stage_t *stage) -{ - int32_t sp = ++ stage->cmd_sp; - ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); - stage->cmd = &stage->cmd_stack[sp]; -} - -void flecs_commands_pop( - ecs_stage_t *stage) -{ - int32_t sp = -- stage->cmd_sp; - ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); - stage->cmd = &stage->cmd_stack[sp]; -} - ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system) @@ -17733,7 +17714,7 @@ ecs_stage_t* flecs_stage_new( ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); int32_t i; - for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + for (i = 0; i < 2; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } @@ -17759,7 +17740,7 @@ void flecs_stage_free( ecs_vec_fini(NULL, &stage->operations, 0); int32_t i; - for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + for (i = 0; i < 2; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } @@ -58624,16 +58605,15 @@ void flecs_on_template_set_event( ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); EcsTemplateSetEvent *evt = it->param; - ecs_suspend_readonly_state_t srs; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); - flecs_suspend_readonly(world, &srs); + ecs_defer_suspend(world); flecs_script_template_instantiate( world, evt->template_entity, evt->entities, evt->data, evt->count); - flecs_resume_readonly(world, &srs); + ecs_defer_resume(world); } /* Template on_set handler to update contents for new property values */ diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 3d3a132716..94ed51015c 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -196,16 +196,15 @@ void flecs_on_template_set_event( ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); EcsTemplateSetEvent *evt = it->param; - ecs_suspend_readonly_state_t srs; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); - flecs_suspend_readonly(world, &srs); + ecs_defer_suspend(world); flecs_script_template_instantiate( world, evt->template_entity, evt->entities, evt->data, evt->count); - flecs_resume_readonly(world, &srs); + ecs_defer_resume(world); } /* Template on_set handler to update contents for new property values */ diff --git a/src/entity.c b/src/entity.c index b7e0635f7b..b3945fcac0 100644 --- a/src/entity.c +++ b/src/entity.c @@ -3636,26 +3636,18 @@ void ecs_enable_id( ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, "add CanToggle trait to component"); + ecs_entity_t bs_id = id | ECS_TOGGLE; + ecs_add_id(world, entity, bs_id); + if (flecs_defer_enable(stage, entity, id, enable)) { return; } - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_entity_t bs_id = id | ECS_TOGGLE; - + ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; - int32_t index = -1; - if (table) { - index = ecs_table_get_type_index(world, table, bs_id); - } + int32_t index = ecs_table_get_type_index(world, table, bs_id); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - if (index == -1) { - ecs_add_id(world, entity, bs_id); - flecs_defer_end(world, stage); - ecs_enable_id(world, entity, id, enable); - return; - } - ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); @@ -5106,7 +5098,7 @@ bool flecs_defer_end( ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - if (!--stage->defer) { + if (!--stage->defer && !stage->cmd_flushing) { ecs_os_perf_trace_push("flecs.commands.merge"); /* Test whether we're flushing to another queue or whether we're @@ -5116,11 +5108,17 @@ bool flecs_defer_end( merge_to_world = world->stages[0]->defer == 0; } - ecs_stage_t *dst_stage = flecs_stage_from_world(&world); - ecs_commands_t *commands = stage->cmd; - ecs_vec_t *queue = &commands->queue; + do { + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; + + if (!ecs_vec_count(queue)) { + break; + } + + stage->cmd_flushing = true; - if (ecs_vec_count(queue)) { /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, @@ -5132,7 +5130,12 @@ bool flecs_defer_end( ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); - flecs_commands_push(stage); + + if (stage->cmd == &stage->cmd_stack[0]) { + stage->cmd = &stage->cmd_stack[1]; + } else { + stage->cmd = &stage->cmd_stack[0]; + } for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; @@ -5290,10 +5293,10 @@ bool flecs_defer_end( } } + stage->cmd_flushing = false; + flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); - flecs_commands_pop(stage); - flecs_table_diff_builder_fini(world, &diff); /* Internal callback for capturing commands, signal queue is done */ @@ -5301,7 +5304,7 @@ bool flecs_defer_end( world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } - } + } while (true); ecs_os_perf_trace_pop("flecs.commands.merge"); diff --git a/src/private_types.h b/src/private_types.h index bd5b208b3a..cd30e6f05a 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -201,10 +201,10 @@ struct ecs_stage_t { /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; - /* Command queue stack, for nested execution */ + /* Command queue */ ecs_commands_t *cmd; - ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; - int32_t cmd_sp; + ecs_commands_t cmd_stack[2]; /* Two so we can flush one & populate the other */ + bool cmd_flushing; /* Ensures only one defer_end call flushes */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ diff --git a/src/stage.c b/src/stage.c index c876f2de13..2a862d5ff2 100644 --- a/src/stage.c +++ b/src/stage.c @@ -591,22 +591,6 @@ void flecs_commands_fini( flecs_sparse_fini(&cmd->entries); } -void flecs_commands_push( - ecs_stage_t *stage) -{ - int32_t sp = ++ stage->cmd_sp; - ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); - stage->cmd = &stage->cmd_stack[sp]; -} - -void flecs_commands_pop( - ecs_stage_t *stage) -{ - int32_t sp = -- stage->cmd_sp; - ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); - stage->cmd = &stage->cmd_stack[sp]; -} - ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system) @@ -639,7 +623,7 @@ ecs_stage_t* flecs_stage_new( ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); int32_t i; - for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + for (i = 0; i < 2; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } @@ -665,7 +649,7 @@ void flecs_stage_free( ecs_vec_fini(NULL, &stage->operations, 0); int32_t i; - for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + for (i = 0; i < 2; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } diff --git a/src/stage.h b/src/stage.h index ced22e7ea2..3f6e0f29d5 100644 --- a/src/stage.h +++ b/src/stage.h @@ -94,12 +94,6 @@ void flecs_enqueue( ecs_stage_t *stage, ecs_event_desc_t *desc); -void flecs_commands_push( - ecs_stage_t *stage); - -void flecs_commands_pop( - ecs_stage_t *stage); - ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); diff --git a/test/core/project.json b/test/core/project.json index 3b2436d1e0..caff9b928d 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -2244,7 +2244,8 @@ "defer_emplace_after_remove", "batched_w_table_change_in_observer", "redefine_named_in_threaded_app", - "batched_cmd_w_component_init" + "batched_cmd_w_component_init", + "deep_command_nesting" ] }, { "id": "SingleThreadStaging", diff --git a/test/core/src/Commands.c b/test/core/src/Commands.c index f89dbbbdd5..89285b993d 100644 --- a/test/core/src/Commands.c +++ b/test/core/src/Commands.c @@ -4243,3 +4243,53 @@ void Commands_batched_cmd_w_component_init(void) { ecs_fini(world); } + +typedef struct TestNestEvent { + int32_t depth; +} TestNestEvent; + +static ECS_COMPONENT_DECLARE(TestNestEvent); + +static int test_nest_invoked = 0; + +static +void test_nest_observer(ecs_iter_t *it) { + TestNestEvent *param = it->param; + + TestNestEvent evt = { .depth = param->depth - 1 }; + + test_nest_invoked ++; + + if (param->depth) { + ecs_enqueue(it->world, &(ecs_event_desc_t) { + .event = ecs_id(TestNestEvent), + .param = &evt, + .entity = EcsAny + }); + } +} + +void Commands_deep_command_nesting(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT_DEFINE(world, TestNestEvent); + + ecs_observer(world, { + .events = {{ ecs_id(TestNestEvent) }}, + .query = { .terms = {{ .id = EcsAny }}}, + .callback = test_nest_observer + }); + + ecs_log_set_level(0); + ecs_emit(world, &(ecs_event_desc_t) { + .event = ecs_id(TestNestEvent), + .param = &(TestNestEvent){ .depth = 32 }, + .entity = EcsAny + }); + + test_int(test_nest_invoked, 33); + + ecs_log_set_level(-1); + + ecs_fini(world); +} diff --git a/test/core/src/Event.c b/test/core/src/Event.c index 69e1ac2bf6..5f3137481e 100644 --- a/test/core/src/Event.c +++ b/test/core/src/Event.c @@ -1215,9 +1215,11 @@ void Event_enqueue_event_not_alive_w_data_copy(void) { static void system_delete_callback(ecs_iter_t *it) { + ecs_defer_suspend(it->world); for (int i = 0; i < it->count; i ++) { ecs_delete(it->world, it->entities[i]); } + ecs_defer_resume(it->world); } void Event_enqueue_event_not_alive_after_delete_during_merge(void) { diff --git a/test/core/src/main.c b/test/core/src/main.c index 956cb0439a..46d9a5b7d6 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -2164,6 +2164,7 @@ void Commands_defer_emplace_after_remove(void); void Commands_batched_w_table_change_in_observer(void); void Commands_redefine_named_in_threaded_app(void); void Commands_batched_cmd_w_component_init(void); +void Commands_deep_command_nesting(void); // Testsuite 'SingleThreadStaging' void SingleThreadStaging_setup(void); @@ -10716,6 +10717,10 @@ bake_test_case Commands_testcases[] = { { "batched_cmd_w_component_init", Commands_batched_cmd_w_component_init + }, + { + "deep_command_nesting", + Commands_deep_command_nesting } }; @@ -11600,7 +11605,7 @@ static bake_test_suite suites[] = { "Commands", NULL, NULL, - 142, + 143, Commands_testcases }, { From a541def0a42cb9946c8bbdcc9a9be7a1d67907c7 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 20:45:30 -0800 Subject: [PATCH 47/83] Fix issues with parsing initializers --- distr/flecs.c | 43 ++-- src/addons/script/expr/parser.c | 43 ++-- test/script/project.json | 13 ++ test/script/src/Deserialize.c | 110 +++++++++ test/script/src/Eval.c | 382 ++++++++++++++++++++++++++++++++ test/script/src/Expr.c | 50 +++++ test/script/src/main.c | 71 +++++- 7 files changed, 679 insertions(+), 33 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 7d4039fa07..663627cada 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -74229,20 +74229,21 @@ const char* flecs_script_parse_initializer( do { ParserBegin; - if (first) { - /* End of initializer */ - LookAhead( - case ')': - case '}': { - if ((char)lookahead_token.kind != until) { - Error("expected '%c'", until); - } + /* End of initializer */ + LookAhead( + case ')': + case '}': { + if ((char)lookahead_token.kind != until) { + Error("expected '%c'", until); + } + if (first) { node->node.kind = EcsExprEmptyInitializer; - EndOfRule; - }) + } + pos = lookahead - 1; + EndOfRule; + }) - first = false; - } + first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); @@ -74270,9 +74271,13 @@ const char* flecs_script_parse_initializer( pos = lookahead; break; } - case '\n': + case ')': - case '}': { + case '}': + /* Return last character of initializer */ + pos = lookahead - 1; + + case '\n': { if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } @@ -74476,6 +74481,8 @@ const char* flecs_script_parse_lhs( { TokenFramePush(); + bool can_have_rhs = true; + Parse( case EcsTokNumber: { const char *expr = Token(0); @@ -74564,6 +74571,8 @@ const char* flecs_script_parse_lhs( break; }) + can_have_rhs = false; + *out = (ecs_expr_node_t*)node; break; } @@ -74582,6 +74591,8 @@ const char* flecs_script_parse_lhs( break; }) + can_have_rhs = false; + *out = (ecs_expr_node_t*)node; break; } @@ -74589,7 +74600,9 @@ const char* flecs_script_parse_lhs( TokenFramePop(); - if (!pos[0]) { + /* Return if this was end of expression, or if the parsed expression cannot + * have a right hand side. */ + if (!pos[0] || !can_have_rhs) { return pos; } diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index ab7f1172c6..6e2023e7f5 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -86,20 +86,21 @@ const char* flecs_script_parse_initializer( do { ParserBegin; - if (first) { - /* End of initializer */ - LookAhead( - case ')': - case '}': { - if ((char)lookahead_token.kind != until) { - Error("expected '%c'", until); - } + /* End of initializer */ + LookAhead( + case ')': + case '}': { + if ((char)lookahead_token.kind != until) { + Error("expected '%c'", until); + } + if (first) { node->node.kind = EcsExprEmptyInitializer; - EndOfRule; - }) + } + pos = lookahead - 1; + EndOfRule; + }) - first = false; - } + first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); @@ -127,9 +128,13 @@ const char* flecs_script_parse_initializer( pos = lookahead; break; } - case '\n': + case ')': - case '}': { + case '}': + /* Return last character of initializer */ + pos = lookahead - 1; + + case '\n': { if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } @@ -333,6 +338,8 @@ const char* flecs_script_parse_lhs( { TokenFramePush(); + bool can_have_rhs = true; + Parse( case EcsTokNumber: { const char *expr = Token(0); @@ -421,6 +428,8 @@ const char* flecs_script_parse_lhs( break; }) + can_have_rhs = false; + *out = (ecs_expr_node_t*)node; break; } @@ -439,6 +448,8 @@ const char* flecs_script_parse_lhs( break; }) + can_have_rhs = false; + *out = (ecs_expr_node_t*)node; break; } @@ -446,7 +457,9 @@ const char* flecs_script_parse_lhs( TokenFramePop(); - if (!pos[0]) { + /* Return if this was end of expression, or if the parsed expression cannot + * have a right hand side. */ + if (!pos[0] || !can_have_rhs) { return pos; } diff --git a/test/script/project.json b/test/script/project.json index 8defc6429d..0500317d9b 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -139,6 +139,14 @@ "assign_pair_component_in_scope", "assign_pair_component_in_script", "assign_pair_component_in_script_update", + "assign_pair_component_w_newline", + "assign_pair_component_w_newline_and_spaces", + "assign_pair_component_w_empty", + "assign_pair_component_w_newline_empty", + "assign_pair_component_w_newline_and_spaces_empty", + "assign_pair_component_after_component", + "assign_pair_component_after_int_component", + "assign_pair_component_after_entity_component", "set_entity_names", "oneof", "brief_annotation", @@ -571,6 +579,8 @@ "remainder_after_parens", "remainder_after_initializer", "remainder_after_collection_initializer", + "remainder_after_initializer_w_newlines", + "remainder_after_initializer_before_parens", "space_at_start", "newline_at_start" ] @@ -728,6 +738,9 @@ "struct_w_array_type_struct", "struct_w_2_array_type_i32_i32", "struct_w_2_array_type_struct", + "struct_w_newline", + "struct_w_members_newline", + "struct_w_trailing_comma", "array_i32_2", "array_string_2", "discover_type_int", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 379d1d67ad..ef4f037593 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -2098,6 +2098,116 @@ void Deserialize_struct_w_2_array_type_struct(void) { ecs_fini(world); } +void Deserialize_struct_w_newline(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Point value = {0}; + + { + const char *ptr = ecs_expr_run(world, + "{\n" + " 10, 20\n" + "}", + &(ecs_value_t){ecs_id(Point), &value}, NULL); + + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_fini(world); +} + +void Deserialize_struct_w_members_newline(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Point value = {0}; + + { + const char *ptr = ecs_expr_run(world, + "{\n" + " x: 10,\n" + " y: 20\n" + "}", + &(ecs_value_t){ecs_id(Point), &value}, NULL); + + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_fini(world); +} + +void Deserialize_struct_w_trailing_comma(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + Point value = {0}; + + { + const char *ptr = ecs_expr_run(world, + "{\n" + " x: 10,\n" + " y: 20,\n" + "}", + &(ecs_value_t){ecs_id(Point), &value}, NULL); + + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value.x, 10); + test_int(value.y, 20); + } + + ecs_fini(world); +} + void Deserialize_array_i32_2(void) { typedef int32_t Ints[2]; diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 75d4992f84..484f6721ee 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -3796,6 +3796,388 @@ void Eval_assign_pair_component_in_script_update(void) { ecs_fini(world); } +void Eval_assign_pair_component_w_newline(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " (Position, Foo): {\nx: 10,\n y: 20\n}" + LINE " (Position, Bar): {\nx: 20,\n y: 30\n}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has_pair(world, parent, ecs_id(Position), foo)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get_pair(world, parent, Position, foo); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + + ecs_fini(world); +} + +void Eval_assign_pair_component_w_newline_and_spaces(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " (Position, Foo): {\nx: 10, \n y: 20\n }" + LINE " (Position, Bar): { \nx: 20,\n y: 30 \n}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has_pair(world, parent, ecs_id(Position), foo)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get_pair(world, parent, Position, foo); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + + ecs_fini(world); +} + +void Eval_assign_pair_component_w_empty(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " (Position, Foo): {}" + LINE " (Position, Bar): {}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has_pair(world, parent, ecs_id(Position), foo)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get_pair(world, parent, Position, foo); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ecs_fini(world); +} + +void Eval_assign_pair_component_w_newline_empty(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " (Position, Foo): {\n}" + LINE " (Position, Bar): {\n}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has_pair(world, parent, ecs_id(Position), foo)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get_pair(world, parent, Position, foo); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ecs_fini(world); +} + +void Eval_assign_pair_component_w_newline_and_spaces_empty(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " (Position, Foo): { \n}" + LINE " (Position, Bar): {\n }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has_pair(world, parent, ecs_id(Position), foo)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get_pair(world, parent, Position, foo); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 0); + test_int(ptr->y, 0); + + ecs_fini(world); +} + +void Eval_assign_pair_component_after_component(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " Position: {10, 20}" + LINE " (Position, Bar): {x: 20, y: 30}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has(world, parent, Position)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + const Position * + ptr = ecs_get(world, parent, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + + ecs_fini(world); +} + +void Eval_assign_pair_component_after_int_component(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " i32: {10}" + LINE " (Position, Bar): {x: 20, y: 30}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has(world, parent, ecs_i32_t)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + { + const int32_t *ptr = ecs_get(world, parent, ecs_i32_t); + test_assert(ptr != NULL); + test_int(*ptr, 10); + } + + { + const Position *ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + } + + ecs_fini(world); +} + +void Eval_assign_pair_component_after_entity_component(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + const char *expr = + HEAD "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE + LINE "Parent {" + LINE " entity: {flecs.core}" + LINE " (Position, Bar): {x: 20, y: 30}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t parent = ecs_lookup(world, "Parent"); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + + test_assert(parent != 0); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_id(Position) != 0); + + test_assert( ecs_has(world, parent, ecs_entity_t)); + test_assert( ecs_has_pair(world, parent, ecs_id(Position), bar)); + + { + const ecs_entity_t *ptr = ecs_get(world, parent, ecs_entity_t); + test_assert(ptr != NULL); + test_int(*ptr, EcsFlecsCore); + } + + { + const Position *ptr = ecs_get_pair(world, parent, Position, bar); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + } + + ecs_fini(world); +} + void Eval_set_entity_names(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 7b974e571d..932fdf1d45 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -4885,6 +4885,56 @@ void Expr_remainder_after_collection_initializer(void) { ecs_fini(world); } +void Expr_remainder_after_initializer_w_newlines(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + { "x", ecs_id(ecs_f32_t) }, + { "y", ecs_id(ecs_f32_t) } + } + }); + + Position v = {0, 0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{\n10,\n 20\n} foo", + &ecs_value_ptr(Position, &v), &desc); + test_assert(ptr != NULL); + test_str(ptr, " foo"); + test_int(v.x, 10); + test_int(v.y, 20); + + ecs_fini(world); +} + +void Expr_remainder_after_initializer_before_parens(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + { "x", ecs_id(ecs_f32_t) }, + { "y", ecs_id(ecs_f32_t) } + } + }); + + Position v = {0, 0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "{10, 20} ()", + &ecs_value_ptr(Position, &v), &desc); + test_assert(ptr != NULL); + test_str(ptr, " ()"); + test_int(v.x, 10); + test_int(v.y, 20); + + ecs_fini(world); +} + void Expr_space_at_start(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 7d1658bf95..7fa1237380 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -135,6 +135,14 @@ void Eval_assign_pair_component(void); void Eval_assign_pair_component_in_scope(void); void Eval_assign_pair_component_in_script(void); void Eval_assign_pair_component_in_script_update(void); +void Eval_assign_pair_component_w_newline(void); +void Eval_assign_pair_component_w_newline_and_spaces(void); +void Eval_assign_pair_component_w_empty(void); +void Eval_assign_pair_component_w_newline_empty(void); +void Eval_assign_pair_component_w_newline_and_spaces_empty(void); +void Eval_assign_pair_component_after_component(void); +void Eval_assign_pair_component_after_int_component(void); +void Eval_assign_pair_component_after_entity_component(void); void Eval_set_entity_names(void); void Eval_oneof(void); void Eval_brief_annotation(void); @@ -558,6 +566,8 @@ void Expr_remainder_after_binary(void); void Expr_remainder_after_parens(void); void Expr_remainder_after_initializer(void); void Expr_remainder_after_collection_initializer(void); +void Expr_remainder_after_initializer_w_newlines(void); +void Expr_remainder_after_initializer_before_parens(void); void Expr_space_at_start(void); void Expr_newline_at_start(void); @@ -706,6 +716,9 @@ void Deserialize_struct_w_array_type_i32_i32(void); void Deserialize_struct_w_array_type_struct(void); void Deserialize_struct_w_2_array_type_i32_i32(void); void Deserialize_struct_w_2_array_type_struct(void); +void Deserialize_struct_w_newline(void); +void Deserialize_struct_w_members_newline(void); +void Deserialize_struct_w_trailing_comma(void); void Deserialize_array_i32_2(void); void Deserialize_array_string_2(void); void Deserialize_discover_type_int(void); @@ -1228,6 +1241,38 @@ bake_test_case Eval_testcases[] = { "assign_pair_component_in_script_update", Eval_assign_pair_component_in_script_update }, + { + "assign_pair_component_w_newline", + Eval_assign_pair_component_w_newline + }, + { + "assign_pair_component_w_newline_and_spaces", + Eval_assign_pair_component_w_newline_and_spaces + }, + { + "assign_pair_component_w_empty", + Eval_assign_pair_component_w_empty + }, + { + "assign_pair_component_w_newline_empty", + Eval_assign_pair_component_w_newline_empty + }, + { + "assign_pair_component_w_newline_and_spaces_empty", + Eval_assign_pair_component_w_newline_and_spaces_empty + }, + { + "assign_pair_component_after_component", + Eval_assign_pair_component_after_component + }, + { + "assign_pair_component_after_int_component", + Eval_assign_pair_component_after_int_component + }, + { + "assign_pair_component_after_entity_component", + Eval_assign_pair_component_after_entity_component + }, { "set_entity_names", Eval_set_entity_names @@ -2901,6 +2946,14 @@ bake_test_case Expr_testcases[] = { "remainder_after_collection_initializer", Expr_remainder_after_collection_initializer }, + { + "remainder_after_initializer_w_newlines", + Expr_remainder_after_initializer_w_newlines + }, + { + "remainder_after_initializer_before_parens", + Expr_remainder_after_initializer_before_parens + }, { "space_at_start", Expr_space_at_start @@ -3474,6 +3527,18 @@ bake_test_case Deserialize_testcases[] = { "struct_w_2_array_type_struct", Deserialize_struct_w_2_array_type_struct }, + { + "struct_w_newline", + Deserialize_struct_w_newline + }, + { + "struct_w_members_newline", + Deserialize_struct_w_members_newline + }, + { + "struct_w_trailing_comma", + Deserialize_struct_w_trailing_comma + }, { "array_i32_2", Deserialize_array_i32_2 @@ -3554,7 +3619,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 257, + 265, Eval_testcases }, { @@ -3575,7 +3640,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 185, + 187, Expr_testcases, 1, Expr_params @@ -3598,7 +3663,7 @@ static bake_test_suite suites[] = { "Deserialize", Deserialize_setup, NULL, - 86, + 89, Deserialize_testcases, 1, Deserialize_params From 12860bfe8ce1a63e8e3eba8758798d8dc3c501da Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 9 Dec 2024 05:55:23 +0000 Subject: [PATCH 48/83] Fix issue where value of component defined in script was freed after component was deleted --- distr/flecs.c | 3 ++ src/addons/script/script.c | 3 ++ test/script/project.json | 4 +- test/script/src/Error.c | 83 ++++++++++++++++++++++++++++++++++++++ test/script/src/main.c | 12 +++++- 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 663627cada..e16314453a 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -57789,6 +57789,9 @@ int ecs_script_update( ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); if (ecs_script_eval(s->script, NULL)) { + ecs_script_free(s->script); + s->script = NULL; + ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index a1163c5ca8..2066232425 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -173,6 +173,9 @@ int ecs_script_update( ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); if (ecs_script_eval(s->script, NULL)) { + ecs_script_free(s->script); + s->script = NULL; + ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } diff --git a/test/script/project.json b/test/script/project.json index 0500317d9b..0e0a6cf203 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -387,7 +387,9 @@ "update_template_after_error", "template_in_template", "unterminated_binary", - "component_in_with_scope" + "component_in_with_scope", + "reload_script_w_component_w_error", + "reload_script_w_component_w_error_again" ] }, { "id": "Expr", diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 39734d1954..31bdabb3aa 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1239,3 +1239,86 @@ void Error_unterminated_binary(void) { ecs_fini(world); } + +void Error_reload_script_w_component_w_error(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t s = ecs_script(world, { + .code = + "struct Position {\n" + " x = f32\n" + " y = f32\n" + "}\n" + "\n" + "e {\n" + " Position: {10, 20}\n" + "}\n" + }); + + test_assert(s != 0); + + ecs_log_set_level(-4); + + test_assert(0 != ecs_script_update(world, s, 0, + "struct Position {\n" + " x = f32\n" + " y = f32\n" + "}\n" + "\n" + "e {\n" + " Position: {10, 20}\n" + "}\n" + "\n" + "f\n" + )); + + ecs_fini(world); +} + +void Error_reload_script_w_component_w_error_again(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t s = ecs_script(world, { + .code = + "struct Position {\n" + " x = f32\n" + " y = f32\n" + "}\n" + "\n" + "e {\n" + " Position: {10, 20}\n" + "}\n" + }); + + test_assert(s != 0); + + ecs_log_set_level(-4); + + test_assert(0 != ecs_script_update(world, s, 0, + "struct Position {\n" + " x = f32\n" + " y = f32\n" + "}\n" + "\n" + "e {\n" + " Position: {10, 20}\n" + "}\n" + "\n" + "f\n" + )); + + ecs_log_set_level(-1); + + test_assert(0 == ecs_script_update(world, s, 0, + "struct Position {\n" + " x = f32\n" + " y = f32\n" + "}\n" + "\n" + "e {\n" + " Position: {10, 20}\n" + "}\n" + )); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 7fa1237380..4d2792fdee 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -380,6 +380,8 @@ void Error_update_template_after_error(void); void Error_template_in_template(void); void Error_unterminated_binary(void); void Error_component_in_with_scope(void); +void Error_reload_script_w_component_w_error(void); +void Error_reload_script_w_component_w_error_again(void); // Testsuite 'Expr' void Expr_setup(void); @@ -2210,6 +2212,14 @@ bake_test_case Error_testcases[] = { { "component_in_with_scope", Error_component_in_with_scope + }, + { + "reload_script_w_component_w_error", + Error_reload_script_w_component_w_error + }, + { + "reload_script_w_component_w_error_again", + Error_reload_script_w_component_w_error_again } }; @@ -3633,7 +3643,7 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 65, + 67, Error_testcases }, { From bf5183d1783bce0b905cfca019d1de2407b527cd Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 23:32:23 -0800 Subject: [PATCH 49/83] Fix issue where suspending/resuming readonly mode could corrupt stack allocator --- distr/flecs.c | 33 ++++++++----------- distr/flecs.h | 2 +- examples/c/script/script_managed/src/main.c | 4 +-- examples/c/script/script_run/src/main.c | 4 +-- .../flecs/datastructures/stack_allocator.h | 2 +- src/datastructures/stack_allocator.c | 33 ++++++++----------- test/core/src/Commands.c | 2 +- test/core/src/StackAlloc.c | 2 +- 8 files changed, 34 insertions(+), 48 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index e16314453a..407c0313b6 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -31594,6 +31594,7 @@ ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; + result->sp = 0; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } @@ -31606,10 +31607,7 @@ void* flecs_stack_alloc( ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; - if (page == &stack->first && !page->data) { - page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); - ecs_os_linc(&ecs_stack_allocator_alloc_count); - } + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); @@ -31635,6 +31633,7 @@ void* flecs_stack_alloc( result = ECS_OFFSET(page->data, sp); done: + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif @@ -31728,7 +31727,7 @@ void flecs_stack_restore_cursor( /* If the cursor count is zero, stack should be empty * if the cursor count is non-zero, stack should not be empty */ ecs_dbg_assert((stack->cursor_count == 0) == - (stack->tail_page == &stack->first && stack->tail_page->sp == 0), + (stack->tail_page == stack->first && stack->tail_page->sp == 0), ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); } @@ -31737,8 +31736,8 @@ void flecs_stack_reset( { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); - stack->tail_page = &stack->first; - stack->first.sp = 0; + stack->tail_page = stack->first; + stack->first->sp = 0; stack->tail_cursor = NULL; } @@ -31746,32 +31745,26 @@ void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); - stack->tail_page = &stack->first; - stack->first.data = NULL; + stack->first = flecs_stack_page_new(0); + stack->first->sp = 0; + stack->tail_page = stack->first; } void flecs_stack_fini( ecs_stack_t *stack) { - ecs_stack_page_t *next, *cur = &stack->first; + ecs_stack_page_t *next, *cur = stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); - ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, + ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); do { next = cur->next; - if (cur == &stack->first) { - if (cur->data) { - ecs_os_linc(&ecs_stack_allocator_free_count); - } - ecs_os_free(cur->data); - } else { - ecs_os_linc(&ecs_stack_allocator_free_count); - ecs_os_free(cur); - } + ecs_os_linc(&ecs_stack_allocator_free_count); + ecs_os_free(cur); } while ((cur = next)); } diff --git a/distr/flecs.h b/distr/flecs.h index 16b99070d3..93cffa561b 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -1658,7 +1658,7 @@ typedef struct ecs_stack_cursor_t { } ecs_stack_cursor_t; typedef struct ecs_stack_t { - ecs_stack_page_t first; + ecs_stack_page_t *first; ecs_stack_page_t *tail_page; ecs_stack_cursor_t *tail_cursor; #ifdef FLECS_DEBUG diff --git a/examples/c/script/script_managed/src/main.c b/examples/c/script/script_managed/src/main.c index 6a120ed5a4..83c565c2ea 100644 --- a/examples/c/script/script_managed/src/main.c +++ b/examples/c/script/script_managed/src/main.c @@ -10,8 +10,8 @@ */ void print_planets(ecs_world_t *world) { - ecs_entity_t earth = ecs_lookup(world, "Sun"); - ecs_iter_t it = ecs_children(world, earth); + ecs_entity_t sun = ecs_lookup(world, "Sun"); + ecs_iter_t it = ecs_children(world, sun); while (ecs_children_next(&it)) { for (int i = 0; i < it.count; i ++) { printf(" - %s\n", ecs_get_name(world, it.entities[i])); diff --git a/examples/c/script/script_run/src/main.c b/examples/c/script/script_run/src/main.c index 72afca9789..0fdf07e04f 100644 --- a/examples/c/script/script_run/src/main.c +++ b/examples/c/script/script_run/src/main.c @@ -23,8 +23,8 @@ int main(int argc, char *argv[]) { printf("script failed to run\n"); } - ecs_entity_t earth = ecs_lookup(world, "Sun"); - ecs_iter_t it = ecs_children(world, earth); + ecs_entity_t sun = ecs_lookup(world, "Sun"); + ecs_iter_t it = ecs_children(world, sun); while (ecs_children_next(&it)) { for (int i = 0; i < it.count; i ++) { printf(" - %s\n", ecs_get_name(world, it.entities[i])); diff --git a/include/flecs/datastructures/stack_allocator.h b/include/flecs/datastructures/stack_allocator.h index 9e2e5cd001..31b428d0af 100644 --- a/include/flecs/datastructures/stack_allocator.h +++ b/include/flecs/datastructures/stack_allocator.h @@ -27,7 +27,7 @@ typedef struct ecs_stack_cursor_t { } ecs_stack_cursor_t; typedef struct ecs_stack_t { - ecs_stack_page_t first; + ecs_stack_page_t *first; ecs_stack_page_t *tail_page; ecs_stack_cursor_t *tail_cursor; #ifdef FLECS_DEBUG diff --git a/src/datastructures/stack_allocator.c b/src/datastructures/stack_allocator.c index e41b164575..11a112e16b 100644 --- a/src/datastructures/stack_allocator.c +++ b/src/datastructures/stack_allocator.c @@ -24,6 +24,7 @@ ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; + result->sp = 0; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } @@ -36,10 +37,7 @@ void* flecs_stack_alloc( ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; - if (page == &stack->first && !page->data) { - page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); - ecs_os_linc(&ecs_stack_allocator_alloc_count); - } + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); @@ -65,6 +63,7 @@ void* flecs_stack_alloc( result = ECS_OFFSET(page->data, sp); done: + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif @@ -158,7 +157,7 @@ void flecs_stack_restore_cursor( /* If the cursor count is zero, stack should be empty * if the cursor count is non-zero, stack should not be empty */ ecs_dbg_assert((stack->cursor_count == 0) == - (stack->tail_page == &stack->first && stack->tail_page->sp == 0), + (stack->tail_page == stack->first && stack->tail_page->sp == 0), ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); } @@ -167,8 +166,8 @@ void flecs_stack_reset( { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); - stack->tail_page = &stack->first; - stack->first.sp = 0; + stack->tail_page = stack->first; + stack->first->sp = 0; stack->tail_cursor = NULL; } @@ -176,31 +175,25 @@ void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); - stack->tail_page = &stack->first; - stack->first.data = NULL; + stack->first = flecs_stack_page_new(0); + stack->first->sp = 0; + stack->tail_page = stack->first; } void flecs_stack_fini( ecs_stack_t *stack) { - ecs_stack_page_t *next, *cur = &stack->first; + ecs_stack_page_t *next, *cur = stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); - ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, + ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); do { next = cur->next; - if (cur == &stack->first) { - if (cur->data) { - ecs_os_linc(&ecs_stack_allocator_free_count); - } - ecs_os_free(cur->data); - } else { - ecs_os_linc(&ecs_stack_allocator_free_count); - ecs_os_free(cur); - } + ecs_os_linc(&ecs_stack_allocator_free_count); + ecs_os_free(cur); } while ((cur = next)); } diff --git a/test/core/src/Commands.c b/test/core/src/Commands.c index 89285b993d..b3e7bb0f2b 100644 --- a/test/core/src/Commands.c +++ b/test/core/src/Commands.c @@ -4275,7 +4275,7 @@ void Commands_deep_command_nesting(void) { ECS_COMPONENT_DEFINE(world, TestNestEvent); ecs_observer(world, { - .events = {{ ecs_id(TestNestEvent) }}, + .events = { ecs_id(TestNestEvent) }, .query = { .terms = {{ .id = EcsAny }}}, .callback = test_nest_observer }); diff --git a/test/core/src/StackAlloc.c b/test/core/src/StackAlloc.c index a7e342c434..2a7c864659 100644 --- a/test/core/src/StackAlloc.c +++ b/test/core/src/StackAlloc.c @@ -8,7 +8,7 @@ void StackAlloc_init_fini(void) { flecs_stack_init(&stack); test_int(stack.tail_page->sp, 0); - test_assert(stack.tail_page == &stack.first); + test_assert(stack.tail_page == stack.first); flecs_stack_fini(&stack); ecs_fini(world); From cdf08dffca29ba4f7a2196a924456cfea8e21fa1 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 8 Dec 2024 23:54:05 -0800 Subject: [PATCH 50/83] Allow for trailing commas in collection initializers --- distr/flecs.c | 16 +++---- src/addons/script/expr/parser.c | 16 +++---- test/script/project.json | 5 +++ test/script/src/Deserialize.c | 32 +++++++++++++ test/script/src/Error.c | 79 ++++++++++++++++++++++++++++++++- test/script/src/main.c | 29 +++++++++++- 6 files changed, 158 insertions(+), 19 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 407c0313b6..c0cf9af929 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -74300,16 +74300,16 @@ const char* flecs_script_parse_collection_initializer( do { ParserBegin; - if (first) { - /* End of initializer */ - LookAhead_1(']', { - pos = lookahead; + /* End of initializer */ + LookAhead_1(']', { + if (first) { node->node.kind = EcsExprEmptyInitializer; - EndOfRule; - }) + } + pos = lookahead - 1; + EndOfRule; + }) - first = false; - } + first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 6e2023e7f5..efa3fb988b 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -161,16 +161,16 @@ const char* flecs_script_parse_collection_initializer( do { ParserBegin; - if (first) { - /* End of initializer */ - LookAhead_1(']', { - pos = lookahead; + /* End of initializer */ + LookAhead_1(']', { + if (first) { node->node.kind = EcsExprEmptyInitializer; - EndOfRule; - }) + } + pos = lookahead - 1; + EndOfRule; + }) - first = false; - } + first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); diff --git a/test/script/project.json b/test/script/project.json index 0e0a6cf203..f6f4461591 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -387,7 +387,11 @@ "update_template_after_error", "template_in_template", "unterminated_binary", + "tag_in_with_scope", "component_in_with_scope", + "component_in_with_scope_nested", + "component_in_with_scope_after_entity", + "component_in_with_var_scope", "reload_script_w_component_w_error", "reload_script_w_component_w_error_again" ] @@ -743,6 +747,7 @@ "struct_w_newline", "struct_w_members_newline", "struct_w_trailing_comma", + "array_w_trailing_comma", "array_i32_2", "array_string_2", "discover_type_int", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index ef4f037593..28b7ab6da4 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -2208,6 +2208,38 @@ void Deserialize_struct_w_trailing_comma(void) { ecs_fini(world); } +void Deserialize_array_w_trailing_comma(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ECS_COMPONENT(world, Ints); + + ecs_array(world, { + .entity = ecs_id(Ints), + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + Ints value = {0}; + + { + const char *ptr = ecs_expr_run(world, + "[\n" + " 10,\n" + " 20,\n" + "]", + &(ecs_value_t){ecs_id(Ints), &value}, NULL); + + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + test_int(value[0], 10); + test_int(value[1], 20); + } + + ecs_fini(world); +} + void Deserialize_array_i32_2(void) { typedef int32_t Ints[2]; diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 31bdabb3aa..14f29735da 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -350,7 +350,7 @@ void Error_with_value_not_a_component(void) { ecs_fini(world); } -void Error_component_in_with_scope(void) { +void Error_tag_in_with_scope(void) { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); @@ -375,6 +375,31 @@ void Error_component_in_with_scope(void) { ecs_fini(world); } +void Error_component_in_with_scope(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "with Position(10, 20) {\n" + LINE " Position: {10, 20}\n" + LINE "}\n" + LINE "\n"; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + void Error_component_in_with_scope_nested(void) { ecs_world_t *world = ecs_init(); @@ -411,6 +436,58 @@ void Error_component_in_with_scope_nested(void) { ecs_fini(world); } +void Error_component_in_with_scope_after_entity(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "with Position(10, 20) {" + LINE " _ {}" + LINE " Position: {10, 20}" + LINE "}" + LINE ""; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_component_in_with_var_scope(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "const pos = Position: {10, 20}" + LINE "with $pos {" + LINE " Position: {10, 20}" + LINE "}" + LINE ""; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + void Error_assign_after_with_in_scope(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 4d2792fdee..8d000b9a18 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -379,7 +379,11 @@ void Error_run_template_after_error(void); void Error_update_template_after_error(void); void Error_template_in_template(void); void Error_unterminated_binary(void); +void Error_tag_in_with_scope(void); void Error_component_in_with_scope(void); +void Error_component_in_with_scope_nested(void); +void Error_component_in_with_scope_after_entity(void); +void Error_component_in_with_var_scope(void); void Error_reload_script_w_component_w_error(void); void Error_reload_script_w_component_w_error_again(void); @@ -721,6 +725,7 @@ void Deserialize_struct_w_2_array_type_struct(void); void Deserialize_struct_w_newline(void); void Deserialize_struct_w_members_newline(void); void Deserialize_struct_w_trailing_comma(void); +void Deserialize_array_w_trailing_comma(void); void Deserialize_array_i32_2(void); void Deserialize_array_string_2(void); void Deserialize_discover_type_int(void); @@ -2209,10 +2214,26 @@ bake_test_case Error_testcases[] = { "unterminated_binary", Error_unterminated_binary }, + { + "tag_in_with_scope", + Error_tag_in_with_scope + }, { "component_in_with_scope", Error_component_in_with_scope }, + { + "component_in_with_scope_nested", + Error_component_in_with_scope_nested + }, + { + "component_in_with_scope_after_entity", + Error_component_in_with_scope_after_entity + }, + { + "component_in_with_var_scope", + Error_component_in_with_var_scope + }, { "reload_script_w_component_w_error", Error_reload_script_w_component_w_error @@ -3549,6 +3570,10 @@ bake_test_case Deserialize_testcases[] = { "struct_w_trailing_comma", Deserialize_struct_w_trailing_comma }, + { + "array_w_trailing_comma", + Deserialize_array_w_trailing_comma + }, { "array_i32_2", Deserialize_array_i32_2 @@ -3643,7 +3668,7 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 67, + 71, Error_testcases }, { @@ -3673,7 +3698,7 @@ static bake_test_suite suites[] = { "Deserialize", Deserialize_setup, NULL, - 89, + 90, Deserialize_testcases, 1, Deserialize_params From b4bcd63d71d2674d138827c648307d66733dd633 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 9 Dec 2024 10:32:33 -0800 Subject: [PATCH 51/83] Fix issues with template variable hosting --- src/addons/script/template.c | 15 +++-- test/script/project.json | 5 +- test/script/src/Template.c | 104 +++++++++++++++++++++++++++++++++++ test/script/src/main.c | 17 +++++- 4 files changed, 134 insertions(+), 7 deletions(-) diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 94ed51015c..c2325fea8b 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -391,20 +391,25 @@ int flecs_script_template_hoist_vars( ecs_script_template_t *template, ecs_script_vars_t *vars) { - if (vars->parent) { - flecs_script_template_hoist_vars(v, template, vars); - } - int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src = &src_vars[i]; + if (ecs_script_vars_lookup(template->vars, src->name)) { + /* If variable is masked, don't declare it twice */ + continue; + } ecs_script_var_t *dst = ecs_script_vars_define_id( template->vars, src->name, src->value.type); - ecs_value_copy(v->world, + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); } + if (vars->parent) { + flecs_script_template_hoist_vars(v, template, vars->parent); + } + return 0; } diff --git a/test/script/project.json b/test/script/project.json index f6f4461591..7e6cb0e24c 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -312,13 +312,16 @@ "template_w_pair_w_this_var", "prop_without_using_meta", "hoist_var", + "hoist_vars_nested", + "hoist_vars_nested_w_masked", "anonymous_template_instance", "anonymous_template_instance_no_scope", "anonymous_template_instance_w_prop", "anonymous_template_instance_w_prop_no_scope", "with_after_template", "with_in_scope_after_template", - "prefab_w_template" + "prefab_w_template", + "template_in_scope" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 9e0759ea71..ef6d0ecb39 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -1726,6 +1726,85 @@ void Template_hoist_var(void) { ecs_fini(world); } +void Template_hoist_vars_nested(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "const x = 10" + LINE "parent {" + LINE " const y = 20" + LINE " template Tree {" + LINE " Position: {$x, $y}" + LINE " }" + LINE "}" + LINE "parent.Tree foo()"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t tree = ecs_lookup(world, "parent.Tree"); + test_assert(tree != 0); + + ecs_entity_t foo = ecs_lookup(world, "foo"); + test_assert(foo != 0); + + const Position *p = ecs_get(world, foo, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Template_hoist_vars_nested_w_masked(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "const x = 10" + LINE "parent {" + HEAD " const x = 30" + LINE " const y = 20" + LINE " template Tree {" + LINE " Position: {$x, $y}" + LINE " }" + LINE "}" + LINE "parent.Tree foo()"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t tree = ecs_lookup(world, "parent.Tree"); + test_assert(tree != 0); + + ecs_entity_t foo = ecs_lookup(world, "foo"); + test_assert(foo != 0); + + const Position *p = ecs_get(world, foo, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 20); + + ecs_fini(world); +} + void Template_anonymous_template_instance(void) { ecs_world_t *world = ecs_init(); @@ -1972,3 +2051,28 @@ void Template_prefab_w_template(void) { ecs_fini(world); } + +void Template_template_in_scope(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + LINE "parent {" + LINE " template Foo {" + LINE " }" + LINE "}" + LINE "parent.Foo ent"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + test_assert(ecs_lookup(world, "Foo") == 0); + ecs_entity_t foo = ecs_lookup(world, "parent.Foo"); + test_assert(foo != 0); + + ecs_entity_t ent = ecs_lookup(world, "ent"); + test_assert(ent != 0); + test_assert(ecs_has_id(world, ent, foo)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 8d000b9a18..33fe25ae26 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -306,6 +306,8 @@ void Template_module_w_nested_template(void); void Template_template_w_pair_w_this_var(void); void Template_prop_without_using_meta(void); void Template_hoist_var(void); +void Template_hoist_vars_nested(void); +void Template_hoist_vars_nested_w_masked(void); void Template_anonymous_template_instance(void); void Template_anonymous_template_instance_no_scope(void); void Template_anonymous_template_instance_w_prop(void); @@ -313,6 +315,7 @@ void Template_anonymous_template_instance_w_prop_no_scope(void); void Template_with_after_template(void); void Template_with_in_scope_after_template(void); void Template_prefab_w_template(void); +void Template_template_in_scope(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -1927,6 +1930,14 @@ bake_test_case Template_testcases[] = { "hoist_var", Template_hoist_var }, + { + "hoist_vars_nested", + Template_hoist_vars_nested + }, + { + "hoist_vars_nested_w_masked", + Template_hoist_vars_nested_w_masked + }, { "anonymous_template_instance", Template_anonymous_template_instance @@ -1954,6 +1965,10 @@ bake_test_case Template_testcases[] = { { "prefab_w_template", Template_prefab_w_template + }, + { + "template_in_scope", + Template_template_in_scope } }; @@ -3661,7 +3676,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 37, + 40, Template_testcases }, { From bd75cf64d0723f020a926ecb678d1fe1405ab92b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 9 Dec 2024 17:48:36 -0800 Subject: [PATCH 52/83] Implement detection of invalid components in with scope, allow for N template instances per entity --- distr/flecs.c | 110 +++++++++++++-- distr/flecs.h | 3 + include/flecs/addons/script.h | 3 + src/addons/script/script.c | 17 ++- src/addons/script/template.c | 23 +++- src/addons/script/visit_eval.c | 34 ++++- src/addons/script/visit_eval.h | 4 +- src/addons/script/visit_to_str.c | 15 +++ src/entity_name.c | 2 + test/script/project.json | 15 ++- test/script/src/Error.c | 224 +++++++++++++++++++++++++++++++ test/script/src/Eval.c | 78 ++++++----- test/script/src/Template.c | 105 +++++++++++++++ test/script/src/main.c | 59 +++++++- 14 files changed, 619 insertions(+), 73 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index c0cf9af929..b3034856aa 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5356,12 +5356,14 @@ typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; ecs_script_runtime_t *r; - ecs_script_template_t *template; + ecs_script_template_t *template; /* Set when creating template */ + ecs_entity_t template_entity; /* Set when creating template instance */ ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; ecs_entity_t with_relationship; int32_t with_relationship_sp; + bool is_with_scope; ecs_script_vars_t *vars; } ecs_script_eval_visitor_t; @@ -11293,6 +11295,8 @@ ecs_entity_t ecs_lookup_path_w_sep( } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!parent || ecs_is_valid(world, parent), + ECS_INVALID_PARAMETER, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); @@ -57676,7 +57680,21 @@ void ecs_script_clear( ecs_entity_t script, ecs_entity_t instance) { - ecs_delete_with(world, flecs_script_tag(script, instance)); + if (!instance) { + ecs_delete_with(world, ecs_pair_t(EcsScript, script)); + } else { + ecs_defer_begin(world); + ecs_iter_t it = ecs_children(world, instance); + while (ecs_children_next(&it)) { + if (ecs_table_has_id(world, it.table, ecs_pair(EcsTemplate, script))) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_delete(world, it.entities[i]); + } + } + } + ecs_defer_end(world); + } } int ecs_script_run( @@ -57784,7 +57802,6 @@ int ecs_script_update( if (ecs_script_eval(s->script, NULL)) { ecs_script_free(s->script); s->script = NULL; - ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } @@ -58414,6 +58431,7 @@ char* ecs_ptr_to_str( #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); +ECS_DECLARE(EcsTemplate); static ECS_MOVE(EcsTemplateSetEvent, dst, src, { @@ -58535,6 +58553,8 @@ void flecs_script_template_instantiate( ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; + v.template_entity = template_entity; + ecs_script_scope_t *scope = template->node->scope; /* Dummy entity node for instance */ @@ -58551,6 +58571,8 @@ void flecs_script_template_instantiate( int32_t i, m; for (i = 0; i < count; i ++) { v.parent = entities[i]; + ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); + instance_node.eval = entities[i]; /* Create variables to hold template properties */ @@ -58603,7 +58625,7 @@ void flecs_on_template_set_event( EcsTemplateSetEvent *evt = it->param; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); - + ecs_defer_suspend(world); flecs_script_template_instantiate( @@ -58714,10 +58736,16 @@ int flecs_script_template_eval( ecs_script_node_t *node) { switch(node->kind) { - case EcsAstScope: case EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + break; + case EcsAstEntity: + case EcsAstScope: case EcsAstDefaultComponent: case EcsAstWithVar: case EcsAstWithTag: @@ -58726,20 +58754,23 @@ int flecs_script_template_eval( case EcsAstModule: case EcsAstAnnotation: case EcsAstConst: - case EcsAstEntity: case EcsAstPairScope: case EcsAstTemplate: break; case EcsAstProp: return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); - case EcsAstWith: + case EcsAstWith: { if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { return -1; } + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { return -1; } + v->is_with_scope = old_is_with_scope; return 0; + } case EcsAstIf: if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_true)) { return -1; @@ -58796,20 +58827,25 @@ int flecs_script_template_hoist_vars( ecs_script_template_t *template, ecs_script_vars_t *vars) { - if (vars->parent) { - flecs_script_template_hoist_vars(v, template, vars); - } - int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src = &src_vars[i]; + if (ecs_script_vars_lookup(template->vars, src->name)) { + /* If variable is masked, don't declare it twice */ + continue; + } ecs_script_var_t *dst = ecs_script_vars_define_id( template->vars, src->name, src->value.type); - ecs_value_copy(v->world, + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); } + if (vars->parent) { + flecs_script_template_hoist_vars(v, template, vars->parent); + } + return 0; } @@ -58922,6 +58958,7 @@ void flecs_script_template_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsTemplate); ecs_set_hooks(world, EcsTemplateSetEvent, { .ctor = flecs_default_ctor, @@ -60396,15 +60433,22 @@ int flecs_script_eval_entity( } if (v->template) { + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = false; if (ecs_script_visit_node(v, node->scope)) { return -1; } + v->is_with_scope = old_is_with_scope; return 0; } node->eval = flecs_script_create_entity(v, node->name); node->parent = v->entity; + if (v->template_entity) { + ecs_add_pair(v->world, node->eval, EcsTemplate, v->template_entity); + } + if (is_slot) { ecs_entity_t parent = ecs_get_target( v->world, node->eval, EcsChildOf, 0); @@ -60447,9 +60491,16 @@ int flecs_script_eval_entity( ecs_vec_clear(&v->r->annot); } + + bool old_is_with_scope = v->is_with_scope; + ecs_entity_t old_template_entity = v->template_entity; + v->is_with_scope = false; + v->template_entity = 0; if (ecs_script_visit_node(v, node->scope)) { return -1; } + v->template_entity = old_template_entity; + v->is_with_scope = old_is_with_scope; if (node->eval_kind) { if (!node->kind_w_expr) { @@ -60493,6 +60544,11 @@ int flecs_script_eval_tag( return 0; } + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + if (!v->entity) { if (node->id.second) { flecs_script_eval_error( @@ -60540,8 +60596,9 @@ int flecs_script_eval_component( return -1; } - if (v->template) { - return 0; + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); @@ -60630,6 +60687,11 @@ int flecs_script_eval_var_component( return 0; } + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + ecs_id_t var_id = var->value.type; if (var->value.ptr) { @@ -60821,11 +60883,16 @@ int flecs_script_eval_with( } } + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; + if (ecs_script_visit_scope(v, node->scope)) { result = -1; goto error; } + v->is_with_scope = old_is_with_scope; + error: flecs_script_with_set_count(a, v, prev_with_count); flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); @@ -61786,6 +61853,21 @@ int ecs_script_ast_to_buf( return - 1; } +char* ecs_script_ast_node_to_str( + ecs_script_t *script, + ecs_script_node_t *node) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = &buf }; + + flecs_script_stmt_to_str(&v, node); + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + char* ecs_script_ast_to_str( ecs_script_t *script) { diff --git a/distr/flecs.h b/distr/flecs.h index 93cffa561b..aa49c6959c 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14310,6 +14310,9 @@ extern "C" { FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); +FLECS_API +extern ECS_DECLARE(EcsTemplate); + FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 6897dde31b..72259d4693 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -36,6 +36,9 @@ extern "C" { FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); +FLECS_API +extern ECS_DECLARE(EcsTemplate); + FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 2066232425..901661a4bc 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -67,7 +67,21 @@ void ecs_script_clear( ecs_entity_t script, ecs_entity_t instance) { - ecs_delete_with(world, flecs_script_tag(script, instance)); + if (!instance) { + ecs_delete_with(world, ecs_pair_t(EcsScript, script)); + } else { + ecs_defer_begin(world); + ecs_iter_t it = ecs_children(world, instance); + while (ecs_children_next(&it)) { + if (ecs_table_has_id(world, it.table, ecs_pair(EcsTemplate, script))) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_delete(world, it.entities[i]); + } + } + } + ecs_defer_end(world); + } } int ecs_script_run( @@ -175,7 +189,6 @@ int ecs_script_update( if (ecs_script_eval(s->script, NULL)) { ecs_script_free(s->script); s->script = NULL; - ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } diff --git a/src/addons/script/template.c b/src/addons/script/template.c index c2325fea8b..de53c060bc 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -9,6 +9,7 @@ #include "script.h" ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); +ECS_DECLARE(EcsTemplate); static ECS_MOVE(EcsTemplateSetEvent, dst, src, { @@ -130,6 +131,8 @@ void flecs_script_template_instantiate( ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; + v.template_entity = template_entity; + ecs_script_scope_t *scope = template->node->scope; /* Dummy entity node for instance */ @@ -146,6 +149,8 @@ void flecs_script_template_instantiate( int32_t i, m; for (i = 0; i < count; i ++) { v.parent = entities[i]; + ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); + instance_node.eval = entities[i]; /* Create variables to hold template properties */ @@ -198,7 +203,7 @@ void flecs_on_template_set_event( EcsTemplateSetEvent *evt = it->param; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); - + ecs_defer_suspend(world); flecs_script_template_instantiate( @@ -309,10 +314,16 @@ int flecs_script_template_eval( ecs_script_node_t *node) { switch(node->kind) { - case EcsAstScope: case EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + break; + case EcsAstEntity: + case EcsAstScope: case EcsAstDefaultComponent: case EcsAstWithVar: case EcsAstWithTag: @@ -321,20 +332,23 @@ int flecs_script_template_eval( case EcsAstModule: case EcsAstAnnotation: case EcsAstConst: - case EcsAstEntity: case EcsAstPairScope: case EcsAstTemplate: break; case EcsAstProp: return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); - case EcsAstWith: + case EcsAstWith: { if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { return -1; } + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { return -1; } + v->is_with_scope = old_is_with_scope; return 0; + } case EcsAstIf: if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_true)) { return -1; @@ -522,6 +536,7 @@ void flecs_script_template_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsTemplate); ecs_set_hooks(world, EcsTemplateSetEvent, { .ctor = flecs_default_ctor, diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index f01f4063b4..9628893d8b 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -460,15 +460,22 @@ int flecs_script_eval_entity( } if (v->template) { + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = false; if (ecs_script_visit_node(v, node->scope)) { return -1; } + v->is_with_scope = old_is_with_scope; return 0; } node->eval = flecs_script_create_entity(v, node->name); node->parent = v->entity; + if (v->template_entity) { + ecs_add_pair(v->world, node->eval, EcsTemplate, v->template_entity); + } + if (is_slot) { ecs_entity_t parent = ecs_get_target( v->world, node->eval, EcsChildOf, 0); @@ -511,9 +518,16 @@ int flecs_script_eval_entity( ecs_vec_clear(&v->r->annot); } + + bool old_is_with_scope = v->is_with_scope; + ecs_entity_t old_template_entity = v->template_entity; + v->is_with_scope = false; + v->template_entity = 0; if (ecs_script_visit_node(v, node->scope)) { return -1; } + v->template_entity = old_template_entity; + v->is_with_scope = old_is_with_scope; if (node->eval_kind) { if (!node->kind_w_expr) { @@ -557,6 +571,11 @@ int flecs_script_eval_tag( return 0; } + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + if (!v->entity) { if (node->id.second) { flecs_script_eval_error( @@ -604,8 +623,9 @@ int flecs_script_eval_component( return -1; } - if (v->template) { - return 0; + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); @@ -694,6 +714,11 @@ int flecs_script_eval_var_component( return 0; } + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + ecs_id_t var_id = var->value.type; if (var->value.ptr) { @@ -885,11 +910,16 @@ int flecs_script_eval_with( } } + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; + if (ecs_script_visit_scope(v, node->scope)) { result = -1; goto error; } + v->is_with_scope = old_is_with_scope; + error: flecs_script_with_set_count(a, v, prev_with_count); flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index bf4df1111d..ba74b92ef3 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -10,12 +10,14 @@ typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; ecs_script_runtime_t *r; - ecs_script_template_t *template; + ecs_script_template_t *template; /* Set when creating template */ + ecs_entity_t template_entity; /* Set when creating template instance */ ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; ecs_entity_t with_relationship; int32_t with_relationship_sp; + bool is_with_scope; ecs_script_vars_t *vars; } ecs_script_eval_visitor_t; diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 90423b8dde..9b339713b9 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -401,6 +401,21 @@ int ecs_script_ast_to_buf( return - 1; } +char* ecs_script_ast_node_to_str( + ecs_script_t *script, + ecs_script_node_t *node) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = &buf }; + + flecs_script_stmt_to_str(&v, node); + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + char* ecs_script_ast_to_str( ecs_script_t *script) { diff --git a/src/entity_name.c b/src/entity_name.c index 8b590986d4..977b9a25b0 100644 --- a/src/entity_name.c +++ b/src/entity_name.c @@ -447,6 +447,8 @@ ecs_entity_t ecs_lookup_path_w_sep( } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!parent || ecs_is_valid(world, parent), + ECS_INVALID_PARAMETER, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); diff --git a/test/script/project.json b/test/script/project.json index 7e6cb0e24c..a2bf879379 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -208,7 +208,6 @@ "with_pair_component_in_scope", "pair_w_rel_var", "pair_w_tgt_var", - "component_in_with_scope_in_scope", "array_component", "on_set_w_kind_paren_no_reflection", "on_set_w_kind_paren", @@ -277,7 +276,8 @@ "assign_call_scoped_func", "assign_call_scoped_func_w_using", "eval_w_vars", - "eval_w_runtime" + "eval_w_runtime", + "component_in_entity_in_with_scope" ] }, { "id": "Template", @@ -321,7 +321,9 @@ "with_after_template", "with_in_scope_after_template", "prefab_w_template", - "template_in_scope" + "template_in_scope", + "nested_templates_in_prefab", + "entity_w_2_template_instances" ] }, { "id": "Error", @@ -391,10 +393,17 @@ "template_in_template", "unterminated_binary", "tag_in_with_scope", + "tag_in_with_scope_2", + "pair_tag_in_with_scope_2", "component_in_with_scope", + "component_in_with_scope_2", + "component_in_with_scope_3", + "component_in_with_scope_4", + "component_in_with_scope_5", "component_in_with_scope_nested", "component_in_with_scope_after_entity", "component_in_with_var_scope", + "component_in_with_in_template", "reload_script_w_component_w_error", "reload_script_w_component_w_error_again" ] diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 14f29735da..4e376d0c96 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -375,6 +375,49 @@ void Error_tag_in_with_scope(void) { ecs_fini(world); } +void Error_tag_in_with_scope_2(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + HEAD "Foo {}" + LINE "" + LINE "e {" + LINE " with Foo {" + LINE " Foo" + LINE " }" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_pair_tag_in_with_scope_2(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + HEAD "Foo {}" + LINE "Bar" + LINE "" + LINE "e {" + LINE " with Foo {" + LINE " (Foo, Bar)" + LINE " }" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + void Error_component_in_with_scope(void) { ecs_world_t *world = ecs_init(); @@ -400,6 +443,187 @@ void Error_component_in_with_scope(void) { ecs_fini(world); } +void Error_component_in_with_scope_2(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Frame {\n" + LINE " with Position {\n" + LINE " Position: {}\n" + LINE " }\n" + LINE "}\n" + LINE "\n" + LINE "template Room { }\n" + LINE "\n" + LINE "template House {\n" + LINE " building {\n" + LINE " walls = Frame: {}\n" + LINE " }\n" + LINE "}\n" + LINE "\n" + LINE "prefab HouseWithSide {\n" + LINE " House: {}\n" + LINE " _ {\n" + LINE " Room: {}\n" + LINE " }\n" + LINE "}\n" + LINE "\n" + LINE "e : HouseWithSide\n" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_component_in_with_scope_3(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE "" + LINE "e {" + LINE " with Position {" + LINE " Position: {10, 20}" + LINE " }" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_component_in_with_scope_4(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE "" + LINE "Foo {}" + LINE "" + LINE "e {" + LINE " with Position {" + LINE " (Foo, Position): {10, 20}" + LINE " }" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_component_in_with_scope_5(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE "" + LINE "Foo {}" + LINE "" + LINE "e {" + LINE " const pos = Position: {10, 20}" + LINE " with Position {" + LINE " $pos" + LINE " }" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Error_component_in_with_in_template(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "struct Position {" + LINE " x = f32" + LINE " y = f32" + LINE "}" + LINE "" + LINE "template Foo {" + LINE " with Position {" + LINE " Position: {10, 20}" + LINE " }" + LINE "}" + LINE "" + LINE "e {" + LINE " Foo: {}" + LINE "}" + ; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + void Error_component_in_with_scope_nested(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 484f6721ee..98799af354 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -6385,46 +6385,6 @@ void Eval_pair_w_tgt_var(void) { ecs_fini(world); } -void Eval_component_in_with_scope_in_scope(void) { - ecs_world_t *world = ecs_init(); - - ECS_COMPONENT(world, Position); - ECS_TAG(world, Bar); - - ecs_struct(world, { - .entity = ecs_id(Position), - .members = { - {"x", ecs_id(ecs_f32_t)}, - {"y", ecs_id(ecs_f32_t)} - } - }); - - const char *expr = - LINE "foo {\n" - LINE " Position: {10, 20}\n" - LINE " with Position(30, 40) {\n" - LINE " Bar\n" - LINE " }\n" - LINE "}\n"; - - test_assert(ecs_script_run(world, NULL, expr) == 0); - - ecs_entity_t foo = ecs_lookup(world, "foo"); - test_assert(foo != 0); - ecs_entity_t bar = ecs_lookup(world, "Bar"); - test_assert(bar != 0); - - test_assert(ecs_has(world, foo, Position)); - test_assert(ecs_has_id(world, foo, bar)); - - const Position *p = ecs_get(world, foo, Position); - test_assert(p != NULL); - test_int(p->x, 10); - test_int(p->y, 20); - - ecs_fini(world); -} - void Eval_array_component(void) { ecs_world_t *world = ecs_init(); @@ -8839,3 +8799,41 @@ void Eval_eval_w_runtime(void) { ecs_fini(world); } + +void Eval_component_in_entity_in_with_scope(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {}" + LINE "with Foo {" + LINE " e {" + LINE " Position: {10, 20}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + test_assert(ecs_has_id(world, e, foo)); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} diff --git a/test/script/src/Template.c b/test/script/src/Template.c index ef6d0ecb39..610871f404 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2076,3 +2076,108 @@ void Template_template_in_scope(void) { ecs_fini(world); } + +void Template_nested_templates_in_prefab(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + HEAD "template Frame {" + LINE " wall {}" + LINE "}" + LINE "" + LINE "template Room { }" + LINE "" + LINE "template House {" + LINE " building {" + LINE " walls = Frame: {}" + LINE " }" + LINE "}" + LINE "" + LINE "prefab HousePrefab {" + LINE " House: {}" + LINE " room {" + LINE " Room: {}" + LINE " }" + LINE "}" + LINE "" + LINE "e : HousePrefab" + ; + + // ecs_log_set_level(0); + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t frame = ecs_lookup(world, "Frame"); + test_assert(frame != 0); + + ecs_entity_t room = ecs_lookup(world, "Room"); + test_assert(room != 0); + + ecs_entity_t house = ecs_lookup(world, "House"); + test_assert(house != 0); + + ecs_entity_t house_prefab = ecs_lookup(world, "HousePrefab"); + test_assert(house_prefab != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + ecs_entity_t e_room = ecs_lookup(world, "e.room"); + test_assert(e_room != 0); + + ecs_entity_t e_building = ecs_lookup(world, "e.building"); + test_assert(e_building != 0); + + ecs_entity_t e_building_walls = ecs_lookup(world, "e.building.walls"); + test_assert(e_building_walls != 0); + + ecs_entity_t e_building_walls_wall = ecs_lookup(world, "e.building.walls.wall"); + test_assert(e_building_walls_wall != 0); + + ecs_fini(world); +} + +void Template_entity_w_2_template_instances(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + HEAD "template Foo {" + LINE " child_a {}" + LINE "}" + LINE "" + LINE "template Bar {" + LINE " child_b {}" + LINE "}" + LINE "" + LINE "e {" + LINE " Foo: {}" + LINE " Bar: {}" + LINE "}" + ; + + // ecs_log_set_level(0); + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(bar != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + ecs_entity_t child_a = ecs_lookup(world, "e.child_a"); + test_assert(child_a != 0); + + ecs_entity_t child_b = ecs_lookup(world, "e.child_b"); + test_assert(child_b != 0); + + test_assert(ecs_has_id(world, e, foo)); + test_assert(ecs_has_id(world, e, bar)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 33fe25ae26..53644a8501 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -204,7 +204,6 @@ void Eval_with_pair_in_scope(void); void Eval_with_pair_component_in_scope(void); void Eval_pair_w_rel_var(void); void Eval_pair_w_tgt_var(void); -void Eval_component_in_with_scope_in_scope(void); void Eval_array_component(void); void Eval_on_set_w_kind_paren_no_reflection(void); void Eval_on_set_w_kind_paren(void); @@ -274,6 +273,7 @@ void Eval_assign_call_scoped_func(void); void Eval_assign_call_scoped_func_w_using(void); void Eval_eval_w_vars(void); void Eval_eval_w_runtime(void); +void Eval_component_in_entity_in_with_scope(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -316,6 +316,8 @@ void Template_with_after_template(void); void Template_with_in_scope_after_template(void); void Template_prefab_w_template(void); void Template_template_in_scope(void); +void Template_nested_templates_in_prefab(void); +void Template_entity_w_2_template_instances(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -383,10 +385,17 @@ void Error_update_template_after_error(void); void Error_template_in_template(void); void Error_unterminated_binary(void); void Error_tag_in_with_scope(void); +void Error_tag_in_with_scope_2(void); +void Error_pair_tag_in_with_scope_2(void); void Error_component_in_with_scope(void); +void Error_component_in_with_scope_2(void); +void Error_component_in_with_scope_3(void); +void Error_component_in_with_scope_4(void); +void Error_component_in_with_scope_5(void); void Error_component_in_with_scope_nested(void); void Error_component_in_with_scope_after_entity(void); void Error_component_in_with_var_scope(void); +void Error_component_in_with_in_template(void); void Error_reload_script_w_component_w_error(void); void Error_reload_script_w_component_w_error_again(void); @@ -1527,10 +1536,6 @@ bake_test_case Eval_testcases[] = { "pair_w_tgt_var", Eval_pair_w_tgt_var }, - { - "component_in_with_scope_in_scope", - Eval_component_in_with_scope_in_scope - }, { "array_component", Eval_array_component @@ -1806,6 +1811,10 @@ bake_test_case Eval_testcases[] = { { "eval_w_runtime", Eval_eval_w_runtime + }, + { + "component_in_entity_in_with_scope", + Eval_component_in_entity_in_with_scope } }; @@ -1969,6 +1978,14 @@ bake_test_case Template_testcases[] = { { "template_in_scope", Template_template_in_scope + }, + { + "nested_templates_in_prefab", + Template_nested_templates_in_prefab + }, + { + "entity_w_2_template_instances", + Template_entity_w_2_template_instances } }; @@ -2233,10 +2250,34 @@ bake_test_case Error_testcases[] = { "tag_in_with_scope", Error_tag_in_with_scope }, + { + "tag_in_with_scope_2", + Error_tag_in_with_scope_2 + }, + { + "pair_tag_in_with_scope_2", + Error_pair_tag_in_with_scope_2 + }, { "component_in_with_scope", Error_component_in_with_scope }, + { + "component_in_with_scope_2", + Error_component_in_with_scope_2 + }, + { + "component_in_with_scope_3", + Error_component_in_with_scope_3 + }, + { + "component_in_with_scope_4", + Error_component_in_with_scope_4 + }, + { + "component_in_with_scope_5", + Error_component_in_with_scope_5 + }, { "component_in_with_scope_nested", Error_component_in_with_scope_nested @@ -2249,6 +2290,10 @@ bake_test_case Error_testcases[] = { "component_in_with_var_scope", Error_component_in_with_var_scope }, + { + "component_in_with_in_template", + Error_component_in_with_in_template + }, { "reload_script_w_component_w_error", Error_reload_script_w_component_w_error @@ -3676,14 +3721,14 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 40, + 42, Template_testcases }, { "Error", NULL, NULL, - 71, + 78, Error_testcases }, { From 8104954ba5635891ca7eb0c737e1a444b31407d2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 9 Dec 2024 20:43:18 -0800 Subject: [PATCH 53/83] Fix operator precedence issue, ensure Template pair is always a tag --- distr/flecs.c | 6 ++- src/addons/script/expr/parser.c | 4 +- src/addons/script/template.c | 2 + test/script/project.json | 7 ++- test/script/src/Expr.c | 92 +++++++++++++++++++++++++++++++++ test/script/src/Template.c | 38 ++++++++++++++ test/script/src/main.c | 29 ++++++++++- 7 files changed, 173 insertions(+), 5 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index b3034856aa..acf720894b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -58960,6 +58960,8 @@ void flecs_script_template_import( ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); ECS_TAG_DEFINE(world, EcsTemplate); + ecs_add_id(world, EcsTemplate, EcsPairIsTag); + ecs_set_hooks(world, EcsTemplateSetEvent, { .ctor = flecs_default_ctor, .move = ecs_move(EcsTemplateSetEvent), @@ -74612,7 +74614,9 @@ const char* flecs_script_parse_lhs( case EcsTokSub: { ecs_expr_binary_t *node = flecs_expr_binary(parser); - pos = flecs_script_parse_expr(parser, pos, 0, &node->right); + + /* Use EcsTokNot as it has the same precedence as a unary - */ + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->right); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index efa3fb988b..3f37827312 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -391,7 +391,9 @@ const char* flecs_script_parse_lhs( case EcsTokSub: { ecs_expr_binary_t *node = flecs_expr_binary(parser); - pos = flecs_script_parse_expr(parser, pos, 0, &node->right); + + /* Use EcsTokNot as it has the same precedence as a unary - */ + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->right); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index de53c060bc..695dc22e7e 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -538,6 +538,8 @@ void flecs_script_template_import( ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); ECS_TAG_DEFINE(world, EcsTemplate); + ecs_add_id(world, EcsTemplate, EcsPairIsTag); + ecs_set_hooks(world, EcsTemplateSetEvent, { .ctor = flecs_default_ctor, .move = ecs_move(EcsTemplateSetEvent), diff --git a/test/script/project.json b/test/script/project.json index a2bf879379..e980941381 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -323,7 +323,8 @@ "prefab_w_template", "template_in_scope", "nested_templates_in_prefab", - "entity_w_2_template_instances" + "entity_w_2_template_instances", + "template_w_prefab_and_instance" ] }, { "id": "Error", @@ -510,6 +511,10 @@ "min_var", "min_lparen_int_rparen_to_i64", "min_lparen_int_rparen_to_i32", + "min_var_add", + "min_var_sub", + "min_var_mul", + "min_var_div", "struct_w_min_var", "struct_w_min_lparen_int_rparen", "struct_w_min_lparen_var_rparen", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 932fdf1d45..4c46b0e561 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -2370,6 +2370,98 @@ void Expr_struct_w_min_var(void) { ecs_fini(world); } +void Expr_min_var_add(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(ecs_i64_t*)var->value.ptr = 10; + + int64_t v = 0; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-$foo + 5", &(ecs_value_t){ + .type = ecs_id(ecs_i64_t), .ptr = &v + }, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_int(v, -5); + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_min_var_sub(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(ecs_i64_t*)var->value.ptr = 10; + + int64_t v = 0; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-$foo - 5", &(ecs_value_t){ + .type = ecs_id(ecs_i64_t), .ptr = &v + }, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_int(v, -15); + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_min_var_mul(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(ecs_i64_t*)var->value.ptr = 10; + + int64_t v = 0; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-$foo * 5", &(ecs_value_t){ + .type = ecs_id(ecs_i64_t), .ptr = &v + }, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_int(v, -50); + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_min_var_div(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(ecs_i64_t*)var->value.ptr = 10; + + int64_t v = 0; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "-$foo / 5", &(ecs_value_t){ + .type = ecs_id(ecs_i64_t), .ptr = &v + }, &desc); + test_assert(ptr != NULL); + test_assert(!ptr[0]); + + test_int(v, -2); + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Expr_struct_w_min_lparen_int_rparen(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 610871f404..69758e24d9 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2181,3 +2181,41 @@ void Template_entity_w_2_template_instances(void) { ecs_fini(world); } + +void Template_template_w_prefab_and_instance(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "template Foo {" + LINE " prefab Base" + LINE "" + LINE " child {" + LINE " grand_child : Base" + LINE " }" + LINE "}" + LINE "" + LINE "e = Foo: {}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, foo)); + + ecs_entity_t e_child = ecs_lookup(world, "e.child"); + test_assert(e_child != 0); + + ecs_entity_t grand_child = ecs_lookup(world, "e.child.grand_child"); + test_assert(grand_child != 0); + + ecs_entity_t base = ecs_lookup(world, "e.Base"); + test_assert(base != 0); + + test_assert(ecs_has_pair(world, grand_child, EcsIsA, base)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 53644a8501..af51bf2593 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -318,6 +318,7 @@ void Template_prefab_w_template(void); void Template_template_in_scope(void); void Template_nested_templates_in_prefab(void); void Template_entity_w_2_template_instances(void); +void Template_template_w_prefab_and_instance(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -497,6 +498,10 @@ void Expr_min_lparen_int_add_int_rparen(void); void Expr_min_var(void); void Expr_min_lparen_int_rparen_to_i64(void); void Expr_min_lparen_int_rparen_to_i32(void); +void Expr_min_var_add(void); +void Expr_min_var_sub(void); +void Expr_min_var_mul(void); +void Expr_min_var_div(void); void Expr_struct_w_min_var(void); void Expr_struct_w_min_lparen_int_rparen(void); void Expr_struct_w_min_lparen_var_rparen(void); @@ -1986,6 +1991,10 @@ bake_test_case Template_testcases[] = { { "entity_w_2_template_instances", Template_entity_w_2_template_instances + }, + { + "template_w_prefab_and_instance", + Template_template_w_prefab_and_instance } }; @@ -2689,6 +2698,22 @@ bake_test_case Expr_testcases[] = { "min_lparen_int_rparen_to_i32", Expr_min_lparen_int_rparen_to_i32 }, + { + "min_var_add", + Expr_min_var_add + }, + { + "min_var_sub", + Expr_min_var_sub + }, + { + "min_var_mul", + Expr_min_var_mul + }, + { + "min_var_div", + Expr_min_var_div + }, { "struct_w_min_var", Expr_struct_w_min_var @@ -3721,7 +3746,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 42, + 43, Template_testcases }, { @@ -3735,7 +3760,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 187, + 191, Expr_testcases, 1, Expr_params From b57c2af700dd8f65e6201ef87ef36b80e2cfefb1 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 9 Dec 2024 21:12:11 -0800 Subject: [PATCH 54/83] Fix issue with using ecs_entity_desc_t::set while deferring is suspended --- distr/flecs.c | 17 +----- src/addons/script/visit_to_str.c | 15 ------ src/entity.c | 2 +- test/core/project.json | 1 + test/core/src/Entity.c | 34 ++++++++++++ test/core/src/main.c | 7 ++- test/script/project.json | 4 +- test/script/src/Template.c | 92 ++++++++++++++++++++++++++++++++ test/script/src/main.c | 12 ++++- 9 files changed, 149 insertions(+), 35 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index acf720894b..f731701f5c 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -7596,7 +7596,7 @@ ecs_entity_t ecs_entity_init( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); - if (stage->defer) { + if (ecs_is_deferred(world)) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { @@ -61855,21 +61855,6 @@ int ecs_script_ast_to_buf( return - 1; } -char* ecs_script_ast_node_to_str( - ecs_script_t *script, - ecs_script_node_t *node) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = &buf }; - - flecs_script_stmt_to_str(&v, node); - - return ecs_strbuf_get(&buf); -error: - return NULL; -} - char* ecs_script_ast_to_str( ecs_script_t *script) { diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 9b339713b9..90423b8dde 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -401,21 +401,6 @@ int ecs_script_ast_to_buf( return - 1; } -char* ecs_script_ast_node_to_str( - ecs_script_t *script, - ecs_script_node_t *node) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = &buf }; - - flecs_script_stmt_to_str(&v, node); - - return ecs_strbuf_get(&buf); -error: - return NULL; -} - char* ecs_script_ast_to_str( ecs_script_t *script) { diff --git a/src/entity.c b/src/entity.c index b3945fcac0..3dd25bb85a 100644 --- a/src/entity.c +++ b/src/entity.c @@ -2095,7 +2095,7 @@ ecs_entity_t ecs_entity_init( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); - if (stage->defer) { + if (ecs_is_deferred(world)) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { diff --git a/test/core/project.json b/test/core/project.json index caff9b928d..9906ff644c 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -196,6 +196,7 @@ "entity_init_w_set_1_comp_existing_defer", "entity_init_w_set_1_comp_existing_empty_defer", "entity_init_w_set_1_comp_1_tag_w_set_defer", + "entity_init_w_set_1_comp_suspend_defer", "insert_1_comp", "insert_2_comp", "insert_1_comp_1_tag", diff --git a/test/core/src/Entity.c b/test/core/src/Entity.c index f0bd7abfd3..5d3ffa23ee 100644 --- a/test/core/src/Entity.c +++ b/test/core/src/Entity.c @@ -2864,6 +2864,40 @@ void Entity_entity_init_w_set_1_comp_1_tag_w_set_defer(void) { ecs_fini(world); } +void Entity_entity_init_w_set_1_comp_suspend_defer(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, TagA); + + ecs_defer_begin(world); + + ecs_defer_suspend(world); + + ecs_entity_t e = ecs_entity(world, { + .set = ecs_values( ecs_value(Position, {10, 20}), {TagA} ) + }); + + test_assert(e != 0); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, TagA)); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_resume(world); + + ecs_defer_end(world); + + p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + void Entity_insert_1_comp(void) { ecs_world_t *world = ecs_mini(); diff --git a/test/core/src/main.c b/test/core/src/main.c index 46d9a5b7d6..c169c31630 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -189,6 +189,7 @@ void Entity_entity_init_w_set_1_comp_w_name_defer(void); void Entity_entity_init_w_set_1_comp_existing_defer(void); void Entity_entity_init_w_set_1_comp_existing_empty_defer(void); void Entity_entity_init_w_set_1_comp_1_tag_w_set_defer(void); +void Entity_entity_init_w_set_1_comp_suspend_defer(void); void Entity_insert_1_comp(void); void Entity_insert_2_comp(void); void Entity_insert_1_comp_1_tag(void); @@ -3047,6 +3048,10 @@ bake_test_case Entity_testcases[] = { "entity_init_w_set_1_comp_1_tag_w_set_defer", Entity_entity_init_w_set_1_comp_1_tag_w_set_defer }, + { + "entity_init_w_set_1_comp_suspend_defer", + Entity_entity_init_w_set_1_comp_suspend_defer + }, { "insert_1_comp", Entity_insert_1_comp @@ -11346,7 +11351,7 @@ static bake_test_suite suites[] = { "Entity", NULL, NULL, - 140, + 141, Entity_testcases }, { diff --git a/test/script/project.json b/test/script/project.json index e980941381..1fec199406 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -324,7 +324,9 @@ "template_in_scope", "nested_templates_in_prefab", "entity_w_2_template_instances", - "template_w_prefab_and_instance" + "template_w_prefab_and_instance", + "template_w_with_var", + "template_w_with_prop" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 69758e24d9..bfe8289d57 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2219,3 +2219,95 @@ void Template_template_w_prefab_and_instance(void) { ecs_fini(world); } + +void Template_template_w_with_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " const pos = Position: {10, 20}" + LINE "" + LINE " with $pos {" + LINE " child {}" + LINE " }" + LINE "}" + LINE "" + LINE "e = Foo: {}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, foo)); + + ecs_entity_t child = ecs_lookup(world, "e.child"); + test_assert(child != 0); + + const Position *p = ecs_get(world, child, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Template_template_w_with_prop(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " prop pos = Position: {10, 20}" + LINE "" + LINE " with $pos {" + LINE " child {}" + LINE " }" + LINE "}" + LINE "" + LINE "e = Foo: {" + LINE " pos: {30, 40}" + LINE "}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, foo)); + + ecs_entity_t child = ecs_lookup(world, "e.child"); + test_assert(child != 0); + + const Position *p = ecs_get(world, child, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index af51bf2593..678fee443e 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -319,6 +319,8 @@ void Template_template_in_scope(void); void Template_nested_templates_in_prefab(void); void Template_entity_w_2_template_instances(void); void Template_template_w_prefab_and_instance(void); +void Template_template_w_with_var(void); +void Template_template_w_with_prop(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -1995,6 +1997,14 @@ bake_test_case Template_testcases[] = { { "template_w_prefab_and_instance", Template_template_w_prefab_and_instance + }, + { + "template_w_with_var", + Template_template_w_with_var + }, + { + "template_w_with_prop", + Template_template_w_with_prop } }; @@ -3746,7 +3756,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 43, + 45, Template_testcases }, { From c3a24501b9bf88e96bc006fea9cc38e0a675cd13 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 10 Dec 2024 11:49:15 -0800 Subject: [PATCH 55/83] Implement folding of const variables --- distr/flecs.c | 86 +++++++++++++++++++++------ distr/flecs.h | 1 + include/flecs/addons/script.h | 1 + src/addons/script/expr/visit_fold.c | 32 ++++++++++ src/addons/script/expr/visit_to_str.c | 44 ++++++++------ src/addons/script/vars.c | 1 + src/addons/script/visit_eval.c | 6 ++ src/bootstrap.c | 1 + src/entity.c | 2 + test/script/project.json | 3 +- test/script/src/Template.c | 69 ++++++++++++++++++++- test/script/src/main.c | 7 ++- 12 files changed, 211 insertions(+), 42 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index f731701f5c..92266c2ade 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -3989,6 +3989,7 @@ void flecs_bootstrap( ecs_make_alive(world, EcsChildOf); ecs_make_alive(world, EcsFlecs); ecs_make_alive(world, EcsFlecsCore); + ecs_make_alive(world, EcsFlecsInternals); ecs_make_alive(world, EcsOnAdd); ecs_make_alive(world, EcsOnRemove); ecs_make_alive(world, EcsOnSet); @@ -7526,6 +7527,8 @@ ecs_entity_t ecs_entity_init( /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; + ecs_check(ecs_is_valid(world, desc->parent), + ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); } /* Find or create entity */ @@ -59675,6 +59678,7 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; + var->is_const = false; flecs_name_index_ensure(&vars->var_index, flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); @@ -61041,6 +61045,12 @@ int flecs_script_eval_const( flecs_free(&v->world->allocator, ti->size, value.ptr); } + /* If variable resolves to a constant expression, mark it as const so that + * its value can be folded. */ + if (node->expr->kind == EcsExprValue) { + var->is_const = true; + } + return 0; } @@ -76133,6 +76143,35 @@ int flecs_expr_identifier_visit_fold( return 0; } +static +int flecs_expr_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + + ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->name); + /* Should've been caught by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t type = node->node.type; + + // if (var->is_const) { + // ecs_expr_value_node_t *result = flecs_expr_value_from( + // script, (ecs_expr_node_t*)node, type); + // void *value = ecs_value_new(script->world, type); + // ecs_value_copy(script->world, type, value, var->value.ptr); + // result->ptr = value; + // flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + // } + + return 0; +} + static int flecs_expr_arguments_visit_fold( ecs_script_t *script, @@ -76248,6 +76287,9 @@ int flecs_expr_visit_fold( } break; case EcsExprVariable: + if (flecs_expr_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprFunction: case EcsExprMethod: @@ -76465,8 +76507,11 @@ int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { - return ecs_ptr_to_str_buf( + ecs_strbuf_appendstr(v->buf, ECS_YELLOW); + int ret = ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); + ecs_strbuf_appendstr(v->buf, ECS_NORMAL); + return ret; } static @@ -76560,8 +76605,10 @@ int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { + ecs_strbuf_appendlit(v->buf, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); + ecs_strbuf_appendlit(v->buf, ECS_NORMAL); return 0; } @@ -76625,7 +76672,25 @@ int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_cast_t *node) { - return flecs_expr_node_to_str(v, node->expr); + ecs_entity_t type = node->node.type; + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + const char *name = ecs_get_name(v->world, type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; + } + + ecs_strbuf_append(v->buf, ")"); + + return 0; } static @@ -76635,19 +76700,6 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - const char *name = ecs_get_name(v->world, node->type); - if (name) { - ecs_strbuf_appendstr(v->buf, name); - } else { - char *path = ecs_get_path(v->world, node->type); - ecs_strbuf_appendstr(v->buf, path); - ecs_os_free(path); - } - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } - switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, @@ -76726,10 +76778,6 @@ int flecs_expr_node_to_str( ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); } - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } - return 0; error: return -1; diff --git a/distr/flecs.h b/distr/flecs.h index aa49c6959c..a980c2c95b 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14327,6 +14327,7 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; + bool is_const; } ecs_script_var_t; /** Script variable scope. */ diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 72259d4693..13bfb7d165 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -53,6 +53,7 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; + bool is_const; } ecs_script_var_t; /** Script variable scope. */ diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index dfc37833eb..c6a62ec449 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -291,6 +291,35 @@ int flecs_expr_identifier_visit_fold( return 0; } +static +int flecs_expr_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + + ecs_script_var_t *var = ecs_script_vars_lookup( + desc->vars, node->name); + /* Should've been caught by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t type = node->node.type; + + if (var->is_const) { + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, var->value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + } + + return 0; +} + static int flecs_expr_arguments_visit_fold( ecs_script_t *script, @@ -406,6 +435,9 @@ int flecs_expr_visit_fold( } break; case EcsExprVariable: + if (flecs_expr_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } break; case EcsExprFunction: case EcsExprMethod: diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 3666a4f435..b83af2b1c0 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -25,8 +25,11 @@ int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { - return ecs_ptr_to_str_buf( + ecs_strbuf_appendstr(v->buf, ECS_YELLOW); + int ret = ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); + ecs_strbuf_appendstr(v->buf, ECS_NORMAL); + return ret; } static @@ -120,8 +123,10 @@ int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { + ecs_strbuf_appendlit(v->buf, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); + ecs_strbuf_appendlit(v->buf, ECS_NORMAL); return 0; } @@ -185,7 +190,25 @@ int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_cast_t *node) { - return flecs_expr_node_to_str(v, node->expr); + ecs_entity_t type = node->node.type; + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + const char *name = ecs_get_name(v->world, type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; + } + + ecs_strbuf_append(v->buf, ")"); + + return 0; } static @@ -195,19 +218,6 @@ int flecs_expr_node_to_str( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - const char *name = ecs_get_name(v->world, node->type); - if (name) { - ecs_strbuf_appendstr(v->buf, name); - } else { - char *path = ecs_get_path(v->world, node->type); - ecs_strbuf_appendstr(v->buf, path); - ecs_os_free(path); - } - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } - switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, @@ -286,10 +296,6 @@ int flecs_expr_node_to_str( ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); } - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } - return 0; error: return -1; diff --git a/src/addons/script/vars.c b/src/addons/script/vars.c index 7399c7ee6a..49ff6aebb6 100644 --- a/src/addons/script/vars.c +++ b/src/addons/script/vars.c @@ -126,6 +126,7 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; + var->is_const = false; flecs_name_index_ensure(&vars->var_index, flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 9628893d8b..c20d19268e 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1066,6 +1066,12 @@ int flecs_script_eval_const( flecs_free(&v->world->allocator, ti->size, value.ptr); } + /* If variable resolves to a constant expression, mark it as const so that + * its value can be folded. */ + if (node->expr->kind == EcsExprValue) { + var->is_const = true; + } + return 0; } diff --git a/src/bootstrap.c b/src/bootstrap.c index fa835d8713..ee45ac19a3 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -708,6 +708,7 @@ void flecs_bootstrap( ecs_make_alive(world, EcsChildOf); ecs_make_alive(world, EcsFlecs); ecs_make_alive(world, EcsFlecsCore); + ecs_make_alive(world, EcsFlecsInternals); ecs_make_alive(world, EcsOnAdd); ecs_make_alive(world, EcsOnRemove); ecs_make_alive(world, EcsOnSet); diff --git a/src/entity.c b/src/entity.c index 3dd25bb85a..a33cc72385 100644 --- a/src/entity.c +++ b/src/entity.c @@ -2025,6 +2025,8 @@ ecs_entity_t ecs_entity_init( /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; + ecs_check(ecs_is_valid(world, desc->parent), + ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); } /* Find or create entity */ diff --git a/test/script/project.json b/test/script/project.json index 1fec199406..cb389a53bc 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -326,7 +326,8 @@ "entity_w_2_template_instances", "template_w_prefab_and_instance", "template_w_with_var", - "template_w_with_prop" + "template_w_with_prop", + "fold_const" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index bfe8289d57..a45a0f752b 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2306,8 +2306,73 @@ void Template_template_w_with_prop(void) { const Position *p = ecs_get(world, child, Position); test_assert(p != NULL); - test_int(p->x, 10); - test_int(p->y, 20); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Template_fold_const(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " prop size = i32: 10" + LINE " const size_h = $size / 2" + LINE " const size_h_2 = $size_h + 2" + LINE " Position: {$size_h, $size_h_2}" + LINE "}" + LINE "" + LINE "Foo e1(6)" + LINE "Foo e2(10)" + LINE "Foo e3(16)" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e1 = ecs_lookup(world, "e1"); + ecs_entity_t e2 = ecs_lookup(world, "e2"); + ecs_entity_t e3 = ecs_lookup(world, "e3"); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + test_assert(ecs_has_id(world, e1, foo)); + test_assert(ecs_has_id(world, e2, foo)); + test_assert(ecs_has_id(world, e3, foo)); + + { + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 5); + } + + { + const Position *p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 5); + test_int(p->y, 7); + } + + { + const Position *p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 8); + test_int(p->y, 10); + } ecs_fini(world); } diff --git a/test/script/src/main.c b/test/script/src/main.c index 678fee443e..8494bdfa42 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -321,6 +321,7 @@ void Template_entity_w_2_template_instances(void); void Template_template_w_prefab_and_instance(void); void Template_template_w_with_var(void); void Template_template_w_with_prop(void); +void Template_fold_const(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2005,6 +2006,10 @@ bake_test_case Template_testcases[] = { { "template_w_with_prop", Template_template_w_with_prop + }, + { + "fold_const", + Template_fold_const } }; @@ -3756,7 +3761,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 45, + 46, Template_testcases }, { From 74625ff62432f25e3e155c2cc682d73afab7f398 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 10 Dec 2024 19:46:10 -0800 Subject: [PATCH 56/83] Improve type inferencing in boolean expressions --- distr/flecs.c | 552 +++++++++++++++++--------- distr/flecs.h | 6 +- include/flecs/addons/script.h | 6 +- src/addons/metrics.c | 2 +- src/addons/script/expr/expr.h | 104 +++-- src/addons/script/expr/util.c | 2 + src/addons/script/expr/visit_to_str.c | 28 +- src/addons/script/expr/visit_type.c | 380 ++++++++++++------ src/addons/script/script.c | 2 +- src/addons/script/visit_to_str.c | 16 +- test/script/project.json | 9 + test/script/src/Expr.c | 49 ++- test/script/src/ExprAst.c | 123 ++++++ test/script/src/main.c | 39 +- 14 files changed, 949 insertions(+), 369 deletions(-) create mode 100644 test/script/src/ExprAst.c diff --git a/distr/flecs.c b/distr/flecs.c index 92266c2ade..c78d184b21 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5186,78 +5186,96 @@ const char* flecs_script_parse_initializer( void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, - ecs_strbuf_t *buf); + ecs_strbuf_t *buf, + bool colors); #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ +#define ECS_BOP(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) -#define ECS_BINARY_INT_OP(left, right, result, op)\ +#define ECS_BOP_COND(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +/* Unsigned operations */ +#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ + OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ + } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ + } + +/* Unsigned + signed operations */ +#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_i64_t)) { \ + OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ + } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ + } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ + } + +/* Unsigned + signed + floating point operations */ +#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ + ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_f64_t)) { \ + OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ + } + + +/* Combinations + error checking */ + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } @@ -25484,7 +25502,7 @@ int flecs_oneof_metric_init( ecs_entity_t mbr = ecs_entity(world, { .name = to_snake_case, - .parent = ecs_childof(metric) + .parent = metric }); ecs_os_free(to_snake_case); @@ -57903,7 +57921,7 @@ int EcsScript_serialize( ser->member(ser, "code"); ser->value(ser, ecs_id(ecs_string_t), &data->script->code); - char *ast = ecs_script_ast_to_str(data->script); + char *ast = ecs_script_ast_to_str(data->script, true); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &ast); ecs_os_free(ast); @@ -61477,6 +61495,7 @@ typedef struct ecs_script_str_visitor_t { ecs_strbuf_t *buf; int32_t depth; bool newline; + bool colors; } ecs_script_str_visitor_t; static @@ -61549,7 +61568,8 @@ void flecs_expr_to_str( const ecs_expr_node_t *expr) { if (expr) { - flecs_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); + flecs_expr_to_str_buf( + v->base.script->pub.world, expr, v->buf, v->colors); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -61850,11 +61870,12 @@ int flecs_script_stmt_to_str( int ecs_script_ast_to_buf( ecs_script_t *script, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = buf }; + ecs_script_str_visitor_t v = { .buf = buf, .colors = colors }; if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { goto error; } @@ -61866,16 +61887,17 @@ int ecs_script_ast_to_buf( } char* ecs_script_ast_to_str( - ecs_script_t *script) + ecs_script_t *script, + bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; if (flecs_script_impl(script)->expr) { flecs_expr_to_str_buf( - script->world, flecs_script_impl(script)->expr, &buf); + script->world, flecs_script_impl(script)->expr, &buf, colors); } else { - if (ecs_script_ast_to_buf(script, &buf)) { + if (ecs_script_ast_to_buf(script, &buf, colors)) { goto error; } } @@ -75083,6 +75105,8 @@ int flecs_value_binary( ecs_value_t *out, ecs_script_token_kind_t operator) { + (void)script; + switch(operator) { case EcsTokAdd: ECS_BINARY_OP(left, right, out, +); @@ -76160,14 +76184,14 @@ int flecs_expr_variable_visit_fold( ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); ecs_entity_t type = node->node.type; - // if (var->is_const) { - // ecs_expr_value_node_t *result = flecs_expr_value_from( - // script, (ecs_expr_node_t*)node, type); - // void *value = ecs_value_new(script->world, type); - // ecs_value_copy(script->world, type, value, var->value.ptr); - // result->ptr = value; - // flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - // } + if (var->is_const) { + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, var->value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + } return 0; } @@ -76495,6 +76519,7 @@ typedef struct ecs_expr_str_visitor_t { ecs_strbuf_t *buf; int32_t depth; bool newline; + bool colors; } ecs_expr_str_visitor_t; static @@ -76502,15 +76527,23 @@ int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); +static +void flecs_expr_color_to_str( + ecs_expr_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); +} + static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { - ecs_strbuf_appendstr(v->buf, ECS_YELLOW); + flecs_expr_color_to_str(v, ECS_YELLOW); int ret = ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); - ecs_strbuf_appendstr(v->buf, ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); return ret; } @@ -76605,10 +76638,10 @@ int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { - ecs_strbuf_appendlit(v->buf, ECS_GREEN); + flecs_expr_color_to_str(v, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); - ecs_strbuf_appendlit(v->buf, ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); return 0; } @@ -76673,7 +76706,8 @@ int flecs_expr_cast_to_str( const ecs_expr_cast_t *node) { ecs_entity_t type = node->node.type; - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + + flecs_expr_color_to_str(v, ECS_BLUE); const char *name = ecs_get_name(v->world, type); if (name) { ecs_strbuf_appendstr(v->buf, name); @@ -76682,7 +76716,8 @@ int flecs_expr_cast_to_str( ecs_strbuf_appendstr(v->buf, path); ecs_os_free(path); } - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); if (flecs_expr_node_to_str(v, node->expr)) { return -1; @@ -76786,9 +76821,10 @@ int flecs_expr_node_to_str( void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool colors) { - ecs_expr_str_visitor_t v = { .world = world, .buf = buf }; + ecs_expr_str_visitor_t v = { .world = world, .buf = buf, .colors = colors }; if (flecs_expr_node_to_str(&v, expr)) { ecs_strbuf_reset(buf); } @@ -76811,66 +76847,6 @@ int flecs_expr_visit_type_priv( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc); -static -bool flecs_expr_operator_is_equality( - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return true; - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - return false; - case EcsTokUnknown: - case EcsTokScopeOpen: - case EcsTokScopeClose: - case EcsTokParenOpen: - case EcsTokParenClose: - case EcsTokBracketOpen: - case EcsTokBracketClose: - case EcsTokMember: - case EcsTokComma: - case EcsTokSemiColon: - case EcsTokColon: - case EcsTokAssign: - case EcsTokMod: - case EcsTokNot: - case EcsTokOptional: - case EcsTokAnnotation: - case EcsTokNewline: - case EcsTokMatch: - case EcsTokIdentifier: - case EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordTemplate: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - case EcsTokEnd: - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - static bool flecs_expr_is_type_integer( ecs_entity_t type) @@ -76892,63 +76868,188 @@ bool flecs_expr_is_type_number( { if (type == ecs_id(ecs_bool_t)) return false; else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return true; + else if (type == ecs_id(ecs_u16_t)) return true; + else if (type == ecs_id(ecs_u32_t)) return true; else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_uptr_t)) return true; + else if (type == ecs_id(ecs_i8_t)) return true; + else if (type == ecs_id(ecs_i16_t)) return true; + else if (type == ecs_id(ecs_i32_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_iptr_t)) return true; + else if (type == ecs_id(ecs_f32_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; } +/* Returns how expressive a type is. This is used to determine whether an + * implicit cast is allowed, where only casts from less to more expressive types + * are valid. */ +static +int32_t flecs_expr_expressiveness_score( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 2; + + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 5; + else if (type == ecs_id(ecs_u64_t)) return 6; + + else if (type == ecs_id(ecs_i8_t)) return 7; + else if (type == ecs_id(ecs_i16_t)) return 8; + else if (type == ecs_id(ecs_i32_t)) return 9; + else if (type == ecs_id(ecs_iptr_t)) return 10; + else if (type == ecs_id(ecs_i64_t)) return 11; + + else if (type == ecs_id(ecs_f32_t)) return 12; + else if (type == ecs_id(ecs_f64_t)) return 13; + + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; +} + +/* Returns a score based on the storage size of a type. This is used in + * combination with expressiveness to determine whether a type can be implicitly + * casted. An implicit cast is only valid if the destination type is both more + * expressive and has a larger storage size. */ static -ecs_entity_t flecs_expr_largest_type( - const EcsPrimitive *type) -{ - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); +ecs_size_t flecs_expr_storage_score( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 1; + + /* Unsigned integers have a larger storage size than signed integers, since + * the unsigned range of a signed integer is smaller. */ + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 6; + else if (type == ecs_id(ecs_u64_t)) return 7; + + else if (type == ecs_id(ecs_i8_t)) return 1; + else if (type == ecs_id(ecs_i16_t)) return 2; + else if (type == ecs_id(ecs_i32_t)) return 3; + else if (type == ecs_id(ecs_iptr_t)) return 5; + else if (type == ecs_id(ecs_i64_t)) return 6; + + /* Floating points have a smaller storage score, since the largest integer + * that can be represented exactly is lower than the actual storage size. */ + else if (type == ecs_id(ecs_f32_t)) return 3; + else if (type == ecs_id(ecs_f64_t)) return 4; + + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; +} + +/* This function returns true if an type can be casted without changing the + * precision of the value. It is used to determine a type for operands in a + * binary expression in case they are of different types. */ +static +bool flecs_expr_implicit_cast_allowed( + ecs_entity_t from, + ecs_entity_t to) +{ + int32_t from_e = flecs_expr_expressiveness_score(from); + int32_t to_e = flecs_expr_expressiveness_score(to); + if (from_e == -1 || to_e == -1) { + return false; } -error: - return 0; + + if (to_e >= from_e) { + return flecs_expr_storage_score(to) >= flecs_expr_storage_score(from); + } + + return false; } -/** Promote type to most expressive (f64 > i64 > u64) */ static -ecs_entity_t flecs_expr_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) +ecs_entity_t flecs_expr_cast_to_lvalue( + ecs_entity_t lvalue, + ecs_entity_t operand) { - if (type == ecs_id(ecs_u64_t)) { - return promote_to; + if (flecs_expr_implicit_cast_allowed(operand, lvalue)) { + return lvalue; } - if (promote_to == ecs_id(ecs_u64_t)) { + + return operand; +} + +static +ecs_entity_t flecs_expr_narrow_type( + ecs_entity_t lvalue, + ecs_expr_node_t *node) +{ + ecs_entity_t type = node->type; + + if (node->kind != EcsExprValue) { return type; } - if (type == ecs_id(ecs_f64_t)) { + + if (!flecs_expr_is_type_number(type)) { return type; } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; + + void *ptr = ((ecs_expr_value_node_t*)node)->ptr; + + uint64_t uval; + + if (type == ecs_id(ecs_u8_t)) { + uval = *(ecs_u8_t*)ptr; + } else if (type == ecs_id(ecs_u16_t)) { + uval = *(ecs_u16_t*)ptr; + } else if (type == ecs_id(ecs_u32_t)) { + uval = *(ecs_u32_t*)ptr; + } else if (type == ecs_id(ecs_u64_t)) { + uval = *(ecs_u32_t*)ptr; + } else { + int64_t ival; + + if (type == ecs_id(ecs_i8_t)) { + ival = *(ecs_i8_t*)ptr; + } else if (type == ecs_id(ecs_i16_t)) { + ival = *(ecs_i16_t*)ptr; + } else if (type == ecs_id(ecs_i32_t)) { + ival = *(ecs_i32_t*)ptr; + } else if (type == ecs_id(ecs_i64_t)) { + ival = *(ecs_i64_t*)ptr; + } else { + /* If the lvalue type is a floating point type we can narrow the + * literal to that since we'll lose double precision anyway. */ + if (lvalue == ecs_id(ecs_f32_t)) { + return ecs_id(ecs_f32_t); + } + return type; + } + + if (ival <= INT8_MAX && ival >= INT8_MIN) { + return ecs_id(ecs_i8_t); + } else if (ival <= INT16_MAX && ival >= INT16_MIN) { + return ecs_id(ecs_i16_t); + } else if (ival <= INT32_MAX && ival >= INT32_MIN) { + return ecs_id(ecs_i32_t); + } else { + return ecs_id(ecs_i64_t); + } + } + + if (uval <= UINT8_MAX) { + return ecs_id(ecs_u8_t); + } else if (uval <= UINT16_MAX) { + return ecs_id(ecs_u16_t); + } else if (uval <= UINT32_MAX) { + return ecs_id(ecs_u32_t); + } else { + return ecs_id(ecs_u64_t); } - return ecs_id(ecs_i64_t); } static @@ -77033,8 +77134,13 @@ int flecs_expr_type_for_oper( switch(node->operator) { case EcsTokDiv: /* Result type of a division is always a float */ - *operand_type = ecs_id(ecs_f64_t); - *result_type = ecs_id(ecs_f64_t); + if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + } else { + *operand_type = left->type; + *result_type = left->type; + } return 0; case EcsTokAnd: case EcsTokOr: @@ -77115,31 +77221,105 @@ int flecs_expr_type_for_oper( goto error; } - ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + /* If left and right type are the same, do nothing */ + if (left->type == right->type) { + *operand_type = left->type; + goto done; + } + + /* If types are not the same, find the smallest type for literals that can + * represent the value without losing precision. */ + ecs_entity_t ltype = flecs_expr_narrow_type(node->node.type, left); + ecs_entity_t rtype = flecs_expr_narrow_type(node->node.type, right); + + /* If types are not the same, try to implicitly cast to expression type */ + ltype = flecs_expr_cast_to_lvalue(node->node.type, ltype); + rtype = flecs_expr_cast_to_lvalue(node->node.type, rtype); + if (ltype == rtype) { *operand_type = ltype; goto done; } - if (flecs_expr_operator_is_equality(node->operator)) { - char *lname = ecs_id_str(world, ltype); - char *rname = ecs_id_str(world, rtype); - flecs_expr_visit_error(script, node, - "mismatching types in equality expression (%s vs %s)", - lname, rname); - ecs_os_free(rname); - ecs_os_free(lname); - goto error; + if (node->operator == EcsTokEq || node->operator == EcsTokNeq) { + /* If this is an equality comparison and one of the operands is a bool, + * cast the other operand to a bool as well. This ensures that + * expressions such as true == 2 evaluate to true. */ + if (ltype == ecs_id(ecs_bool_t) || rtype == ecs_id(ecs_bool_t)) { + *operand_type = ecs_id(ecs_bool_t); + goto done; + } + + /* Equality comparisons between floating point types are invalid */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } + + if (rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } } - if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { - flecs_expr_visit_error(script, node, - "incompatible types in binary expression"); - goto error; + /* If after the implicit cast types are not the same, try to implicitly cast + * to the most expressive type. */ + if (flecs_expr_expressiveness_score(ltype) >= + flecs_expr_expressiveness_score(rtype)) + { + if (flecs_expr_implicit_cast_allowed(rtype, ltype)) { + *operand_type = ltype; + goto done; + } + } else { + if (flecs_expr_implicit_cast_allowed(ltype, rtype)) { + *operand_type = rtype; + goto done; + } + } + + /* If we get here one or both operands cannot be coerced to the same type + * while guaranteeing no loss of precision. Pick the type that's least + * likely to cause trouble. */ + + if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { + + /* If one of the types is a floating point, use f64 */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t) || + rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) + { + *operand_type = ecs_id(ecs_f64_t); + goto done; + } + + /* If one of the types is an integer, use i64 */ + if (ltype == ecs_id(ecs_i8_t) || ltype == ecs_id(ecs_i16_t) || + ltype == ecs_id(ecs_i32_t) || ltype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; + } + if (rtype == ecs_id(ecs_i8_t) || rtype == ecs_id(ecs_i16_t) || + rtype == ecs_id(ecs_i32_t) || rtype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; + } } - *operand_type = flecs_expr_promote_type(ltype, rtype); + /* If all of that didn't work, give up */ + + char *ltype_str = ecs_id_str(world, ltype); + char *rtype_str = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "incompatible types in binary expression (%s vs %s)", + ltype_str, rtype_str); + ecs_os_free(ltype_str); + ecs_os_free(rtype_str); +error: + return -1; done: if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { @@ -77152,8 +77332,6 @@ int flecs_expr_type_for_oper( } return 0; -error: - return -1; } static @@ -77291,6 +77469,12 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; + if (cur->valid) { + /* Provides a hint to the type visitor. The lvalue type will be used to + * reduce the number of casts where possible. */ + node->node.type = ecs_meta_get_type(cur); + } + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } diff --git a/distr/flecs.h b/distr/flecs.h index a980c2c95b..f7947fd656 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14531,7 +14531,8 @@ void ecs_script_runtime_free( FLECS_API int ecs_script_ast_to_buf( ecs_script_t *script, - ecs_strbuf_t *buf); + ecs_strbuf_t *buf, + bool colors); /** Convert script AST to string. * This operation converts the script abstract syntax tree to a string, which @@ -14542,7 +14543,8 @@ int ecs_script_ast_to_buf( */ FLECS_API char* ecs_script_ast_to_str( - ecs_script_t *script); + ecs_script_t *script, + bool colors); /* Managed scripts (script associated with entity that outlives the function) */ diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 13bfb7d165..dabb65bb3b 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -257,7 +257,8 @@ void ecs_script_runtime_free( FLECS_API int ecs_script_ast_to_buf( ecs_script_t *script, - ecs_strbuf_t *buf); + ecs_strbuf_t *buf, + bool colors); /** Convert script AST to string. * This operation converts the script abstract syntax tree to a string, which @@ -268,7 +269,8 @@ int ecs_script_ast_to_buf( */ FLECS_API char* ecs_script_ast_to_str( - ecs_script_t *script); + ecs_script_t *script, + bool colors); /* Managed scripts (script associated with entity that outlives the function) */ diff --git a/src/addons/metrics.c b/src/addons/metrics.c index 1e31f55775..ec66b59e7a 100644 --- a/src/addons/metrics.c +++ b/src/addons/metrics.c @@ -650,7 +650,7 @@ int flecs_oneof_metric_init( ecs_entity_t mbr = ecs_entity(world, { .name = to_snake_case, - .parent = ecs_childof(metric) + .parent = metric }); ecs_os_free(to_snake_case); diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 5adc38ad24..912521baad 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -48,78 +48,96 @@ const char* flecs_script_parse_initializer( void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, - ecs_strbuf_t *buf); + ecs_strbuf_t *buf, + bool colors); #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ +#define ECS_BOP(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) -#define ECS_BINARY_INT_OP(left, right, result, op)\ +#define ECS_BOP_COND(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +/* Unsigned operations */ +#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ + OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ + } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ + } + +/* Unsigned + signed operations */ +#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_i64_t)) { \ + OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ + } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ + } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ + } + +/* Unsigned + signed + floating point operations */ +#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ + ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_f64_t)) { \ + OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ + } + + +/* Combinations + error checking */ + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index ab0fb8ec94..44cdfd1625 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -135,6 +135,8 @@ int flecs_value_binary( ecs_value_t *out, ecs_script_token_kind_t operator) { + (void)script; + switch(operator) { case EcsTokAdd: ECS_BINARY_OP(left, right, out, +); diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index b83af2b1c0..abe4c52004 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -13,6 +13,7 @@ typedef struct ecs_expr_str_visitor_t { ecs_strbuf_t *buf; int32_t depth; bool newline; + bool colors; } ecs_expr_str_visitor_t; static @@ -20,15 +21,23 @@ int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); +static +void flecs_expr_color_to_str( + ecs_expr_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); +} + static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { - ecs_strbuf_appendstr(v->buf, ECS_YELLOW); + flecs_expr_color_to_str(v, ECS_YELLOW); int ret = ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); - ecs_strbuf_appendstr(v->buf, ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); return ret; } @@ -123,10 +132,10 @@ int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { - ecs_strbuf_appendlit(v->buf, ECS_GREEN); + flecs_expr_color_to_str(v, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); - ecs_strbuf_appendlit(v->buf, ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); return 0; } @@ -191,7 +200,8 @@ int flecs_expr_cast_to_str( const ecs_expr_cast_t *node) { ecs_entity_t type = node->node.type; - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + + flecs_expr_color_to_str(v, ECS_BLUE); const char *name = ecs_get_name(v->world, type); if (name) { ecs_strbuf_appendstr(v->buf, name); @@ -200,7 +210,8 @@ int flecs_expr_cast_to_str( ecs_strbuf_appendstr(v->buf, path); ecs_os_free(path); } - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); if (flecs_expr_node_to_str(v, node->expr)) { return -1; @@ -304,9 +315,10 @@ int flecs_expr_node_to_str( void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool colors) { - ecs_expr_str_visitor_t v = { .world = world, .buf = buf }; + ecs_expr_str_visitor_t v = { .world = world, .buf = buf, .colors = colors }; if (flecs_expr_node_to_str(&v, expr)) { ecs_strbuf_reset(buf); } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index fc4389dc5e..4cfa4832c0 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -15,66 +15,6 @@ int flecs_expr_visit_type_priv( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc); -static -bool flecs_expr_operator_is_equality( - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return true; - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - return false; - case EcsTokUnknown: - case EcsTokScopeOpen: - case EcsTokScopeClose: - case EcsTokParenOpen: - case EcsTokParenClose: - case EcsTokBracketOpen: - case EcsTokBracketClose: - case EcsTokMember: - case EcsTokComma: - case EcsTokSemiColon: - case EcsTokColon: - case EcsTokAssign: - case EcsTokMod: - case EcsTokNot: - case EcsTokOptional: - case EcsTokAnnotation: - case EcsTokNewline: - case EcsTokMatch: - case EcsTokIdentifier: - case EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordTemplate: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - case EcsTokEnd: - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - static bool flecs_expr_is_type_integer( ecs_entity_t type) @@ -96,63 +36,188 @@ bool flecs_expr_is_type_number( { if (type == ecs_id(ecs_bool_t)) return false; else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return true; + else if (type == ecs_id(ecs_u16_t)) return true; + else if (type == ecs_id(ecs_u32_t)) return true; else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_uptr_t)) return true; + else if (type == ecs_id(ecs_i8_t)) return true; + else if (type == ecs_id(ecs_i16_t)) return true; + else if (type == ecs_id(ecs_i32_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_iptr_t)) return true; + else if (type == ecs_id(ecs_f32_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; } +/* Returns how expressive a type is. This is used to determine whether an + * implicit cast is allowed, where only casts from less to more expressive types + * are valid. */ +static +int32_t flecs_expr_expressiveness_score( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 2; + + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 5; + else if (type == ecs_id(ecs_u64_t)) return 6; + + else if (type == ecs_id(ecs_i8_t)) return 7; + else if (type == ecs_id(ecs_i16_t)) return 8; + else if (type == ecs_id(ecs_i32_t)) return 9; + else if (type == ecs_id(ecs_iptr_t)) return 10; + else if (type == ecs_id(ecs_i64_t)) return 11; + + else if (type == ecs_id(ecs_f32_t)) return 12; + else if (type == ecs_id(ecs_f64_t)) return 13; + + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; +} + +/* Returns a score based on the storage size of a type. This is used in + * combination with expressiveness to determine whether a type can be implicitly + * casted. An implicit cast is only valid if the destination type is both more + * expressive and has a larger storage size. */ static -ecs_entity_t flecs_expr_largest_type( - const EcsPrimitive *type) +ecs_size_t flecs_expr_storage_score( + ecs_entity_t type) { - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 1; + + /* Unsigned integers have a larger storage size than signed integers, since + * the unsigned range of a signed integer is smaller. */ + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 6; + else if (type == ecs_id(ecs_u64_t)) return 7; + + else if (type == ecs_id(ecs_i8_t)) return 1; + else if (type == ecs_id(ecs_i16_t)) return 2; + else if (type == ecs_id(ecs_i32_t)) return 3; + else if (type == ecs_id(ecs_iptr_t)) return 5; + else if (type == ecs_id(ecs_i64_t)) return 6; + + /* Floating points have a smaller storage score, since the largest integer + * that can be represented exactly is lower than the actual storage size. */ + else if (type == ecs_id(ecs_f32_t)) return 3; + else if (type == ecs_id(ecs_f64_t)) return 4; + + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; +} + +/* This function returns true if an type can be casted without changing the + * precision of the value. It is used to determine a type for operands in a + * binary expression in case they are of different types. */ +static +bool flecs_expr_implicit_cast_allowed( + ecs_entity_t from, + ecs_entity_t to) +{ + int32_t from_e = flecs_expr_expressiveness_score(from); + int32_t to_e = flecs_expr_expressiveness_score(to); + if (from_e == -1 || to_e == -1) { + return false; } -error: - return 0; + + if (to_e >= from_e) { + return flecs_expr_storage_score(to) >= flecs_expr_storage_score(from); + } + + return false; } -/** Promote type to most expressive (f64 > i64 > u64) */ static -ecs_entity_t flecs_expr_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) +ecs_entity_t flecs_expr_cast_to_lvalue( + ecs_entity_t lvalue, + ecs_entity_t operand) { - if (type == ecs_id(ecs_u64_t)) { - return promote_to; + if (flecs_expr_implicit_cast_allowed(operand, lvalue)) { + return lvalue; } - if (promote_to == ecs_id(ecs_u64_t)) { + + return operand; +} + +static +ecs_entity_t flecs_expr_narrow_type( + ecs_entity_t lvalue, + ecs_expr_node_t *node) +{ + ecs_entity_t type = node->type; + + if (node->kind != EcsExprValue) { return type; } - if (type == ecs_id(ecs_f64_t)) { + + if (!flecs_expr_is_type_number(type)) { return type; } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; + + void *ptr = ((ecs_expr_value_node_t*)node)->ptr; + + uint64_t uval; + + if (type == ecs_id(ecs_u8_t)) { + uval = *(ecs_u8_t*)ptr; + } else if (type == ecs_id(ecs_u16_t)) { + uval = *(ecs_u16_t*)ptr; + } else if (type == ecs_id(ecs_u32_t)) { + uval = *(ecs_u32_t*)ptr; + } else if (type == ecs_id(ecs_u64_t)) { + uval = *(ecs_u32_t*)ptr; + } else { + int64_t ival; + + if (type == ecs_id(ecs_i8_t)) { + ival = *(ecs_i8_t*)ptr; + } else if (type == ecs_id(ecs_i16_t)) { + ival = *(ecs_i16_t*)ptr; + } else if (type == ecs_id(ecs_i32_t)) { + ival = *(ecs_i32_t*)ptr; + } else if (type == ecs_id(ecs_i64_t)) { + ival = *(ecs_i64_t*)ptr; + } else { + /* If the lvalue type is a floating point type we can narrow the + * literal to that since we'll lose double precision anyway. */ + if (lvalue == ecs_id(ecs_f32_t)) { + return ecs_id(ecs_f32_t); + } + return type; + } + + if (ival <= INT8_MAX && ival >= INT8_MIN) { + return ecs_id(ecs_i8_t); + } else if (ival <= INT16_MAX && ival >= INT16_MIN) { + return ecs_id(ecs_i16_t); + } else if (ival <= INT32_MAX && ival >= INT32_MIN) { + return ecs_id(ecs_i32_t); + } else { + return ecs_id(ecs_i64_t); + } + } + + if (uval <= UINT8_MAX) { + return ecs_id(ecs_u8_t); + } else if (uval <= UINT16_MAX) { + return ecs_id(ecs_u16_t); + } else if (uval <= UINT32_MAX) { + return ecs_id(ecs_u32_t); + } else { + return ecs_id(ecs_u64_t); } - return ecs_id(ecs_i64_t); } static @@ -237,8 +302,13 @@ int flecs_expr_type_for_oper( switch(node->operator) { case EcsTokDiv: /* Result type of a division is always a float */ - *operand_type = ecs_id(ecs_f64_t); - *result_type = ecs_id(ecs_f64_t); + if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + } else { + *operand_type = left->type; + *result_type = left->type; + } return 0; case EcsTokAnd: case EcsTokOr: @@ -319,31 +389,105 @@ int flecs_expr_type_for_oper( goto error; } - ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + /* If left and right type are the same, do nothing */ + if (left->type == right->type) { + *operand_type = left->type; + goto done; + } + + /* If types are not the same, find the smallest type for literals that can + * represent the value without losing precision. */ + ecs_entity_t ltype = flecs_expr_narrow_type(node->node.type, left); + ecs_entity_t rtype = flecs_expr_narrow_type(node->node.type, right); + + /* If types are not the same, try to implicitly cast to expression type */ + ltype = flecs_expr_cast_to_lvalue(node->node.type, ltype); + rtype = flecs_expr_cast_to_lvalue(node->node.type, rtype); + if (ltype == rtype) { *operand_type = ltype; goto done; } - if (flecs_expr_operator_is_equality(node->operator)) { - char *lname = ecs_id_str(world, ltype); - char *rname = ecs_id_str(world, rtype); - flecs_expr_visit_error(script, node, - "mismatching types in equality expression (%s vs %s)", - lname, rname); - ecs_os_free(rname); - ecs_os_free(lname); - goto error; + if (node->operator == EcsTokEq || node->operator == EcsTokNeq) { + /* If this is an equality comparison and one of the operands is a bool, + * cast the other operand to a bool as well. This ensures that + * expressions such as true == 2 evaluate to true. */ + if (ltype == ecs_id(ecs_bool_t) || rtype == ecs_id(ecs_bool_t)) { + *operand_type = ecs_id(ecs_bool_t); + goto done; + } + + /* Equality comparisons between floating point types are invalid */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } + + if (rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } } - if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { - flecs_expr_visit_error(script, node, - "incompatible types in binary expression"); - goto error; + /* If after the implicit cast types are not the same, try to implicitly cast + * to the most expressive type. */ + if (flecs_expr_expressiveness_score(ltype) >= + flecs_expr_expressiveness_score(rtype)) + { + if (flecs_expr_implicit_cast_allowed(rtype, ltype)) { + *operand_type = ltype; + goto done; + } + } else { + if (flecs_expr_implicit_cast_allowed(ltype, rtype)) { + *operand_type = rtype; + goto done; + } } - *operand_type = flecs_expr_promote_type(ltype, rtype); + /* If we get here one or both operands cannot be coerced to the same type + * while guaranteeing no loss of precision. Pick the type that's least + * likely to cause trouble. */ + + if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { + + /* If one of the types is a floating point, use f64 */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t) || + rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) + { + *operand_type = ecs_id(ecs_f64_t); + goto done; + } + + /* If one of the types is an integer, use i64 */ + if (ltype == ecs_id(ecs_i8_t) || ltype == ecs_id(ecs_i16_t) || + ltype == ecs_id(ecs_i32_t) || ltype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; + } + if (rtype == ecs_id(ecs_i8_t) || rtype == ecs_id(ecs_i16_t) || + rtype == ecs_id(ecs_i32_t) || rtype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; + } + } + + /* If all of that didn't work, give up */ + + char *ltype_str = ecs_id_str(world, ltype); + char *rtype_str = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "incompatible types in binary expression (%s vs %s)", + ltype_str, rtype_str); + ecs_os_free(ltype_str); + ecs_os_free(rtype_str); +error: + return -1; done: if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { @@ -356,8 +500,6 @@ int flecs_expr_type_for_oper( } return 0; -error: - return -1; } static @@ -495,6 +637,12 @@ int flecs_expr_binary_visit_type( /* Resulting type of binary expression */ ecs_entity_t result_type = 0; + if (cur->valid) { + /* Provides a hint to the type visitor. The lvalue type will be used to + * reduce the number of casts where possible. */ + node->node.type = ecs_meta_get_type(cur); + } + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 901661a4bc..66faef64eb 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -287,7 +287,7 @@ int EcsScript_serialize( ser->member(ser, "code"); ser->value(ser, ecs_id(ecs_string_t), &data->script->code); - char *ast = ecs_script_ast_to_str(data->script); + char *ast = ecs_script_ast_to_str(data->script, true); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &ast); ecs_os_free(ast); diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 90423b8dde..3629f40b74 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -13,6 +13,7 @@ typedef struct ecs_script_str_visitor_t { ecs_strbuf_t *buf; int32_t depth; bool newline; + bool colors; } ecs_script_str_visitor_t; static @@ -85,7 +86,8 @@ void flecs_expr_to_str( const ecs_expr_node_t *expr) { if (expr) { - flecs_expr_to_str_buf(v->base.script->pub.world, expr, v->buf); + flecs_expr_to_str_buf( + v->base.script->pub.world, expr, v->buf, v->colors); } else { flecs_scriptbuf_appendstr(v, "{}"); } @@ -386,11 +388,12 @@ int flecs_script_stmt_to_str( int ecs_script_ast_to_buf( ecs_script_t *script, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = buf }; + ecs_script_str_visitor_t v = { .buf = buf, .colors = colors }; if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { goto error; } @@ -402,16 +405,17 @@ int ecs_script_ast_to_buf( } char* ecs_script_ast_to_str( - ecs_script_t *script) + ecs_script_t *script, + bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; if (flecs_script_impl(script)->expr) { flecs_expr_to_str_buf( - script->world, flecs_script_impl(script)->expr, &buf); + script->world, flecs_script_impl(script)->expr, &buf, colors); } else { - if (ecs_script_ast_to_buf(script, &buf)) { + if (ecs_script_ast_to_buf(script, &buf, colors)) { goto error; } } diff --git a/test/script/project.json b/test/script/project.json index cb389a53bc..5b661e4036 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -610,6 +610,15 @@ "space_at_start", "newline_at_start" ] + }, { + "id": "ExprAst", + "testcases": [ + "binary_f32_var_add_f32_var", + "binary_f32_var_add_int", + "binary_f32_var_div_int", + "binary_f32_var_add_flt", + "binary_f32_var_div_by_int_sub_int" + ] }, { "id": "Vars", "testcases": [ diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 4c46b0e561..d1e354930f 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1845,12 +1845,47 @@ void Expr_cond_neq_int(void) { void Expr_cond_eq_bool_int(void) { ecs_world_t *world = ecs_init(); - ecs_value_t v = {0}; + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 1", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + } - ecs_log_set_level(-4); - test_assert(ecs_expr_run(world, "true == 1", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "true == 0", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "false == 0", &v, NULL) == NULL); + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 2", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 0", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 1", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 2", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 0", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + } ecs_fini(world); } @@ -1865,6 +1900,10 @@ void Expr_cond_eq_int_flt(void) { test_assert(ecs_expr_run(world, "1 == 0.0", &v, NULL) == NULL); test_assert(ecs_expr_run(world, "0 == 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 != 1.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 != 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "0 != 0.0", &v, NULL) == NULL); + ecs_fini(world); } diff --git a/test/script/src/ExprAst.c b/test/script/src/ExprAst.c new file mode 100644 index 0000000000..56674f23c3 --- /dev/null +++ b/test/script/src/ExprAst.c @@ -0,0 +1,123 @@ +#include + +void ExprAst_binary_f32_var_add_f32_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_f32_t); + *(ecs_f32_t*)foo->value.ptr = 10; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_f32_t); + *(ecs_f32_t*)bar->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .type = ecs_id(ecs_f32_t) }; + ecs_script_t *expr = ecs_expr_parse(world, "$foo + $bar", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "($foo + $bar)"); + ecs_os_free(ast); + + float v; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_f32_t, &v), &desc)); + test_int(v, 30); + + ecs_script_vars_fini(vars); + ecs_script_free(expr); + + ecs_fini(world); +} + +void ExprAst_binary_f32_var_add_int(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); + *(ecs_f32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .type = ecs_id(ecs_f32_t) }; + ecs_script_t *expr = ecs_expr_parse(world, "$foo + 3", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "($foo + 3)"); + ecs_os_free(ast); + + float v; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_f32_t, &v), &desc)); + test_int(v, 13); + + ecs_script_vars_fini(vars); + ecs_script_free(expr); + + ecs_fini(world); +} + +void ExprAst_binary_f32_var_div_int(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); + *(ecs_f32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .type = ecs_id(ecs_f32_t) }; + ecs_script_t *expr = ecs_expr_parse(world, "$foo / 2", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "($foo / 2)"); + ecs_os_free(ast); + + float v; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_f32_t, &v), &desc)); + test_int(v, 5); + + ecs_script_vars_fini(vars); + ecs_script_free(expr); + + ecs_fini(world); +} + +void ExprAst_binary_f32_var_add_flt(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); + *(ecs_f32_t*)foo->value.ptr = 10.5; + + ecs_expr_eval_desc_t desc = { .vars = vars, .type = ecs_id(ecs_f32_t) }; + ecs_script_t *expr = ecs_expr_parse(world, "$foo + 3.5", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "($foo + 3.5)"); + ecs_os_free(ast); + + float v; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_f32_t, &v), &desc)); + test_int(v, 14); + + ecs_script_vars_fini(vars); + ecs_script_free(expr); + + ecs_fini(world); +} + +void ExprAst_binary_f32_var_div_by_int_sub_int(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); + *(ecs_f32_t*)foo->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .type = ecs_id(ecs_f32_t) }; + ecs_script_t *expr = ecs_expr_parse(world, "$foo / 2 - 4", &desc); + + char *ast = ecs_script_ast_to_str(expr, false); + test_str(ast, "(($foo / 2) - 4)"); + ecs_os_free(ast); + + float v; + test_int(0, ecs_expr_eval(expr, &ecs_value_ptr(ecs_f32_t, &v), &desc)); + test_int(v, 1); + + ecs_script_vars_fini(vars); + ecs_script_free(expr); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 8494bdfa42..1a561e2051 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -597,6 +597,13 @@ void Expr_remainder_after_initializer_before_parens(void); void Expr_space_at_start(void); void Expr_newline_at_start(void); +// Testsuite 'ExprAst' +void ExprAst_binary_f32_var_add_f32_var(void); +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); + // Testsuite 'Vars' void Vars_declare_1_var(void); void Vars_declare_2_vars(void); @@ -3095,6 +3102,29 @@ bake_test_case Expr_testcases[] = { } }; +bake_test_case ExprAst_testcases[] = { + { + "binary_f32_var_add_f32_var", + ExprAst_binary_f32_var_add_f32_var + }, + { + "binary_f32_var_add_int", + ExprAst_binary_f32_var_add_int + }, + { + "binary_f32_var_div_int", + ExprAst_binary_f32_var_div_int + }, + { + "binary_f32_var_add_flt", + ExprAst_binary_f32_var_add_flt + }, + { + "binary_f32_var_div_by_int_sub_int", + ExprAst_binary_f32_var_div_by_int_sub_int + } +}; + bake_test_case Vars_testcases[] = { { "declare_1_var", @@ -3780,6 +3810,13 @@ static bake_test_suite suites[] = { 1, Expr_params }, + { + "ExprAst", + NULL, + NULL, + 5, + ExprAst_testcases + }, { "Vars", NULL, @@ -3806,5 +3843,5 @@ static bake_test_suite suites[] = { }; int main(int argc, char *argv[]) { - return bake_test_run("script", argc, argv, suites, 7); + return bake_test_run("script", argc, argv, suites, 8); } From be4e73608795f4d3c507b330b8945acf8f375eba Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 11 Dec 2024 04:57:34 +0000 Subject: [PATCH 57/83] Fix illegal memory access in ecs_add_path_w_sep --- distr/flecs.c | 6 +++++- src/entity_name.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index c78d184b21..8694068a5b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -11504,6 +11504,10 @@ ecs_entity_t ecs_add_path_w_sep( const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *real_world = world; + if (flecs_poly_is(world, ecs_stage_t)) { + real_world = ecs_get_world(world); + } if (!sep) { sep = "."; @@ -11534,7 +11538,7 @@ ecs_entity_t ecs_add_path_w_sep( * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_is_deferred(world) && - !(world->flags & EcsWorldMultiThreaded); + !(real_world->flags & EcsWorldMultiThreaded); ecs_entity_t cur = parent; char *name = NULL; diff --git a/src/entity_name.c b/src/entity_name.c index 977b9a25b0..1bc0a11206 100644 --- a/src/entity_name.c +++ b/src/entity_name.c @@ -635,6 +635,10 @@ ecs_entity_t ecs_add_path_w_sep( const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *real_world = world; + if (flecs_poly_is(world, ecs_stage_t)) { + real_world = ecs_get_world(world); + } if (!sep) { sep = "."; @@ -665,7 +669,7 @@ ecs_entity_t ecs_add_path_w_sep( * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_is_deferred(world) && - !(world->flags & EcsWorldMultiThreaded); + !(real_world->flags & EcsWorldMultiThreaded); ecs_entity_t cur = parent; char *name = NULL; From 0bddcc686988e0554d13c7c1547b1a88ac14ea63 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 10 Dec 2024 21:08:29 -0800 Subject: [PATCH 58/83] Remove unused ecs_stage_t::parser_tokens member --- distr/flecs.c | 5 ----- src/private_types.h | 5 ----- 2 files changed, 10 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 8694068a5b..30d88729c8 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -881,11 +881,6 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; - - /* Temporary token storage for DSL parser. This allows for parsing and - * interpreting a term without having to do allocations. */ - char parser_tokens[1024]; - char *parser_token; /* Pointer to next token */ }; /* Component monitor */ diff --git a/src/private_types.h b/src/private_types.h index cd30e6f05a..aeaa39622b 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -230,11 +230,6 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; - - /* Temporary token storage for DSL parser. This allows for parsing and - * interpreting a term without having to do allocations. */ - char parser_tokens[1024]; - char *parser_token; /* Pointer to next token */ }; /* Component monitor */ From 99bc0734d400f3f3c673ac8651eeec940d4252c8 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 10 Dec 2024 22:37:18 -0800 Subject: [PATCH 59/83] Add reusable script runtime to stage, reduce allocations during template instantiation --- distr/flecs.c | 106 ++++++++++++++++++++++++++++----- src/addons/script/script.c | 19 ++++++ src/addons/script/script.h | 3 + src/addons/script/template.c | 53 +++++++++++++---- src/addons/script/template.h | 6 ++ src/addons/script/visit_eval.c | 14 ++++- src/private_types.h | 5 ++ src/stage.c | 6 ++ test/script/project.json | 3 +- test/script/src/Template.c | 64 ++++++++++++++++++++ test/script/src/main.c | 7 ++- 11 files changed, 256 insertions(+), 30 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 30d88729c8..666676b6e6 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -881,6 +881,11 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; + +#ifdef FLECS_SCRIPT + /* Thread specific runtime for script execution */ + ecs_script_runtime_t *runtime; +#endif }; /* Component monitor */ @@ -5454,12 +5459,18 @@ struct ecs_script_template_t { const ecs_type_info_t *type_info; }; +#define ECS_TEMPLATE_SMALL_SIZE (36) + /* Event used for deferring template instantiation */ typedef struct EcsTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; int32_t count; + + /* Storage for small template types */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; } EcsTemplateSetEvent; int flecs_script_eval_template( @@ -5521,6 +5532,9 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); + void flecs_script_register_builtin_functions( ecs_world_t *world); @@ -17768,6 +17782,12 @@ void flecs_stage_free( flecs_commands_fini(stage, &stage->cmd_stack[i]); } +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); + } +#endif + flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); @@ -57908,6 +57928,25 @@ void ecs_script_runtime_free( ecs_os_free(r); } +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world) +{ + ecs_stage_t *stage; + if (flecs_poly_is(world, ecs_stage_t)) { + stage = (ecs_stage_t*)world; + } else { + stage = world->stages[0]; + } + + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!stage->runtime) { + stage->runtime = ecs_script_runtime_new(); + } + + return stage->runtime; +} + static int EcsScript_serialize( const ecs_serializer_t *ser, @@ -58453,22 +58492,37 @@ char* ecs_ptr_to_str( ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); ECS_DECLARE(EcsTemplate); +static +void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { + if (ptr->entities != &ptr->entity_storage) { + ecs_os_free(ptr->entities); + } + if (ptr->data != ptr->data_storage) { + ecs_os_free(ptr->data); + } +} + static ECS_MOVE(EcsTemplateSetEvent, dst, src, { - ecs_os_free(dst->entities); - ecs_os_free(dst->data); - dst->entities = src->entities; - dst->data = src->data; - dst->template_entity = src->template_entity; - dst->count = src->count; + flecs_template_set_event_free(dst); + + *dst = *src; + + if (src->entities == &src->entity_storage) { + dst->entities = &dst->entity_storage; + } + + if (src->data == src->data_storage) { + dst->data = &dst->data_storage; + } + src->entities = NULL; src->data = NULL; }) static ECS_DTOR(EcsTemplateSetEvent, ptr, { - ecs_os_free(ptr->entities); - ecs_os_free(ptr->data); + flecs_template_set_event_free(ptr); }) /* Template ctor to initialize with default property values */ @@ -58528,8 +58582,18 @@ void flecs_script_template_defer_on_set( void *data) { EcsTemplateSetEvent evt; - evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); - evt.data = ecs_os_memdup(data, ti->size * it->count); + + if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { + /* This should be true for the vast majority of templates */ + evt.entities = &evt.entity_storage; + evt.data = evt.data_storage; + evt.entity_storage = it->entities[0]; + ecs_os_memcpy(evt.data, data, ti->size); + } else { + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + } + evt.count = it->count; evt.template_entity = template_entity; @@ -58569,7 +58633,11 @@ void flecs_script_template_instantiate( const EcsStruct *st = ecs_record_get(world, r, EcsStruct); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; + + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -58633,7 +58701,7 @@ void flecs_script_template_instantiate( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v, NULL); + flecs_script_eval_visit_fini(&v, &desc); } static @@ -61296,9 +61364,19 @@ int ecs_script_eval( ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v, desc); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v, desc); + flecs_script_eval_visit_fini(&v, &priv_desc); return result; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 66faef64eb..cb49e8529f 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -275,6 +275,25 @@ void ecs_script_runtime_free( ecs_os_free(r); } +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world) +{ + ecs_stage_t *stage; + if (flecs_poly_is(world, ecs_stage_t)) { + stage = (ecs_stage_t*)world; + } else { + stage = world->stages[0]; + } + + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!stage->runtime) { + stage->runtime = ecs_script_runtime_new(); + } + + return stage->runtime; +} + static int EcsScript_serialize( const ecs_serializer_t *ser, diff --git a/src/addons/script/script.h b/src/addons/script/script.h index b1d7d5bd13..6724fa2c75 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -101,6 +101,9 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); + void flecs_script_register_builtin_functions( ecs_world_t *world); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 695dc22e7e..c3d49de079 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -11,22 +11,37 @@ ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); ECS_DECLARE(EcsTemplate); +static +void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { + if (ptr->entities != &ptr->entity_storage) { + ecs_os_free(ptr->entities); + } + if (ptr->data != ptr->data_storage) { + ecs_os_free(ptr->data); + } +} + static ECS_MOVE(EcsTemplateSetEvent, dst, src, { - ecs_os_free(dst->entities); - ecs_os_free(dst->data); - dst->entities = src->entities; - dst->data = src->data; - dst->template_entity = src->template_entity; - dst->count = src->count; + flecs_template_set_event_free(dst); + + *dst = *src; + + if (src->entities == &src->entity_storage) { + dst->entities = &dst->entity_storage; + } + + if (src->data == src->data_storage) { + dst->data = &dst->data_storage; + } + src->entities = NULL; src->data = NULL; }) static ECS_DTOR(EcsTemplateSetEvent, ptr, { - ecs_os_free(ptr->entities); - ecs_os_free(ptr->data); + flecs_template_set_event_free(ptr); }) /* Template ctor to initialize with default property values */ @@ -86,8 +101,18 @@ void flecs_script_template_defer_on_set( void *data) { EcsTemplateSetEvent evt; - evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); - evt.data = ecs_os_memdup(data, ti->size * it->count); + + if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { + /* This should be true for the vast majority of templates */ + evt.entities = &evt.entity_storage; + evt.data = evt.data_storage; + evt.entity_storage = it->entities[0]; + ecs_os_memcpy(evt.data, data, ti->size); + } else { + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + } + evt.count = it->count; evt.template_entity = template_entity; @@ -127,7 +152,11 @@ void flecs_script_template_instantiate( const EcsStruct *st = ecs_record_get(world, r, EcsStruct); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; + + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -191,7 +220,7 @@ void flecs_script_template_instantiate( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v, NULL); + flecs_script_eval_visit_fini(&v, &desc); } static diff --git a/src/addons/script/template.h b/src/addons/script/template.h index 9a5a42bb08..28e49f2633 100644 --- a/src/addons/script/template.h +++ b/src/addons/script/template.h @@ -28,12 +28,18 @@ struct ecs_script_template_t { const ecs_type_info_t *type_info; }; +#define ECS_TEMPLATE_SMALL_SIZE (36) + /* Event used for deferring template instantiation */ typedef struct EcsTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; int32_t count; + + /* Storage for small template types */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; } EcsTemplateSetEvent; int flecs_script_eval_template( diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index c20d19268e..9c63aeed20 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1300,9 +1300,19 @@ int ecs_script_eval( ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v, desc); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v, desc); + flecs_script_eval_visit_fini(&v, &priv_desc); return result; } diff --git a/src/private_types.h b/src/private_types.h index aeaa39622b..f2649dbce4 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -230,6 +230,11 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; + +#ifdef FLECS_SCRIPT + /* Thread specific runtime for script execution */ + ecs_script_runtime_t *runtime; +#endif }; /* Component monitor */ diff --git a/src/stage.c b/src/stage.c index 2a862d5ff2..d4421c300b 100644 --- a/src/stage.c +++ b/src/stage.c @@ -653,6 +653,12 @@ void flecs_stage_free( flecs_commands_fini(stage, &stage->cmd_stack[i]); } +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); + } +#endif + flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); diff --git a/test/script/project.json b/test/script/project.json index 5b661e4036..4e99947c9a 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -327,7 +327,8 @@ "template_w_prefab_and_instance", "template_w_with_var", "template_w_with_prop", - "fold_const" + "fold_const", + "bulk_create_template" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index a45a0f752b..b390218dc1 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2376,3 +2376,67 @@ void Template_fold_const(void) { ecs_fini(world); } + +void Template_bulk_create_template(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_struct(world, { + .entity = ecs_id(Velocity), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Position {" + LINE " prop x = f32: 0" + LINE " prop y = f32: 0" + LINE " Velocity: {$x + 5, $y + 5}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + Position p[] = { + {10, 20}, + {30, 40} + }; + + void *data[] = {p}; + + const ecs_entity_t *entities = ecs_bulk_init(world, &(ecs_bulk_desc_t) { + .count = 2, + .ids = {ecs_id(Position)}, + .data = data + }); + + test_assert(entities[0] != 0); + test_assert(entities[1] != 0); + + { + const Velocity *v = ecs_get(world, entities[0], Velocity); + test_assert(v != NULL); + test_int(v->x, 15); + test_int(v->y, 25); + } + + { + const Velocity *v = ecs_get(world, entities[1], Velocity); + test_assert(v != NULL); + test_int(v->x, 35); + test_int(v->y, 45); + } + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 1a561e2051..092e82a329 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -322,6 +322,7 @@ void Template_template_w_prefab_and_instance(void); void Template_template_w_with_var(void); void Template_template_w_with_prop(void); void Template_fold_const(void); +void Template_bulk_create_template(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2017,6 +2018,10 @@ bake_test_case Template_testcases[] = { { "fold_const", Template_fold_const + }, + { + "bulk_create_template", + Template_bulk_create_template } }; @@ -3791,7 +3796,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 46, + 47, Template_testcases }, { From f86e608fd0cdb81dd3d282405743e7fcbdd0a779 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 11 Dec 2024 07:20:51 +0000 Subject: [PATCH 60/83] Fix memory leaks from casting non-trivial values --- distr/flecs.c | 10 +++++++++- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/addons/script/expr/visit_fold.c | 10 +++++++++- test/script/src/Expr.c | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 666676b6e6..e8b5ac455f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -76088,7 +76088,9 @@ int flecs_expr_cast_visit_fold( return 0; } - ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + void *dst_ptr = ecs_value_new(script->world, dst_type); + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, dst_ptr); ecs_value_t value = { .type = src_type, .ptr = expr->ptr @@ -76096,10 +76098,16 @@ int flecs_expr_cast_visit_fold( if (ecs_meta_set_value(&cur, &value)) { flecs_expr_visit_error(script, node, "failed to assign value"); + ecs_value_free(script->world, dst_type, dst_ptr); goto error; } + if (expr->ptr != &expr->storage) { + ecs_value_free(script->world, expr->node.type, expr->ptr); + } + expr->node.type = dst_type; + expr->ptr = dst_ptr; node->expr = NULL; /* Prevent cleanup */ flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); diff --git a/distr/flecs.h b/distr/flecs.h index f7947fd656..7c4b9951e9 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -265,7 +265,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index 027abc2392..ec96077c27 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -263,7 +263,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index c6a62ec449..e641cee979 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -135,7 +135,9 @@ int flecs_expr_cast_visit_fold( return 0; } - ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + void *dst_ptr = ecs_value_new(script->world, dst_type); + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, dst_ptr); ecs_value_t value = { .type = src_type, .ptr = expr->ptr @@ -143,10 +145,16 @@ int flecs_expr_cast_visit_fold( if (ecs_meta_set_value(&cur, &value)) { flecs_expr_visit_error(script, node, "failed to assign value"); + ecs_value_free(script->world, dst_type, dst_ptr); goto error; } + if (expr->ptr != &expr->storage) { + ecs_value_free(script->world, expr->node.type, expr->ptr); + } + expr->node.type = dst_type; + expr->ptr = dst_ptr; node->expr = NULL; /* Prevent cleanup */ flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index d1e354930f..e754a3f55d 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -1850,6 +1850,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "true == 1", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); } { @@ -1857,6 +1858,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "true == 2", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); } { @@ -1864,6 +1866,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "true == 0", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); } { @@ -1871,6 +1874,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "false == 1", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); } { @@ -1878,6 +1882,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "false == 2", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); } { @@ -1885,6 +1890,7 @@ void Expr_cond_eq_bool_int(void) { test_assert(ecs_expr_run(world, "false == 0", &v, NULL) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); } ecs_fini(world); From 3f9c4bf8137fef7ef09dea5d8e58ad802a029193 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 11 Dec 2024 17:57:53 +0000 Subject: [PATCH 61/83] Fix leaks when using FLECS_USE_OS_ALLOC --- distr/flecs.c | 17 ++++++++++++----- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/datastructures/hashmap.c | 2 +- src/datastructures/map.c | 2 +- src/query/engine/eval_iter.c | 4 +++- src/world.c | 9 +++++++-- 7 files changed, 26 insertions(+), 12 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index e8b5ac455f..1a408490c9 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -20662,8 +20662,13 @@ ecs_entities_t ecs_get_entities( ecs_flags32_t ecs_world_get_flags( const ecs_world_t *world) { - flecs_poly_assert(world, ecs_world_t); - return world->flags; + if (flecs_poly_is(world, ecs_world_t)) { + return world->flags; + } else { + flecs_poly_assert(world, ecs_stage_t); + const ecs_stage_t *stage = (const ecs_stage_t*)world; + return stage->world->flags; + } } /** @@ -29984,7 +29989,7 @@ void flecs_hashmap_fini( ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); -#ifdef FLECS_SANITIZE +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) flecs_bfree(&map->bucket_allocator, bucket); #endif } @@ -30439,7 +30444,7 @@ void ecs_map_fini( } bool sanitize = false; -#ifdef FLECS_SANITIZE +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) sanitize = true; #endif @@ -71024,7 +71029,9 @@ ecs_iter_t ecs_query_iter( if (cache) { /* If monitors changed, do query rematching */ ecs_flags32_t flags = q->flags; - if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { + if (!(ecs_world_get_flags(world) & EcsWorldReadonly) && + (flags & EcsQueryHasRefs)) + { flecs_eval_component_monitors(q->world); } } diff --git a/distr/flecs.h b/distr/flecs.h index 7c4b9951e9..f7947fd656 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -265,7 +265,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index ec96077c27..027abc2392 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -263,7 +263,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/datastructures/hashmap.c b/src/datastructures/hashmap.c index 987e9e6a6d..e60c39ce27 100644 --- a/src/datastructures/hashmap.c +++ b/src/datastructures/hashmap.c @@ -53,7 +53,7 @@ void flecs_hashmap_fini( ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); -#ifdef FLECS_SANITIZE +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) flecs_bfree(&map->bucket_allocator, bucket); #endif } diff --git a/src/datastructures/map.c b/src/datastructures/map.c index 271dcfd252..cda7b19219 100644 --- a/src/datastructures/map.c +++ b/src/datastructures/map.c @@ -243,7 +243,7 @@ void ecs_map_fini( } bool sanitize = false; -#ifdef FLECS_SANITIZE +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) sanitize = true; #endif diff --git a/src/query/engine/eval_iter.c b/src/query/engine/eval_iter.c index 052955dda1..7e2be7789c 100644 --- a/src/query/engine/eval_iter.c +++ b/src/query/engine/eval_iter.c @@ -379,7 +379,9 @@ ecs_iter_t ecs_query_iter( if (cache) { /* If monitors changed, do query rematching */ ecs_flags32_t flags = q->flags; - if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { + if (!(ecs_world_get_flags(world) & EcsWorldReadonly) && + (flags & EcsQueryHasRefs)) + { flecs_eval_component_monitors(q->world); } } diff --git a/src/world.c b/src/world.c index cbe66941cc..ab2008ea5d 100644 --- a/src/world.c +++ b/src/world.c @@ -2400,6 +2400,11 @@ ecs_entities_t ecs_get_entities( ecs_flags32_t ecs_world_get_flags( const ecs_world_t *world) { - flecs_poly_assert(world, ecs_world_t); - return world->flags; + if (flecs_poly_is(world, ecs_world_t)) { + return world->flags; + } else { + flecs_poly_assert(world, ecs_stage_t); + const ecs_stage_t *stage = (const ecs_stage_t*)world; + return stage->world->flags; + } } From 61c5088ff6a2420afe507d8d9dd104cd79b8e3ed Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 11 Dec 2024 10:57:00 -0800 Subject: [PATCH 62/83] Fix issue where code was accessing world->flags on stage object --- distr/flecs.c | 25 ++++++++++++++++++++++--- src/addons/module.c | 1 + src/addons/system/system.c | 3 +++ src/observer.c | 2 +- src/query/api.c | 2 +- src/query/engine/cache.c | 6 ++++++ src/query/engine/cache_order_by.c | 1 + src/stage.c | 2 +- src/storage/table.c | 6 ++++++ src/world.c | 2 ++ 10 files changed, 44 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 1a408490c9..2d2dabb8f1 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -15786,7 +15786,7 @@ ecs_entity_t ecs_observer_init( const ecs_observer_desc_t *desc) { ecs_entity_t entity = 0; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_observer_desc_t was not initialized to zero"); @@ -17205,7 +17205,7 @@ void flecs_stage_merge( bool is_stage = flecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); - bool measure_frame_time = ECS_BIT_IS_SET(world->flags, + bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; @@ -20208,6 +20208,8 @@ void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { + flecs_poly_assert(world, ecs_world_t); + if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not @@ -25867,6 +25869,7 @@ ecs_entity_t ecs_import( ecs_module_action_t module, const char *module_name) { + flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); @@ -33230,7 +33233,7 @@ void flecs_query_fini( ecs_id_record_t *idr = flecs_id_record_get(q->real_world, term->id); if (idr) { - if (!(q->world->flags & EcsWorldQuit)) { + if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { if (ecs_os_has_threading()) { int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); @@ -38177,6 +38180,8 @@ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { + flecs_poly_assert(world, ecs_world_t); + bool is_root = table == &world->store.root; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); @@ -38499,6 +38504,8 @@ int32_t flecs_table_grow_data( int32_t size, const ecs_entity_t *ids) { + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_table_count(table); @@ -39361,6 +39368,8 @@ void flecs_table_notify( ecs_id_t id, ecs_table_event_t *event) { + flecs_poly_assert(world, ecs_world_t); + if (world->flags & EcsWorldFini) { return; } @@ -63694,6 +63703,7 @@ ecs_entity_t flecs_run_intern( ecs_ftime_t delta_time, void *param) { + flecs_poly_assert(world, ecs_world_t); ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; @@ -63743,6 +63753,8 @@ ecs_entity_t flecs_run_intern( stage = world->stages[0]; } + flecs_poly_assert(stage, ecs_stage_t); + /* Prepare the query iterator */ ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); ecs_iter_t *it = &qit; @@ -67534,6 +67546,8 @@ void flecs_query_cache_rematch_tables( ecs_world_t *world, ecs_query_impl_t *impl) { + flecs_poly_assert(world, ecs_world_t); + ecs_iter_t it; ecs_table_t *table = NULL; ecs_query_cache_table_t *qt = NULL; @@ -67893,7 +67907,11 @@ ecs_query_cache_t* flecs_query_cache_init( const ecs_query_desc_t *const_desc) { ecs_world_t *world = impl->pub.real_world; + flecs_poly_assert(world, ecs_world_t); + ecs_stage_t *stage = impl->stage; + flecs_poly_assert(stage, ecs_stage_t); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, @@ -68393,6 +68411,7 @@ void flecs_query_cache_build_sorted_table_range( ecs_query_cache_table_list_t *list) { ecs_world_t *world = cache->query->world; + flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); diff --git a/src/addons/module.c b/src/addons/module.c index 6c1bc4965e..f1b0cf6beb 100644 --- a/src/addons/module.c +++ b/src/addons/module.c @@ -36,6 +36,7 @@ ecs_entity_t ecs_import( ecs_module_action_t module, const char *module_name) { + flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); diff --git a/src/addons/system/system.c b/src/addons/system/system.c index 55216554d8..7f08e6f79a 100644 --- a/src/addons/system/system.c +++ b/src/addons/system/system.c @@ -31,6 +31,7 @@ ecs_entity_t flecs_run_intern( ecs_ftime_t delta_time, void *param) { + flecs_poly_assert(world, ecs_world_t); ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; @@ -80,6 +81,8 @@ ecs_entity_t flecs_run_intern( stage = world->stages[0]; } + flecs_poly_assert(stage, ecs_stage_t); + /* Prepare the query iterator */ ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); ecs_iter_t *it = &qit; diff --git a/src/observer.c b/src/observer.c index 4eb7433e72..8d57d0e006 100644 --- a/src/observer.c +++ b/src/observer.c @@ -1084,7 +1084,7 @@ ecs_entity_t ecs_observer_init( const ecs_observer_desc_t *desc) { ecs_entity_t entity = 0; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_observer_desc_t was not initialized to zero"); diff --git a/src/query/api.c b/src/query/api.c index 6924e959db..3af9ff1cc7 100644 --- a/src/query/api.c +++ b/src/query/api.c @@ -277,7 +277,7 @@ void flecs_query_fini( ecs_id_record_t *idr = flecs_id_record_get(q->real_world, term->id); if (idr) { - if (!(q->world->flags & EcsWorldQuit)) { + if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { if (ecs_os_has_threading()) { int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); diff --git a/src/query/engine/cache.c b/src/query/engine/cache.c index 1c00b515d2..4d1b34edc6 100644 --- a/src/query/engine/cache.c +++ b/src/query/engine/cache.c @@ -844,6 +844,8 @@ void flecs_query_cache_rematch_tables( ecs_world_t *world, ecs_query_impl_t *impl) { + flecs_poly_assert(world, ecs_world_t); + ecs_iter_t it; ecs_table_t *table = NULL; ecs_query_cache_table_t *qt = NULL; @@ -1203,7 +1205,11 @@ ecs_query_cache_t* flecs_query_cache_init( const ecs_query_desc_t *const_desc) { ecs_world_t *world = impl->pub.real_world; + flecs_poly_assert(world, ecs_world_t); + ecs_stage_t *stage = impl->stage; + flecs_poly_assert(stage, ecs_stage_t); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, diff --git a/src/query/engine/cache_order_by.c b/src/query/engine/cache_order_by.c index fd5979840a..67e929edb7 100644 --- a/src/query/engine/cache_order_by.c +++ b/src/query/engine/cache_order_by.c @@ -85,6 +85,7 @@ void flecs_query_cache_build_sorted_table_range( ecs_query_cache_table_list_t *list) { ecs_world_t *world = cache->query->world; + flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); diff --git a/src/stage.c b/src/stage.c index d4421c300b..f27f554a2e 100644 --- a/src/stage.c +++ b/src/stage.c @@ -76,7 +76,7 @@ void flecs_stage_merge( bool is_stage = flecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); - bool measure_frame_time = ECS_BIT_IS_SET(world->flags, + bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; diff --git a/src/storage/table.c b/src/storage/table.c index c6e88734f9..46af419c9a 100644 --- a/src/storage/table.c +++ b/src/storage/table.c @@ -1008,6 +1008,8 @@ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { + flecs_poly_assert(world, ecs_world_t); + bool is_root = table == &world->store.root; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); @@ -1330,6 +1332,8 @@ int32_t flecs_table_grow_data( int32_t size, const ecs_entity_t *ids) { + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_table_count(table); @@ -2192,6 +2196,8 @@ void flecs_table_notify( ecs_id_t id, ecs_table_event_t *event) { + flecs_poly_assert(world, ecs_world_t); + if (world->flags & EcsWorldFini) { return; } diff --git a/src/world.c b/src/world.c index ab2008ea5d..11ee0ea698 100644 --- a/src/world.c +++ b/src/world.c @@ -1946,6 +1946,8 @@ void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { + flecs_poly_assert(world, ecs_world_t); + if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not From ad1d496e0c479a389a6e0fbcee90cc107262aed0 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 11 Dec 2024 18:40:52 -0800 Subject: [PATCH 63/83] Implement ability to define global script variables --- distr/flecs.c | 295 ++++++++++++++++++++++---- distr/flecs.h | 45 +++- include/flecs/addons/script.h | 45 +++- src/addons/script/expr/ast.h | 2 + src/addons/script/expr/parser.c | 13 +- src/addons/script/expr/visit.h | 4 + src/addons/script/expr/visit_eval.c | 24 +++ src/addons/script/expr/visit_fold.c | 28 +++ src/addons/script/expr/visit_free.c | 1 + src/addons/script/expr/visit_to_str.c | 1 + src/addons/script/expr/visit_type.c | 70 +++++- src/addons/script/function.c | 94 +++++++- src/addons/script/functions_math.c | 17 ++ src/addons/script/script.c | 3 +- src/addons/script/template.c | 30 +-- src/addons/script/template.h | 6 +- src/addons/script/visit_eval.c | 2 +- test/script/project.json | 4 +- test/script/src/Expr.c | 48 +++++ test/script/src/main.c | 12 +- 20 files changed, 668 insertions(+), 76 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 2d2dabb8f1..731dcdd532 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4957,6 +4957,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprBinary, EcsExprIdentifier, EcsExprVariable, + EcsExprGlobalVariable, EcsExprFunction, EcsExprMethod, EcsExprMember, @@ -5001,6 +5002,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; + ecs_value_t global_value; /* Only set for global variables */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { @@ -5145,6 +5147,10 @@ void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); +ecs_script_var_t flecs_expr_find_var( + ecs_script_t *script, + const char *name); + #endif @@ -5437,7 +5443,7 @@ int flecs_script_eval_node( #ifndef FLECS_SCRIPT_TEMPLATE_H #define FLECS_SCRIPT_TEMPLATE_H -extern ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); +extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); struct ecs_script_template_t { /* Template handle */ @@ -5462,7 +5468,7 @@ struct ecs_script_template_t { #define ECS_TEMPLATE_SMALL_SIZE (36) /* Event used for deferring template instantiation */ -typedef struct EcsTemplateSetEvent { +typedef struct EcsScriptTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; @@ -5471,7 +5477,7 @@ typedef struct EcsTemplateSetEvent { /* Storage for small template types */ char data_storage[ECS_TEMPLATE_SMALL_SIZE]; ecs_entity_t entity_storage; -} EcsTemplateSetEvent; +} EcsScriptTemplateSetEvent; int flecs_script_eval_template( ecs_script_eval_visitor_t *v, @@ -55143,6 +55149,26 @@ void ecs_script_params_free(ecs_vec_t *params) { ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); } +static +ECS_MOVE(EcsScriptConstVar, dst, src, { + if (dst->type_info->hooks.dtor) { + dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); + } + + *dst = *src; + + src->value.ptr = NULL; + src->value.type = 0; + src->type_info = NULL; +}) + +static +ECS_DTOR(EcsScriptConstVar, ptr, { + if (ptr->type_info->hooks.dtor) { + ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); + } +}) + static ECS_MOVE(EcsScriptFunction, dst, src, { ecs_script_params_free(&dst->params); @@ -55167,21 +55193,61 @@ ECS_DTOR(EcsScriptMethod, ptr, { ecs_script_params_free(&ptr->params); }) +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "ecs_const_var_desc_t::type is not a valid type"); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + if (!result) { + goto error; + } + + EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); + v->value.ptr = ecs_os_malloc(ti->size); + v->value.type = desc->type; + v->type_info = ti; + ecs_value_init(world, desc->type, v->value.ptr); + ecs_value_copy(world, desc->type, v->value.ptr, desc->value); + ecs_modified(world, result, EcsScriptConstVar); + + return result; +error: + return 0; +} + ecs_entity_t ecs_function_init( ecs_world_t *world, const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); + if (!result) { + goto error; + } + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); f->return_type = desc->return_type; f->callback = desc->callback; @@ -55206,6 +55272,8 @@ ecs_entity_t ecs_function_init( ecs_modified(world, result, EcsScriptFunction); return result; +error: + return 0; } ecs_entity_t ecs_method_init( @@ -55213,17 +55281,21 @@ ecs_entity_t ecs_method_init( const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); + if (!result) { + goto error; + } + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); f->return_type = desc->return_type; f->callback = desc->callback; @@ -55248,12 +55320,15 @@ ecs_entity_t ecs_method_init( ecs_modified(world, result, EcsScriptMethod); return result; +error: + return 0; } void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); ECS_COMPONENT_DEFINE(world, EcsScriptFunction); ECS_COMPONENT_DEFINE(world, EcsScriptMethod); @@ -55271,6 +55346,13 @@ void flecs_function_import( } }); + ecs_set_hooks(world, EcsScriptConstVar, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptConstVar), + .move = ecs_move(EcsScriptConstVar), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + ecs_set_hooks(world, EcsScriptFunction, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptFunction), @@ -55549,6 +55631,23 @@ void FlecsScriptMathImport( ECS_IMPORT(world, FlecsScript); + /* Constants */ + double E = 2.71828182845904523536028747135266250; + ecs_const_var(world, { + .name = "E", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &E + }); + + double PI = 3.14159265358979323846264338327950288; + ecs_const_var(world, { + .name = "PI", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &PI + }); + /* Trigonometric functions */ FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); @@ -57676,6 +57775,7 @@ const char* ecs_query_args_parse( #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScript); +ECS_COMPONENT_DECLARE(EcsScriptConstVar); ECS_COMPONENT_DECLARE(EcsScriptFunction); ECS_COMPONENT_DECLARE(EcsScriptMethod); @@ -57740,7 +57840,7 @@ void ecs_script_clear( ecs_defer_begin(world); ecs_iter_t it = ecs_children(world, instance); while (ecs_children_next(&it)) { - if (ecs_table_has_id(world, it.table, ecs_pair(EcsTemplate, script))) { + if (ecs_table_has_id(world, it.table, ecs_pair(EcsScriptTemplate, script))) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_delete(world, it.entities[i]); @@ -58503,11 +58603,11 @@ char* ecs_ptr_to_str( #ifdef FLECS_SCRIPT -ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); -ECS_DECLARE(EcsTemplate); +ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); +ECS_DECLARE(EcsScriptTemplate); static -void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { +void flecs_template_set_event_free(EcsScriptTemplateSetEvent *ptr) { if (ptr->entities != &ptr->entity_storage) { ecs_os_free(ptr->entities); } @@ -58517,7 +58617,7 @@ void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { } static -ECS_MOVE(EcsTemplateSetEvent, dst, src, { +ECS_MOVE(EcsScriptTemplateSetEvent, dst, src, { flecs_template_set_event_free(dst); *dst = *src; @@ -58535,7 +58635,7 @@ ECS_MOVE(EcsTemplateSetEvent, dst, src, { }) static -ECS_DTOR(EcsTemplateSetEvent, ptr, { +ECS_DTOR(EcsScriptTemplateSetEvent, ptr, { flecs_template_set_event_free(ptr); }) @@ -58595,7 +58695,7 @@ void flecs_script_template_defer_on_set( const ecs_type_info_t *ti, void *data) { - EcsTemplateSetEvent evt; + EcsScriptTemplateSetEvent evt; if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { /* This should be true for the vast majority of templates */ @@ -58612,7 +58712,7 @@ void flecs_script_template_defer_on_set( evt.template_entity = template_entity; ecs_enqueue(it->world, &(ecs_event_desc_t){ - .event = ecs_id(EcsTemplateSetEvent), + .event = ecs_id(EcsScriptTemplateSetEvent), .entity = EcsAny, .param = &evt }); @@ -58724,7 +58824,7 @@ void flecs_on_template_set_event( { ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); - EcsTemplateSetEvent *evt = it->param; + EcsScriptTemplateSetEvent *evt = it->param; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); @@ -59059,22 +59159,22 @@ int flecs_script_eval_template( void flecs_script_template_import( ecs_world_t *world) { - ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); - ECS_TAG_DEFINE(world, EcsTemplate); + ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsScriptTemplate); - ecs_add_id(world, EcsTemplate, EcsPairIsTag); + ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); - ecs_set_hooks(world, EcsTemplateSetEvent, { + ecs_set_hooks(world, EcsScriptTemplateSetEvent, { .ctor = flecs_default_ctor, - .move = ecs_move(EcsTemplateSetEvent), - .dtor = ecs_dtor(EcsTemplateSetEvent), + .move = ecs_move(EcsScriptTemplateSetEvent), + .dtor = ecs_dtor(EcsScriptTemplateSetEvent), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), .query.terms = {{ .id = EcsAny }}, - .events = { ecs_id(EcsTemplateSetEvent) }, + .events = { ecs_id(EcsScriptTemplateSetEvent) }, .callback = flecs_on_template_set_event }); } @@ -60551,7 +60651,7 @@ int flecs_script_eval_entity( node->parent = v->entity; if (v->template_entity) { - ecs_add_pair(v->world, node->eval, EcsTemplate, v->template_entity); + ecs_add_pair(v->world, node->eval, EcsScriptTemplate, v->template_entity); } if (is_slot) { @@ -74714,7 +74814,18 @@ const char* flecs_script_parse_lhs( } else if (!ecs_os_strcmp(expr, "false")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { - *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + char *last_elem = strrchr(expr, '.'); + 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_strlen(&last_elem[2]) + 1); + v->node.kind = EcsExprGlobalVariable; + *out = (ecs_expr_node_t*)v; + } else { + /* Entity identifier */ + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + } } break; } @@ -75575,6 +75686,23 @@ int flecs_expr_variable_visit_eval( return -1; } +static +int flecs_expr_global_variable_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_variable_t *node, + ecs_expr_value_t *out) +{ + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + + ecs_assert(node->global_value.type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + out->value = node->global_value; + out->owned = false; + + return 0; +} + static int flecs_expr_cast_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -75858,6 +75986,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_eval( + ctx, (ecs_expr_variable_t*)node, out)) + { + goto error; + } + break; case EcsExprFunction: if (flecs_expr_function_visit_eval( ctx, (ecs_expr_function_t*)node, out)) @@ -76307,6 +76442,29 @@ int flecs_expr_variable_visit_fold( return 0; } +static +int flecs_expr_global_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + ecs_entity_t type = node->node.type; + + /* Global const variables are always const, so we can always fold */ + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, node->global_value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + + return 0; +} + static int flecs_expr_arguments_visit_fold( ecs_script_t *script, @@ -76426,6 +76584,11 @@ int flecs_expr_visit_fold( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprFunction: case EcsExprMethod: if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { @@ -76588,6 +76751,7 @@ void flecs_expr_visit_free( flecs_free_t(a, ecs_expr_identifier_t, node); break; case EcsExprVariable: + case EcsExprGlobalVariable: flecs_free_t(a, ecs_expr_variable_t, node); break; case EcsExprFunction: @@ -76884,6 +77048,7 @@ int flecs_expr_node_to_str( } break; case EcsExprVariable: + case EcsExprGlobalVariable: if (flecs_expr_variable_to_str(v, (const ecs_expr_variable_t*)node)) { @@ -77676,6 +77841,39 @@ int flecs_expr_identifier_visit_type( return -1; } +static +int flecs_expr_global_variable_resolve( + ecs_script_t *script, + ecs_expr_variable_t *node, + const ecs_expr_eval_desc_t *desc) +{ + ecs_world_t *world = script->world; + ecs_entity_t global = desc->lookup_action( + world, node->name, desc->lookup_ctx); + if (!global) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->name); + goto error; + } + + const EcsScriptConstVar *v = ecs_get(world, global, EcsScriptConstVar); + if (!v) { + char *str = ecs_get_path(world, global); + flecs_expr_visit_error(script, node, + "entity '%s' is not a variable", node->name); + ecs_os_free(str); + goto error; + } + + node->node.kind = EcsExprGlobalVariable; + node->node.type = v->value.type; + node->global_value = v->value; + + return 0; +error: + return -1; +} + static int flecs_expr_variable_visit_type( ecs_script_t *script, @@ -77685,15 +77883,33 @@ int flecs_expr_variable_visit_type( { ecs_script_var_t *var = ecs_script_vars_lookup( desc->vars, node->name); - if (!var) { - flecs_expr_visit_error(script, node, "unresolved variable '%s'", - node->name); - goto error; + if (var) { + node->node.type = var->value.type; + } else { + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; + } } - node->node.type = var->value.type; + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); - *cur = ecs_meta_cursor(script->world, var->value.type, NULL); + return 0; +error: + return -1; +} + +static +int flecs_expr_global_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + (void)cur; + + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; + } return 0; error: @@ -78068,6 +78284,13 @@ int flecs_expr_visit_type_priv( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_type( + script, (ecs_expr_variable_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprFunction: if (flecs_expr_function_visit_type( script, (ecs_expr_function_t*)node, cur, desc)) diff --git a/distr/flecs.h b/distr/flecs.h index f7947fd656..a9a17c1d54 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14311,7 +14311,10 @@ FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); FLECS_API -extern ECS_DECLARE(EcsTemplate); +extern ECS_DECLARE(EcsScriptTemplate); + +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScriptConstVar); FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); @@ -14380,6 +14383,14 @@ typedef struct ecs_script_parameter_t { ecs_entity_t type; } ecs_script_parameter_t; +/** Const component. + * This component describes a const variable that can be used from scripts. + */ +typedef struct EcsScriptConstVar { + ecs_value_t value; + const ecs_type_info_t *type_info; +} EcsScriptConstVar; + /** Function component. * This component describes a function that can be called from a script. */ @@ -14846,6 +14857,38 @@ char* ecs_script_string_interpolate( const ecs_script_vars_t *vars); +/* Global const variables */ + +/** Used with ecs_const_var_init */ +typedef struct ecs_const_var_desc_t { + /* Variable name. */ + const char *name; + + /* Variable parent (namespace). */ + ecs_entity_t parent; + + /* Variable type. */ + ecs_entity_t type; + + /* Pointer to value of variable. The value will be copied to an internal + * storage and does not need to be kept alive. */ + void *value; +} ecs_const_var_desc_t; + +/** Create a const variable that can be accessed by scripts. + * + * @param world The world. + * @param desc Const var parameters. + * @return The const var, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc); + +#define ecs_const_var(world, ...)\ + ecs_const_var_init(world, &(ecs_const_var_desc_t)__VA_ARGS__) + /* Functions */ /** Used with ecs_function_init and ecs_method_init */ diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index dabb65bb3b..d642f0f387 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -37,7 +37,10 @@ FLECS_API extern ECS_COMPONENT_DECLARE(EcsScript); FLECS_API -extern ECS_DECLARE(EcsTemplate); +extern ECS_DECLARE(EcsScriptTemplate); + +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScriptConstVar); FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptFunction); @@ -106,6 +109,14 @@ typedef struct ecs_script_parameter_t { ecs_entity_t type; } ecs_script_parameter_t; +/** Const component. + * This component describes a const variable that can be used from scripts. + */ +typedef struct EcsScriptConstVar { + ecs_value_t value; + const ecs_type_info_t *type_info; +} EcsScriptConstVar; + /** Function component. * This component describes a function that can be called from a script. */ @@ -572,6 +583,38 @@ char* ecs_script_string_interpolate( const ecs_script_vars_t *vars); +/* Global const variables */ + +/** Used with ecs_const_var_init */ +typedef struct ecs_const_var_desc_t { + /* Variable name. */ + const char *name; + + /* Variable parent (namespace). */ + ecs_entity_t parent; + + /* Variable type. */ + ecs_entity_t type; + + /* Pointer to value of variable. The value will be copied to an internal + * storage and does not need to be kept alive. */ + void *value; +} ecs_const_var_desc_t; + +/** Create a const variable that can be accessed by scripts. + * + * @param world The world. + * @param desc Const var parameters. + * @return The const var, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc); + +#define ecs_const_var(world, ...)\ + ecs_const_var_init(world, &(ecs_const_var_desc_t)__VA_ARGS__) + /* Functions */ /** Used with ecs_function_init and ecs_method_init */ diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 49a1833d7a..6cf645478d 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -16,6 +16,7 @@ typedef enum ecs_expr_node_kind_t { EcsExprBinary, EcsExprIdentifier, EcsExprVariable, + EcsExprGlobalVariable, EcsExprFunction, EcsExprMethod, EcsExprMember, @@ -60,6 +61,7 @@ typedef struct ecs_expr_identifier_t { typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; + ecs_value_t global_value; /* Only set for global variables */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 3f37827312..39aea3361a 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -371,7 +371,18 @@ const char* flecs_script_parse_lhs( } else if (!ecs_os_strcmp(expr, "false")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { - *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + char *last_elem = strrchr(expr, '.'); + 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_strlen(&last_elem[2]) + 1); + v->node.kind = EcsExprGlobalVariable; + *out = (ecs_expr_node_t*)v; + } else { + /* Entity identifier */ + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + } } break; } diff --git a/src/addons/script/expr/visit.h b/src/addons/script/expr/visit.h index 8874d0c387..732ba80516 100644 --- a/src/addons/script/expr/visit.h +++ b/src/addons/script/expr/visit.h @@ -32,4 +32,8 @@ void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); +ecs_script_var_t flecs_expr_find_var( + ecs_script_t *script, + const char *name); + #endif diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 1aa83be061..4f99b80bb3 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -275,6 +275,23 @@ int flecs_expr_variable_visit_eval( return -1; } +static +int flecs_expr_global_variable_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_variable_t *node, + ecs_expr_value_t *out) +{ + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + + ecs_assert(node->global_value.type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + out->value = node->global_value; + out->owned = false; + + return 0; +} + static int flecs_expr_cast_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -558,6 +575,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_eval( + ctx, (ecs_expr_variable_t*)node, out)) + { + goto error; + } + break; case EcsExprFunction: if (flecs_expr_function_visit_eval( ctx, (ecs_expr_function_t*)node, out)) diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index e641cee979..79f6165d47 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -328,6 +328,29 @@ int flecs_expr_variable_visit_fold( return 0; } +static +int flecs_expr_global_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + ecs_entity_t type = node->node.type; + + /* Global const variables are always const, so we can always fold */ + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, node->global_value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + + return 0; +} + static int flecs_expr_arguments_visit_fold( ecs_script_t *script, @@ -447,6 +470,11 @@ int flecs_expr_visit_fold( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprFunction: case EcsExprMethod: if (flecs_expr_function_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 32b6a9de94..a6b2132401 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -131,6 +131,7 @@ void flecs_expr_visit_free( flecs_free_t(a, ecs_expr_identifier_t, node); break; case EcsExprVariable: + case EcsExprGlobalVariable: flecs_free_t(a, ecs_expr_variable_t, node); break; case EcsExprFunction: diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index abe4c52004..98d02607e9 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -267,6 +267,7 @@ int flecs_expr_node_to_str( } break; case EcsExprVariable: + case EcsExprGlobalVariable: if (flecs_expr_variable_to_str(v, (const ecs_expr_variable_t*)node)) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 4cfa4832c0..03822b09b3 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -733,6 +733,39 @@ int flecs_expr_identifier_visit_type( return -1; } +static +int flecs_expr_global_variable_resolve( + ecs_script_t *script, + ecs_expr_variable_t *node, + const ecs_expr_eval_desc_t *desc) +{ + ecs_world_t *world = script->world; + ecs_entity_t global = desc->lookup_action( + world, node->name, desc->lookup_ctx); + if (!global) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->name); + goto error; + } + + const EcsScriptConstVar *v = ecs_get(world, global, EcsScriptConstVar); + if (!v) { + char *str = ecs_get_path(world, global); + flecs_expr_visit_error(script, node, + "entity '%s' is not a variable", node->name); + ecs_os_free(str); + goto error; + } + + node->node.kind = EcsExprGlobalVariable; + node->node.type = v->value.type; + node->global_value = v->value; + + return 0; +error: + return -1; +} + static int flecs_expr_variable_visit_type( ecs_script_t *script, @@ -742,15 +775,33 @@ int flecs_expr_variable_visit_type( { ecs_script_var_t *var = ecs_script_vars_lookup( desc->vars, node->name); - if (!var) { - flecs_expr_visit_error(script, node, "unresolved variable '%s'", - node->name); - goto error; + if (var) { + node->node.type = var->value.type; + } else { + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; + } } - node->node.type = var->value.type; + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); - *cur = ecs_meta_cursor(script->world, var->value.type, NULL); + return 0; +error: + return -1; +} + +static +int flecs_expr_global_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + (void)cur; + + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; + } return 0; error: @@ -1125,6 +1176,13 @@ int flecs_expr_visit_type_priv( goto error; } break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_type( + script, (ecs_expr_variable_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprFunction: if (flecs_expr_function_visit_type( script, (ecs_expr_function_t*)node, cur, desc)) diff --git a/src/addons/script/function.c b/src/addons/script/function.c index 5041523519..1bc3e76fbd 100644 --- a/src/addons/script/function.c +++ b/src/addons/script/function.c @@ -19,6 +19,26 @@ void ecs_script_params_free(ecs_vec_t *params) { ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); } +static +ECS_MOVE(EcsScriptConstVar, dst, src, { + if (dst->type_info->hooks.dtor) { + dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); + } + + *dst = *src; + + src->value.ptr = NULL; + src->value.type = 0; + src->type_info = NULL; +}) + +static +ECS_DTOR(EcsScriptConstVar, ptr, { + if (ptr->type_info->hooks.dtor) { + ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); + } +}) + static ECS_MOVE(EcsScriptFunction, dst, src, { ecs_script_params_free(&dst->params); @@ -43,21 +63,61 @@ ECS_DTOR(EcsScriptMethod, ptr, { ecs_script_params_free(&ptr->params); }) +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "ecs_const_var_desc_t::type is not a valid type"); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + if (!result) { + goto error; + } + + EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); + v->value.ptr = ecs_os_malloc(ti->size); + v->value.type = desc->type; + v->type_info = ti; + ecs_value_init(world, desc->type, v->value.ptr); + ecs_value_copy(world, desc->type, v->value.ptr, desc->value); + ecs_modified(world, result, EcsScriptConstVar); + + return result; +error: + return 0; +} + ecs_entity_t ecs_function_init( ecs_world_t *world, const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); + if (!result) { + goto error; + } + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); f->return_type = desc->return_type; f->callback = desc->callback; @@ -82,6 +142,8 @@ ecs_entity_t ecs_function_init( ecs_modified(world, result, EcsScriptFunction); return result; +error: + return 0; } ecs_entity_t ecs_method_init( @@ -89,17 +151,21 @@ ecs_entity_t ecs_method_init( const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); + if (!result) { + goto error; + } + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); f->return_type = desc->return_type; f->callback = desc->callback; @@ -124,12 +190,15 @@ ecs_entity_t ecs_method_init( ecs_modified(world, result, EcsScriptMethod); return result; +error: + return 0; } void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); ECS_COMPONENT_DEFINE(world, EcsScriptFunction); ECS_COMPONENT_DEFINE(world, EcsScriptMethod); @@ -147,6 +216,13 @@ void flecs_function_import( } }); + ecs_set_hooks(world, EcsScriptConstVar, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptConstVar), + .move = ecs_move(EcsScriptConstVar), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + ecs_set_hooks(world, EcsScriptFunction, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptFunction), diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index e102390621..0a728967f1 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -141,6 +141,23 @@ void FlecsScriptMathImport( ECS_IMPORT(world, FlecsScript); + /* Constants */ + double E = 2.71828182845904523536028747135266250; + ecs_const_var(world, { + .name = "E", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &E + }); + + double PI = 3.14159265358979323846264338327950288; + ecs_const_var(world, { + .name = "PI", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &PI + }); + /* Trigonometric functions */ FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); diff --git a/src/addons/script/script.c b/src/addons/script/script.c index cb49e8529f..9244969f2e 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -9,6 +9,7 @@ #include "script.h" ECS_COMPONENT_DECLARE(EcsScript); +ECS_COMPONENT_DECLARE(EcsScriptConstVar); ECS_COMPONENT_DECLARE(EcsScriptFunction); ECS_COMPONENT_DECLARE(EcsScriptMethod); @@ -73,7 +74,7 @@ void ecs_script_clear( ecs_defer_begin(world); ecs_iter_t it = ecs_children(world, instance); while (ecs_children_next(&it)) { - if (ecs_table_has_id(world, it.table, ecs_pair(EcsTemplate, script))) { + if (ecs_table_has_id(world, it.table, ecs_pair(EcsScriptTemplate, script))) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_delete(world, it.entities[i]); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index c3d49de079..30692c6799 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -8,11 +8,11 @@ #ifdef FLECS_SCRIPT #include "script.h" -ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); -ECS_DECLARE(EcsTemplate); +ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); +ECS_DECLARE(EcsScriptTemplate); static -void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { +void flecs_template_set_event_free(EcsScriptTemplateSetEvent *ptr) { if (ptr->entities != &ptr->entity_storage) { ecs_os_free(ptr->entities); } @@ -22,7 +22,7 @@ void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { } static -ECS_MOVE(EcsTemplateSetEvent, dst, src, { +ECS_MOVE(EcsScriptTemplateSetEvent, dst, src, { flecs_template_set_event_free(dst); *dst = *src; @@ -40,7 +40,7 @@ ECS_MOVE(EcsTemplateSetEvent, dst, src, { }) static -ECS_DTOR(EcsTemplateSetEvent, ptr, { +ECS_DTOR(EcsScriptTemplateSetEvent, ptr, { flecs_template_set_event_free(ptr); }) @@ -100,7 +100,7 @@ void flecs_script_template_defer_on_set( const ecs_type_info_t *ti, void *data) { - EcsTemplateSetEvent evt; + EcsScriptTemplateSetEvent evt; if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { /* This should be true for the vast majority of templates */ @@ -117,7 +117,7 @@ void flecs_script_template_defer_on_set( evt.template_entity = template_entity; ecs_enqueue(it->world, &(ecs_event_desc_t){ - .event = ecs_id(EcsTemplateSetEvent), + .event = ecs_id(EcsScriptTemplateSetEvent), .entity = EcsAny, .param = &evt }); @@ -229,7 +229,7 @@ void flecs_on_template_set_event( { ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); - EcsTemplateSetEvent *evt = it->param; + EcsScriptTemplateSetEvent *evt = it->param; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); @@ -564,22 +564,22 @@ int flecs_script_eval_template( void flecs_script_template_import( ecs_world_t *world) { - ECS_COMPONENT_DEFINE(world, EcsTemplateSetEvent); - ECS_TAG_DEFINE(world, EcsTemplate); + ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsScriptTemplate); - ecs_add_id(world, EcsTemplate, EcsPairIsTag); + ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); - ecs_set_hooks(world, EcsTemplateSetEvent, { + ecs_set_hooks(world, EcsScriptTemplateSetEvent, { .ctor = flecs_default_ctor, - .move = ecs_move(EcsTemplateSetEvent), - .dtor = ecs_dtor(EcsTemplateSetEvent), + .move = ecs_move(EcsScriptTemplateSetEvent), + .dtor = ecs_dtor(EcsScriptTemplateSetEvent), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), .query.terms = {{ .id = EcsAny }}, - .events = { ecs_id(EcsTemplateSetEvent) }, + .events = { ecs_id(EcsScriptTemplateSetEvent) }, .callback = flecs_on_template_set_event }); } diff --git a/src/addons/script/template.h b/src/addons/script/template.h index 28e49f2633..a3ab59b039 100644 --- a/src/addons/script/template.h +++ b/src/addons/script/template.h @@ -6,7 +6,7 @@ #ifndef FLECS_SCRIPT_TEMPLATE_H #define FLECS_SCRIPT_TEMPLATE_H -extern ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); +extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); struct ecs_script_template_t { /* Template handle */ @@ -31,7 +31,7 @@ struct ecs_script_template_t { #define ECS_TEMPLATE_SMALL_SIZE (36) /* Event used for deferring template instantiation */ -typedef struct EcsTemplateSetEvent { +typedef struct EcsScriptTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; @@ -40,7 +40,7 @@ typedef struct EcsTemplateSetEvent { /* Storage for small template types */ char data_storage[ECS_TEMPLATE_SMALL_SIZE]; ecs_entity_t entity_storage; -} EcsTemplateSetEvent; +} EcsScriptTemplateSetEvent; int flecs_script_eval_template( ecs_script_eval_visitor_t *v, diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 9c63aeed20..2508e74dac 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -473,7 +473,7 @@ int flecs_script_eval_entity( node->parent = v->entity; if (v->template_entity) { - ecs_add_pair(v->world, node->eval, EcsTemplate, v->template_entity); + ecs_add_pair(v->world, node->eval, EcsScriptTemplate, v->template_entity); } if (is_slot) { diff --git a/test/script/project.json b/test/script/project.json index 4e99947c9a..b49346e1d7 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -609,7 +609,9 @@ "remainder_after_initializer_w_newlines", "remainder_after_initializer_before_parens", "space_at_start", - "newline_at_start" + "newline_at_start", + "global_const_var", + "scoped_global_const_var" ] }, { "id": "ExprAst", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index e754a3f55d..23b1e6a92a 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -5099,3 +5099,51 @@ void Expr_newline_at_start(void) { ecs_fini(world); } + +void Expr_global_const_var(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 10; + + test_assert(0 != ecs_const_var(world, { + .name = "FOO", + .type = ecs_id(ecs_i32_t), + .value = &v + })); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, "$FOO + 20", + &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 30); + + ecs_fini(world); +} + +void Expr_scoped_global_const_var(void) { + ecs_world_t *world = ecs_init(); + + int32_t v = 10; + + ecs_entity_t parent = ecs_entity(world, { + .name = "parent" + }); + + test_assert(0 != ecs_const_var(world, { + .name = "FOO", + .parent = parent, + .type = ecs_id(ecs_i32_t), + .value = &v + })); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, + "parent.$FOO + 20", + &ecs_value_ptr(ecs_i32_t, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == 0); + test_int(v, 30); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 092e82a329..a278776aa5 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -597,6 +597,8 @@ void Expr_remainder_after_initializer_w_newlines(void); void Expr_remainder_after_initializer_before_parens(void); 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); // Testsuite 'ExprAst' void ExprAst_binary_f32_var_add_f32_var(void); @@ -3104,6 +3106,14 @@ bake_test_case Expr_testcases[] = { { "newline_at_start", Expr_newline_at_start + }, + { + "global_const_var", + Expr_global_const_var + }, + { + "scoped_global_const_var", + Expr_scoped_global_const_var } }; @@ -3810,7 +3820,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 191, + 193, Expr_testcases, 1, Expr_params From 4388be9eefab5e86e5e2aafb4f97341669a5199e Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 12 Dec 2024 19:54:18 -0800 Subject: [PATCH 64/83] 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 731dcdd532..9d71badab5 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 a9a17c1d54..107949e8e4 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 d642f0f387..520b291df7 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 932bf7e6d0..fd76c47c74 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 e0e3f74292..5559c2ec1b 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 58126678a9..0e35e437ed 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 1882fe5f4a..28fb95538b 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 6cf645478d..90ab0ad5c0 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 912521baad..8afe020092 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 39aea3361a..a9041873bf 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 44cdfd1625..4db0207c70 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 4f99b80bb3..95697b3d15 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 79f6165d47..8482f6df6c 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 a6b2132401..0d88ae9385 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 98d02607e9..b2ffb0e308 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 03822b09b3..8c378bcf86 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 1bc3e76fbd..696e39b2eb 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 9833f5b689..0000000000 --- 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 533cea40c7..bddde1a61e 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 6724fa2c75..6162963470 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 a671431177..3179c45ea8 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 0091100949..b81d4181e5 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 f61cce8d05..ab62b6809b 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 b49346e1d7..3aed2a0c1d 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 98799af354..ef6df52624 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 23b1e6a92a..1472d35571 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 56674f23c3..cfd32b1e56 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 a278776aa5..6d59483892 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 }, { From bb99459e685f8f9a84929019e4bfb3c568861f67 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 07:10:13 +0000 Subject: [PATCH 65/83] Implement support for entity name expressions --- distr/flecs.c | 111 +++++++++++++++++-- src/addons/script/ast.c | 15 ++- src/addons/script/ast.h | 4 +- src/addons/script/parser.c | 46 ++++++-- src/addons/script/visit_eval.c | 43 +++++++- src/addons/script/visit_free.c | 3 + test/script/project.json | 12 +- test/script/src/Eval.c | 196 +++++++++++++++++++++++++++++++++ test/script/src/main.c | 52 ++++++++- 9 files changed, 456 insertions(+), 26 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 9d71badab5..5b1d764e9f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4729,6 +4729,7 @@ struct ecs_script_entity_t { bool name_is_var; bool kind_w_expr; ecs_script_scope_t *scope; + ecs_expr_node_t *name_expr; // Populated during eval ecs_script_entity_t *parent; @@ -4800,7 +4801,8 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, - const char *name); + const char *name, + bool name_is_expr); ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_script_parser_t *parser, @@ -54953,7 +54955,8 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, - const char *name) + const char *name, + bool name_is_expr) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); @@ -54967,12 +54970,24 @@ ecs_script_entity_t* flecs_script_insert_entity( result->name = name; + if (name_is_expr) { + parser->significant_newline = false; + result->name_expr = (ecs_expr_node_t*) + flecs_expr_interpolated_string(parser, name); + if (!result->name_expr) { + goto error; + } + parser->significant_newline = true; + } + ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); result->scope = entity_scope; flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); return result; +error: + return NULL; } static @@ -56098,7 +56113,7 @@ const char* flecs_script_comma_expr( if (is_base_list) { flecs_script_insert_pair_tag(parser, "IsA", Token(0)); } else { - flecs_script_insert_entity(parser, Token(0)); + flecs_script_insert_entity(parser, Token(0), false); } LookAhead_1(',', @@ -56256,8 +56271,12 @@ const char* flecs_script_stmt( { ParserBegin; + bool name_is_expr_0 = false; + bool name_is_expr_1 = false; + Parse( case EcsTokIdentifier: goto identifier; + case EcsTokString: goto string_name; case '{': return flecs_script_scope(parser, flecs_script_insert_scope(parser), pos); case '(': goto paren; @@ -56272,6 +56291,15 @@ const char* flecs_script_stmt( EcsTokEndOfStatement: EndOfRule; ); +string_name: + /* If this is an interpolated string, we need to evaluate it as expression + * at evaluation time. Otherwise we can just use the string as name. The + * latter is useful if an entity name contains special characters that are + * not allowed in identifier tokens. */ + if (flecs_string_is_interpolated(Token(0))) { + name_is_expr_0 = true; + } + identifier: { // enterprise } (end of scope) LookAhead_1('}', @@ -56283,12 +56311,16 @@ identifier: { case '{': { return flecs_script_scope(parser, flecs_script_insert_entity( - parser, Token(0))->scope, pos); + parser, Token(0), name_is_expr_0)->scope, pos); } // Red, case ',': { - flecs_script_insert_entity(parser, Token(0)); + if (name_is_expr_0) { + Error("expression not allowed as entity name here"); + } + + flecs_script_insert_entity(parser, Token(0), false); pos = flecs_script_comma_expr(parser, pos, false); EndOfRule; } @@ -56300,7 +56332,7 @@ identifier: { pos = lookahead; return flecs_script_scope(parser, flecs_script_insert_entity( - parser, Token(0))->scope, pos); + parser, Token(0), name_is_expr_0)->scope, pos); ) goto insert_tag; @@ -56330,6 +56362,11 @@ identifier: { case EcsTokIdentifier: { goto identifier_identifier; } + + // Spaceship "enterprise" + case EcsTokString: { + goto identifier_string; + } ) } @@ -56647,7 +56684,7 @@ identifier_colon: { // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0)); + parser, Token(0), name_is_expr_0); Scope(entity->scope, flecs_script_insert_pair_tag(parser, "IsA", Token(2)); @@ -56673,7 +56710,7 @@ identifier_colon: { // x = identifier_assign: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0)); + parser, Token(0), name_is_expr_0); // x = Position: LookAhead_2(EcsTokIdentifier, ':', @@ -56711,9 +56748,15 @@ identifier_assign: { } // Spaceship enterprise +identifier_string: { + if (flecs_string_is_interpolated(Token(1))) { + name_is_expr_1 = true; + } +} + identifier_identifier: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(1)); + parser, Token(1), name_is_expr_1); entity->kind = Token(0); // Spaceship enterprise : @@ -56761,7 +56804,7 @@ identifier_paren: { // SpaceShip(expr)\n EcsTokEndOfStatement: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL); + parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = @@ -56775,7 +56818,7 @@ identifier_paren: { // SpaceShip(expr) { case '{': { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL); + parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = @@ -60554,7 +60597,48 @@ int flecs_script_eval_entity( return 0; } - node->eval = flecs_script_create_entity(v, node->name); + ecs_expr_node_t *name_expr = node->name_expr; + if (name_expr) { + ecs_script_t *script = &v->base.script->pub; + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = ecs_id(ecs_string_t), + .runtime = v->r + }; + + if (!name_expr->type_info) { + if (flecs_expr_visit_type(script, name_expr, &desc)) { + return -1; + } + + if (flecs_expr_visit_fold(script, &node->name_expr, &desc)) { + return -1; + } + + name_expr = node->name_expr; + } + + ecs_value_t value = { .type = ecs_id(ecs_string_t) }; + if (flecs_expr_visit_eval(script, name_expr, &desc, &value)) { + return -1; + } + + char *name = *(char**)value.ptr; + if (!name) { + flecs_script_eval_error(v, node, "failed to evaluate entity name"); + return -1; + } + + node->eval = flecs_script_create_entity(v, name); + + ecs_value_free(script->world, value.type, value.ptr); + } else { + node->eval = flecs_script_create_entity(v, node->name); + } + node->parent = v->entity; if (v->template_entity) { @@ -61444,6 +61528,9 @@ void flecs_script_entity_free( ecs_script_entity_t *node) { flecs_script_scope_free(v, node->scope); + if (node->name_expr) { + flecs_expr_visit_free(&v->script->pub, node->name_expr); + } } static diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index 2b91e03533..3a9ae1365a 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -57,7 +57,8 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, - const char *name) + const char *name, + bool name_is_expr) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); @@ -71,12 +72,24 @@ ecs_script_entity_t* flecs_script_insert_entity( result->name = name; + if (name_is_expr) { + parser->significant_newline = false; + result->name_expr = (ecs_expr_node_t*) + flecs_expr_interpolated_string(parser, name); + if (!result->name_expr) { + goto error; + } + parser->significant_newline = true; + } + ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); result->scope = entity_scope; flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); return result; +error: + return NULL; } static diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index 0e35e437ed..3be22b5c7b 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -77,6 +77,7 @@ struct ecs_script_entity_t { bool name_is_var; bool kind_w_expr; ecs_script_scope_t *scope; + ecs_expr_node_t *name_expr; // Populated during eval ecs_script_entity_t *parent; @@ -148,7 +149,8 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, - const char *name); + const char *name, + bool name_is_expr); ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_script_parser_t *parser, diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index bddde1a61e..3ed25c0c51 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -80,7 +80,7 @@ const char* flecs_script_comma_expr( if (is_base_list) { flecs_script_insert_pair_tag(parser, "IsA", Token(0)); } else { - flecs_script_insert_entity(parser, Token(0)); + flecs_script_insert_entity(parser, Token(0), false); } LookAhead_1(',', @@ -238,8 +238,12 @@ const char* flecs_script_stmt( { ParserBegin; + bool name_is_expr_0 = false; + bool name_is_expr_1 = false; + Parse( case EcsTokIdentifier: goto identifier; + case EcsTokString: goto string_name; case '{': return flecs_script_scope(parser, flecs_script_insert_scope(parser), pos); case '(': goto paren; @@ -254,6 +258,15 @@ const char* flecs_script_stmt( EcsTokEndOfStatement: EndOfRule; ); +string_name: + /* If this is an interpolated string, we need to evaluate it as expression + * at evaluation time. Otherwise we can just use the string as name. The + * latter is useful if an entity name contains special characters that are + * not allowed in identifier tokens. */ + if (flecs_string_is_interpolated(Token(0))) { + name_is_expr_0 = true; + } + identifier: { // enterprise } (end of scope) LookAhead_1('}', @@ -265,12 +278,16 @@ identifier: { case '{': { return flecs_script_scope(parser, flecs_script_insert_entity( - parser, Token(0))->scope, pos); + parser, Token(0), name_is_expr_0)->scope, pos); } // Red, case ',': { - flecs_script_insert_entity(parser, Token(0)); + if (name_is_expr_0) { + Error("expression not allowed as entity name here"); + } + + flecs_script_insert_entity(parser, Token(0), false); pos = flecs_script_comma_expr(parser, pos, false); EndOfRule; } @@ -282,7 +299,7 @@ identifier: { pos = lookahead; return flecs_script_scope(parser, flecs_script_insert_entity( - parser, Token(0))->scope, pos); + parser, Token(0), name_is_expr_0)->scope, pos); ) goto insert_tag; @@ -312,6 +329,11 @@ identifier: { case EcsTokIdentifier: { goto identifier_identifier; } + + // Spaceship "enterprise" + case EcsTokString: { + goto identifier_string; + } ) } @@ -629,7 +651,7 @@ identifier_colon: { // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0)); + parser, Token(0), name_is_expr_0); Scope(entity->scope, flecs_script_insert_pair_tag(parser, "IsA", Token(2)); @@ -655,7 +677,7 @@ identifier_colon: { // x = identifier_assign: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0)); + parser, Token(0), name_is_expr_0); // x = Position: LookAhead_2(EcsTokIdentifier, ':', @@ -693,9 +715,15 @@ identifier_assign: { } // Spaceship enterprise +identifier_string: { + if (flecs_string_is_interpolated(Token(1))) { + name_is_expr_1 = true; + } +} + identifier_identifier: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(1)); + parser, Token(1), name_is_expr_1); entity->kind = Token(0); // Spaceship enterprise : @@ -743,7 +771,7 @@ identifier_paren: { // SpaceShip(expr)\n EcsTokEndOfStatement: { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL); + parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = @@ -757,7 +785,7 @@ identifier_paren: { // SpaceShip(expr) { case '{': { ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL); + parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 2508e74dac..b9b8392893 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -469,7 +469,48 @@ int flecs_script_eval_entity( return 0; } - node->eval = flecs_script_create_entity(v, node->name); + ecs_expr_node_t *name_expr = node->name_expr; + if (name_expr) { + ecs_script_t *script = &v->base.script->pub; + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = ecs_id(ecs_string_t), + .runtime = v->r + }; + + if (!name_expr->type_info) { + if (flecs_expr_visit_type(script, name_expr, &desc)) { + return -1; + } + + if (flecs_expr_visit_fold(script, &node->name_expr, &desc)) { + return -1; + } + + name_expr = node->name_expr; + } + + ecs_value_t value = { .type = ecs_id(ecs_string_t) }; + if (flecs_expr_visit_eval(script, name_expr, &desc, &value)) { + return -1; + } + + char *name = *(char**)value.ptr; + if (!name) { + flecs_script_eval_error(v, node, "failed to evaluate entity name"); + return -1; + } + + node->eval = flecs_script_create_entity(v, name); + + ecs_value_free(script->world, value.type, value.ptr); + } else { + node->eval = flecs_script_create_entity(v, node->name); + } + node->parent = v->entity; if (v->template_entity) { diff --git a/src/addons/script/visit_free.c b/src/addons/script/visit_free.c index 06c4433cc7..1bfdc66d96 100644 --- a/src/addons/script/visit_free.c +++ b/src/addons/script/visit_free.c @@ -41,6 +41,9 @@ void flecs_script_entity_free( ecs_script_entity_t *node) { flecs_script_scope_free(v, node->scope); + if (node->name_expr) { + flecs_expr_visit_free(&v->script->pub, node->name_expr); + } } static diff --git a/test/script/project.json b/test/script/project.json index 3aed2a0c1d..235c2a7dc5 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -277,7 +277,17 @@ "assign_call_scoped_func_w_using", "eval_w_vars", "eval_w_runtime", - "component_in_entity_in_with_scope" + "component_in_entity_in_with_scope", + "entity_w_string_name", + "entity_w_interpolated_name", + "entity_w_interpolated_name_w_var", + "entity_w_string_name_w_inherit", + "entity_w_string_name_w_inherit_scope", + "entity_w_string_name_w_kind", + "entity_w_string_name_w_kind_value", + "entity_w_string_name_w_kind_scope", + "entity_w_string_name_w_kind_value_scope", + "entity_w_interpolated_name_w_var_in_scope" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index ef6df52624..cfa433fc21 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -8837,3 +8837,199 @@ void Eval_component_in_entity_in_with_scope(void) { ecs_fini(world); } + +void Eval_entity_w_string_name(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "\"e\" { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + ecs_fini(world); +} + +void Eval_entity_w_interpolated_name(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "\"e_{10 + 20}\" { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + + ecs_fini(world); +} + +void Eval_entity_w_interpolated_name_w_var(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const i = 10" + LINE "\"e_{$i + 20}\" { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_inherit(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + + const char *expr = + HEAD "\"e_{10 + 20}\" : Foo"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, EcsIsA, Foo)); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_inherit_scope(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + + const char *expr = + HEAD "\"e_{10 + 20}\" : Foo { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, EcsIsA, Foo)); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_kind(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Position \"e_{10 + 20}\""; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_kind_value(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Position \"e_{10 + 20}\"(10, 20)"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_kind_scope(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Position \"e_{10 + 20}\" { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Eval_entity_w_string_name_w_kind_value_scope(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Position \"e_{10 + 20}\"(10, 20) { }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e_30"); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Eval_entity_w_interpolated_name_w_var_in_scope(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "parent {" + LINE " const i = 10" + LINE " \"e_{$i + 20}\" { }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "parent.e_30"); + test_assert(e != 0); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 6d59483892..708845b428 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -274,6 +274,16 @@ void Eval_assign_call_scoped_func_w_using(void); void Eval_eval_w_vars(void); void Eval_eval_w_runtime(void); void Eval_component_in_entity_in_with_scope(void); +void Eval_entity_w_string_name(void); +void Eval_entity_w_interpolated_name(void); +void Eval_entity_w_interpolated_name_w_var(void); +void Eval_entity_w_string_name_w_inherit(void); +void Eval_entity_w_string_name_w_inherit_scope(void); +void Eval_entity_w_string_name_w_kind(void); +void Eval_entity_w_string_name_w_kind_value(void); +void Eval_entity_w_string_name_w_kind_scope(void); +void Eval_entity_w_string_name_w_kind_value_scope(void); +void Eval_entity_w_interpolated_name_w_var_in_scope(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -1849,6 +1859,46 @@ bake_test_case Eval_testcases[] = { { "component_in_entity_in_with_scope", Eval_component_in_entity_in_with_scope + }, + { + "entity_w_string_name", + Eval_entity_w_string_name + }, + { + "entity_w_interpolated_name", + Eval_entity_w_interpolated_name + }, + { + "entity_w_interpolated_name_w_var", + Eval_entity_w_interpolated_name_w_var + }, + { + "entity_w_string_name_w_inherit", + Eval_entity_w_string_name_w_inherit + }, + { + "entity_w_string_name_w_inherit_scope", + Eval_entity_w_string_name_w_inherit_scope + }, + { + "entity_w_string_name_w_kind", + Eval_entity_w_string_name_w_kind + }, + { + "entity_w_string_name_w_kind_value", + Eval_entity_w_string_name_w_kind_value + }, + { + "entity_w_string_name_w_kind_scope", + Eval_entity_w_string_name_w_kind_scope + }, + { + "entity_w_string_name_w_kind_value_scope", + Eval_entity_w_string_name_w_kind_value_scope + }, + { + "entity_w_interpolated_name_w_var_in_scope", + Eval_entity_w_interpolated_name_w_var_in_scope } }; @@ -3879,7 +3929,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 265, + 275, Eval_testcases }, { From 8b7c330e45a382c12b9bfa4dfc65833c5efccf59 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 08:09:55 +0000 Subject: [PATCH 66/83] Implement support for for range loops --- distr/flecs.c | 192 ++++++++++++++++++++++-- src/addons/script/ast.c | 14 ++ src/addons/script/ast.h | 14 +- src/addons/script/expr/util.c | 6 + src/addons/script/expr/visit_type.c | 6 + src/addons/script/parser.c | 29 ++++ src/addons/script/template.c | 5 + src/addons/script/tokenizer.c | 10 ++ src/addons/script/tokenizer.h | 29 ++-- src/addons/script/visit_eval.c | 42 ++++++ src/addons/script/visit_free.c | 14 ++ src/addons/script/visit_to_str.c | 23 +++ test/script/project.json | 6 +- test/script/src/Eval.c | 220 ++++++++++++++++++++++++++++ test/script/src/main.c | 22 ++- 15 files changed, 602 insertions(+), 30 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 5b1d764e9f..c8cb345408 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4569,19 +4569,22 @@ typedef enum ecs_script_token_kind_t { EcsTokAnd = 106, EcsTokOr = 107, EcsTokMatch = 108, - EcsTokShiftLeft = 109, - EcsTokShiftRight = 110, - EcsTokIdentifier = 111, - EcsTokString = 112, - EcsTokNumber = 113, - EcsTokKeywordModule = 114, - EcsTokKeywordUsing = 115, - EcsTokKeywordWith = 116, - EcsTokKeywordIf = 117, - EcsTokKeywordElse = 118, - EcsTokKeywordTemplate = 119, - EcsTokKeywordProp = 120, - EcsTokKeywordConst = 121, + EcsTokRange = 109, + EcsTokShiftLeft = 110, + EcsTokShiftRight = 111, + EcsTokIdentifier = 112, + EcsTokString = 113, + EcsTokNumber = 114, + EcsTokKeywordModule = 115, + EcsTokKeywordUsing = 116, + EcsTokKeywordWith = 117, + EcsTokKeywordIf = 118, + EcsTokKeywordFor = 119, + EcsTokKeywordIn = 120, + EcsTokKeywordElse = 121, + EcsTokKeywordTemplate = 122, + EcsTokKeywordProp = 130, + EcsTokKeywordConst = 131, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { @@ -4676,7 +4679,8 @@ typedef enum ecs_script_node_kind_t { EcsAstConst, EcsAstEntity, EcsAstPairScope, - EcsAstIf + EcsAstIf, + EcsAstFor } ecs_script_node_kind_t; typedef struct ecs_script_node_t { @@ -4790,6 +4794,14 @@ typedef struct ecs_script_if_t { ecs_expr_node_t *expr; } ecs_script_if_t; +typedef struct ecs_script_for_range_t { + ecs_script_node_t node; + const char *loop_var; + ecs_expr_node_t *from; + ecs_expr_node_t *to; + ecs_script_scope_t *scope; +} ecs_script_for_range_t; + #define ecs_script_node(kind, node)\ ((ecs_script_##kind##_t*)node) @@ -4861,6 +4873,9 @@ ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_if_t* flecs_script_insert_if( ecs_script_parser_t *parser); +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_script_parser_t *parser); + #endif /** @@ -55208,6 +55223,20 @@ ecs_script_if_t* flecs_script_insert_if( return result; } +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_script_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_for_range_t *result = flecs_ast_new( + parser, ecs_script_for_range_t, EcsAstFor); + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); + return result; +} + #endif /** @@ -56288,6 +56317,7 @@ const char* flecs_script_stmt( case EcsTokKeywordProp: goto prop_var; case EcsTokKeywordConst: goto const_var; case EcsTokKeywordIf: goto if_stmt; + case EcsTokKeywordFor: goto for_stmt; EcsTokEndOfStatement: EndOfRule; ); @@ -56547,6 +56577,34 @@ if_stmt: { ) } +// for +for_stmt: { + // for $i + Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { + if (Token(1)[0] != '$') { + Error("expected loop variable name"); + } + + Expr(0, { + ecs_expr_node_t *from = EXPR; + Parse_1(EcsTokRange, { + Expr(0, { + ecs_expr_node_t *to = EXPR; + ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); + stmt->loop_var = &Token(1)[1]; + stmt->from = from; + stmt->to = to; + + Parse_1('{', { + return flecs_script_scope(parser, stmt->scope, pos); + }); + }); + }); + }); + + }); +} + // ( paren: { // (Likes, Apples) @@ -58921,6 +58979,11 @@ int flecs_script_template_eval( } } return 0; + case EcsAstFor: + if (ecs_script_visit_scope(v, ((ecs_script_for_range_t*)node)->scope)) { + return -1; + } + return 0; } return flecs_script_eval_node(v, node); @@ -59180,6 +59243,7 @@ const char* flecs_script_token_kind_str( case EcsTokAnd: case EcsTokOr: case EcsTokMatch: + case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: return ""; @@ -59190,6 +59254,8 @@ const char* flecs_script_token_kind_str( case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordModule: return "keyword "; case EcsTokIdentifier: @@ -59243,6 +59309,7 @@ const char* flecs_script_token_str( case EcsTokAnd: return "&&"; case EcsTokOr: return "||"; case EcsTokMatch: return "~="; + case EcsTokRange: return ".."; case EcsTokShiftLeft: return "<<"; case EcsTokShiftRight: return ">>"; case EcsTokKeywordWith: return "with"; @@ -59252,6 +59319,8 @@ const char* flecs_script_token_str( case EcsTokKeywordConst: return "const"; case EcsTokKeywordIf: return "if"; case EcsTokKeywordElse: return "else"; + case EcsTokKeywordFor: return "for"; + case EcsTokKeywordIn: return "in"; case EcsTokKeywordModule: return "module"; case EcsTokIdentifier: return "identifier"; case EcsTokString: return "string"; @@ -59652,6 +59721,8 @@ const char* flecs_script_token( Operator ("/", EcsTokDiv) Operator ("%%", EcsTokMod) Operator ("?", EcsTokOptional) + + OperatorMultiChar ("..", EcsTokRange) Operator (".", EcsTokMember) OperatorMultiChar ("==", EcsTokEq) @@ -59679,6 +59750,8 @@ const char* flecs_script_token( Keyword ("const", EcsTokKeywordConst) Keyword ("if", EcsTokKeywordIf) Keyword ("else", EcsTokKeywordElse) + Keyword ("for", EcsTokKeywordFor) + Keyword ("in", EcsTokKeywordIn) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { @@ -61332,6 +61405,45 @@ int flecs_script_eval_if( return 0; } +static +int flecs_script_eval_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + ecs_value_t from_val = { .type = ecs_id(ecs_i32_t) }; + ecs_value_t to_val = { .type = ecs_id(ecs_i32_t) }; + + if (flecs_script_eval_expr(v, &node->from, &from_val)) { + return -1; + } + + if (flecs_script_eval_expr(v, &node->to, &to_val)) { + return -1; + } + + int32_t from = *(int32_t*)from_val.ptr; + int32_t to = *(int32_t*)to_val.ptr; + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + var->value.ptr = flecs_stack_calloc(&v->r->stack, 4, 4); + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + + int32_t i; + for (i = from; i < to; i ++) { + *(int32_t*)var->value.ptr = i; + if (flecs_script_eval_scope(v, node->scope)) { + return -1; + } + } + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} + static int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, @@ -61414,6 +61526,9 @@ int flecs_script_eval_node( case EcsAstIf: return flecs_script_eval_if( v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_eval_for_range( + v, (ecs_script_for_range_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); @@ -61551,6 +61666,16 @@ void flecs_script_if_free( flecs_expr_visit_free(&v->script->pub, node->expr); } +static +void flecs_script_for_range_free( + ecs_script_visit_t *v, + ecs_script_for_range_t *node) +{ + flecs_expr_visit_free(&v->script->pub, node->from); + flecs_expr_visit_free(&v->script->pub, node->to); + flecs_script_scope_free(v, node->scope); +} + static void flecs_script_component_free( ecs_script_visit_t *v, @@ -61605,6 +61730,10 @@ int flecs_script_stmt_free( flecs_script_if_free(v, (ecs_script_if_t*)node); flecs_free_t(a, ecs_script_if_t, node); break; + case EcsAstFor: + flecs_script_for_range_free(v, (ecs_script_for_range_t*)node); + flecs_free_t(a, ecs_script_for_range_t, node); + break; case EcsAstTag: flecs_free_t(a, ecs_script_tag_t, node); break; @@ -61783,6 +61912,7 @@ const char* flecs_script_node_to_str( case EcsAstEntity: return "entity"; case EcsAstPairScope: return "pair_scope"; case EcsAstIf: return "if"; + case EcsAstFor: return "for"; } return "???"; } @@ -61967,6 +62097,25 @@ void flecs_script_if_to_str( flecs_scriptbuf_appendstr(v, "}\n"); } +static +void flecs_script_for_range_to_str( + ecs_script_str_visitor_t *v, + ecs_script_for_range_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_appendstr(v, node->loop_var); + flecs_scriptbuf_appendstr(v, " "); + flecs_expr_to_str(v, node->from); + flecs_scriptbuf_appendstr(v, " .. "); + flecs_expr_to_str(v, node->to); + + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} + static int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, @@ -62048,6 +62197,9 @@ int flecs_script_stmt_to_str( case EcsAstIf: flecs_script_if_to_str(v, (ecs_script_if_t*)node); break; + case EcsAstFor: + flecs_script_for_range_to_str(v, (ecs_script_for_range_t*)node); + break; } return 0; @@ -75357,6 +75509,7 @@ int flecs_value_unary( case EcsTokAnd: case EcsTokOr: case EcsTokMatch: + case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokIdentifier: @@ -75367,6 +75520,8 @@ int flecs_value_unary( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -75456,6 +75611,7 @@ int flecs_value_binary( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -75464,6 +75620,8 @@ int flecs_value_binary( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -77707,6 +77865,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -77715,6 +77874,8 @@ bool flecs_expr_oper_valid_for_type( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -77787,6 +77948,7 @@ int flecs_expr_type_for_oper( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -77795,6 +77957,8 @@ int flecs_expr_type_for_oper( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index 3a9ae1365a..c0d76cbd62 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -310,4 +310,18 @@ ecs_script_if_t* flecs_script_insert_if( return result; } +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_script_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_for_range_t *result = flecs_ast_new( + parser, ecs_script_for_range_t, EcsAstFor); + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); + return result; +} + #endif diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index 3be22b5c7b..c23f5c1439 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -24,7 +24,8 @@ typedef enum ecs_script_node_kind_t { EcsAstConst, EcsAstEntity, EcsAstPairScope, - EcsAstIf + EcsAstIf, + EcsAstFor } ecs_script_node_kind_t; typedef struct ecs_script_node_t { @@ -138,6 +139,14 @@ typedef struct ecs_script_if_t { ecs_expr_node_t *expr; } ecs_script_if_t; +typedef struct ecs_script_for_range_t { + ecs_script_node_t node; + const char *loop_var; + ecs_expr_node_t *from; + ecs_expr_node_t *to; + ecs_script_scope_t *scope; +} ecs_script_for_range_t; + #define ecs_script_node(kind, node)\ ((ecs_script_##kind##_t*)node) @@ -209,4 +218,7 @@ ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_if_t* flecs_script_insert_if( ecs_script_parser_t *parser); +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_script_parser_t *parser); + #endif diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 4db0207c70..113cfd6480 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -108,6 +108,7 @@ int flecs_value_unary( case EcsTokAnd: case EcsTokOr: case EcsTokMatch: + case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokIdentifier: @@ -118,6 +119,8 @@ int flecs_value_unary( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -207,6 +210,7 @@ int flecs_value_binary( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -215,6 +219,8 @@ int flecs_value_binary( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 8c378bcf86..4178f245db 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -272,6 +272,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -280,6 +281,8 @@ bool flecs_expr_oper_valid_for_type( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -352,6 +355,7 @@ int flecs_expr_type_for_oper( case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: + case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -360,6 +364,8 @@ int flecs_expr_type_for_oper( case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 3ed25c0c51..97faa953ad 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -255,6 +255,7 @@ const char* flecs_script_stmt( case EcsTokKeywordProp: goto prop_var; case EcsTokKeywordConst: goto const_var; case EcsTokKeywordIf: goto if_stmt; + case EcsTokKeywordFor: goto for_stmt; EcsTokEndOfStatement: EndOfRule; ); @@ -514,6 +515,34 @@ if_stmt: { ) } +// for +for_stmt: { + // for $i + Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { + if (Token(1)[0] != '$') { + Error("expected loop variable name"); + } + + Expr(0, { + ecs_expr_node_t *from = EXPR; + Parse_1(EcsTokRange, { + Expr(0, { + ecs_expr_node_t *to = EXPR; + ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); + stmt->loop_var = &Token(1)[1]; + stmt->from = from; + stmt->to = to; + + Parse_1('{', { + return flecs_script_scope(parser, stmt->scope, pos); + }); + }); + }); + }); + + }); +} + // ( paren: { // (Likes, Apples) diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 30692c6799..dc3adc060f 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -388,6 +388,11 @@ int flecs_script_template_eval( } } return 0; + case EcsAstFor: + if (ecs_script_visit_scope(v, ((ecs_script_for_range_t*)node)->scope)) { + return -1; + } + return 0; } return flecs_script_eval_node(v, node); diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 3179c45ea8..0603ef7eb4 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -61,6 +61,7 @@ const char* flecs_script_token_kind_str( case EcsTokAnd: case EcsTokOr: case EcsTokMatch: + case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: return ""; @@ -71,6 +72,8 @@ const char* flecs_script_token_kind_str( case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: case EcsTokKeywordModule: return "keyword "; case EcsTokIdentifier: @@ -124,6 +127,7 @@ const char* flecs_script_token_str( case EcsTokAnd: return "&&"; case EcsTokOr: return "||"; case EcsTokMatch: return "~="; + case EcsTokRange: return ".."; case EcsTokShiftLeft: return "<<"; case EcsTokShiftRight: return ">>"; case EcsTokKeywordWith: return "with"; @@ -133,6 +137,8 @@ const char* flecs_script_token_str( case EcsTokKeywordConst: return "const"; case EcsTokKeywordIf: return "if"; case EcsTokKeywordElse: return "else"; + case EcsTokKeywordFor: return "for"; + case EcsTokKeywordIn: return "in"; case EcsTokKeywordModule: return "module"; case EcsTokIdentifier: return "identifier"; case EcsTokString: return "string"; @@ -533,6 +539,8 @@ const char* flecs_script_token( Operator ("/", EcsTokDiv) Operator ("%%", EcsTokMod) Operator ("?", EcsTokOptional) + + OperatorMultiChar ("..", EcsTokRange) Operator (".", EcsTokMember) OperatorMultiChar ("==", EcsTokEq) @@ -560,6 +568,8 @@ const char* flecs_script_token( Keyword ("const", EcsTokKeywordConst) Keyword ("if", EcsTokKeywordIf) Keyword ("else", EcsTokKeywordElse) + Keyword ("for", EcsTokKeywordFor) + Keyword ("in", EcsTokKeywordIn) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index b81d4181e5..98f3cac1fc 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -41,19 +41,22 @@ typedef enum ecs_script_token_kind_t { EcsTokAnd = 106, EcsTokOr = 107, EcsTokMatch = 108, - EcsTokShiftLeft = 109, - EcsTokShiftRight = 110, - EcsTokIdentifier = 111, - EcsTokString = 112, - EcsTokNumber = 113, - EcsTokKeywordModule = 114, - EcsTokKeywordUsing = 115, - EcsTokKeywordWith = 116, - EcsTokKeywordIf = 117, - EcsTokKeywordElse = 118, - EcsTokKeywordTemplate = 119, - EcsTokKeywordProp = 120, - EcsTokKeywordConst = 121, + EcsTokRange = 109, + EcsTokShiftLeft = 110, + EcsTokShiftRight = 111, + EcsTokIdentifier = 112, + EcsTokString = 113, + EcsTokNumber = 114, + EcsTokKeywordModule = 115, + EcsTokKeywordUsing = 116, + EcsTokKeywordWith = 117, + EcsTokKeywordIf = 118, + EcsTokKeywordFor = 119, + EcsTokKeywordIn = 120, + EcsTokKeywordElse = 121, + EcsTokKeywordTemplate = 122, + EcsTokKeywordProp = 130, + EcsTokKeywordConst = 131, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index b9b8392893..d8df950812 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1204,6 +1204,45 @@ int flecs_script_eval_if( return 0; } +static +int flecs_script_eval_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + ecs_value_t from_val = { .type = ecs_id(ecs_i32_t) }; + ecs_value_t to_val = { .type = ecs_id(ecs_i32_t) }; + + if (flecs_script_eval_expr(v, &node->from, &from_val)) { + return -1; + } + + if (flecs_script_eval_expr(v, &node->to, &to_val)) { + return -1; + } + + int32_t from = *(int32_t*)from_val.ptr; + int32_t to = *(int32_t*)to_val.ptr; + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + var->value.ptr = flecs_stack_calloc(&v->r->stack, 4, 4); + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + + int32_t i; + for (i = from; i < to; i ++) { + *(int32_t*)var->value.ptr = i; + if (flecs_script_eval_scope(v, node->scope)) { + return -1; + } + } + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} + static int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, @@ -1286,6 +1325,9 @@ int flecs_script_eval_node( case EcsAstIf: return flecs_script_eval_if( v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_eval_for_range( + v, (ecs_script_for_range_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); diff --git a/src/addons/script/visit_free.c b/src/addons/script/visit_free.c index 1bfdc66d96..275cecffca 100644 --- a/src/addons/script/visit_free.c +++ b/src/addons/script/visit_free.c @@ -64,6 +64,16 @@ void flecs_script_if_free( flecs_expr_visit_free(&v->script->pub, node->expr); } +static +void flecs_script_for_range_free( + ecs_script_visit_t *v, + ecs_script_for_range_t *node) +{ + flecs_expr_visit_free(&v->script->pub, node->from); + flecs_expr_visit_free(&v->script->pub, node->to); + flecs_script_scope_free(v, node->scope); +} + static void flecs_script_component_free( ecs_script_visit_t *v, @@ -118,6 +128,10 @@ int flecs_script_stmt_free( flecs_script_if_free(v, (ecs_script_if_t*)node); flecs_free_t(a, ecs_script_if_t, node); break; + case EcsAstFor: + flecs_script_for_range_free(v, (ecs_script_for_range_t*)node); + flecs_free_t(a, ecs_script_for_range_t, node); + break; case EcsAstTag: flecs_free_t(a, ecs_script_tag_t, node); break; diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index 3629f40b74..f4ef876168 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -116,6 +116,7 @@ const char* flecs_script_node_to_str( case EcsAstEntity: return "entity"; case EcsAstPairScope: return "pair_scope"; case EcsAstIf: return "if"; + case EcsAstFor: return "for"; } return "???"; } @@ -300,6 +301,25 @@ void flecs_script_if_to_str( flecs_scriptbuf_appendstr(v, "}\n"); } +static +void flecs_script_for_range_to_str( + ecs_script_str_visitor_t *v, + ecs_script_for_range_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_appendstr(v, node->loop_var); + flecs_scriptbuf_appendstr(v, " "); + flecs_expr_to_str(v, node->from); + flecs_scriptbuf_appendstr(v, " .. "); + flecs_expr_to_str(v, node->to); + + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} + static int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, @@ -381,6 +401,9 @@ int flecs_script_stmt_to_str( case EcsAstIf: flecs_script_if_to_str(v, (ecs_script_if_t*)node); break; + case EcsAstFor: + flecs_script_for_range_to_str(v, (ecs_script_for_range_t*)node); + break; } return 0; diff --git a/test/script/project.json b/test/script/project.json index 235c2a7dc5..c89e634b8f 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -287,7 +287,11 @@ "entity_w_string_name_w_kind_value", "entity_w_string_name_w_kind_scope", "entity_w_string_name_w_kind_value_scope", - "entity_w_interpolated_name_w_var_in_scope" + "entity_w_interpolated_name_w_var_in_scope", + "for_range", + "for_range_vars", + "for_range_1_4", + "for_range_min_1_2" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index cfa433fc21..44548b0980 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -9033,3 +9033,223 @@ void Eval_entity_w_interpolated_name_w_var_in_scope(void) { ecs_fini(world); } + +void Eval_for_range(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "for $i in 0..3 {" + LINE " \"e_{$i}\" {" + LINE " Position: {$i, $i * 2}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e_0 = ecs_lookup(world, "e_0"); + ecs_entity_t e_1 = ecs_lookup(world, "e_1"); + ecs_entity_t e_2 = ecs_lookup(world, "e_2"); + ecs_entity_t e_3 = ecs_lookup(world, "e_3"); + + test_assert(e_0 != 0); + test_assert(e_1 != 0); + test_assert(e_2 != 0); + test_assert(e_3 == 0); + + { + const Position *p = ecs_get(world, e_0, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + } + + { + const Position *p = ecs_get(world, e_1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + } + + { + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + } + + ecs_fini(world); +} + +void Eval_for_range_vars(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "const x = 0" + LINE "const y = 3" + LINE "for $i in $x..$y {" + LINE " \"e_{$i}\" {" + LINE " Position: {$i, $i * 2}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e_0 = ecs_lookup(world, "e_0"); + ecs_entity_t e_1 = ecs_lookup(world, "e_1"); + ecs_entity_t e_2 = ecs_lookup(world, "e_2"); + ecs_entity_t e_3 = ecs_lookup(world, "e_3"); + + test_assert(e_0 != 0); + test_assert(e_1 != 0); + test_assert(e_2 != 0); + test_assert(e_3 == 0); + + { + const Position *p = ecs_get(world, e_0, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + } + + { + const Position *p = ecs_get(world, e_1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + } + + { + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + } + + ecs_fini(world); +} + +void Eval_for_range_1_4(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "for $i in 1..4 {" + LINE " \"e_{$i}\" {" + LINE " Position: {$i, $i * 2}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e_0 = ecs_lookup(world, "e_0"); + ecs_entity_t e_1 = ecs_lookup(world, "e_1"); + ecs_entity_t e_2 = ecs_lookup(world, "e_2"); + ecs_entity_t e_3 = ecs_lookup(world, "e_3"); + ecs_entity_t e_4 = ecs_lookup(world, "e_4"); + + test_assert(e_0 == 0); + test_assert(e_1 != 0); + test_assert(e_2 != 0); + test_assert(e_3 != 0); + test_assert(e_4 == 0); + + { + const Position *p = ecs_get(world, e_1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + } + + { + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + } + + { + const Position *p = ecs_get(world, e_3, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 6); + } + + ecs_fini(world); +} + +void Eval_for_range_min_1_2(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "for $i in -1..2 {" + LINE " \"e_{$i}\" {" + LINE " Position: {$i, $i * 2}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e_min_1 = ecs_lookup(world, "e_-1"); + ecs_entity_t e_0 = ecs_lookup(world, "e_0"); + ecs_entity_t e_1 = ecs_lookup(world, "e_1"); + ecs_entity_t e_2 = ecs_lookup(world, "e_2"); + + test_assert(e_min_1 != 0); + test_assert(e_0 != 0); + test_assert(e_1 != 0); + test_assert(e_2 == 0); + + { + const Position *p = ecs_get(world, e_min_1, Position); + test_assert(p != NULL); + test_int(p->x, -1); + test_int(p->y, -2); + } + + { + const Position *p = ecs_get(world, e_0, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + } + + { + const Position *p = ecs_get(world, e_1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + } + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 708845b428..199ee9bf54 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -284,6 +284,10 @@ void Eval_entity_w_string_name_w_kind_value(void); void Eval_entity_w_string_name_w_kind_scope(void); void Eval_entity_w_string_name_w_kind_value_scope(void); void Eval_entity_w_interpolated_name_w_var_in_scope(void); +void Eval_for_range(void); +void Eval_for_range_vars(void); +void Eval_for_range_1_4(void); +void Eval_for_range_min_1_2(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -1899,6 +1903,22 @@ bake_test_case Eval_testcases[] = { { "entity_w_interpolated_name_w_var_in_scope", Eval_entity_w_interpolated_name_w_var_in_scope + }, + { + "for_range", + Eval_for_range + }, + { + "for_range_vars", + Eval_for_range_vars + }, + { + "for_range_1_4", + Eval_for_range_1_4 + }, + { + "for_range_min_1_2", + Eval_for_range_min_1_2 } }; @@ -3929,7 +3949,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 275, + 279, Eval_testcases }, { From c439fe245b911d1e78157a66af608c4220678201 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 15:04:46 -0800 Subject: [PATCH 67/83] Add flecs.script.math.abs --- distr/flecs.c | 4 ++++ src/addons/script/functions_math.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/distr/flecs.c b/distr/flecs.c index c8cb345408..3eff520250 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -55735,6 +55735,8 @@ FLECS_MATH_FUNC_F64(ceil, ceil(x)) FLECS_MATH_FUNC_F64(floor, floor(x)) FLECS_MATH_FUNC_F64(round, round(x)) +FLECS_MATH_FUNC_F64(abs, fabs(x)) + FLECS_API void FlecsScriptMathImport( ecs_world_t *world) @@ -55794,6 +55796,8 @@ void FlecsScriptMathImport( FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); + + FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); } #endif diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index 0a728967f1..b3e7c8cb21 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -133,6 +133,8 @@ FLECS_MATH_FUNC_F64(ceil, ceil(x)) FLECS_MATH_FUNC_F64(floor, floor(x)) FLECS_MATH_FUNC_F64(round, round(x)) +FLECS_MATH_FUNC_F64(abs, fabs(x)) + FLECS_API void FlecsScriptMathImport( ecs_world_t *world) @@ -192,6 +194,8 @@ void FlecsScriptMathImport( FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); + + FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); } #endif From 9e10fc5eb95435af0ccd558febc00ca300b85bdc Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 16:12:11 -0800 Subject: [PATCH 68/83] Fix issue with mod operator, check for division by zero --- distr/flecs.c | 101 ++++++++++++++++++++++++++-- src/addons/rest.c | 32 ++++++++- src/addons/script/expr/expr.h | 3 + src/addons/script/expr/util.c | 42 ++++++++++++ src/addons/script/expr/visit_type.c | 24 ++++++- test/script/project.json | 6 ++ test/script/src/Expr.c | 86 +++++++++++++++++++++++ test/script/src/main.c | 32 ++++++++- 8 files changed, 315 insertions(+), 11 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 3eff520250..f1a930eccf 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5243,6 +5243,9 @@ bool flecs_string_is_interpolated( char* flecs_string_escape( char *str); +bool flecs_value_is_0( + const ecs_value_t *value); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ @@ -26272,6 +26275,16 @@ static ECS_DTOR(EcsRest, ptr, { static char *rest_last_err; static ecs_os_api_log_t rest_prev_log; +static ecs_os_api_log_t rest_prev_fatal_log; + +static +void flecs_set_prev_log( + ecs_os_api_log_t prev_log, + bool try) +{ + rest_prev_log = try ? NULL : prev_log; + rest_prev_fatal_log = prev_log; +} static void flecs_rest_capture_log( @@ -26282,7 +26295,20 @@ void flecs_rest_capture_log( { (void)file; (void)line; + if (level <= -4) { + /* Make sure to always log fatal errors */ + if (rest_prev_fatal_log) { + ecs_log_enable_colors(true); + rest_prev_fatal_log(level, file, line, msg); + ecs_log_enable_colors(false); + return; + } else { + fprintf(stderr, "%s:%d: %s", file, line, msg); + } + } + #ifdef FLECS_DEBUG + /* In debug mode, log unexpected errors and warnings to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { @@ -26724,7 +26750,7 @@ bool flecs_rest_script( bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; - rest_prev_log = try ? NULL : prev_log; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; script = ecs_script(world, { @@ -26815,6 +26841,9 @@ bool flecs_rest_reply_existing_query( return true; } + bool try = false; + flecs_rest_bool_param(req, "try", &try); + ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { @@ -26841,7 +26870,7 @@ bool flecs_rest_reply_existing_query( ecs_dbg_2("rest: request query '%s'", name); bool prev_color = ecs_log_enable_colors(false); - rest_prev_log = ecs_os_api.log_; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); @@ -26891,7 +26920,7 @@ bool flecs_rest_get_query( ecs_dbg_2("rest: request query '%s'", expr); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; - rest_prev_log = try ? NULL : prev_log; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; ecs_query_t *q = ecs_query(world, { .expr = expr }); @@ -75545,6 +75574,14 @@ int flecs_value_binary( { (void)script; + if (operator == EcsTokDiv || operator == EcsTokMod) { + if (flecs_value_is_0(right)) { + ecs_err("%s: division by zero", + script->name ? script->name : "anonymous script"); + return -1; + } + } + switch(operator) { case EcsTokAdd: ECS_BINARY_OP(left, right, out, +); @@ -75705,6 +75742,40 @@ char* flecs_string_escape( return out + 1; } +bool flecs_value_is_0( + const ecs_value_t *value) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + if (type == ecs_id(ecs_i8_t)) { + return *(ecs_i8_t*)ptr == 0; + } else if (type == ecs_id(ecs_i16_t)) { + return *(ecs_i16_t*)ptr == 0; + } else if (type == ecs_id(ecs_i32_t)) { + return *(ecs_i32_t*)ptr == 0; + } else if (type == ecs_id(ecs_i64_t)) { + return *(ecs_i64_t*)ptr == 0; + } else if (type == ecs_id(ecs_iptr_t)) { + return *(ecs_iptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_u8_t)) { + return *(ecs_u8_t*)ptr == 0; + } else if (type == ecs_id(ecs_u16_t)) { + return *(ecs_u16_t*)ptr == 0; + } else if (type == ecs_id(ecs_u32_t)) { + return *(ecs_u32_t*)ptr == 0; + } else if (type == ecs_id(ecs_u64_t)) { + return *(ecs_u64_t*)ptr == 0; + } else if (type == ecs_id(ecs_uptr_t)) { + return *(ecs_uptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_f32_t)) { + return ECS_EQZERO(*(ecs_f32_t*)ptr); + } else if (type == ecs_id(ecs_f64_t)) { + return ECS_EQZERO(*(ecs_f64_t*)ptr); + } else { + return true; + } +} + #endif /** @@ -77828,6 +77899,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokSub: case EcsTokMul: case EcsTokDiv: + case EcsTokMod: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: @@ -77863,7 +77935,6 @@ bool flecs_expr_oper_valid_for_type( case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: - case EcsTokMod: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: @@ -77899,6 +77970,18 @@ int flecs_expr_type_for_oper( ecs_world_t *world = script->world; ecs_expr_node_t *left = node->left, *right = node->right; + if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (right->kind == EcsExprValue) { + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; + ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; + if (flecs_value_is_0(&v)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + return -1; + } + } + } + switch(node->operator) { case EcsTokDiv: /* Result type of a division is always a float */ @@ -77909,6 +77992,15 @@ int flecs_expr_type_for_oper( *operand_type = left->type; *result_type = left->type; } + + return 0; + case EcsTokMod: + /* Mod only accepts integer operands, and results in an integer. We + * could disallow doing mod on floating point types, but in practice + * that would likely just result in code having to do a manual + * conversion to an integer. */ + *operand_type = ecs_id(ecs_i64_t); + *result_type = ecs_id(ecs_i64_t); return 0; case EcsTokAnd: case EcsTokOr: @@ -77946,7 +78038,6 @@ int flecs_expr_type_for_oper( case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: - case EcsTokMod: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: diff --git a/src/addons/rest.c b/src/addons/rest.c index 42c7183b2a..4f3ce9840e 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -63,6 +63,16 @@ static ECS_DTOR(EcsRest, ptr, { static char *rest_last_err; static ecs_os_api_log_t rest_prev_log; +static ecs_os_api_log_t rest_prev_fatal_log; + +static +void flecs_set_prev_log( + ecs_os_api_log_t prev_log, + bool try) +{ + rest_prev_log = try ? NULL : prev_log; + rest_prev_fatal_log = prev_log; +} static void flecs_rest_capture_log( @@ -73,7 +83,20 @@ void flecs_rest_capture_log( { (void)file; (void)line; + if (level <= -4) { + /* Make sure to always log fatal errors */ + if (rest_prev_fatal_log) { + ecs_log_enable_colors(true); + rest_prev_fatal_log(level, file, line, msg); + ecs_log_enable_colors(false); + return; + } else { + fprintf(stderr, "%s:%d: %s", file, line, msg); + } + } + #ifdef FLECS_DEBUG + /* In debug mode, log unexpected errors and warnings to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { @@ -515,7 +538,7 @@ bool flecs_rest_script( bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; - rest_prev_log = try ? NULL : prev_log; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; script = ecs_script(world, { @@ -606,6 +629,9 @@ bool flecs_rest_reply_existing_query( return true; } + bool try = false; + flecs_rest_bool_param(req, "try", &try); + ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { @@ -632,7 +658,7 @@ bool flecs_rest_reply_existing_query( ecs_dbg_2("rest: request query '%s'", name); bool prev_color = ecs_log_enable_colors(false); - rest_prev_log = ecs_os_api.log_; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); @@ -682,7 +708,7 @@ bool flecs_rest_get_query( ecs_dbg_2("rest: request query '%s'", expr); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; - rest_prev_log = try ? NULL : prev_log; + flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_rest_capture_log; ecs_query_t *q = ecs_query(world, { .expr = expr }); diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 8afe020092..27c2b845f2 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -57,6 +57,9 @@ bool flecs_string_is_interpolated( char* flecs_string_escape( char *str); +bool flecs_value_is_0( + const ecs_value_t *value); + #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/util.c b/src/addons/script/expr/util.c index 113cfd6480..77d3d14532 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -140,6 +140,14 @@ int flecs_value_binary( { (void)script; + if (operator == EcsTokDiv || operator == EcsTokMod) { + if (flecs_value_is_0(right)) { + ecs_err("%s: division by zero", + script->name ? script->name : "anonymous script"); + return -1; + } + } + switch(operator) { case EcsTokAdd: ECS_BINARY_OP(left, right, out, +); @@ -300,4 +308,38 @@ char* flecs_string_escape( return out + 1; } +bool flecs_value_is_0( + const ecs_value_t *value) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + if (type == ecs_id(ecs_i8_t)) { + return *(ecs_i8_t*)ptr == 0; + } else if (type == ecs_id(ecs_i16_t)) { + return *(ecs_i16_t*)ptr == 0; + } else if (type == ecs_id(ecs_i32_t)) { + return *(ecs_i32_t*)ptr == 0; + } else if (type == ecs_id(ecs_i64_t)) { + return *(ecs_i64_t*)ptr == 0; + } else if (type == ecs_id(ecs_iptr_t)) { + return *(ecs_iptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_u8_t)) { + return *(ecs_u8_t*)ptr == 0; + } else if (type == ecs_id(ecs_u16_t)) { + return *(ecs_u16_t*)ptr == 0; + } else if (type == ecs_id(ecs_u32_t)) { + return *(ecs_u32_t*)ptr == 0; + } else if (type == ecs_id(ecs_u64_t)) { + return *(ecs_u64_t*)ptr == 0; + } else if (type == ecs_id(ecs_uptr_t)) { + return *(ecs_uptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_f32_t)) { + return ECS_EQZERO(*(ecs_f32_t*)ptr); + } else if (type == ecs_id(ecs_f64_t)) { + return ECS_EQZERO(*(ecs_f64_t*)ptr); + } else { + return true; + } +} + #endif diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 4178f245db..d2ac219dc2 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -231,6 +231,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokSub: case EcsTokMul: case EcsTokDiv: + case EcsTokMod: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: @@ -266,7 +267,6 @@ bool flecs_expr_oper_valid_for_type( case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: - case EcsTokMod: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: @@ -302,6 +302,18 @@ int flecs_expr_type_for_oper( ecs_world_t *world = script->world; ecs_expr_node_t *left = node->left, *right = node->right; + if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (right->kind == EcsExprValue) { + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; + ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; + if (flecs_value_is_0(&v)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + return -1; + } + } + } + switch(node->operator) { case EcsTokDiv: /* Result type of a division is always a float */ @@ -312,6 +324,15 @@ int flecs_expr_type_for_oper( *operand_type = left->type; *result_type = left->type; } + + return 0; + case EcsTokMod: + /* Mod only accepts integer operands, and results in an integer. We + * could disallow doing mod on floating point types, but in practice + * that would likely just result in code having to do a manual + * conversion to an integer. */ + *operand_type = ecs_id(ecs_i64_t); + *result_type = ecs_id(ecs_i64_t); return 0; case EcsTokAnd: case EcsTokOr: @@ -349,7 +370,6 @@ int flecs_expr_type_for_oper( case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: - case EcsTokMod: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: diff --git a/test/script/project.json b/test/script/project.json index c89e634b8f..eaec42167e 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -443,6 +443,12 @@ "sub_3_int_literals", "mul_3_int_literals", "div_3_int_literals", + "mod_2_int_literals", + "mod_2_flt_literals", + "div_by_0", + "div_by_0_var", + "mod_by_0", + "mod_by_0_var", "int_to_bool", "bool_to_int", "bool_to_uint", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 1472d35571..597bda6b1c 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -165,6 +165,92 @@ void Expr_div_3_int_literals(void) { ecs_fini(world); } +void Expr_mod_2_int_literals(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 % 3", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_flt(*(ecs_i64_t*)v.ptr, 1); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_mod_2_flt_literals(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 % 3.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_flt(*(ecs_i64_t*)v.ptr, 1); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_div_by_0(void) { + ecs_world_t *world = ecs_init(); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + ecs_log_set_level(-4); + test_assert(ecs_expr_parse(world, "10 / 0", &desc) == NULL); + + ecs_fini(world); +} + +void Expr_div_by_0_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i32_t); + *(int32_t*)var->value.ptr = 0; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + ecs_log_set_level(-4); + test_assert(ecs_expr_run(world, "10 / $foo", &v, &desc) == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_mod_by_0(void) { + ecs_world_t *world = ecs_init(); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + ecs_log_set_level(-4); + test_assert(ecs_expr_parse(world, "10 % 0", &desc) == NULL); + + ecs_fini(world); +} + +void Expr_mod_by_0_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i32_t); + *(int32_t*)var->value.ptr = 0; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + ecs_log_set_level(-4); + test_assert(ecs_expr_run(world, "10 % $foo", &v, &desc) == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Expr_int_to_bool(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 199ee9bf54..51283c617c 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -430,6 +430,12 @@ void Expr_add_3_int_literals_twice(void); void Expr_sub_3_int_literals(void); void Expr_mul_3_int_literals(void); void Expr_div_3_int_literals(void); +void Expr_mod_2_int_literals(void); +void Expr_mod_2_flt_literals(void); +void Expr_div_by_0(void); +void Expr_div_by_0_var(void); +void Expr_mod_by_0(void); +void Expr_mod_by_0_var(void); void Expr_int_to_bool(void); void Expr_bool_to_int(void); void Expr_bool_to_uint(void); @@ -2469,6 +2475,30 @@ bake_test_case Expr_testcases[] = { "div_3_int_literals", Expr_div_3_int_literals }, + { + "mod_2_int_literals", + Expr_mod_2_int_literals + }, + { + "mod_2_flt_literals", + Expr_mod_2_flt_literals + }, + { + "div_by_0", + Expr_div_by_0 + }, + { + "div_by_0_var", + Expr_div_by_0_var + }, + { + "mod_by_0", + Expr_mod_by_0 + }, + { + "mod_by_0_var", + Expr_mod_by_0_var + }, { "int_to_bool", Expr_int_to_bool @@ -3970,7 +4000,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 206, + 212, Expr_testcases, 1, Expr_params From 976ae685b56546d5f8dafe8189c33aba38a209a2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 16:21:26 -0800 Subject: [PATCH 69/83] Fix issue with reporting errors that contain format characters --- distr/flecs.c | 4 ++-- src/addons/rest.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index f1a930eccf..0283ffdf7b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -26762,7 +26762,7 @@ bool flecs_rest_script( char *err = flecs_rest_get_captured_log(); char *escaped_err = flecs_astresc('"', err); if (escaped_err) { - flecs_reply_error(reply, escaped_err); + flecs_reply_error(reply, "%s", escaped_err); } else { flecs_reply_error(reply, "error parsing script"); } @@ -26789,7 +26789,7 @@ void flecs_rest_reply_set_captured_log( char *err = flecs_rest_get_captured_log(); if (err) { char *escaped_err = flecs_astresc('"', err); - flecs_reply_error(reply, escaped_err); + flecs_reply_error(reply, "%s", escaped_err); ecs_os_free(escaped_err); ecs_os_free(err); } diff --git a/src/addons/rest.c b/src/addons/rest.c index 4f3ce9840e..940a292926 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -550,7 +550,7 @@ bool flecs_rest_script( char *err = flecs_rest_get_captured_log(); char *escaped_err = flecs_astresc('"', err); if (escaped_err) { - flecs_reply_error(reply, escaped_err); + flecs_reply_error(reply, "%s", escaped_err); } else { flecs_reply_error(reply, "error parsing script"); } @@ -577,7 +577,7 @@ void flecs_rest_reply_set_captured_log( char *err = flecs_rest_get_captured_log(); if (err) { char *escaped_err = flecs_astresc('"', err); - flecs_reply_error(reply, escaped_err); + flecs_reply_error(reply, "%s", escaped_err); ecs_os_free(escaped_err); ecs_os_free(err); } From 0e1757fe1d653df11c54ab7c641c8b4434eb9cfa Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 16:39:07 -0800 Subject: [PATCH 70/83] Implement divide by zero detection in fold visitor --- distr/flecs.c | 16 +++++++++++++--- src/addons/script/expr/visit_fold.c | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 0283ffdf7b..59afd29f03 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -76629,11 +76629,21 @@ int flecs_expr_binary_visit_fold( ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, node->node.type); - ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; + + /* flecs_value_binary will detect division by 0, but we have more + * information about where it happens here. */ + if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (flecs_value_is_0(&rop)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + goto error; + } + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 8482f6df6c..69aa5cf3ec 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -92,11 +92,21 @@ int flecs_expr_binary_visit_fold( ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, node->node.type); - ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; + + /* flecs_value_binary will detect division by 0, but we have more + * information about where it happens here. */ + if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (flecs_value_is_0(&rop)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + goto error; + } + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { From 4f919989602b33451f8e9f0474559f119afe2c5a Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 20:22:20 -0800 Subject: [PATCH 71/83] Implement improved error checking for templates --- distr/flecs.c | 685 ++++++++++++++++++++++++++----- meson.build | 1 + src/addons/script/script.h | 4 + src/addons/script/template.c | 59 +-- src/addons/script/visit_check.c | 494 ++++++++++++++++++++++ src/addons/script/visit_eval.c | 67 +-- src/addons/script/visit_eval.h | 28 ++ src/addons/script/visit_to_str.c | 34 +- test/script/project.json | 4 +- test/script/src/Error.c | 25 ++ test/script/src/ExprAst.c | 64 +++ test/script/src/main.c | 14 +- 12 files changed, 1262 insertions(+), 217 deletions(-) create mode 100644 src/addons/script/visit_check.c diff --git a/distr/flecs.c b/distr/flecs.c index 59afd29f03..7e65b36055 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5484,6 +5484,34 @@ int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); +int flecs_script_check_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node); + +/* Functions shared between check and eval visitor */ + +int flecs_script_eval_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + +int flecs_script_eval_id( + ecs_script_eval_visitor_t *v, + void *node, + ecs_script_id_t *id); + +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node); + +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node); + +ecs_entity_t flecs_script_find_entity_action( + const ecs_world_t *world, + const char *path, + void *ctx); + #endif /** @@ -5598,6 +5626,10 @@ void flecs_script_register_builtin_functions( void flecs_function_import( ecs_world_t *world); +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H @@ -58970,11 +59002,6 @@ int flecs_script_template_eval( case EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: - if (v->is_with_scope) { - flecs_script_eval_error(v, node, "invalid component in with scope"); - return -1; - } - break; case EcsAstEntity: case EcsAstScope: case EcsAstDefaultComponent: @@ -58986,40 +59013,18 @@ int flecs_script_template_eval( case EcsAstAnnotation: case EcsAstConst: case EcsAstPairScope: - case EcsAstTemplate: + case EcsAstWith: + case EcsAstIf: + case EcsAstFor: break; + case EcsAstTemplate: + flecs_script_eval_error(v, node, "nested templates are not allowed"); + return -1; case EcsAstProp: return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); - case EcsAstWith: { - if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { - return -1; - } - bool old_is_with_scope = v->is_with_scope; - v->is_with_scope = true; - if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { - return -1; - } - v->is_with_scope = old_is_with_scope; - return 0; - } - case EcsAstIf: - if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_true)) { - return -1; - } - if (((ecs_script_if_t*)node)->if_false) { - if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_false)) { - return -1; - } - } - return 0; - case EcsAstFor: - if (ecs_script_visit_scope(v, ((ecs_script_for_range_t*)node)->scope)) { - return -1; - } - return 0; } - return flecs_script_eval_node(v, node); + return flecs_script_check_node(v, node); } static @@ -59029,12 +59034,25 @@ int flecs_script_template_preprocess( { ecs_visit_action_t prev_visit = v->base.visit; v->template = template; + + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos + } + }; + + v->entity = &instance_node; + v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; v->template = NULL; + v->entity = NULL; + return result; } @@ -59126,11 +59144,6 @@ int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node) { - if (v->template) { - flecs_script_eval_error(v, node, "nested templates are not allowed"); - return -1; - } - ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); if (!template_entity) { return -1; @@ -60234,6 +60247,499 @@ int ecs_script_visit_( #endif +/** + * @file addons/script/visit_validate.c + * @brief Script AST validation. + */ + + +#ifdef FLECS_SCRIPT + +int flecs_script_check_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_entity_t *type) +{ + ecs_expr_node_t *expr = *expr_ptr; + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; + + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = type ? type[0] : 0, + .runtime = v->r + }; + + ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_expr_visit_type(script, expr, &desc)) { + goto error; + } + + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; + } + + if (type) { + type[0] = expr_ptr[0]->type; + } + + return 0; +error: + return -1; +} + +static +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node) +{ + return flecs_script_eval_scope(v, node); +} + +static +int flecs_script_check_entity( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node) +{ + if (node->kind) { + ecs_script_id_t id = { + .first = node->kind + }; + + if (!ecs_os_strcmp(node->kind, "prefab")) { + id.eval = EcsPrefab; + } else if (!ecs_os_strcmp(node->kind, "slot")) { + } else if (flecs_script_eval_id(v, node, &id)) { + return -1; + } + + node->eval_kind = id.eval; + } else { + /* Inherit kind from parent kind's DefaultChildComponent, if it existst */ + ecs_script_scope_t *scope = ecs_script_current_scope(v); + if (scope && scope->default_component_eval) { + node->eval_kind = scope->default_component_eval; + } + } + + ecs_script_entity_t *old_entity = v->entity; + v->entity = node; + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = false; + + if (ecs_script_visit_node(v, node->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + v->entity = old_entity; + + return 0; +} + +static +int flecs_script_check_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error( + v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for tag %s", + node->id.first); + } + return -1; + } + + return 0; +} + +static +int flecs_script_check_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (node->expr) { + const ecs_type_info_t *ti = ecs_get_type_info(v->world, node->id.eval); + if (!ti) { + return 0; + } + + const EcsType *type = ecs_get(v->world, ti->component, EcsType); + if (type) { + bool is_collection = false; + + switch(type->kind) { + case EcsPrimitiveType: + case EcsBitmaskType: + case EcsEnumType: + case EcsStructType: + case EcsOpaqueType: + break; + case EcsArrayType: + case EcsVectorType: + is_collection = true; + break; + } + + if (node->is_collection != is_collection) { + char *id_str = ecs_id_str(v->world, ti->component); + if (node->is_collection && !is_collection) { + flecs_script_eval_error(v, node, + "type %s is not a collection (use '%s: {...}')", + id_str, id_str); + } else { + flecs_script_eval_error(v, node, + "type %s is a collection (use '%s: [...]')", + id_str, id_str); + } + ecs_os_free(id_str); + return -1; + } + } + + ecs_entity_t expr_type = node->id.eval; + if (flecs_script_check_expr(v, &node->expr, &expr_type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_var_component( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + return 0; +} + +static +int flecs_script_check_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (node->expr) { + ecs_entity_t type = node->id.eval; + + if (flecs_script_check_expr(v, &node->expr, &type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { + return -1; + } + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; + + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + + return 0; +} + +static +int flecs_script_check_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + return flecs_script_eval_using(v, node); +} + +static +int flecs_script_check_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + return flecs_script_eval_const(v, node); +} + +static +int flecs_script_check_pair_scope( + ecs_script_eval_visitor_t *v, + ecs_script_pair_scope_t *node) +{ + if (!flecs_script_find_entity(v, 0, node->id.first)) { + return -1; + } + + if (!flecs_script_find_entity(v, 0, node->id.second)) { + return -1; + } + + if (ecs_script_visit_scope(v, node->scope)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_if( + ecs_script_eval_visitor_t *v, + ecs_script_if_t *node) +{ + if (flecs_script_check_expr(v, &node->expr, NULL)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_true)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_false)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + ecs_entity_t type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->from, &type)) { + return -1; + } + + type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->to, &type)) { + return -1; + } + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + var->value.ptr = NULL; + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + + if (flecs_script_eval_scope(v, node->scope)) { + return -1; + } + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} + +static +int flecs_script_check_annot( + ecs_script_eval_visitor_t *v, + ecs_script_annot_t *node) +{ + if (!v->base.next) { + flecs_script_eval_error(v, node, + "annotation '%s' is not applied to anything", node->name); + return -1; + } + + ecs_script_node_kind_t kind = v->base.next->kind; + if (kind != EcsAstEntity && kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "annotation must be applied to an entity"); + return -1; + } + + return 0; +} + +int flecs_script_check_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node) +{ + switch(node->kind) { + case EcsAstScope: + return flecs_script_check_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_check_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_check_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_check_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_check_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_check_with_var( + v, (ecs_script_var_node_t*)node); + case EcsAstWithTag: + return flecs_script_check_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_check_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_check_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_check_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return 0; + case EcsAstAnnotation: + return flecs_script_check_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return 0; + case EcsAstProp: + return 0; + case EcsAstConst: + return flecs_script_check_const( + v, (ecs_script_var_node_t*)node); + case EcsAstEntity: + return flecs_script_check_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_check_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_check_if( + v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_check_for_range( + v, (ecs_script_for_range_t*)node); + } + + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} + +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); + int result = ecs_script_visit(impl, &v, flecs_script_check_node); + flecs_script_eval_visit_fini(&v, &priv_desc); + + return result; +} + +#endif + /** * @file addons/script/visit_eval.c * @brief Script evaluation visitor. @@ -60357,6 +60863,7 @@ const ecs_type_info_t* flecs_script_get_type_info( char *idstr = ecs_id_str(v->world, id); flecs_script_eval_error(v, node, "cannot set value of '%s': not a component", idstr); + flecs_dump_backtrace(stdout); ecs_os_free(idstr); } return NULL; @@ -60438,7 +60945,6 @@ ecs_entity_t flecs_script_create_entity( return ecs_entity_init(v->world, &desc); } -static ecs_entity_t flecs_script_find_entity_action( const ecs_world_t *world, const char *path, @@ -60474,7 +60980,6 @@ int flecs_script_find_template_entity( return -1; } -static int flecs_script_eval_id( ecs_script_eval_visitor_t *v, void *node, @@ -60508,6 +61013,7 @@ int flecs_script_eval_id( flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->first); + return -1; } else if (id->second) { second_from = flecs_get_oneof(v->world, first); @@ -60542,6 +61048,7 @@ int flecs_script_eval_id( flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->second); + return -1; } @@ -60583,7 +61090,7 @@ int flecs_script_eval_expr( .runtime = v->r }; - if (!expr->type_info) { + if (expr->type_info == NULL) { if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } @@ -60602,7 +61109,6 @@ int flecs_script_eval_expr( return -1; } -static int flecs_script_eval_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) @@ -60693,16 +61199,6 @@ int flecs_script_eval_entity( } } - if (v->template) { - bool old_is_with_scope = v->is_with_scope; - v->is_with_scope = false; - if (ecs_script_visit_node(v, node->scope)) { - return -1; - } - v->is_with_scope = old_is_with_scope; - return 0; - } - ecs_expr_node_t *name_expr = node->name_expr; if (name_expr) { ecs_script_t *script = &v->base.script->pub; @@ -60842,10 +61338,6 @@ int flecs_script_eval_tag( return -1; } - if (v->template) { - return 0; - } - if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; @@ -60863,10 +61355,6 @@ int flecs_script_eval_tag( return -1; } - if (v->template) { - return 0; - } - ecs_entity_t src = flecs_script_get_src( v, v->entity->eval, node->id.eval); ecs_add_id(v->world, src, node->id.eval); @@ -60883,10 +61371,6 @@ int flecs_script_eval_component( return -1; } - if (v->template) { - return 0; - } - if (!v->entity) { if (node->id.second) { flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", @@ -60985,10 +61469,6 @@ int flecs_script_eval_var_component( return -1; } - if (v->template) { - return 0; - } - if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; @@ -61029,10 +61509,6 @@ int flecs_script_eval_default_component( return -1; } - if (v->template) { - return 0; - } - ecs_script_scope_t *scope = ecs_script_current_scope(v); ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); @@ -61088,10 +61564,6 @@ int flecs_script_eval_with_var( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types *value = var->value; @@ -61108,10 +61580,6 @@ int flecs_script_eval_with_tag( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); value->type = node->id.eval; @@ -61129,10 +61597,6 @@ int flecs_script_eval_with_component( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); @@ -61201,7 +61665,6 @@ int flecs_script_eval_with( return result; } -static int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) @@ -61270,7 +61733,6 @@ int flecs_script_eval_module( return 0; } -static int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) @@ -61482,18 +61944,7 @@ int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, ecs_script_annot_t *node) { - if (!v->base.next) { - flecs_script_eval_error(v, node, - "annotation '%s' is not applied to anything", node->name); - return -1; - } - - ecs_script_node_kind_t kind = v->base.next->kind; - if (kind != EcsAstEntity && kind != EcsAstAnnotation) { - flecs_script_eval_error(v, node, - "annotation must be applied to an entity"); - return -1; - } + ecs_assert(v->base.next != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &v->r->allocator; ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; @@ -61505,6 +61956,8 @@ int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { + ecs_assert(v->template == NULL, ECS_INTERNAL_ERROR, NULL); + switch(node->kind) { case EcsAstScope: return flecs_script_eval_scope( @@ -61850,6 +62303,14 @@ int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope); +static +void flecs_script_color_to_str( + ecs_script_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); +} + static void flecs_scriptbuf_append( ecs_script_str_visitor_t *v, @@ -61955,8 +62416,9 @@ void flecs_scriptbuf_node( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { - flecs_scriptbuf_append(v, "%s%s%s: ", - ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); + flecs_script_color_to_str(v, ECS_BLUE); + flecs_scriptbuf_append(v, "%s: ", flecs_script_node_to_str(node)); + flecs_script_color_to_str(v, ECS_NORMAL); } static @@ -62014,9 +62476,13 @@ void flecs_script_with_to_str( flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; - flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "expressions: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->expressions); - flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_append(v, "scope: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->scope); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); @@ -62046,8 +62512,9 @@ void flecs_script_annot_to_str( ecs_script_annot_t *node) { flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, - ECS_GREEN, node->expr, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_GREEN); + flecs_scriptbuf_append(v, "%s = \"%s\"", node->name, node->expr); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_scriptbuf_appendstr(v, "\n"); } @@ -62122,9 +62589,13 @@ void flecs_script_if_to_str( flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; - flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "true: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_true); - flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "false: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_false); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); diff --git a/meson.build b/meson.build index fd76c47c74..6e47ccc214 100644 --- a/meson.build +++ b/meson.build @@ -68,6 +68,7 @@ flecs_src = files( 'src/addons/script/serialize.c', 'src/addons/script/tokenizer.c', 'src/addons/script/vars.c', + 'src/addons/script/visit_check.c', 'src/addons/script/visit_eval.c', 'src/addons/script/visit_free.c', 'src/addons/script/visit_to_str.c', diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 6162963470..6319894b56 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -111,5 +111,9 @@ void flecs_script_register_builtin_functions( void flecs_function_import( ecs_world_t *world); +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); + #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H diff --git a/src/addons/script/template.c b/src/addons/script/template.c index dc3adc060f..17f0088d03 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -346,11 +346,6 @@ int flecs_script_template_eval( case EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: - if (v->is_with_scope) { - flecs_script_eval_error(v, node, "invalid component in with scope"); - return -1; - } - break; case EcsAstEntity: case EcsAstScope: case EcsAstDefaultComponent: @@ -362,40 +357,18 @@ int flecs_script_template_eval( case EcsAstAnnotation: case EcsAstConst: case EcsAstPairScope: - case EcsAstTemplate: + case EcsAstWith: + case EcsAstIf: + case EcsAstFor: break; + case EcsAstTemplate: + flecs_script_eval_error(v, node, "nested templates are not allowed"); + return -1; case EcsAstProp: return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); - case EcsAstWith: { - if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { - return -1; - } - bool old_is_with_scope = v->is_with_scope; - v->is_with_scope = true; - if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { - return -1; - } - v->is_with_scope = old_is_with_scope; - return 0; - } - case EcsAstIf: - if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_true)) { - return -1; - } - if (((ecs_script_if_t*)node)->if_false) { - if (ecs_script_visit_scope(v, ((ecs_script_if_t*)node)->if_false)) { - return -1; - } - } - return 0; - case EcsAstFor: - if (ecs_script_visit_scope(v, ((ecs_script_for_range_t*)node)->scope)) { - return -1; - } - return 0; } - return flecs_script_eval_node(v, node); + return flecs_script_check_node(v, node); } static @@ -405,12 +378,25 @@ int flecs_script_template_preprocess( { ecs_visit_action_t prev_visit = v->base.visit; v->template = template; + + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos + } + }; + + v->entity = &instance_node; + v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; v->template = NULL; + v->entity = NULL; + return result; } @@ -502,11 +488,6 @@ int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node) { - if (v->template) { - flecs_script_eval_error(v, node, "nested templates are not allowed"); - return -1; - } - ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); if (!template_entity) { return -1; diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c new file mode 100644 index 0000000000..adad390d76 --- /dev/null +++ b/src/addons/script/visit_check.c @@ -0,0 +1,494 @@ +/** + * @file addons/script/visit_validate.c + * @brief Script AST validation. + */ + +#include "flecs.h" + +#ifdef FLECS_SCRIPT +#include "script.h" + +int flecs_script_check_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_entity_t *type) +{ + ecs_expr_node_t *expr = *expr_ptr; + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; + + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = type ? type[0] : 0, + .runtime = v->r + }; + + ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_expr_visit_type(script, expr, &desc)) { + goto error; + } + + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; + } + + if (type) { + type[0] = expr_ptr[0]->type; + } + + return 0; +error: + return -1; +} + +static +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node) +{ + return flecs_script_eval_scope(v, node); +} + +static +int flecs_script_check_entity( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node) +{ + if (node->kind) { + ecs_script_id_t id = { + .first = node->kind + }; + + if (!ecs_os_strcmp(node->kind, "prefab")) { + id.eval = EcsPrefab; + } else if (!ecs_os_strcmp(node->kind, "slot")) { + } else if (flecs_script_eval_id(v, node, &id)) { + return -1; + } + + node->eval_kind = id.eval; + } else { + /* Inherit kind from parent kind's DefaultChildComponent, if it existst */ + ecs_script_scope_t *scope = ecs_script_current_scope(v); + if (scope && scope->default_component_eval) { + node->eval_kind = scope->default_component_eval; + } + } + + ecs_script_entity_t *old_entity = v->entity; + v->entity = node; + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = false; + + if (ecs_script_visit_node(v, node->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + v->entity = old_entity; + + return 0; +} + +static +int flecs_script_check_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error( + v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for tag %s", + node->id.first); + } + return -1; + } + + return 0; +} + +static +int flecs_script_check_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (node->expr) { + const ecs_type_info_t *ti = ecs_get_type_info(v->world, node->id.eval); + if (!ti) { + return 0; + } + + const EcsType *type = ecs_get(v->world, ti->component, EcsType); + if (type) { + bool is_collection = false; + + switch(type->kind) { + case EcsPrimitiveType: + case EcsBitmaskType: + case EcsEnumType: + case EcsStructType: + case EcsOpaqueType: + break; + case EcsArrayType: + case EcsVectorType: + is_collection = true; + break; + } + + if (node->is_collection != is_collection) { + char *id_str = ecs_id_str(v->world, ti->component); + if (node->is_collection && !is_collection) { + flecs_script_eval_error(v, node, + "type %s is not a collection (use '%s: {...}')", + id_str, id_str); + } else { + flecs_script_eval_error(v, node, + "type %s is a collection (use '%s: [...]')", + id_str, id_str); + } + ecs_os_free(id_str); + return -1; + } + } + + ecs_entity_t expr_type = node->id.eval; + if (flecs_script_check_expr(v, &node->expr, &expr_type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_var_component( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + return 0; +} + +static +int flecs_script_check_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (node->expr) { + ecs_entity_t type = node->id.eval; + + if (flecs_script_check_expr(v, &node->expr, &type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { + return -1; + } + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; + + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + + return 0; +} + +static +int flecs_script_check_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + return flecs_script_eval_using(v, node); +} + +static +int flecs_script_check_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + return flecs_script_eval_const(v, node); +} + +static +int flecs_script_check_pair_scope( + ecs_script_eval_visitor_t *v, + ecs_script_pair_scope_t *node) +{ + if (!flecs_script_find_entity(v, 0, node->id.first)) { + return -1; + } + + if (!flecs_script_find_entity(v, 0, node->id.second)) { + return -1; + } + + if (ecs_script_visit_scope(v, node->scope)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_if( + ecs_script_eval_visitor_t *v, + ecs_script_if_t *node) +{ + if (flecs_script_check_expr(v, &node->expr, NULL)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_true)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_false)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + ecs_entity_t type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->from, &type)) { + return -1; + } + + type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->to, &type)) { + return -1; + } + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + var->value.ptr = NULL; + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + + if (flecs_script_eval_scope(v, node->scope)) { + return -1; + } + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} + +static +int flecs_script_check_annot( + ecs_script_eval_visitor_t *v, + ecs_script_annot_t *node) +{ + if (!v->base.next) { + flecs_script_eval_error(v, node, + "annotation '%s' is not applied to anything", node->name); + return -1; + } + + ecs_script_node_kind_t kind = v->base.next->kind; + if (kind != EcsAstEntity && kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "annotation must be applied to an entity"); + return -1; + } + + return 0; +} + +int flecs_script_check_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node) +{ + switch(node->kind) { + case EcsAstScope: + return flecs_script_check_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_check_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_check_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_check_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_check_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_check_with_var( + v, (ecs_script_var_node_t*)node); + case EcsAstWithTag: + return flecs_script_check_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_check_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_check_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_check_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return 0; + case EcsAstAnnotation: + return flecs_script_check_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return 0; + case EcsAstProp: + return 0; + case EcsAstConst: + return flecs_script_check_const( + v, (ecs_script_var_node_t*)node); + case EcsAstEntity: + return flecs_script_check_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_check_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_check_if( + v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_check_for_range( + v, (ecs_script_for_range_t*)node); + } + + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} + +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); + int result = ecs_script_visit(impl, &v, flecs_script_check_node); + flecs_script_eval_visit_fini(&v, &priv_desc); + + return result; +} + +#endif diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index d8df950812..7e9146f639 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -123,6 +123,7 @@ const ecs_type_info_t* flecs_script_get_type_info( char *idstr = ecs_id_str(v->world, id); flecs_script_eval_error(v, node, "cannot set value of '%s': not a component", idstr); + flecs_dump_backtrace(stdout); ecs_os_free(idstr); } return NULL; @@ -204,7 +205,6 @@ ecs_entity_t flecs_script_create_entity( return ecs_entity_init(v->world, &desc); } -static ecs_entity_t flecs_script_find_entity_action( const ecs_world_t *world, const char *path, @@ -240,7 +240,6 @@ int flecs_script_find_template_entity( return -1; } -static int flecs_script_eval_id( ecs_script_eval_visitor_t *v, void *node, @@ -274,6 +273,7 @@ int flecs_script_eval_id( flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->first); + return -1; } else if (id->second) { second_from = flecs_get_oneof(v->world, first); @@ -308,6 +308,7 @@ int flecs_script_eval_id( flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->second); + return -1; } @@ -349,7 +350,7 @@ int flecs_script_eval_expr( .runtime = v->r }; - if (!expr->type_info) { + if (expr->type_info == NULL) { if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } @@ -368,7 +369,6 @@ int flecs_script_eval_expr( return -1; } -static int flecs_script_eval_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) @@ -459,16 +459,6 @@ int flecs_script_eval_entity( } } - if (v->template) { - bool old_is_with_scope = v->is_with_scope; - v->is_with_scope = false; - if (ecs_script_visit_node(v, node->scope)) { - return -1; - } - v->is_with_scope = old_is_with_scope; - return 0; - } - ecs_expr_node_t *name_expr = node->name_expr; if (name_expr) { ecs_script_t *script = &v->base.script->pub; @@ -608,10 +598,6 @@ int flecs_script_eval_tag( return -1; } - if (v->template) { - return 0; - } - if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; @@ -629,10 +615,6 @@ int flecs_script_eval_tag( return -1; } - if (v->template) { - return 0; - } - ecs_entity_t src = flecs_script_get_src( v, v->entity->eval, node->id.eval); ecs_add_id(v->world, src, node->id.eval); @@ -649,10 +631,6 @@ int flecs_script_eval_component( return -1; } - if (v->template) { - return 0; - } - if (!v->entity) { if (node->id.second) { flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", @@ -751,10 +729,6 @@ int flecs_script_eval_var_component( return -1; } - if (v->template) { - return 0; - } - if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; @@ -795,10 +769,6 @@ int flecs_script_eval_default_component( return -1; } - if (v->template) { - return 0; - } - ecs_script_scope_t *scope = ecs_script_current_scope(v); ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); @@ -854,10 +824,6 @@ int flecs_script_eval_with_var( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types *value = var->value; @@ -874,10 +840,6 @@ int flecs_script_eval_with_tag( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); value->type = node->id.eval; @@ -895,10 +857,6 @@ int flecs_script_eval_with_component( return -1; } - if (v->template) { - return 0; - } - ecs_allocator_t *a = &v->r->allocator; const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); @@ -967,7 +925,6 @@ int flecs_script_eval_with( return result; } -static int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) @@ -1036,7 +993,6 @@ int flecs_script_eval_module( return 0; } -static int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) @@ -1248,18 +1204,7 @@ int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, ecs_script_annot_t *node) { - if (!v->base.next) { - flecs_script_eval_error(v, node, - "annotation '%s' is not applied to anything", node->name); - return -1; - } - - ecs_script_node_kind_t kind = v->base.next->kind; - if (kind != EcsAstEntity && kind != EcsAstAnnotation) { - flecs_script_eval_error(v, node, - "annotation must be applied to an entity"); - return -1; - } + ecs_assert(v->base.next != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &v->r->allocator; ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; @@ -1271,6 +1216,8 @@ int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { + ecs_assert(v->template == NULL, ECS_INTERNAL_ERROR, NULL); + switch(node->kind) { case EcsAstScope: return flecs_script_eval_scope( diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index ba74b92ef3..6ec536f523 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -62,4 +62,32 @@ int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); +int flecs_script_check_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node); + +/* Functions shared between check and eval visitor */ + +int flecs_script_eval_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + +int flecs_script_eval_id( + ecs_script_eval_visitor_t *v, + void *node, + ecs_script_id_t *id); + +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node); + +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node); + +ecs_entity_t flecs_script_find_entity_action( + const ecs_world_t *world, + const char *path, + void *ctx); + #endif diff --git a/src/addons/script/visit_to_str.c b/src/addons/script/visit_to_str.c index f4ef876168..6b7f97f719 100644 --- a/src/addons/script/visit_to_str.c +++ b/src/addons/script/visit_to_str.c @@ -21,6 +21,14 @@ int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope); +static +void flecs_script_color_to_str( + ecs_script_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); +} + static void flecs_scriptbuf_append( ecs_script_str_visitor_t *v, @@ -126,8 +134,9 @@ void flecs_scriptbuf_node( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { - flecs_scriptbuf_append(v, "%s%s%s: ", - ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); + flecs_script_color_to_str(v, ECS_BLUE); + flecs_scriptbuf_append(v, "%s: ", flecs_script_node_to_str(node)); + flecs_script_color_to_str(v, ECS_NORMAL); } static @@ -185,9 +194,13 @@ void flecs_script_with_to_str( flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; - flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "expressions: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->expressions); - flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_append(v, "scope: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->scope); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); @@ -217,8 +230,9 @@ void flecs_script_annot_to_str( ecs_script_annot_t *node) { flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, - ECS_GREEN, node->expr, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_GREEN); + flecs_scriptbuf_append(v, "%s = \"%s\"", node->name, node->expr); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_scriptbuf_appendstr(v, "\n"); } @@ -293,9 +307,13 @@ void flecs_script_if_to_str( flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; - flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "true: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_true); - flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "false: "); + flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_false); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); diff --git a/test/script/project.json b/test/script/project.json index eaec42167e..d001e47131 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -407,6 +407,7 @@ "template_redeclare_prop_as_const", "template_redeclare_prop_as_prop", "template_redeclare_const_as_const", + "template_w_invalid_var_in_expr", "run_template_after_error", "update_template_after_error", "template_in_template", @@ -656,7 +657,8 @@ "binary_f32_var_div_by_int_sub_int", "interpolated_string_var", "interpolated_string_curly_brackets", - "interpolated_string_curly_brackets_w_var" + "interpolated_string_curly_brackets_w_var", + "template_w_foldable_const" ] }, { "id": "Vars", diff --git a/test/script/src/Error.c b/test/script/src/Error.c index 4e376d0c96..a60ef94c94 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1623,3 +1623,28 @@ void Error_reload_script_w_component_w_error_again(void) { ecs_fini(world); } + +void Error_template_w_invalid_var_in_expr(void) { + ecs_world_t *world = ecs_init(); + + ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Tree {" + LINE " prop height = f32: 10" + LINE " e {" + LINE " Position: {0, $h / 2, 0}" + LINE " }" + LINE "}"; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/ExprAst.c b/test/script/src/ExprAst.c index cfd32b1e56..93703ac9e5 100644 --- a/test/script/src/ExprAst.c +++ b/test/script/src/ExprAst.c @@ -246,3 +246,67 @@ void ExprAst_interpolated_string_curly_brackets_w_var(void) { ecs_fini(world); } + +void ExprAst_template_w_foldable_const(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {}" + LINE + LINE "template Bar {" + LINE " const a = 10" + LINE " const b = $a + 10" + LINE " Position: {$a, $b}" + LINE "}" + LINE + LINE "Bar e"; + + const char *result_before = + HEAD "{" + LINE " entity: Foo " + LINE " template: Bar {" + LINE " const: a = 10" + LINE " const: b = ($a + 10)" + LINE " component: Position: {$a, $b}" + LINE " }" + LINE " entity: Bar e " + LINE "}\n"; + + const char *result_after = + HEAD "{" + LINE " entity: Foo " + LINE " template: Bar {" + LINE " const: a = 10" + LINE " const: b = 20" + LINE " component: Position: {x: 10, y: 20}" + LINE " }" + LINE " entity: Bar e " + LINE "}\n"; + + ecs_script_t *script = ecs_script_parse(world, NULL, expr, NULL); + test_assert(script != NULL); + { + char *ast = ecs_script_ast_to_str(script, false); + test_str(ast, result_before); + ecs_os_free(ast); + } + + test_assert(ecs_script_eval(script, NULL) == 0); + { + char *ast = ecs_script_ast_to_str(script, false); + test_str(ast, result_after); + ecs_os_free(ast); + } + + ecs_script_free(script); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 51283c617c..a87cd9e816 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -399,6 +399,7 @@ void Error_template_w_composite_prop_invalid_assignment(void); void Error_template_redeclare_prop_as_const(void); void Error_template_redeclare_prop_as_prop(void); void Error_template_redeclare_const_as_const(void); +void Error_template_w_invalid_var_in_expr(void); void Error_run_template_after_error(void); void Error_update_template_after_error(void); void Error_template_in_template(void); @@ -642,6 +643,7 @@ 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); +void ExprAst_template_w_foldable_const(void); // Testsuite 'Vars' void Vars_declare_1_var(void); @@ -2360,6 +2362,10 @@ bake_test_case Error_testcases[] = { "template_redeclare_const_as_const", Error_template_redeclare_const_as_const }, + { + "template_w_invalid_var_in_expr", + Error_template_w_invalid_var_in_expr + }, { "run_template_after_error", Error_run_template_after_error @@ -3317,6 +3323,10 @@ bake_test_case ExprAst_testcases[] = { { "interpolated_string_curly_brackets_w_var", ExprAst_interpolated_string_curly_brackets_w_var + }, + { + "template_w_foldable_const", + ExprAst_template_w_foldable_const } }; @@ -3993,7 +4003,7 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 78, + 79, Error_testcases }, { @@ -4009,7 +4019,7 @@ static bake_test_suite suites[] = { "ExprAst", NULL, NULL, - 8, + 9, ExprAst_testcases }, { From c632d5650fc9923064cdfbe0639bec4fa0814469 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 21:27:19 -0800 Subject: [PATCH 72/83] Improve type conversion error messages --- distr/flecs.c | 108 +++++++++++++++++++++++++++- src/addons/script/expr/ast.c | 82 +++++++++++++++++++++ src/addons/script/expr/expr.h | 6 ++ src/addons/script/expr/visit_type.c | 20 +++++- test/script/project.json | 4 +- test/script/src/Error.c | 49 +++++++++++++ test/script/src/main.c | 12 +++- 7 files changed, 275 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 7e65b36055..18bd67400d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5246,6 +5246,12 @@ char* flecs_string_escape( bool flecs_value_is_0( const ecs_value_t *value); +bool flecs_expr_is_type_integer( + ecs_entity_t type); + +bool flecs_expr_is_type_number( + ecs_entity_t type); + #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ @@ -75112,11 +75118,93 @@ ecs_expr_element_t* flecs_expr_element( return result; } +static +bool flecs_expr_explicit_cast_allowed( + ecs_world_t *world, + ecs_entity_t from, + ecs_entity_t to) +{ + if (from == to) { + return true; + } + + const EcsType *from_type = ecs_get(world, from, EcsType); + const EcsType *to_type = ecs_get(world, to, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Treat opaque types asthe types that they're pretending to be*/ + if (from_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, from, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + from_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); + } + if (to_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, to, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + to_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (from_type->kind != EcsPrimitiveType || + to_type->kind != EcsPrimitiveType) + { + if (from_type->kind == EcsEnumType || + from_type->kind == EcsBitmaskType) + { + if (flecs_expr_is_type_integer(to)) { + /* Can cast enums/bitmasks to integers */ + return true; + } + } + + if (flecs_expr_is_type_integer(from)) { + if (to_type->kind == EcsEnumType || + to_type->kind == EcsBitmaskType) + { + /* Can integers to enums/bitmasks */ + return true; + } + } + + /* Cannot cast complex types that are not the same */ + return false; + } + + /* Anything can be casted to a number */ + if (flecs_expr_is_type_number(to)) { + return true; + } + + /* Anything can be casted to a number */ + if (to == ecs_id(ecs_string_t)) { + return true; + } + + // const EcsPrimitive *from_ptype = ecs_get(world, from, EcsPrimitive); + // const EcsPrimitive *to_ptype = ecs_get(world, to, EcsPrimitive); + // ecs_assert(from_ptype != NULL, ECS_INTERNAL_ERROR, NULL); + // ecs_assert(to_ptype != NULL, ECS_INTERNAL_ERROR, NULL); + + return true; +} + ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *expr, ecs_entity_t type) { + if (!flecs_expr_explicit_cast_allowed(script->world, expr->type, type)) { + char *from = ecs_id_str(script->world, expr->type); + char *to = ecs_id_str(script->world, type); + flecs_expr_visit_error(script, expr, "invalid cast from %s to %s", + from, to); + ecs_os_free(from); + ecs_os_free(to); + return NULL; + } + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; @@ -78164,7 +78252,6 @@ int flecs_expr_visit_type_priv( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc); -static bool flecs_expr_is_type_integer( ecs_entity_t type) { @@ -78179,7 +78266,6 @@ bool flecs_expr_is_type_integer( else return false; } -static bool flecs_expr_is_type_number( ecs_entity_t type) { @@ -78767,6 +78853,9 @@ int flecs_expr_interpolated_string_visit_type( 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)); + if (!result) { + goto error; + } } ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, @@ -78884,6 +78973,9 @@ int flecs_expr_initializer_visit_type( if (elem->value->type != elem_type) { elem->value = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, elem_type); + if (!elem->value) { + goto error; + } } if (!is_opaque) { @@ -78919,6 +79011,9 @@ int flecs_expr_unary_visit_type( if (node->expr->type != ecs_id(ecs_bool_t)) { node->expr = (ecs_expr_node_t*)flecs_expr_cast( script, node->expr, ecs_id(ecs_bool_t)); + if (!node->expr) { + goto error; + } } return 0; @@ -78974,11 +79069,17 @@ int flecs_expr_binary_visit_type( if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); + if (!node->left) { + goto error; + } } if (operand_type != node->right->type) { node->right = (ecs_expr_node_t*)flecs_expr_cast( script, node->right, operand_type); + if (!node->right) { + goto error; + } } node->node.type = result_type; @@ -79141,6 +79242,9 @@ int flecs_expr_arguments_visit_type( if (elem->value->type != params[i].type) { elem->value = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, params[i].type); + if (!elem->value) { + goto error; + } } } diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 28fb95538b..a75a8164d7 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -219,11 +219,93 @@ ecs_expr_element_t* flecs_expr_element( return result; } +static +bool flecs_expr_explicit_cast_allowed( + ecs_world_t *world, + ecs_entity_t from, + ecs_entity_t to) +{ + if (from == to) { + return true; + } + + const EcsType *from_type = ecs_get(world, from, EcsType); + const EcsType *to_type = ecs_get(world, to, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Treat opaque types asthe types that they're pretending to be*/ + if (from_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, from, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + from_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); + } + if (to_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, to, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + to_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (from_type->kind != EcsPrimitiveType || + to_type->kind != EcsPrimitiveType) + { + if (from_type->kind == EcsEnumType || + from_type->kind == EcsBitmaskType) + { + if (flecs_expr_is_type_integer(to)) { + /* Can cast enums/bitmasks to integers */ + return true; + } + } + + if (flecs_expr_is_type_integer(from)) { + if (to_type->kind == EcsEnumType || + to_type->kind == EcsBitmaskType) + { + /* Can integers to enums/bitmasks */ + return true; + } + } + + /* Cannot cast complex types that are not the same */ + return false; + } + + /* Anything can be casted to a number */ + if (flecs_expr_is_type_number(to)) { + return true; + } + + /* Anything can be casted to a number */ + if (to == ecs_id(ecs_string_t)) { + return true; + } + + // const EcsPrimitive *from_ptype = ecs_get(world, from, EcsPrimitive); + // const EcsPrimitive *to_ptype = ecs_get(world, to, EcsPrimitive); + // ecs_assert(from_ptype != NULL, ECS_INTERNAL_ERROR, NULL); + // ecs_assert(to_ptype != NULL, ECS_INTERNAL_ERROR, NULL); + + return true; +} + ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *expr, ecs_entity_t type) { + if (!flecs_expr_explicit_cast_allowed(script->world, expr->type, type)) { + char *from = ecs_id_str(script->world, expr->type); + char *to = ecs_id_str(script->world, type); + flecs_expr_visit_error(script, expr, "invalid cast from %s to %s", + from, to); + ecs_os_free(from); + ecs_os_free(to); + return NULL; + } + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 27c2b845f2..df92b83817 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -60,6 +60,12 @@ char* flecs_string_escape( bool flecs_value_is_0( const ecs_value_t *value); +bool flecs_expr_is_type_integer( + ecs_entity_t type); + +bool flecs_expr_is_type_number( + ecs_entity_t type); + #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/visit_type.c b/src/addons/script/expr/visit_type.c index d2ac219dc2..2e3b5c147c 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -15,7 +15,6 @@ int flecs_expr_visit_type_priv( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc); -static bool flecs_expr_is_type_integer( ecs_entity_t type) { @@ -30,7 +29,6 @@ bool flecs_expr_is_type_integer( else return false; } -static bool flecs_expr_is_type_number( ecs_entity_t type) { @@ -618,6 +616,9 @@ int flecs_expr_interpolated_string_visit_type( 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)); + if (!result) { + goto error; + } } ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, @@ -735,6 +736,9 @@ int flecs_expr_initializer_visit_type( if (elem->value->type != elem_type) { elem->value = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, elem_type); + if (!elem->value) { + goto error; + } } if (!is_opaque) { @@ -770,6 +774,9 @@ int flecs_expr_unary_visit_type( if (node->expr->type != ecs_id(ecs_bool_t)) { node->expr = (ecs_expr_node_t*)flecs_expr_cast( script, node->expr, ecs_id(ecs_bool_t)); + if (!node->expr) { + goto error; + } } return 0; @@ -825,11 +832,17 @@ int flecs_expr_binary_visit_type( if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); + if (!node->left) { + goto error; + } } if (operand_type != node->right->type) { node->right = (ecs_expr_node_t*)flecs_expr_cast( script, node->right, operand_type); + if (!node->right) { + goto error; + } } node->node.type = result_type; @@ -992,6 +1005,9 @@ int flecs_expr_arguments_visit_type( if (elem->value->type != params[i].type) { elem->value = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, params[i].type); + if (!elem->value) { + goto error; + } } } diff --git a/test/script/project.json b/test/script/project.json index d001e47131..7cee10fe8e 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -425,7 +425,9 @@ "component_in_with_var_scope", "component_in_with_in_template", "reload_script_w_component_w_error", - "reload_script_w_component_w_error_again" + "reload_script_w_component_w_error_again", + "initializer_w_int_to_struct", + "script_initializer_w_int_to_struct" ] }, { "id": "Expr", diff --git a/test/script/src/Error.c b/test/script/src/Error.c index a60ef94c94..ff2dca5665 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1648,3 +1648,52 @@ void Error_template_w_invalid_var_in_expr(void) { ecs_fini(world); } + +void Error_initializer_w_int_to_struct(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + Position v = {0}; + ecs_log_set_level(-4); + test_assert(NULL == ecs_expr_run(world, "10", + &ecs_value_ptr(Position, &v), NULL)); + + ecs_fini(world); +} + +void Error_script_initializer_w_int_to_struct(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Line"}), + .members = { + {"start", ecs_id(Position)}, + {"stop", ecs_id(Position)} + } + }); + + const char *expr = + HEAD "e {" + LINE " Line: {10}" + LINE "}"; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index a87cd9e816..27759aad21 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -418,6 +418,8 @@ void Error_component_in_with_var_scope(void); void Error_component_in_with_in_template(void); void Error_reload_script_w_component_w_error(void); void Error_reload_script_w_component_w_error_again(void); +void Error_initializer_w_int_to_struct(void); +void Error_script_initializer_w_int_to_struct(void); // Testsuite 'Expr' void Expr_setup(void); @@ -2437,6 +2439,14 @@ bake_test_case Error_testcases[] = { { "reload_script_w_component_w_error_again", Error_reload_script_w_component_w_error_again + }, + { + "initializer_w_int_to_struct", + Error_initializer_w_int_to_struct + }, + { + "script_initializer_w_int_to_struct", + Error_script_initializer_w_int_to_struct } }; @@ -4003,7 +4013,7 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 79, + 81, Error_testcases }, { From 0d1dc4fa67316d6241ec2daba958003be78147a7 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 13 Dec 2024 22:03:27 -0800 Subject: [PATCH 73/83] Fix issue with templates that contain expressions with self references --- distr/flecs.c | 35 +++++++++++++++++++---- distr/flecs.h | 9 +++++- include/flecs/addons/script.h | 9 +++++- src/addons/script/expr/visit_eval.c | 20 ++++++++++++- src/addons/script/expr/visit_type.c | 12 +++++--- src/addons/script/visit_check.c | 3 +- test/script/project.json | 3 +- test/script/src/Template.c | 44 +++++++++++++++++++++++++++++ test/script/src/main.c | 7 ++++- 9 files changed, 126 insertions(+), 16 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 18bd67400d..7929f2cbe7 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -60276,7 +60276,8 @@ int flecs_script_check_expr( .lookup_ctx = v, .vars = v->vars, .type = type ? type[0] : 0, - .runtime = v->r + .runtime = v->r, + .allow_unresolved_identifiers = true }; ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); @@ -76627,7 +76628,25 @@ int flecs_expr_identifier_visit_eval( ecs_expr_identifier_t *node, ecs_expr_value_t *out) { - return flecs_expr_visit_eval_priv(ctx, node->expr, out); + if (node->expr) { + return flecs_expr_visit_eval_priv(ctx, node->expr, out); + } else { + ecs_entity_t e = ctx->desc->lookup_action( + ctx->world, node->value, ctx->desc->lookup_ctx); + if (!e) { + flecs_expr_visit_error(ctx->script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + ecs_assert(out->value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + *(ecs_entity_t*)out->value.ptr = e; + } + + return 0; +error: + return -1; } static @@ -79112,13 +79131,17 @@ int flecs_expr_identifier_visit_type( if (type == ecs_id(ecs_entity_t)) { result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); + result->ptr = &result->storage.entity; if (!result->storage.entity) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; + if (!desc->allow_unresolved_identifiers) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + result = NULL; } - result->ptr = &result->storage.entity; } else { ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); diff --git a/distr/flecs.h b/distr/flecs.h index 107949e8e4..c90dd41b12 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14778,7 +14778,14 @@ typedef struct ecs_expr_eval_desc_t { void *lookup_ctx; /**< Context passed to lookup function */ 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) */ + + /* Disable constant folding (slower evaluation, faster parsing) */ + bool disable_folding; + + /* Allow for unresolved identifiers when parsing. Useful when entities can + * be created in between parsing & evaluating. */ + bool allow_unresolved_identifiers; + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ } ecs_expr_eval_desc_t; diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 520b291df7..71ae11853f 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -504,7 +504,14 @@ typedef struct ecs_expr_eval_desc_t { void *lookup_ctx; /**< Context passed to lookup function */ 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) */ + + /* Disable constant folding (slower evaluation, faster parsing) */ + bool disable_folding; + + /* Allow for unresolved identifiers when parsing. Useful when entities can + * be created in between parsing & evaluating. */ + bool allow_unresolved_identifiers; + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ } ecs_expr_eval_desc_t; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 95697b3d15..7bf0eb216d 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -290,7 +290,25 @@ int flecs_expr_identifier_visit_eval( ecs_expr_identifier_t *node, ecs_expr_value_t *out) { - return flecs_expr_visit_eval_priv(ctx, node->expr, out); + if (node->expr) { + return flecs_expr_visit_eval_priv(ctx, node->expr, out); + } else { + ecs_entity_t e = ctx->desc->lookup_action( + ctx->world, node->value, ctx->desc->lookup_ctx); + if (!e) { + flecs_expr_visit_error(ctx->script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + ecs_assert(out->value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + *(ecs_entity_t*)out->value.ptr = e; + } + + return 0; +error: + return -1; } static diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 2e3b5c147c..53eb731761 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -875,13 +875,17 @@ int flecs_expr_identifier_visit_type( if (type == ecs_id(ecs_entity_t)) { result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); + result->ptr = &result->storage.entity; if (!result->storage.entity) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; + if (!desc->allow_unresolved_identifiers) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; + } + + result = NULL; } - result->ptr = &result->storage.entity; } else { ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c index adad390d76..35cf4a544e 100644 --- a/src/addons/script/visit_check.c +++ b/src/addons/script/visit_check.c @@ -23,7 +23,8 @@ int flecs_script_check_expr( .lookup_ctx = v, .vars = v->vars, .type = type ? type[0] : 0, - .runtime = v->r + .runtime = v->r, + .allow_unresolved_identifiers = true }; ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); diff --git a/test/script/project.json b/test/script/project.json index 7cee10fe8e..25df4a9aad 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -342,7 +342,8 @@ "template_w_with_var", "template_w_with_prop", "fold_const", - "bulk_create_template" + "bulk_create_template", + "template_w_expr_w_self_ref" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index b390218dc1..a2cfc3735e 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2440,3 +2440,47 @@ void Template_bulk_create_template(void) { ecs_fini(world); } + +void Template_template_w_expr_w_self_ref(void) { + ecs_world_t *world = ecs_init(); + + typedef struct Ref { + ecs_entity_t e; + } Ref; + + ECS_COMPONENT(world, Ref); + + ecs_struct(world, { + .entity = ecs_id(Ref), + .members = {{ .name = "e", .type = ecs_id(ecs_entity_t) }} + }); + + const char *expr = + HEAD "template Foo {" + LINE " a {}" + LINE " b = Ref: {a}" + LINE "}" + LINE "Foo ent()"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t tree = ecs_lookup(world, "Foo"); + test_assert(tree != 0); + + ecs_entity_t ent = ecs_lookup(world, "ent"); + test_assert(ent != 0); + + ecs_entity_t a = ecs_lookup(world, "ent.a"); + test_assert(a != 0); + + ecs_entity_t b = ecs_lookup(world, "ent.b"); + test_assert(b != 0); + + { + const Ref *r = ecs_get(world, b, Ref); + test_assert(r != NULL); + test_assert(r->e == a); + } + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 27759aad21..36c4a66e21 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -337,6 +337,7 @@ void Template_template_w_with_var(void); void Template_template_w_with_prop(void); void Template_fold_const(void); void Template_bulk_create_template(void); +void Template_template_w_expr_w_self_ref(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2120,6 +2121,10 @@ bake_test_case Template_testcases[] = { { "bulk_create_template", Template_bulk_create_template + }, + { + "template_w_expr_w_self_ref", + Template_template_w_expr_w_self_ref } }; @@ -4006,7 +4011,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 47, + 48, Template_testcases }, { From 5b5c27d09fc799eeeb6e07e887342ddf3f58be0b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 00:08:59 -0800 Subject: [PATCH 74/83] Fix issue where global with was applied to template code --- distr/flecs.c | 23 +++++++++----- src/addons/script/parser.c | 8 ++--- src/addons/script/template.c | 12 +++++++- src/addons/script/visit_eval.c | 3 +- test/script/project.json | 3 +- test/script/src/Eval.c | 8 ++--- test/script/src/Template.c | 56 ++++++++++++++++++++++++++++++++++ test/script/src/main.c | 7 ++++- 8 files changed, 98 insertions(+), 22 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 7929f2cbe7..3135923a23 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56650,19 +56650,15 @@ if_stmt: { // for for_stmt: { - // for $i + // for i Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { - if (Token(1)[0] != '$') { - Error("expected loop variable name"); - } - Expr(0, { ecs_expr_node_t *from = EXPR; Parse_1(EcsTokRange, { Expr(0, { ecs_expr_node_t *to = EXPR; ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); - stmt->loop_var = &Token(1)[1]; + stmt->loop_var = Token(1); stmt->from = from; stmt->to = to; @@ -58820,9 +58816,12 @@ void flecs_script_template_instantiate( flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; + ecs_vec_t prev_with = desc.runtime->with; + ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; v.r->using = template->using_; - v.template_entity = template_entity; + ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); + ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); ecs_script_scope_t *scope = template->node->scope; @@ -58881,6 +58880,13 @@ void flecs_script_template_instantiate( data = ECS_OFFSET(data, ti->size); } + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with, ecs_value_t); + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with_type_info, ecs_type_info_t*); + + v.r->with = prev_with; + v.r->with_type_info = prev_with_type_info; v.r->using = prev_using; flecs_script_eval_visit_fini(&v, &desc); } @@ -61251,7 +61257,8 @@ int flecs_script_eval_entity( node->parent = v->entity; if (v->template_entity) { - ecs_add_pair(v->world, node->eval, EcsScriptTemplate, v->template_entity); + ecs_add_pair( + v->world, node->eval, EcsScriptTemplate, v->template_entity); } if (is_slot) { diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 97faa953ad..7f55cfd147 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -517,19 +517,15 @@ if_stmt: { // for for_stmt: { - // for $i + // for i Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { - if (Token(1)[0] != '$') { - Error("expected loop variable name"); - } - Expr(0, { ecs_expr_node_t *from = EXPR; Parse_1(EcsTokRange, { Expr(0, { ecs_expr_node_t *to = EXPR; ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); - stmt->loop_var = &Token(1)[1]; + stmt->loop_var = Token(1); stmt->from = from; stmt->to = to; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 17f0088d03..2b4964b088 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -158,9 +158,12 @@ void flecs_script_template_instantiate( flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; + ecs_vec_t prev_with = desc.runtime->with; + ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; v.r->using = template->using_; - v.template_entity = template_entity; + ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); + ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); ecs_script_scope_t *scope = template->node->scope; @@ -219,6 +222,13 @@ void flecs_script_template_instantiate( data = ECS_OFFSET(data, ti->size); } + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with, ecs_value_t); + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with_type_info, ecs_type_info_t*); + + v.r->with = prev_with; + v.r->with_type_info = prev_with_type_info; v.r->using = prev_using; flecs_script_eval_visit_fini(&v, &desc); } diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 7e9146f639..8d688029b5 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -504,7 +504,8 @@ int flecs_script_eval_entity( node->parent = v->entity; if (v->template_entity) { - ecs_add_pair(v->world, node->eval, EcsScriptTemplate, v->template_entity); + ecs_add_pair( + v->world, node->eval, EcsScriptTemplate, v->template_entity); } if (is_slot) { diff --git a/test/script/project.json b/test/script/project.json index 25df4a9aad..5bf9413d48 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -343,7 +343,8 @@ "template_w_with_prop", "fold_const", "bulk_create_template", - "template_w_expr_w_self_ref" + "template_w_expr_w_self_ref", + "entity_w_assign_with_nested_template" ] }, { "id": "Error", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 44548b0980..faf106952b 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -9046,7 +9046,7 @@ void Eval_for_range(void) { }); const char *expr = - HEAD "for $i in 0..3 {" + HEAD "for i in 0..3 {" LINE " \"e_{$i}\" {" LINE " Position: {$i, $i * 2}" LINE " }" @@ -9102,7 +9102,7 @@ void Eval_for_range_vars(void) { const char *expr = HEAD "const x = 0" LINE "const y = 3" - LINE "for $i in $x..$y {" + LINE "for i in $x..$y {" LINE " \"e_{$i}\" {" LINE " Position: {$i, $i * 2}" LINE " }" @@ -9156,7 +9156,7 @@ void Eval_for_range_1_4(void) { }); const char *expr = - HEAD "for $i in 1..4 {" + HEAD "for i in 1..4 {" LINE " \"e_{$i}\" {" LINE " Position: {$i, $i * 2}" LINE " }" @@ -9212,7 +9212,7 @@ void Eval_for_range_min_1_2(void) { }); const char *expr = - HEAD "for $i in -1..2 {" + HEAD "for i in -1..2 {" LINE " \"e_{$i}\" {" LINE " Position: {$i, $i * 2}" LINE " }" diff --git a/test/script/src/Template.c b/test/script/src/Template.c index a2cfc3735e..e3b99b563a 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2484,3 +2484,59 @@ void Template_template_w_expr_w_self_ref(void) { ecs_fini(world); } + +void Template_entity_w_assign_with_nested_template(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE "}" + LINE "template Bar {" + LINE " Foo child()" + LINE "}" + LINE "with Bar() {" + LINE " e = Position: {10, 20}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(bar != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + ecs_entity_t child = ecs_lookup(world, "e.child"); + test_assert(child != 0); + + test_assert(!ecs_has_id(world, e, foo)); + test_assert(ecs_has_id(world, e, bar)); + test_assert(ecs_has(world, e, Position)); + + { + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + } + + test_assert(ecs_has_id(world, child, foo)); + test_assert(!ecs_has_id(world, child, bar)); + test_assert(!ecs_has(world, child, Position)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 36c4a66e21..a160f59bc4 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -338,6 +338,7 @@ void Template_template_w_with_prop(void); void Template_fold_const(void); void Template_bulk_create_template(void); void Template_template_w_expr_w_self_ref(void); +void Template_entity_w_assign_with_nested_template(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2125,6 +2126,10 @@ bake_test_case Template_testcases[] = { { "template_w_expr_w_self_ref", Template_template_w_expr_w_self_ref + }, + { + "entity_w_assign_with_nested_template", + Template_entity_w_assign_with_nested_template } }; @@ -4011,7 +4016,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 48, + 49, Template_testcases }, { From 75efeaf0de97caa1a59bea4f85e6c2efaf89885f Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 00:50:38 -0800 Subject: [PATCH 75/83] Fix issue with for loops in templates --- distr/flecs.c | 8 +++-- src/addons/script/visit_check.c | 8 +++-- test/script/project.json | 3 +- test/script/src/Template.c | 59 +++++++++++++++++++++++++++++++++ test/script/src/main.c | 7 +++- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 3135923a23..98a9cbe40c 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -60628,14 +60628,18 @@ int flecs_script_check_for_range( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); - var->value.ptr = NULL; + const ecs_type_info_t *ti = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + int32_t dummy = 0; + var->value.ptr = &dummy; var->value.type = ecs_id(ecs_i32_t); - var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + var->type_info = ti; if (flecs_script_eval_scope(v, node->scope)) { return -1; } + var->value.ptr = NULL; + v->vars = ecs_script_vars_pop(v->vars); return 0; diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c index 35cf4a544e..519ae43eb9 100644 --- a/src/addons/script/visit_check.c +++ b/src/addons/script/visit_check.c @@ -369,14 +369,18 @@ int flecs_script_check_for_range( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); - var->value.ptr = NULL; + const ecs_type_info_t *ti = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + int32_t dummy = 0; + var->value.ptr = &dummy; var->value.type = ecs_id(ecs_i32_t); - var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + var->type_info = ti; if (flecs_script_eval_scope(v, node->scope)) { return -1; } + var->value.ptr = NULL; + v->vars = ecs_script_vars_pop(v->vars); return 0; diff --git a/test/script/project.json b/test/script/project.json index 5bf9413d48..d4d7ea4227 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -344,7 +344,8 @@ "fold_const", "bulk_create_template", "template_w_expr_w_self_ref", - "entity_w_assign_with_nested_template" + "entity_w_assign_with_nested_template", + "template_w_for" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index e3b99b563a..0196ff9f4f 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2540,3 +2540,62 @@ void Template_entity_w_assign_with_nested_template(void) { ecs_fini(world); } + +void Template_template_w_for(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " for i in 0..2 {" + LINE" const t = $i" + LINE " \"child_$i\" = Position: {$t, $t + 2}" + LINE " }" + LINE "}" + LINE "Foo e()"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + ecs_entity_t child_0 = ecs_lookup(world, "e.child_0"); + test_assert(child_0 != 0); + ecs_entity_t child_1 = ecs_lookup(world, "e.child_1"); + test_assert(child_1 != 0); + + test_assert(ecs_has_id(world, e, foo)); + test_assert(!ecs_has(world, e, Position)); + + test_assert(ecs_has(world, child_0, Position)); + test_assert(ecs_has(world, child_1, Position)); + + { + const Position *p = ecs_get(world, child_0, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 2); + } + + { + const Position *p = ecs_get(world, child_1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 3); + } + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index a160f59bc4..35c77fdec4 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -339,6 +339,7 @@ void Template_fold_const(void); void Template_bulk_create_template(void); void Template_template_w_expr_w_self_ref(void); void Template_entity_w_assign_with_nested_template(void); +void Template_template_w_for(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2130,6 +2131,10 @@ bake_test_case Template_testcases[] = { { "entity_w_assign_with_nested_template", Template_entity_w_assign_with_nested_template + }, + { + "template_w_for", + Template_template_w_for } }; @@ -4016,7 +4021,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 49, + 50, Template_testcases }, { From 5deb1cb3426c86da6d7897a1cb6d26f83f847281 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 03:26:40 -0800 Subject: [PATCH 76/83] Fix issues with parsing binary expressions without spaces --- distr/flecs.c | 18 ++- src/addons/script/expr/parser.c | 7 + src/addons/script/tokenizer.c | 11 +- test/script/project.json | 12 ++ test/script/src/Expr.c | 233 ++++++++++++++++++++++++++++++++ test/script/src/main.c | 62 ++++++++- 6 files changed, 338 insertions(+), 5 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 98a9cbe40c..50151b08a0 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -59461,6 +59461,7 @@ const char* flecs_script_identifier( ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; char *outpos = NULL; + const char *start = pos; if (parser) { outpos = parser->token_cur; if (parser->merge_variable_members) { @@ -59470,8 +59471,7 @@ const char* flecs_script_identifier( do { char c = pos[0]; - bool is_ident = flecs_script_is_identifier(c) || - isdigit(c) || (c == '*'); + bool is_ident = flecs_script_is_identifier(c) || isdigit(c); if (!is_var) { is_ident = is_ident || (c == '.'); @@ -59482,6 +59482,13 @@ const char* flecs_script_identifier( is_ident = true; } + /* Retain .* for using wildcard expressions */ + if (!is_ident && c == '*') { + if (pos != start && pos[-1] == '.') { + is_ident = true; + } + } + if (!is_ident) { if (c == '\\') { pos ++; @@ -75443,6 +75450,13 @@ const char* flecs_script_parse_rhs( last_pos = pos; LookAhead( + case EcsTokNumber: + if (pos[0] == '-') { + lookahead = &pos[1]; + lookahead_token.kind = EcsTokSub; + } else { + Error("unexpected number"); + } case EcsTokAdd: case EcsTokSub: case EcsTokMul: diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index a9041873bf..d0ac573680 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -214,6 +214,13 @@ const char* flecs_script_parse_rhs( last_pos = pos; LookAhead( + case EcsTokNumber: + if (pos[0] == '-') { + lookahead = &pos[1]; + lookahead_token.kind = EcsTokSub; + } else { + Error("unexpected number"); + } case EcsTokAdd: case EcsTokSub: case EcsTokMul: diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 0603ef7eb4..3793c065e6 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -221,6 +221,7 @@ const char* flecs_script_identifier( ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; char *outpos = NULL; + const char *start = pos; if (parser) { outpos = parser->token_cur; if (parser->merge_variable_members) { @@ -230,8 +231,7 @@ const char* flecs_script_identifier( do { char c = pos[0]; - bool is_ident = flecs_script_is_identifier(c) || - isdigit(c) || (c == '*'); + bool is_ident = flecs_script_is_identifier(c) || isdigit(c); if (!is_var) { is_ident = is_ident || (c == '.'); @@ -242,6 +242,13 @@ const char* flecs_script_identifier( is_ident = true; } + /* Retain .* for using wildcard expressions */ + if (!is_ident && c == '*') { + if (pos != start && pos[-1] == '.') { + is_ident = true; + } + } + if (!is_ident) { if (c == '\\') { pos ++; diff --git a/test/script/project.json b/test/script/project.json index d4d7ea4227..db3797c057 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -451,6 +451,18 @@ "div_3_int_literals", "mod_2_int_literals", "mod_2_flt_literals", + "add_no_space", + "sub_no_space", + "div_no_space", + "mul_no_space", + "add_no_space_var", + "sub_no_space_var", + "div_no_space_var", + "mul_no_space_var", + "add_no_space_var_reverse", + "sub_no_space_var_reverse", + "div_no_space_var_reverse", + "mul_no_space_var_reverse", "div_by_0", "div_by_0_var", "mod_by_0", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 597bda6b1c..b2eb347f22 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -193,6 +193,239 @@ void Expr_mod_2_flt_literals(void) { ecs_fini(world); } +void Expr_add_no_space(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10+20", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(uint64_t*)v.ptr, 10 + 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_sub_no_space(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20-10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(uint64_t*)v.ptr, 20 - 10); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_div_no_space(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "20/2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_f64_t)); + test_assert(v.ptr != NULL); + test_uint(*(double*)v.ptr, 20 / 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_mul_no_space(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10*2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(uint64_t*)v.ptr, 10 * 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + + +void Expr_add_no_space_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "10+$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 10 + 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_sub_no_space_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "20-$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 20 - 10); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_div_no_space_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 2; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "20/$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_f64_t)); + test_assert(v.ptr != NULL); + test_uint(*(double*)v.ptr, 20 / 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_mul_no_space_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 2; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "10*$foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 10 * 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_add_no_space_var_reverse(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "$foo+10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 10 + 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_sub_no_space_var_reverse(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "$foo-10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 20 - 10); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_div_no_space_var_reverse(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "$foo/2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_f64_t)); + test_assert(v.ptr != NULL); + test_uint(*(double*)v.ptr, 20 / 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_mul_no_space_var_reverse(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 10; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "$foo*2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 10 * 2); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + void Expr_div_by_0(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 35c77fdec4..abb16c7775 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -438,6 +438,18 @@ void Expr_mul_3_int_literals(void); void Expr_div_3_int_literals(void); void Expr_mod_2_int_literals(void); void Expr_mod_2_flt_literals(void); +void Expr_add_no_space(void); +void Expr_sub_no_space(void); +void Expr_div_no_space(void); +void Expr_mul_no_space(void); +void Expr_add_no_space_var(void); +void Expr_sub_no_space_var(void); +void Expr_div_no_space_var(void); +void Expr_mul_no_space_var(void); +void Expr_add_no_space_var_reverse(void); +void Expr_sub_no_space_var_reverse(void); +void Expr_div_no_space_var_reverse(void); +void Expr_mul_no_space_var_reverse(void); void Expr_div_by_0(void); void Expr_div_by_0_var(void); void Expr_mod_by_0(void); @@ -2514,6 +2526,54 @@ bake_test_case Expr_testcases[] = { "mod_2_flt_literals", Expr_mod_2_flt_literals }, + { + "add_no_space", + Expr_add_no_space + }, + { + "sub_no_space", + Expr_sub_no_space + }, + { + "div_no_space", + Expr_div_no_space + }, + { + "mul_no_space", + Expr_mul_no_space + }, + { + "add_no_space_var", + Expr_add_no_space_var + }, + { + "sub_no_space_var", + Expr_sub_no_space_var + }, + { + "div_no_space_var", + Expr_div_no_space_var + }, + { + "mul_no_space_var", + Expr_mul_no_space_var + }, + { + "add_no_space_var_reverse", + Expr_add_no_space_var_reverse + }, + { + "sub_no_space_var_reverse", + Expr_sub_no_space_var_reverse + }, + { + "div_no_space_var_reverse", + Expr_div_no_space_var_reverse + }, + { + "mul_no_space_var_reverse", + Expr_mul_no_space_var_reverse + }, { "div_by_0", Expr_div_by_0 @@ -4035,7 +4095,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 212, + 224, Expr_testcases, 1, Expr_params From 5d6b84917ad7a4e1a79fb1bf4529575c381745e6 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 19:09:31 +0000 Subject: [PATCH 77/83] Fix memory leaks --- distr/flecs.c | 24 +++++++++++++----------- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/addons/script/expr/visit_type.c | 14 ++++++++------ src/addons/script/visit_check.c | 1 + src/addons/script/visit_eval.c | 9 ++++----- test/script/src/ExprAst.c | 2 +- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 50151b08a0..58b20b1ef7 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -60274,6 +60274,7 @@ int ecs_script_visit_( #ifdef FLECS_SCRIPT +static int flecs_script_check_expr( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr_ptr, @@ -61930,8 +61931,10 @@ int flecs_script_eval_for_range( ecs_script_eval_visitor_t *v, ecs_script_for_range_t *node) { - ecs_value_t from_val = { .type = ecs_id(ecs_i32_t) }; - ecs_value_t to_val = { .type = ecs_id(ecs_i32_t) }; + int32_t from; + int32_t to; + ecs_value_t from_val = { .type = ecs_id(ecs_i32_t), .ptr = &from }; + ecs_value_t to_val = { .type = ecs_id(ecs_i32_t), .ptr = &to }; if (flecs_script_eval_expr(v, &node->from, &from_val)) { return -1; @@ -61941,9 +61944,6 @@ int flecs_script_eval_for_range( return -1; } - int32_t from = *(int32_t*)from_val.ptr; - int32_t to = *(int32_t*)to_val.ptr; - v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); @@ -78847,10 +78847,10 @@ int flecs_expr_interpolated_string_visit_type( } /* 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_size_t var_name_pos = flecs_ito(int32_t, var_name - node->value); + var_name = &node->buffer[var_name_pos]; + ecs_size_t var_name_end = flecs_ito(int32_t, ptr - node->value); + node->buffer[var_name_end] = '\0'; ecs_expr_variable_t *var = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, var_name); @@ -78898,6 +78898,7 @@ int flecs_expr_interpolated_string_visit_type( result = (ecs_expr_node_t*)flecs_expr_cast(script, (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); if (!result) { + /* Cast failed */ goto error; } } @@ -79015,11 +79016,12 @@ int flecs_expr_initializer_visit_type( } if (elem->value->type != elem_type) { - elem->value = (ecs_expr_node_t*)flecs_expr_cast( + ecs_expr_node_t *cast = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, elem_type); - if (!elem->value) { + if (!cast) { goto error; } + elem->value = cast; } if (!is_opaque) { diff --git a/distr/flecs.h b/distr/flecs.h index c90dd41b12..e9d1f9f680 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -265,7 +265,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index 027abc2392..ec96077c27 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -263,7 +263,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 53eb731761..dfed363071 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -566,10 +566,10 @@ int flecs_expr_interpolated_string_visit_type( } /* 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_size_t var_name_pos = flecs_ito(int32_t, var_name - node->value); + var_name = &node->buffer[var_name_pos]; + ecs_size_t var_name_end = flecs_ito(int32_t, ptr - node->value); + node->buffer[var_name_end] = '\0'; ecs_expr_variable_t *var = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, var_name); @@ -617,6 +617,7 @@ int flecs_expr_interpolated_string_visit_type( result = (ecs_expr_node_t*)flecs_expr_cast(script, (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); if (!result) { + /* Cast failed */ goto error; } } @@ -734,11 +735,12 @@ int flecs_expr_initializer_visit_type( } if (elem->value->type != elem_type) { - elem->value = (ecs_expr_node_t*)flecs_expr_cast( + ecs_expr_node_t *cast = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, elem_type); - if (!elem->value) { + if (!cast) { goto error; } + elem->value = cast; } if (!is_opaque) { diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c index 519ae43eb9..b7ec8bc6b6 100644 --- a/src/addons/script/visit_check.c +++ b/src/addons/script/visit_check.c @@ -8,6 +8,7 @@ #ifdef FLECS_SCRIPT #include "script.h" +static int flecs_script_check_expr( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr_ptr, diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 8d688029b5..249ae15901 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1166,8 +1166,10 @@ int flecs_script_eval_for_range( ecs_script_eval_visitor_t *v, ecs_script_for_range_t *node) { - ecs_value_t from_val = { .type = ecs_id(ecs_i32_t) }; - ecs_value_t to_val = { .type = ecs_id(ecs_i32_t) }; + int32_t from; + int32_t to; + ecs_value_t from_val = { .type = ecs_id(ecs_i32_t), .ptr = &from }; + ecs_value_t to_val = { .type = ecs_id(ecs_i32_t), .ptr = &to }; if (flecs_script_eval_expr(v, &node->from, &from_val)) { return -1; @@ -1177,9 +1179,6 @@ int flecs_script_eval_for_range( return -1; } - int32_t from = *(int32_t*)from_val.ptr; - int32_t to = *(int32_t*)to_val.ptr; - v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); diff --git a/test/script/src/ExprAst.c b/test/script/src/ExprAst.c index 93703ac9e5..59646973ef 100644 --- a/test/script/src/ExprAst.c +++ b/test/script/src/ExprAst.c @@ -250,7 +250,7 @@ void ExprAst_interpolated_string_curly_brackets_w_var(void) { void ExprAst_template_w_foldable_const(void) { ecs_world_t *world = ecs_init(); - ecs_entity_t ecs_id(Position) = ecs_struct(world, { + ecs_struct(world, { .entity = ecs_entity(world, {.name = "Position"}), .members = { {"x", ecs_id(ecs_f32_t)}, From 8dad0d8c3f5c0cfad46d80083ce0432dc78852b8 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 22:29:26 +0000 Subject: [PATCH 78/83] Fix fuzzing errors - Fix invalid write in flecs_path_append - Correctly handle name lookups with multiple consecutive separators - Correctly handle attempts to create entities with ids > UINT32_MAX - Improve cycle detection in observer code/prevent stack overflows --- distr/flecs.c | 70 ++++++-- distr/flecs.h | 10 +- include/flecs.h | 10 +- src/addons/script/expr/visit_eval.c | 2 + src/entity.c | 7 +- src/entity_name.c | 32 +++- src/observable.c | 29 ++- test/core/project.json | 5 +- test/core/src/Entity.c | 40 +++++ test/core/src/main.c | 17 +- test/script/project.json | 18 ++ test/script/src/Fuzzing.c | 266 ++++++++++++++++++++++++++++ test/script/src/main.c | 84 ++++++++- 13 files changed, 559 insertions(+), 31 deletions(-) create mode 100644 test/script/src/Fuzzing.c diff --git a/distr/flecs.c b/distr/flecs.c index 58b20b1ef7..d29f012bf4 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -7377,7 +7377,12 @@ int flecs_traverse_add( /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { - ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); + if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { + if (name[0] == '#') { + /* Numerical ids should always return, unless it's invalid */ + goto error; + } + } } else if (new_entity && scope) { ecs_add_pair(world, result, EcsChildOf, scope); } @@ -11104,7 +11109,11 @@ ecs_entity_t flecs_name_to_id( { ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); - return flecs_ito(uint64_t, atoll(name + 1)); + ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); + if (res >= UINT32_MAX) { + return 0; /* Invalid id */ + } + return res; } static @@ -11178,7 +11187,7 @@ const char* flecs_path_elem( } if (buffer) { - if (pos == (size - 1)) { + if (pos >= (size - 1)) { if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ char *new_buffer = ecs_os_malloc(size * 2 + 1); ecs_os_memcpy(new_buffer, buffer, size); @@ -11201,7 +11210,7 @@ const char* flecs_path_elem( *size_out = size; } - if (pos) { + if (pos || ptr[0]) { return ptr; } else { return NULL; @@ -11229,8 +11238,11 @@ ecs_entity_t flecs_get_parent_from_path( const char **path_ptr, const char *sep, const char *prefix, - bool new_entity) + bool new_entity, + bool *error) { + ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); + bool start_from_root = false; const char *path = *path_ptr; @@ -11242,6 +11254,10 @@ ecs_entity_t flecs_get_parent_from_path( if (path[0] == '#') { parent = flecs_name_to_id(path); + if (!parent && ecs_os_strncmp(path, "#0", 2)) { + *error = true; + return 0; + } path ++; while (path[0] && isdigit(path[0])) { @@ -11451,8 +11467,12 @@ ecs_entity_t ecs_lookup_path_w_sep( sep = "."; } + bool error = false; parent = flecs_get_parent_from_path( - stage, parent, &path, sep, prefix, true); + stage, parent, &path, sep, prefix, true, &error); + if (error) { + return 0; + } if (parent && !(parent = ecs_get_alive(world, parent))) { return 0; @@ -11630,8 +11650,13 @@ ecs_entity_t ecs_add_path_w_sep( } bool root_path = flecs_is_root_path(path, prefix); + bool error = false; parent = flecs_get_parent_from_path( - world, parent, &path, sep, prefix, !entity); + world, parent, &path, sep, prefix, !entity, &error); + if (error) { + /* Invalid id */ + return 0; + } char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; @@ -13816,7 +13841,8 @@ void flecs_emit_forward_up( ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids); + ecs_vec_t *reachable_ids, + int32_t depth); static void flecs_emit_forward_id( @@ -14055,7 +14081,8 @@ void flecs_emit_forward_table_up( ecs_record_t *tgt_record, ecs_id_record_t *tgt_idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids) + ecs_vec_t *reachable_ids, + int32_t depth) { ecs_allocator_t *a = &world->allocator; int32_t i, id_count = tgt_table->type.count; @@ -14092,6 +14119,13 @@ void flecs_emit_forward_table_up( continue; } + if (idr == tgt_idr) { + char *idstr = ecs_id_str(world, idr->id); + ecs_assert(idr != tgt_idr, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } + /* Id has the same relationship, traverse to find ids for forwarding */ if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, @@ -14116,7 +14150,7 @@ void flecs_emit_forward_table_up( /* Cache is dirty, traverse upwards */ do { flecs_emit_forward_up(world, er, er_onset, emit_ids, it, - table, idr, stack, reachable_ids); + table, idr, stack, reachable_ids, depth); if (++i >= id_count) { break; } @@ -14198,8 +14232,16 @@ void flecs_emit_forward_up( ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids) + ecs_vec_t *reachable_ids, + int32_t depth) { + if (depth >= FLECS_DAG_DEPTH_MAX) { + char *idstr = ecs_id_str(world, idr->id); + ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } + ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); @@ -14211,7 +14253,7 @@ void flecs_emit_forward_up( } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, - tgt, tgt_table, tgt_record, idr, stack, reachable_ids); + tgt, tgt_table, tgt_record, idr, stack, reachable_ids, depth + 1); } static @@ -14239,7 +14281,7 @@ void flecs_emit_forward( ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, - idr, &stack, &rc->ids); + idr, &stack, &rc->ids, 0); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); @@ -76656,6 +76698,8 @@ int flecs_expr_identifier_visit_eval( if (node->expr) { return flecs_expr_visit_eval_priv(ctx, node->expr, out); } else { + ecs_assert(ctx->desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx->desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = ctx->desc->lookup_action( ctx->world, node->value, ctx->desc->lookup_ctx); if (!e) { diff --git a/distr/flecs.h b/distr/flecs.h index e9d1f9f680..d53b1ce527 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -265,7 +265,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ @@ -307,6 +307,14 @@ #define FLECS_QUERY_SCOPE_NESTING_MAX (8) #endif +/** @def FLECS_DAG_DEPTH_MAX + * Maximum of levels in a DAG (acyclic relationship graph). If a graph with a + * depth larger than this is encountered, a CYCLE_DETECTED panic is thrown. + */ +#ifndef FLECS_DAG_DEPTH_MAX +#define FLECS_DAG_DEPTH_MAX (128) +#endif + /** @} */ /** diff --git a/include/flecs.h b/include/flecs.h index ec96077c27..9ea3994132 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -263,7 +263,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ @@ -305,6 +305,14 @@ #define FLECS_QUERY_SCOPE_NESTING_MAX (8) #endif +/** @def FLECS_DAG_DEPTH_MAX + * Maximum of levels in a DAG (acyclic relationship graph). If a graph with a + * depth larger than this is encountered, a CYCLE_DETECTED panic is thrown. + */ +#ifndef FLECS_DAG_DEPTH_MAX +#define FLECS_DAG_DEPTH_MAX (128) +#endif + /** @} */ #include "flecs/private/api_defines.h" diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 7bf0eb216d..820927191b 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -293,6 +293,8 @@ int flecs_expr_identifier_visit_eval( if (node->expr) { return flecs_expr_visit_eval_priv(ctx, node->expr, out); } else { + ecs_assert(ctx->desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx->desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = ctx->desc->lookup_action( ctx->world, node->value, ctx->desc->lookup_ctx); if (!e) { diff --git a/src/entity.c b/src/entity.c index a33cc72385..84e288b867 100644 --- a/src/entity.c +++ b/src/entity.c @@ -1753,7 +1753,12 @@ int flecs_traverse_add( /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { - ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); + if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { + if (name[0] == '#') { + /* Numerical ids should always return, unless it's invalid */ + goto error; + } + } } else if (new_entity && scope) { ecs_add_pair(world, result, EcsChildOf, scope); } diff --git a/src/entity_name.c b/src/entity_name.c index 1bc0a11206..5fcd5f7cfe 100644 --- a/src/entity_name.c +++ b/src/entity_name.c @@ -131,7 +131,11 @@ ecs_entity_t flecs_name_to_id( { ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); - return flecs_ito(uint64_t, atoll(name + 1)); + ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); + if (res >= UINT32_MAX) { + return 0; /* Invalid id */ + } + return res; } static @@ -205,7 +209,7 @@ const char* flecs_path_elem( } if (buffer) { - if (pos == (size - 1)) { + if (pos >= (size - 1)) { if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ char *new_buffer = ecs_os_malloc(size * 2 + 1); ecs_os_memcpy(new_buffer, buffer, size); @@ -228,7 +232,7 @@ const char* flecs_path_elem( *size_out = size; } - if (pos) { + if (pos || ptr[0]) { return ptr; } else { return NULL; @@ -256,8 +260,11 @@ ecs_entity_t flecs_get_parent_from_path( const char **path_ptr, const char *sep, const char *prefix, - bool new_entity) + bool new_entity, + bool *error) { + ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); + bool start_from_root = false; const char *path = *path_ptr; @@ -269,6 +276,10 @@ ecs_entity_t flecs_get_parent_from_path( if (path[0] == '#') { parent = flecs_name_to_id(path); + if (!parent && ecs_os_strncmp(path, "#0", 2)) { + *error = true; + return 0; + } path ++; while (path[0] && isdigit(path[0])) { @@ -478,8 +489,12 @@ ecs_entity_t ecs_lookup_path_w_sep( sep = "."; } + bool error = false; parent = flecs_get_parent_from_path( - stage, parent, &path, sep, prefix, true); + stage, parent, &path, sep, prefix, true, &error); + if (error) { + return 0; + } if (parent && !(parent = ecs_get_alive(world, parent))) { return 0; @@ -657,8 +672,13 @@ ecs_entity_t ecs_add_path_w_sep( } bool root_path = flecs_is_root_path(path, prefix); + bool error = false; parent = flecs_get_parent_from_path( - world, parent, &path, sep, prefix, !entity); + world, parent, &path, sep, prefix, !entity, &error); + if (error) { + /* Invalid id */ + return 0; + } char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; diff --git a/src/observable.c b/src/observable.c index d6bcdbe797..bfc9bf95f7 100644 --- a/src/observable.c +++ b/src/observable.c @@ -576,7 +576,8 @@ void flecs_emit_forward_up( ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids); + ecs_vec_t *reachable_ids, + int32_t depth); static void flecs_emit_forward_id( @@ -815,7 +816,8 @@ void flecs_emit_forward_table_up( ecs_record_t *tgt_record, ecs_id_record_t *tgt_idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids) + ecs_vec_t *reachable_ids, + int32_t depth) { ecs_allocator_t *a = &world->allocator; int32_t i, id_count = tgt_table->type.count; @@ -852,6 +854,13 @@ void flecs_emit_forward_table_up( continue; } + if (idr == tgt_idr) { + char *idstr = ecs_id_str(world, idr->id); + ecs_assert(idr != tgt_idr, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } + /* Id has the same relationship, traverse to find ids for forwarding */ if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, @@ -876,7 +885,7 @@ void flecs_emit_forward_table_up( /* Cache is dirty, traverse upwards */ do { flecs_emit_forward_up(world, er, er_onset, emit_ids, it, - table, idr, stack, reachable_ids); + table, idr, stack, reachable_ids, depth); if (++i >= id_count) { break; } @@ -958,8 +967,16 @@ void flecs_emit_forward_up( ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, - ecs_vec_t *reachable_ids) + ecs_vec_t *reachable_ids, + int32_t depth) { + if (depth >= FLECS_DAG_DEPTH_MAX) { + char *idstr = ecs_id_str(world, idr->id); + ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } + ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); @@ -971,7 +988,7 @@ void flecs_emit_forward_up( } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, - tgt, tgt_table, tgt_record, idr, stack, reachable_ids); + tgt, tgt_table, tgt_record, idr, stack, reachable_ids, depth + 1); } static @@ -999,7 +1016,7 @@ void flecs_emit_forward( ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, - idr, &stack, &rc->ids); + idr, &stack, &rc->ids, 0); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); diff --git a/test/core/project.json b/test/core/project.json index 9906ff644c..e68f2b9cd7 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -205,7 +205,10 @@ "entity_w_parent_w_add", "entity_w_parent_w_add_w_parent", "entity_w_parent_w_set", - "entity_w_parent_w_set_w_parent" + "entity_w_parent_w_set_w_parent", + "entity_w_new_id_and_double_dot", + "entity_w_existing_id_and_double_dot", + "entity_w_large_id_name" ] }, { "id": "Each", diff --git a/test/core/src/Entity.c b/test/core/src/Entity.c index 5d3ffa23ee..ddcc899d46 100644 --- a/test/core/src/Entity.c +++ b/test/core/src/Entity.c @@ -3081,3 +3081,43 @@ void Entity_entity_w_parent_w_set_w_parent(void) { ecs_fini(world); } + +void Entity_entity_w_new_id_and_double_dot(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t e = ecs_entity(world, { + .name = "#400..bar" + }); + + test_assert(e != 0); + + ecs_fini(world); +} + +void Entity_entity_w_existing_id_and_double_dot(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t e = ecs_entity(world, { + .name = "#10..bar" + }); + + test_assert(e != 0); + + ecs_fini(world); +} + +void Entity_entity_w_large_id_name(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t e = ecs_entity(world, { + .name = "#44444444444444444444a" + }); + + test_assert(e == 0); + + ecs_entity_t f = ecs_new(world); + + test_assert(f != 0); + + ecs_fini(world); +} diff --git a/test/core/src/main.c b/test/core/src/main.c index c169c31630..5ab5181746 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -199,6 +199,9 @@ void Entity_entity_w_parent_w_add(void); void Entity_entity_w_parent_w_add_w_parent(void); void Entity_entity_w_parent_w_set(void); void Entity_entity_w_parent_w_set_w_parent(void); +void Entity_entity_w_new_id_and_double_dot(void); +void Entity_entity_w_existing_id_and_double_dot(void); +void Entity_entity_w_large_id_name(void); // Testsuite 'Each' void Each_each_tag(void); @@ -3087,6 +3090,18 @@ bake_test_case Entity_testcases[] = { { "entity_w_parent_w_set_w_parent", Entity_entity_w_parent_w_set_w_parent + }, + { + "entity_w_new_id_and_double_dot", + Entity_entity_w_new_id_and_double_dot + }, + { + "entity_w_existing_id_and_double_dot", + Entity_entity_w_existing_id_and_double_dot + }, + { + "entity_w_large_id_name", + Entity_entity_w_large_id_name } }; @@ -11351,7 +11366,7 @@ static bake_test_suite suites[] = { "Entity", NULL, NULL, - 141, + 144, Entity_testcases }, { diff --git a/test/script/project.json b/test/script/project.json index db3797c057..e5720c106f 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -852,6 +852,24 @@ "opaque_struct_w_member_reverse", "struct_w_opaque_member" ] + }, { + "id": "Fuzzing", + "testcases": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14" + ] }] } } diff --git a/test/script/src/Fuzzing.c b/test/script/src/Fuzzing.c new file mode 100644 index 0000000000..a2ebd23c8b --- /dev/null +++ b/test/script/src/Fuzzing.c @@ -0,0 +1,266 @@ +#include + +static +void fuzz(const char *expr) { + ecs_world_t *world = ecs_init(); + ecs_log_set_level(-5); + ecs_script_run(world, "test", expr); + test_assert(true); + ecs_fini(world); +} + +void Fuzzing_1(void) { + const char *expr = + HEAD "const pi = 3.1415926" + LINE "const pi_2 = $pi * 2" + LINE " $pi " + LINE "my_entity {" + LINE "tation: {aUg/ 2}" + ; + + fuzz(expr); +} + +void Fuzzing_2(void) { + const char *expr = + HEAD "const pi = 3.141" + LINE "const pi_2 =415926" + LINE "c�\\entity ⏎" + ; + + fuzz(expr); +} + +void Fuzzing_3(void) { + const char *expr = + HEAD "const pi = 3.1415926" + LINE "ct\\\\\\\\\\\\\nst pist= 3\\415926" + LINE "consu pi_2??? = 0pi * $" + LINE "" + LINE "my_intity {" + LINE "t\\\\\\\\\\\\\\\\\\\\.1" + ; + + fuzz(expr); +} + +void Fuzzing_4(void) { + const char *expr = + HEAD "coRRRRRRla,#44444444444444444444a,C" + LINE "strumela,#44444444444444" + ; + + fuzz(expr); +} + +void Fuzzing_5(void) { + const char *expr = + HEAD "coRRRSVRRco3V.melconsta,#43444444444a,melRRcoRV.melconsta,#43444444444a,#234444" + ; + + fuzz(expr); +} + +void Fuzzing_6(void) { + const char *expr = + HEAD "VRRcoRcoRRRSRRRRRRRRRR,#23444nsta,#434.melRRcoRV,#23444nsta,#434..melco4" + ; + + fuzz(expr); +} + +void Fuzzing_7(void) { + const char *expr = + HEAD "coRRla,X" + LINE "RRRRRR\\.mella,C" + LINE "struct Posi {JRRRR\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRRRRla,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,Ct pi =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,Ct pi =>3.13* 2" + LINE "" + LINE "my_entit}" + LINE "" + LINE "RRla,D" + LINE "s" + ; + + fuzz(expr); +} + +void Fuzzing_8(void) { + const char *expr = + HEAD "coc,a,#44464RN,X,#4444444444444" + LINE "RRRRRR\\.mconstella,C" + LINE "struct Posi {JRla,Ct pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\const.vRRR\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRRRRla,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,Ct pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\.v :C" + LINE "RRRR= f32" + LINE " y = 3.13*" + LINE "my_t}" + LINE "la,D" + ; + + fuzz(expr); +} + +void Fuzzing_9(void) { + install_test_abort(); + + test_expect_abort(); + + const char *expr = + HEAD "coc,a,#44464RN,X" + LINE "RRRRRR\\.mconstella,C" + LINE "struct Posi {JRla,Ct pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\const.vRRR\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRRRRla,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,CtRRRSVR" + LINE "sts\\.RR.m :C" + LINE "ISRRconst\\.vRca,#434lRRcoRV.melcon pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRRconst\\.v =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\.v ;C:C" + LINE "RRRR= f32" + LINE "@ y = 3.13*" + LINE "my_t}" + LINE "la,D" + ; + + fuzz(expr); +} + +void Fuzzing_10(void) { + const char *expr = + HEAD "coc,a,#44464RN,X" + LINE "RRRRRR\\.mconstella,C" + LINE "struct Posi {JRla,CCCCCCCCCCCCCCCCCt pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\const.vRRR\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRTRRla,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,CtRRRSVRhip<:$t {" + LINE " Position: {" + LINE "sts\\.RR.m :C" + LINE "ISRRconst\fab SpaceShip {" + LINE " Position: {" + LINE "sts\\. {.vRca,#434lRRcoRV.melcon pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRRconst\\.v :C" + LINE "RRRR> f32" + LINE "@ y const13*" + LINE "my_t}" + LINE "la,D" + ; + + fuzz(expr); +} + +void Fuzzing_11(void) { + const char *expr = + HEAD "coRRla,X" + LINE "RRRRRR\\.mella,C" + LINE "struct Posi {JRRRR\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRRRRla,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,Ct pi1=*" + LINE "sts\\.RR.m :C" + LINE "ISRR\\.v ;C" + LINE "RRRRRela, #" + LINE "" + LINE "my_entit}" + LINE "" + LINE "RRla,D" + LINE "s" + ; + + fuzz(expr); +} + +void Fuzzing_12(void) { + const char *expr = + HEAD "coRcoa,a,#44464RN,X" + LINE "RR\\.la,C" + LINE "struct Posi {JRla,Ct pU.mesu.RR.R.d {" + LINE " vacts\\.RR.m :C" + LINE "ISRR\\const.v\\.melRQRR.m :C" + LINE "ISRR\\.m :C" + LINE "RRRconstRRla,C" + LINE "ISRR\\.v :C" + LINE "RRSRRela,CtJRRSVR" + LINE "sts\\.RR.m :C" + LINE "IS.melaMaxSpeed {" + LINE " value = f32" + LINE "vRca,#4const399999999.melcon pla,CRR\\..R" + LINE "f =C" + LINE "lue = f32" + LINE "}" + LINE "" + LINE "C" + LINE "sts\\.RR.d {" + LINE " value = f32" + LINE "}" + LINE "" + LINE "m :C" + LINE "ISRRconst\\.v :C" + LINE "RRRR= f32" + LINE "@ y 13� value = f32" + LINE "}" + LINE "" + LINE "m :C" + LINE "ISR*" + LINE "my_t}" + LINE "l" + ; + + fuzz(expr); +} + +void Fuzzing_13(void) { + const char *expr = + HEAD "coc,a,#44464RN,X" + LINE "RRRRRR\\.mconstelld,C" + LINE "struct Posi {JRla,Ct pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRR\\const.vRRR\\.melRQRR.m :C" + LINE "ISRR\\\\\\\\\\\\\\,C" + LINE "ISRR\\.v :C" + LINE "RRRRRela,CtRRRSVRhip<:$t {" + LINE " Position: {" + LINE "sts\\.RR.r :C" + LINE "ISRRconst\fab SpaceShip {.vRca,#434l :C" + LINE "IRRcoRV.melcon pf =C" + LINE "sts\\.RR.m :C" + LINE "ISRRconst\\.v :C" + LINE "RRRR> f32" + LINE "@ y = 3.13*" + LINE "my_t}" + LINE "la,D" + ; + + fuzz(expr); +} + +void Fuzzing_14(void) { + const char *expr = + HEAD "#299 psC" + LINE "RRRRm###44" + ; + + fuzz(expr); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index abb16c7775..1794c3815b 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -828,6 +828,22 @@ void Deserialize_opaque_struct_w_member(void); void Deserialize_opaque_struct_w_member_reverse(void); void Deserialize_struct_w_opaque_member(void); +// Testsuite 'Fuzzing' +void Fuzzing_1(void); +void Fuzzing_2(void); +void Fuzzing_3(void); +void Fuzzing_4(void); +void Fuzzing_5(void); +void Fuzzing_6(void); +void Fuzzing_7(void); +void Fuzzing_8(void); +void Fuzzing_9(void); +void Fuzzing_10(void); +void Fuzzing_11(void); +void Fuzzing_12(void); +void Fuzzing_13(void); +void Fuzzing_14(void); + bake_test_case Eval_testcases[] = { { "null", @@ -4060,6 +4076,65 @@ bake_test_case Deserialize_testcases[] = { } }; +bake_test_case Fuzzing_testcases[] = { + { + "1", + Fuzzing_1 + }, + { + "2", + Fuzzing_2 + }, + { + "3", + Fuzzing_3 + }, + { + "4", + Fuzzing_4 + }, + { + "5", + Fuzzing_5 + }, + { + "6", + Fuzzing_6 + }, + { + "7", + Fuzzing_7 + }, + { + "8", + Fuzzing_8 + }, + { + "9", + Fuzzing_9 + }, + { + "10", + Fuzzing_10 + }, + { + "11", + Fuzzing_11 + }, + { + "12", + Fuzzing_12 + }, + { + "13", + Fuzzing_13 + }, + { + "14", + Fuzzing_14 + } +}; + const char* Expr_folding_param[] = {"enabled", "disabled"}; bake_test_param Expr_params[] = { {"folding", (char**)Expr_folding_param, 2} @@ -4129,9 +4204,16 @@ static bake_test_suite suites[] = { Deserialize_testcases, 1, Deserialize_params + }, + { + "Fuzzing", + NULL, + NULL, + 14, + Fuzzing_testcases } }; int main(int argc, char *argv[]) { - return bake_test_run("script", argc, argv, suites, 8); + return bake_test_run("script", argc, argv, suites, 9); } From b9f0d4bc8ac484ccd9a8d297e58e6cc5d087e4cd Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 14 Dec 2024 23:09:22 +0000 Subject: [PATCH 79/83] Fix memory leaks in meta cursor string conversions --- distr/flecs.c | 11 +++ src/addons/meta/cursor.c | 11 +++ test/meta/project.json | 9 ++ test/meta/src/Cursor.c | 182 +++++++++++++++++++++++++++++++++++++++ test/meta/src/main.c | 47 +++++++++- 5 files changed, 259 insertions(+), 1 deletion(-) diff --git a/distr/flecs.c b/distr/flecs.c index d29f012bf4..fab84c71a6 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -49090,6 +49090,7 @@ int ecs_meta_set_bool( } else { result = ecs_os_strdup("false"); } + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49133,6 +49134,7 @@ int ecs_meta_set_char( cases_T_signed(ptr, value, ecs_meta_bounds_signed); case EcsOpString: { char *result = flecs_asprintf("%c", value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49196,6 +49198,7 @@ int ecs_meta_set_int( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%"PRId64, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49253,6 +49256,7 @@ int ecs_meta_set_uint( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%"PRIu64, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49315,6 +49319,7 @@ int ecs_meta_set_float( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%f", value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49710,6 +49715,10 @@ int ecs_meta_set_string_literal( ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + if (!value) { + return -1; + } + ecs_size_t len = ecs_os_strlen(value); if (value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); @@ -49787,6 +49796,7 @@ int ecs_meta_set_entity( break; case EcsOpString: { char *result = ecs_get_path(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -49848,6 +49858,7 @@ int ecs_meta_set_id( break; case EcsOpString: { char *result = ecs_id_str(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 5559c2ec1b..069300f51e 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -898,6 +898,7 @@ int ecs_meta_set_bool( } else { result = ecs_os_strdup("false"); } + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -941,6 +942,7 @@ int ecs_meta_set_char( cases_T_signed(ptr, value, ecs_meta_bounds_signed); case EcsOpString: { char *result = flecs_asprintf("%c", value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -1004,6 +1006,7 @@ int ecs_meta_set_int( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%"PRId64, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -1061,6 +1064,7 @@ int ecs_meta_set_uint( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%"PRIu64, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -1123,6 +1127,7 @@ int ecs_meta_set_float( cases_T_float(ptr, value); case EcsOpString: { char *result = flecs_asprintf("%f", value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -1518,6 +1523,10 @@ int ecs_meta_set_string_literal( ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + if (!value) { + return -1; + } + ecs_size_t len = ecs_os_strlen(value); if (value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); @@ -1595,6 +1604,7 @@ int ecs_meta_set_entity( break; case EcsOpString: { char *result = ecs_get_path(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } @@ -1656,6 +1666,7 @@ int ecs_meta_set_id( break; case EcsOpString: { char *result = ecs_id_str(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } diff --git a/test/meta/project.json b/test/meta/project.json index a6b187baab..e9147dab11 100644 --- a/test/meta/project.json +++ b/test/meta/project.json @@ -373,6 +373,15 @@ "set_unsigned_as_signed_out_of_range", "set_string_to_null_as_signed", "set_string_to_null_as_unsigned", + "set_string_to_bool", + "set_string_to_char", + "set_string_to_signed", + "set_string_to_unsigned", + "set_string_to_float", + "set_string_to_string", + "set_string_to_string_literal", + "set_string_to_entity", + "set_string_to_id", "set_entity_as_signed", "set_entity_as_unsigned", "set_entity_as_signed_out_of_range", diff --git a/test/meta/src/Cursor.c b/test/meta/src/Cursor.c index ab62b6809b..629efc3414 100644 --- a/test/meta/src/Cursor.c +++ b/test/meta/src/Cursor.c @@ -433,6 +433,7 @@ void Cursor_set_string_to_null_as_signed(void) { test_ok( ecs_meta_set_int(&cur, 0) ); test_str(value, "0"); + ecs_os_free(value); ecs_fini(world); } @@ -446,6 +447,187 @@ void Cursor_set_string_to_null_as_unsigned(void) { test_ok( ecs_meta_set_uint(&cur, 0) ); test_str(value, "0"); + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_bool(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_bool(&cur, true) ); + test_str(value, "true"); + + test_ok( ecs_meta_set_bool(&cur, false) ); + test_str(value, "false"); + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_char(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_char(&cur, 'a') ); + test_str(value, "a"); + + test_ok( ecs_meta_set_char(&cur, '-') ); + test_str(value, "-"); + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_signed(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + 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_ok( ecs_meta_set_int(&cur, 10) ); + test_str(value, "10"); + + test_ok( ecs_meta_set_int(&cur, -10) ); + test_str(value, "-10"); + + test_ok( ecs_meta_set_int(&cur, INT64_MAX) ); + test_str(value, "9223372036854775807"); + + test_ok( ecs_meta_set_int(&cur, INT64_MIN) ); + test_str(value, "-9223372036854775808"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_unsigned(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + 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_ok( ecs_meta_set_uint(&cur, 10) ); + test_str(value, "10"); + + test_ok( ecs_meta_set_uint(&cur, UINT64_MAX) ); + test_str(value, "18446744073709551615"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_float(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_float(&cur, 0) ); + test_str(value, "0.000000"); + + test_ok( ecs_meta_set_float(&cur, 10) ); + test_str(value, "10.000000"); + + test_ok( ecs_meta_set_float(&cur, 10.5) ); + test_str(value, "10.500000"); + + test_ok( ecs_meta_set_float(&cur, 100000) ); + test_str(value, "100000.000000"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_string(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_string(&cur, 0) ); + test_str(value, NULL); + + test_ok( ecs_meta_set_string(&cur, "Hello World") ); + test_str(value, "Hello World"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_string_literal(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_fail( ecs_meta_set_string_literal(&cur, 0) ); + + test_ok( ecs_meta_set_string_literal(&cur, "\"Hello World\"") ); + test_str(value, "Hello World"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_entity(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_entity(&cur, 0) ); + test_str(value, "#0"); + + test_ok( ecs_meta_set_entity(&cur, EcsFlecsCore) ); + test_str(value, "flecs.core"); + + ecs_os_free(value); + + ecs_fini(world); +} + +void Cursor_set_string_to_id(void) { + ecs_world_t *world = ecs_init(); + + char *value = ecs_os_strdup("Hello"); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &value); + + test_ok( ecs_meta_set_id(&cur, 0) ); + test_str(value, "#0"); + + test_ok( ecs_meta_set_id(&cur, EcsFlecsCore) ); + test_str(value, "flecs.core"); + + test_ok( ecs_meta_set_id(&cur, ecs_childof(EcsFlecsCore)) ); + test_str(value, "(ChildOf,flecs.core)"); + + ecs_os_free(value); ecs_fini(world); } diff --git a/test/meta/src/main.c b/test/meta/src/main.c index 45d9906f51..a5d9f23c81 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -348,6 +348,15 @@ void Cursor_set_signed_as_unsigned_out_of_range(void); void Cursor_set_unsigned_as_signed_out_of_range(void); void Cursor_set_string_to_null_as_signed(void); void Cursor_set_string_to_null_as_unsigned(void); +void Cursor_set_string_to_bool(void); +void Cursor_set_string_to_char(void); +void Cursor_set_string_to_signed(void); +void Cursor_set_string_to_unsigned(void); +void Cursor_set_string_to_float(void); +void Cursor_set_string_to_string(void); +void Cursor_set_string_to_string_literal(void); +void Cursor_set_string_to_entity(void); +void Cursor_set_string_to_id(void); void Cursor_set_entity_as_signed(void); void Cursor_set_entity_as_unsigned(void); void Cursor_set_entity_as_signed_out_of_range(void); @@ -2319,6 +2328,42 @@ bake_test_case Cursor_testcases[] = { "set_string_to_null_as_unsigned", Cursor_set_string_to_null_as_unsigned }, + { + "set_string_to_bool", + Cursor_set_string_to_bool + }, + { + "set_string_to_char", + Cursor_set_string_to_char + }, + { + "set_string_to_signed", + Cursor_set_string_to_signed + }, + { + "set_string_to_unsigned", + Cursor_set_string_to_unsigned + }, + { + "set_string_to_float", + Cursor_set_string_to_float + }, + { + "set_string_to_string", + Cursor_set_string_to_string + }, + { + "set_string_to_string_literal", + Cursor_set_string_to_string_literal + }, + { + "set_string_to_entity", + Cursor_set_string_to_entity + }, + { + "set_string_to_id", + Cursor_set_string_to_id + }, { "set_entity_as_signed", Cursor_set_entity_as_signed @@ -4999,7 +5044,7 @@ static bake_test_suite suites[] = { "Cursor", NULL, NULL, - 137, + 146, Cursor_testcases }, { From a65b71c14f1aa362583373ccf09249e6204f60c2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 15 Dec 2024 02:05:57 +0000 Subject: [PATCH 80/83] Fix warnings, add typos exception for Fuzzing.c --- _typos.toml | 2 +- distr/flecs.c | 2 +- src/addons/script/expr/expr.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_typos.toml b/_typos.toml index c1e7980086..bd5a55c630 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,4 +1,4 @@ -files.extend-exclude = ["distr/*"] +files.extend-exclude = ["distr/*", "test/script/src/Fuzzing.c"] # typos can't handle "2nd" as a word (yet?) default.extend-ignore-identifiers-re = ["_2nd"] diff --git a/distr/flecs.c b/distr/flecs.c index fab84c71a6..a5975bc4f9 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5255,7 +5255,7 @@ bool flecs_expr_is_type_number( #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) #define ECS_BOP_COND(left, right, result, op, R, T)\ ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index df92b83817..9f33dd2279 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -69,7 +69,7 @@ bool flecs_expr_is_type_number( #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) #define ECS_BOP_COND(left, right, result, op, R, T)\ ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) From a977d43fea135c3115ba330966ad31601d09a25d Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 15 Dec 2024 02:21:06 +0000 Subject: [PATCH 81/83] Fix access to uninitialized ecs_cmd_t::id field --- distr/flecs.c | 1 + src/stage.c | 1 + 2 files changed, 2 insertions(+) diff --git a/distr/flecs.c b/distr/flecs.c index a5975bc4f9..5fffb66888 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -17290,6 +17290,7 @@ ecs_cmd_t* flecs_cmd_new( ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); cmd->is._1.value = NULL; + cmd->id = 0; cmd->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; diff --git a/src/stage.c b/src/stage.c index f27f554a2e..b4f97b4600 100644 --- a/src/stage.c +++ b/src/stage.c @@ -24,6 +24,7 @@ ecs_cmd_t* flecs_cmd_new( ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); cmd->is._1.value = NULL; + cmd->id = 0; cmd->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; From 308a7287215c5562a4a667b1917583f55bc30230 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 15 Dec 2024 11:27:48 -0800 Subject: [PATCH 82/83] Add more sections to script manual - String entity names - String interpolation - For loops - Updated section on types - Added C examples on how to define functions/methods --- docs/FlecsScript.md | 176 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 0dc3f84f2b..73ee89b556 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -68,6 +68,20 @@ _ { } ``` +Entity names can be specified using a string. This allows for entities with names that contain special characters, like spaces: + +```c +"my parent" { + "my child" {} +} +``` + +String names can be combined with string interpolation (see below) to create names that are computed when the script is evaluated: + +```c +"USS_$name" {} +``` + ### Tags A tag can be added to an entity by simply specifying the tag's identifier in an entity scope. Example: @@ -491,6 +505,52 @@ lantern { } ``` +### For statement +Parts of a script can be repeated with a for loop. Example: + +```c +for i in 0..10 { + Lantern() { + Position: {x: $i * 5} + } +} +``` + +The values specified in the range can be an expression: + +```c +for i in 0..$count { + // ... +} +``` + +When creating entities in a for loop, ensure that they are unique or the for loop will overwrite the same entity: + +```c +for i in 0..10 { + // overwrites entity "e" 10 times + e: { Position: {x: $i * 5} } +} +``` + +To avoid this, scripts can either create anonymous entities: + +```c +for i in 0..10 { + // creates 10 anonymous entities + _ { Position: {x: $i * 5} } +} +``` + +Or use a unique string expression for the entity name: + +```c +for i in 0..10 { + // creates entities with names e_0, e_1, ... e_9 + "e_$i" { Position: {x: $i * 5} } +} +``` + ### Default components A scope can have a default component, which means entities in that scope can assign values of that component without having to specify the component name. @@ -699,6 +759,25 @@ Tree: {color: $color, height: $height} Tree: {color: $, height: $} ``` +### String interpolation +Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: + +```c +const x = "The value of PI is $PI" +``` + +The following example shows how to use an expression: + +```c +const x = "The circumference of the circle is {2 * $PI * $r}" +``` + +To prevent evaluating expressions in an interpolated string, the `$` and `{` characters can be escaped: + +```c +const x = "The value of variable \$x is $x" +``` + ### Types The type of an expression is determined by the kind of expression, its operands and the context in which the expression is evaluated. The words "type" and "component" can be used interchangeably, as every type in Flecs is a component, and every component is a type. For component types to be used with scripts, they have to be described using the meta reflection addon. @@ -721,6 +800,7 @@ Binary expressions have two operands. The following table shows the different bi | `/` | `f64` | Numbers | | `+` | other (see below) | Numbers | | `-` | other (see below) | Numbers | +| `%` | `i64` | `i64` | | `<<` | other (see below) | Integers | | `>>` | other (see below) | Integers | | `>` | `bool` | Numbers | @@ -737,9 +817,71 @@ Binary expressions have two operands. The following table shows the different bi For the operators where the expression type is listed as "other" the type is derived by going through these steps: - If the types of the operands are equal, the expression type will be the operand type. - If the types are different: - - Convert the operand types to their largest storage variant (`i8` becomes `i64`, `f32` becomes `f64`, `u16` becomes `u64`). - - The type of the expression becomes the most expressive of the two. - - Expressiveness is determined as `f64` > `i64` > `u64`. + - For literal values, find the smallest storage type without losing precision. If operand types are now equal, use that. + - Find the most expressive type of the two operands (see below) + - If a cast to the most expressive type does not result in a loss of precision, use that. + - If the types are both numbers follow these rules in order: + - If one of the types is a floating point, use `f64` + - If one of the types is an integer, use `i64` + - If neither, throw a type incompatible error + +For equality expressions (using the `==` or `!=` operators), additional rules are used: + - If one of the operands is a bool, cast the other operand to a bool as well. This ensures that expressions such as `2 == true` evaluate to true. + - Equality expressions between floating point numbers are invalid + +Type expressiveness is determined by the kind of type and its storage size. The following tables show the expressiveness and storage scores: + +| **Type** | **Expressiveness Score** | +|--------------|---------------------------| +| bool | 1 | +| char | 2 | +| u8 | 2 | +| u16 | 3 | +| u32 | 4 | +| uptr | 5 | +| u64 | 6 | +| i8 | 7 | +| i16 | 8 | +| i32 | 9 | +| iptr | 10 | +| i64 | 11 | +| f32 | 12 | +| f64 | 13 | +| string | -1 | +| entity | -1 | + +| **Type** | **Storage Score** | +|--------------|-------------------| +| bool | 1 | +| char | 1 | +| u8 | 2 | +| u16 | 3 | +| u32 | 4 | +| uptr | 6 | +| u64 | 7 | +| i8 | 1 | +| i16 | 2 | +| i32 | 3 | +| iptr | 5 | +| i64 | 6 | +| f32 | 3 | +| f64 | 4 | +| string | -1 | +| entity | -1 | + +The function to determine whether a type is implicitly castable is: + +```c +bool implicit_cast_allowed(from, to) { + if (expressiveness(to) >= expressiveness(from)) { + return storage(to) >= storage(from); + } else { + return false; + } +} +``` + +If either the expressiveness or storage scores are negative, the operand types are not implicitly castable. #### Lvalues Lvalues are the left side of assignments. There are two kinds of assignments possible in Flecs script: @@ -777,6 +919,20 @@ const x = add({10, 20}, {30, 40}) Currently functions can only be defined outside of scripts by the Flecs Script API. Flecs comes with a set of builtin and math functions. Math functions are defined by the script math addon, which must be explicitly enabled by defining `FLECS_SCRIPT_MATH`. +A function can be created in code by doing: + +```c +ecs_function(world, { + .name = "sum", + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) }, + { .name = "b", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum +}); +``` + ### Methods Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: @@ -787,6 +943,20 @@ const x = v1.add(v2) Just like functions, methods can currently only be defined outside of scripts by using the Flecs Script API. +A method can be created in code by doing: + +```c +ecs_method(world, { + .name = "add", + .parent = ecs_id(ecs_i64_t), // Add method to i64 + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum +}); +``` + ## Templates Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: From 0051026f08e0b4bc21d0a172937f87c82b3886b9 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 15 Dec 2024 13:36:13 -0800 Subject: [PATCH 83/83] Add more builtin script functions, add documentation on builtin functions --- distr/flecs.c | 196 ++++++++++++++++++---- distr/flecs.h | 22 +-- docs/FlecsScript.md | 73 ++++++++ include/flecs.h | 22 +-- src/addons/script/functions_builtin.c | 206 ++++++++++++++++++----- src/addons/script/functions_math.c | 2 +- test/script/project.json | 8 + test/script/src/Expr.c | 232 +++++++++++++++++++++++++- test/script/src/main.c | 42 ++++- 9 files changed, 696 insertions(+), 107 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 5fffb66888..a1552ce24e 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -55649,10 +55649,8 @@ void flecs_meta_entity_parent( *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } -#ifdef FLECS_DOC - static -void flecs_meta_entity_doc_name( +void flecs_meta_entity_has( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, @@ -55660,21 +55658,118 @@ void flecs_meta_entity_doc_name( { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); + ecs_id_t id = *(ecs_id_t*)argv[1].ptr; + *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); } +static +void flecs_meta_core_pair( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; + ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; + *(ecs_id_t*)result->ptr = ecs_pair(first, second); +} + +#ifdef FLECS_DOC + +#define FLECS_DOC_FUNC(name)\ + static\ + void flecs_meta_entity_doc_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)argc;\ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ + *(char**)result->ptr = \ + ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ + } + +FLECS_DOC_FUNC(name) +FLECS_DOC_FUNC(uuid) +FLECS_DOC_FUNC(brief) +FLECS_DOC_FUNC(detail) +FLECS_DOC_FUNC(link) +FLECS_DOC_FUNC(color) + +#undef FLECS_DOC_FUNC + static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_method(world, { - .name = "doc_name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - }); + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + }); + + ecs_doc_set_brief(world, m, "Returns entity doc name"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_uuid", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_uuid + }); + + ecs_doc_set_brief(world, m, "Returns entity doc uuid"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_brief", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_brief + }); + + ecs_doc_set_brief(world, m, "Returns entity doc brief description"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_detail", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_detail + }); + + ecs_doc_set_brief(world, m, "Returns entity doc detailed description"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_link", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_link + }); + + ecs_doc_set_brief(world, m, "Returns entity doc link"); + } - ecs_doc_set_brief(world, name, "Returns entity doc name"); + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_color", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_color + }); + + ecs_doc_set_brief(world, m, "Returns entity doc color"); + } } #else @@ -55691,32 +55786,67 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_method(world, { - .name = "name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - }); + { + ecs_entity_t m = ecs_method(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + }); - ecs_doc_set_brief(world, name, "Returns entity name"); + ecs_doc_set_brief(world, m, "Returns entity name"); + } - ecs_entity_t path = ecs_method(world, { - .name = "path", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - }); + { + ecs_entity_t m = ecs_method(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + }); - ecs_doc_set_brief(world, path, "Returns entity path"); + ecs_doc_set_brief(world, m, "Returns entity path"); + } - ecs_entity_t parent = ecs_method(world, { - .name = "parent", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - }); + { + ecs_entity_t m = ecs_method(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + }); + + ecs_doc_set_brief(world, m, "Returns entity parent"); + } - ecs_doc_set_brief(world, parent, "Returns entity parent"); + { + ecs_entity_t m = ecs_method(world, { + .name = "has", + .parent = ecs_id(ecs_entity_t), + .params = { + { .name = "component", .type = ecs_id(ecs_id_t) } + }, + .return_type = ecs_id(ecs_bool_t), + .callback = flecs_meta_entity_has + }); + + ecs_doc_set_brief(world, m, "Returns whether entity has component"); + } + + { + ecs_entity_t m = ecs_function(world, { + .name = "pair", + .parent = ecs_entity(world, { .name = "core"}), + .params = { + { .name = "first", .type = ecs_id(ecs_entity_t) }, + { .name = "second", .type = ecs_id(ecs_entity_t) } + }, + .return_type = ecs_id(ecs_id_t), + .callback = flecs_meta_core_pair + }); + + ecs_doc_set_brief(world, m, "Returns a pair identifier"); + } flecs_script_register_builtin_doc_functions(world); } @@ -55902,7 +56032,7 @@ void FlecsScriptMathImport( /* Exponential and logarithmic functions */ FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); - FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significand and exponent"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); diff --git a/distr/flecs.h b/distr/flecs.h index d53b1ce527..e7212edeaf 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -136,27 +136,17 @@ */ // #define FLECS_KEEP_ASSERT -/** \def FLECS_CPP_NO_AUTO_REGISTRATION - * When set, the C++ API will require that components are registered before they - * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component - * registration is mandatory. Disabling automatic component registration also - * slightly improves performance. - * The C API is not affected by this feature. - */ -// #define FLECS_CPP_NO_AUTO_REGISTRATION - /** @def FLECS_CPP_NO_AUTO_REGISTRATION * When set, the C++ API will require that components are registered before they * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component + * to be registered beforehand, and to catch issues in projects where component * registration is mandatory. Disabling automatic component registration also * slightly improves performance. * The C API is not affected by this feature. */ // #define FLECS_CPP_NO_AUTO_REGISTRATION -/** \def FLECS_CUSTOM_BUILD +/** @def FLECS_CUSTOM_BUILD * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add * features such as systems, scheduling and reflection. If an addon is disabled, @@ -273,7 +263,7 @@ #define FLECS_ID_DESC_MAX (32) #endif -/** \def FLECS_EVENT_DESC_MAX +/** @def FLECS_EVENT_DESC_MAX * Maximum number of events in ecs_observer_desc_t */ #ifndef FLECS_EVENT_DESC_MAX #define FLECS_EVENT_DESC_MAX (8) @@ -283,19 +273,19 @@ * Maximum number of query variables per query */ #define FLECS_VARIABLE_COUNT_MAX (64) -/** \def FLECS_TERM_COUNT_MAX +/** @def FLECS_TERM_COUNT_MAX * Maximum number of terms in queries. Should not exceed 64. */ #ifndef FLECS_TERM_COUNT_MAX #define FLECS_TERM_COUNT_MAX 32 #endif -/** \def FLECS_TERM_ARG_COUNT_MAX +/** @def FLECS_TERM_ARG_COUNT_MAX * Maximum number of arguments for a term. */ #ifndef FLECS_TERM_ARG_COUNT_MAX #define FLECS_TERM_ARG_COUNT_MAX (16) #endif -/** \def FLECS_QUERY_VARIABLE_COUNT_MAX +/** @def FLECS_QUERY_VARIABLE_COUNT_MAX * Maximum number of query variables per query. Should not exceed 128. */ #ifndef FLECS_QUERY_VARIABLE_COUNT_MAX #define FLECS_QUERY_VARIABLE_COUNT_MAX (64) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 73ee89b556..ba62fa1a82 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -957,6 +957,79 @@ ecs_method(world, { }); ``` +### Builtin functions and constants +The following table lists builtin core functions in the `flecs.script.core` namespace: + +| **Function Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|-----------------------------|-----------------|-----------------------------| +| `pair` | Returns a pair identifier | `id` | (`entity`, `entity`) | + +The following table lists builtin methods on the `flecs.meta.entity` type: + +| **Method Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|----------------------------------------|-----------------|----------------------| +| `name` | Returns entity name | `string` | `()` | +| `path` | Returns entity path | `string` | `()` | +| `parent` | Returns entity parent | `entity` | `()` | +| `has` | Returns whether entity has component | `bool` | `(id)` | + +The following table lists doc methods on the `flecs.meta.entity` type: + +| **Method Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|------------------------------------------|------------------|----------------------| +| `doc_name` | Returns entity doc name | `string` | `()` | +| `doc_uuid` | Returns entity doc uuid | `string` | `()` | +| `doc_brief` | Returns entity doc brief description | `string` | `()` | +| `doc_detail` | Returns entity doc detailed description | `string` | `()` | +| `doc_link` | Returns entity doc link | `string` | `()` | +| `doc_color` | Returns entity doc color | `string` | `()` | + +To use the doc functions, make sure to use a Flecs build compiled with `FLECS_DOC` (enabled by default). + +The following table lists math functions in the `flecs.script.math` namespace: + +| **Function Name** | **Description** | **Return Type** | **Arguments** | +|--------------------|-----------------------------------------|-----------------|---------------------| +| `cos` | Compute cosine | `f64` | `(f64)` | +| `sin` | Compute sine | `f64` | `(f64)` | +| `tan` | Compute tangent | `f64` | `(f64)` | +| `acos` | Compute arc cosine | `f64` | `(f64)` | +| `asin` | Compute arc sine | `f64` | `(f64)` | +| `atan` | Compute arc tangent | `f64` | `(f64)` | +| `atan2` | Compute arc tangent with two parameters | `f64` | `(f64, f64)` | +| `cosh` | Compute hyperbolic cosine | `f64` | `(f64)` | +| `sinh` | Compute hyperbolic sine | `f64` | `(f64)` | +| `tanh` | Compute hyperbolic tangent | `f64` | `(f64)` | +| `acosh` | Compute area hyperbolic cosine | `f64` | `(f64)` | +| `asinh` | Compute area hyperbolic sine | `f64` | `(f64)` | +| `atanh` | Compute area hyperbolic tangent | `f64` | `(f64)` | +| `exp` | Compute exponential function | `f64` | `(f64)` | +| `ldexp` | Generate value from significant and exponent | `f64` | `(f64, f32)` | +| `log` | Compute natural logarithm | `f64` | `(f64)` | +| `log10` | Compute common logarithm | `f64` | `(f64)` | +| `exp2` | Compute binary exponential function | `f64` | `(f64)` | +| `log2` | Compute binary logarithm | `f64` | `(f64)` | +| `pow` | Raise to power | `f64` | `(f64, f64)` | +| `sqrt` | Compute square root | `f64` | `(f64)` | +| `sqr` | Compute square | `f64` | `(f64)` | +| `ceil` | Round up value | `f64` | `(f64)` | +| `floor` | Round down value | `f64` | `(f64)` | +| `round` | Round to nearest | `f64` | `(f64)` | +| `abs` | Compute absolute value | `f64` | `(f64)` | + +The following table lists the constants in the `flecs.script.math` namespace: + +| **Function Name** | **Description** | **Type** | **Value** | +|-------------------|-------------------------------------------|----------|----------------------| +| `E` | Euler's number | `f64` | `2.71828182845904523536028747135266250` | +| `PI` | Ratio of circle circumference to diameter | `f64` | `3.14159265358979323846264338327950288` | + +To use the math functions, make sure to use a Flecs build compiled with the `FLECS_SCRIPT_MATH` addon (disabled by default) and that the module is imported: + +```c +ECS_IMPORT(world, FlecsScriptMath); +``` + ## Templates Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: diff --git a/include/flecs.h b/include/flecs.h index 9ea3994132..8219c99253 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -134,27 +134,17 @@ */ // #define FLECS_KEEP_ASSERT -/** \def FLECS_CPP_NO_AUTO_REGISTRATION - * When set, the C++ API will require that components are registered before they - * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component - * registration is mandatory. Disabling automatic component registration also - * slightly improves performance. - * The C API is not affected by this feature. - */ -// #define FLECS_CPP_NO_AUTO_REGISTRATION - /** @def FLECS_CPP_NO_AUTO_REGISTRATION * When set, the C++ API will require that components are registered before they * are used. This is useful in multithreaded applications, where components need - * to be registered beforehand, and to catch issues in projects where component + * to be registered beforehand, and to catch issues in projects where component * registration is mandatory. Disabling automatic component registration also * slightly improves performance. * The C API is not affected by this feature. */ // #define FLECS_CPP_NO_AUTO_REGISTRATION -/** \def FLECS_CUSTOM_BUILD +/** @def FLECS_CUSTOM_BUILD * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add * features such as systems, scheduling and reflection. If an addon is disabled, @@ -271,7 +261,7 @@ #define FLECS_ID_DESC_MAX (32) #endif -/** \def FLECS_EVENT_DESC_MAX +/** @def FLECS_EVENT_DESC_MAX * Maximum number of events in ecs_observer_desc_t */ #ifndef FLECS_EVENT_DESC_MAX #define FLECS_EVENT_DESC_MAX (8) @@ -281,19 +271,19 @@ * Maximum number of query variables per query */ #define FLECS_VARIABLE_COUNT_MAX (64) -/** \def FLECS_TERM_COUNT_MAX +/** @def FLECS_TERM_COUNT_MAX * Maximum number of terms in queries. Should not exceed 64. */ #ifndef FLECS_TERM_COUNT_MAX #define FLECS_TERM_COUNT_MAX 32 #endif -/** \def FLECS_TERM_ARG_COUNT_MAX +/** @def FLECS_TERM_ARG_COUNT_MAX * Maximum number of arguments for a term. */ #ifndef FLECS_TERM_ARG_COUNT_MAX #define FLECS_TERM_ARG_COUNT_MAX (16) #endif -/** \def FLECS_QUERY_VARIABLE_COUNT_MAX +/** @def FLECS_QUERY_VARIABLE_COUNT_MAX * Maximum number of query variables per query. Should not exceed 128. */ #ifndef FLECS_QUERY_VARIABLE_COUNT_MAX #define FLECS_QUERY_VARIABLE_COUNT_MAX (64) diff --git a/src/addons/script/functions_builtin.c b/src/addons/script/functions_builtin.c index 94ae9c32fb..68f0ed371b 100644 --- a/src/addons/script/functions_builtin.c +++ b/src/addons/script/functions_builtin.c @@ -44,10 +44,8 @@ void flecs_meta_entity_parent( *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } -#ifdef FLECS_DOC - static -void flecs_meta_entity_doc_name( +void flecs_meta_entity_has( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, @@ -55,21 +53,118 @@ void flecs_meta_entity_doc_name( { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_os_strdup(ecs_doc_get_name(ctx->world, entity)); + ecs_id_t id = *(ecs_id_t*)argv[1].ptr; + *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); +} + +static +void flecs_meta_core_pair( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; + ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; + *(ecs_id_t*)result->ptr = ecs_pair(first, second); } +#ifdef FLECS_DOC + +#define FLECS_DOC_FUNC(name)\ + static\ + void flecs_meta_entity_doc_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)argc;\ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ + *(char**)result->ptr = \ + ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ + } + +FLECS_DOC_FUNC(name) +FLECS_DOC_FUNC(uuid) +FLECS_DOC_FUNC(brief) +FLECS_DOC_FUNC(detail) +FLECS_DOC_FUNC(link) +FLECS_DOC_FUNC(color) + +#undef FLECS_DOC_FUNC + static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_method(world, { - .name = "doc_name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - }); - - ecs_doc_set_brief(world, name, "Returns entity doc name"); + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + }); + + ecs_doc_set_brief(world, m, "Returns entity doc name"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_uuid", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_uuid + }); + + ecs_doc_set_brief(world, m, "Returns entity doc uuid"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_brief", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_brief + }); + + ecs_doc_set_brief(world, m, "Returns entity doc brief description"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_detail", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_detail + }); + + ecs_doc_set_brief(world, m, "Returns entity doc detailed description"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_link", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_link + }); + + ecs_doc_set_brief(world, m, "Returns entity doc link"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_color", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_color + }); + + ecs_doc_set_brief(world, m, "Returns entity doc color"); + } } #else @@ -86,32 +181,67 @@ void flecs_script_register_builtin_doc_functions( void flecs_script_register_builtin_functions( ecs_world_t *world) { - ecs_entity_t name = ecs_method(world, { - .name = "name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - }); - - ecs_doc_set_brief(world, name, "Returns entity name"); - - ecs_entity_t path = ecs_method(world, { - .name = "path", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - }); - - ecs_doc_set_brief(world, path, "Returns entity path"); - - ecs_entity_t parent = ecs_method(world, { - .name = "parent", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - }); - - ecs_doc_set_brief(world, parent, "Returns entity parent"); + { + ecs_entity_t m = ecs_method(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + }); + + ecs_doc_set_brief(world, m, "Returns entity name"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + }); + + ecs_doc_set_brief(world, m, "Returns entity path"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + }); + + ecs_doc_set_brief(world, m, "Returns entity parent"); + } + + { + ecs_entity_t m = ecs_method(world, { + .name = "has", + .parent = ecs_id(ecs_entity_t), + .params = { + { .name = "component", .type = ecs_id(ecs_id_t) } + }, + .return_type = ecs_id(ecs_bool_t), + .callback = flecs_meta_entity_has + }); + + ecs_doc_set_brief(world, m, "Returns whether entity has component"); + } + + { + ecs_entity_t m = ecs_function(world, { + .name = "pair", + .parent = ecs_entity(world, { .name = "core"}), + .params = { + { .name = "first", .type = ecs_id(ecs_entity_t) }, + { .name = "second", .type = ecs_id(ecs_entity_t) } + }, + .return_type = ecs_id(ecs_id_t), + .callback = flecs_meta_core_pair + }); + + ecs_doc_set_brief(world, m, "Returns a pair identifier"); + } flecs_script_register_builtin_doc_functions(world); } diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index b3e7c8cb21..54be522c3c 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -179,7 +179,7 @@ void FlecsScriptMathImport( /* Exponential and logarithmic functions */ FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); - FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significand and exponent"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); diff --git a/test/script/project.json b/test/script/project.json index e5720c106f..42c20736d2 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -577,7 +577,15 @@ "entity_path_expr", "entity_parent_func", "entity_name_func", + "entity_has_func", + "entity_has_func_w_pair", + "entity_has_func_w_pair_pair_invalid", "entity_doc_name_func", + "entity_doc_uuid_func", + "entity_doc_brief_func", + "entity_doc_detail_func", + "entity_doc_link_func", + "entity_doc_color_func", "entity_path_func", "entity_chain_func", "var_parent_func", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index b2eb347f22..75b71c577c 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -3194,6 +3194,99 @@ void Expr_entity_name_func(void) { ecs_fini(world); } +void Expr_entity_has_func(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + test_assert(e != 0); + + ecs_add(world, e, Position); + + { + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e.has(Position)", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e.has(Velocity)", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_entity_has_func_w_pair(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Apples); + ECS_TAG(world, Pears); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + test_assert(e != 0); + + ecs_add_pair(world, e, Likes, Apples); + + { + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e.has(flecs.script.core.pair(Likes, Apples))", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "e.has(flecs.script.core.pair(Likes, Pears))", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_entity_has_func_w_pair_pair_invalid(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Apples); + ECS_TAG(world, Pears); + + ecs_entity_t e = ecs_entity(world, { .name = "e" }); + test_assert(e != 0); + + ecs_add_pair(world, e, Likes, Apples); + + { + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + ecs_log_set_level(-4); + test_assert(ecs_expr_run(world, + "e.has(flecs.script.core.pair(flecs.script.core.pair(Likes, Apples), Pears))", + &v, &desc) == NULL); + } + + ecs_fini(world); +} + void Expr_entity_doc_name_func(void) { ecs_world_t *world = ecs_init(); @@ -3202,14 +3295,149 @@ void Expr_entity_doc_name_func(void) { ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); test_assert(foo != 0); - ecs_doc_set_name(world, foo, "FooDoc"); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; test_assert(ecs_expr_run(world, "parent.foo.doc_name()", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_string_t)); test_assert(v.ptr != NULL); - test_str(*(char**)v.ptr, "FooDoc"); + test_str(*(char**)v.ptr, "FooName"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_entity_doc_uuid_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_uuid()", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "FooUuid"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_entity_doc_brief_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_brief()", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "FooBrief"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_entity_doc_detail_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_detail()", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "FooDetail"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_entity_doc_link_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_link()", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "FooLink"); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_entity_doc_color_func(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); + test_assert(parent != 0); + + ecs_entity_t foo = ecs_entity(world, { .name = "parent.foo" }); + test_assert(foo != 0); + ecs_doc_set_name(world, foo, "FooName"); + ecs_doc_set_uuid(world, foo, "FooUuid"); + ecs_doc_set_brief(world, foo, "FooBrief"); + ecs_doc_set_detail(world, foo, "FooDetail"); + ecs_doc_set_link(world, foo, "FooLink"); + ecs_doc_set_color(world, foo, "FooColor"); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "parent.foo.doc_color()", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_string_t)); + test_assert(v.ptr != NULL); + test_str(*(char**)v.ptr, "FooColor"); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); diff --git a/test/script/src/main.c b/test/script/src/main.c index 1794c3815b..23a3209411 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -564,7 +564,15 @@ void Expr_entity_expr(void); void Expr_entity_path_expr(void); void Expr_entity_parent_func(void); void Expr_entity_name_func(void); +void Expr_entity_has_func(void); +void Expr_entity_has_func_w_pair(void); +void Expr_entity_has_func_w_pair_pair_invalid(void); void Expr_entity_doc_name_func(void); +void Expr_entity_doc_uuid_func(void); +void Expr_entity_doc_brief_func(void); +void Expr_entity_doc_detail_func(void); +void Expr_entity_doc_link_func(void); +void Expr_entity_doc_color_func(void); void Expr_entity_path_func(void); void Expr_entity_chain_func(void); void Expr_var_parent_func(void); @@ -3046,10 +3054,42 @@ bake_test_case Expr_testcases[] = { "entity_name_func", Expr_entity_name_func }, + { + "entity_has_func", + Expr_entity_has_func + }, + { + "entity_has_func_w_pair", + Expr_entity_has_func_w_pair + }, + { + "entity_has_func_w_pair_pair_invalid", + Expr_entity_has_func_w_pair_pair_invalid + }, { "entity_doc_name_func", Expr_entity_doc_name_func }, + { + "entity_doc_uuid_func", + Expr_entity_doc_uuid_func + }, + { + "entity_doc_brief_func", + Expr_entity_doc_brief_func + }, + { + "entity_doc_detail_func", + Expr_entity_doc_detail_func + }, + { + "entity_doc_link_func", + Expr_entity_doc_link_func + }, + { + "entity_doc_color_func", + Expr_entity_doc_color_func + }, { "entity_path_func", Expr_entity_path_func @@ -4170,7 +4210,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 224, + 232, Expr_testcases, 1, Expr_params