diff --git a/flecs.c b/flecs.c index 0d748e1283..dd4ab795ec 100644 --- a/flecs.c +++ b/flecs.c @@ -4746,7 +4746,7 @@ ecs_table_t *flecs_traverse_from_expr( const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))){ if (!ecs_term_is_initialized(&term)) { break; } @@ -4809,7 +4809,7 @@ void flecs_defer_from_expr( const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))) { if (!ecs_term_is_initialized(&term)) { break; } @@ -11053,23 +11053,48 @@ ecs_filter_t* ecs_filter_init( const char *name = NULL; const char *ptr = desc->expr; ecs_term_t term = {0}; + ecs_term_id_t extra_args[ECS_PARSER_MAX_ARGS] = {0}; int32_t expr_size = 0; if (entity) { name = ecs_get_name(world, entity); } - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && + (ptr = ecs_parse_term(world, name, expr, ptr, &term, extra_args))) + { if (!ecs_term_is_initialized(&term)) { break; } - if (expr_count == expr_size) { - expr_size = expr_size ? expr_size * 2 : 8; - expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); - } + int32_t arg = 0; + + do { + ecs_assert(arg <= ECS_PARSER_MAX_ARGS, ECS_INTERNAL_ERROR, NULL); + + if (expr_count == expr_size) { + expr_size = expr_size ? expr_size * 2 : 8; + expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); + } + + ecs_term_t *expr_term = &expr_terms[expr_count ++]; + *expr_term = term; + + if (arg) { + expr_term->src = expr_term[-1].second; + expr_term->second = extra_args[arg - 1]; + + if (expr_term->first.name != NULL) { + expr_term->first.name = ecs_os_strdup( + expr_term->first.name); + } + if (expr_term->src.name != NULL) { + expr_term->src.name = ecs_os_strdup( + expr_term->src.name); + } + } + } while (ecs_term_id_is_set(&extra_args[arg ++])); - expr_terms[expr_count ++] = term; if (ptr[0] == '\n') { break; } @@ -30577,15 +30602,24 @@ const char* flecs_parse_arguments( int64_t column, const char *ptr, char *token, - ecs_term_t *term) + ecs_term_t *term, + ecs_term_id_t *extra_args) { (void)column; int32_t arg = 0; + if (extra_args) { + ecs_os_memset_n(extra_args, 0, ecs_term_id_t, ECS_PARSER_MAX_ARGS); + } + + if (!term) { + arg = 2; + } + do { if (flecs_valid_token_start_char(ptr[0])) { - if (arg == 2) { + if ((arg == ECS_PARSER_MAX_ARGS) || (!extra_args && arg == 2)) { ecs_parser_error(name, expr, (ptr - expr), "too many arguments in term"); return NULL; @@ -30602,6 +30636,8 @@ const char* flecs_parse_arguments( term_id = &term->src; } else if (arg == 1) { term_id = &term->second; + } else { + term_id = &extra_args[arg - 2]; } /* If token is a colon, the token is an identifier followed by a @@ -30643,7 +30679,9 @@ const char* flecs_parse_arguments( if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); - term->id_flags = ECS_PAIR; + if (term) { + term->id_flags = ECS_PAIR; + } } else if (ptr[0] == TOK_PAREN_CLOSE) { ptr = ecs_parse_ws(ptr + 1); @@ -30689,7 +30727,8 @@ const char* flecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, - ecs_term_t *term_out) + ecs_term_t *term_out, + ecs_term_id_t *extra_args) { const char *ptr = expr; char token[ECS_MAX_TOKEN_SIZE] = {0}; @@ -30837,7 +30876,7 @@ const char* flecs_parse_term( ptr = ecs_parse_ws(ptr); } else { ptr = flecs_parse_arguments( - world, name, expr, (ptr - expr), ptr, token, &term); + world, name, expr, (ptr - expr), ptr, token, &term, extra_args); } goto parse_done; @@ -30955,8 +30994,7 @@ const char* flecs_parse_term( } } - if (ptr[0] == TOK_PAREN_CLOSE) { - ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND) { goto parse_pair_object; } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); @@ -30983,6 +31021,17 @@ const char* flecs_parse_term( term.id_flags = ECS_PAIR; } + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_arguments( + world, name, expr, (ptr - expr), ptr, token, NULL, extra_args); + if (!ptr) { + goto error; + } + } else { + ptr ++; + } + ptr = ecs_parse_ws(ptr); goto parse_done; @@ -31020,7 +31069,8 @@ char* ecs_parse_term( const char *name, const char *expr, const char *ptr, - ecs_term_t *term) + ecs_term_t *term, + ecs_term_id_t *extra_args) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); @@ -31056,7 +31106,7 @@ char* ecs_parse_term( } /* Parse next element */ - ptr = flecs_parse_term(world, name, ptr, term); + ptr = flecs_parse_term(world, name, ptr, term, extra_args); if (!ptr) { goto error; } @@ -32887,7 +32937,7 @@ const char *plecs_parse_plecs_term( decl_id = state->last_predicate; } - ptr = ecs_parse_term(world, name, expr, ptr, &term); + ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL); if (!ptr) { return NULL; } diff --git a/flecs.h b/flecs.h index 8aebdfbb6f..4b03f41731 100644 --- a/flecs.h +++ b/flecs.h @@ -15267,6 +15267,9 @@ void ecs_snapshot_free( #ifndef FLECS_PARSER_H #define FLECS_PARSER_H +/** Maximum number of extra arguments in term expression */ +#define ECS_PARSER_MAX_ARGS (16) + #ifdef __cplusplus extern "C" { #endif @@ -15353,6 +15356,7 @@ const char* ecs_parse_token( * @param expr The expression to parse (optional, improves error logs) * @param ptr The pointer to the current term (must be in expr). * @param term_out Out parameter for the term. + * @param extra_args Out array for extra args, must be of size ECS_PARSER_MAX_ARGS. * @return pointer to next term if successful, NULL if failed. */ FLECS_API @@ -15361,7 +15365,8 @@ char* ecs_parse_term( const char *name, const char *expr, const char *ptr, - ecs_term_t *term_out); + ecs_term_t *term_out, + ecs_term_id_t *extra_args); #ifdef __cplusplus } diff --git a/include/flecs/addons/parser.h b/include/flecs/addons/parser.h index 890afb7d52..b4a8d7c693 100644 --- a/include/flecs/addons/parser.h +++ b/include/flecs/addons/parser.h @@ -19,6 +19,9 @@ #ifndef FLECS_PARSER_H #define FLECS_PARSER_H +/** Maximum number of extra arguments in term expression */ +#define ECS_PARSER_MAX_ARGS (16) + #ifdef __cplusplus extern "C" { #endif @@ -105,6 +108,7 @@ const char* ecs_parse_token( * @param expr The expression to parse (optional, improves error logs) * @param ptr The pointer to the current term (must be in expr). * @param term_out Out parameter for the term. + * @param extra_args Out array for extra args, must be of size ECS_PARSER_MAX_ARGS. * @return pointer to next term if successful, NULL if failed. */ FLECS_API @@ -113,7 +117,8 @@ char* ecs_parse_term( const char *name, const char *expr, const char *ptr, - ecs_term_t *term_out); + ecs_term_t *term_out, + ecs_term_id_t *extra_args); #ifdef __cplusplus } diff --git a/src/addons/parser.c b/src/addons/parser.c index 8e02040903..98c68101cd 100644 --- a/src/addons/parser.c +++ b/src/addons/parser.c @@ -487,15 +487,24 @@ const char* flecs_parse_arguments( int64_t column, const char *ptr, char *token, - ecs_term_t *term) + ecs_term_t *term, + ecs_term_id_t *extra_args) { (void)column; int32_t arg = 0; + if (extra_args) { + ecs_os_memset_n(extra_args, 0, ecs_term_id_t, ECS_PARSER_MAX_ARGS); + } + + if (!term) { + arg = 2; + } + do { if (flecs_valid_token_start_char(ptr[0])) { - if (arg == 2) { + if ((arg == ECS_PARSER_MAX_ARGS) || (!extra_args && arg == 2)) { ecs_parser_error(name, expr, (ptr - expr), "too many arguments in term"); return NULL; @@ -512,6 +521,8 @@ const char* flecs_parse_arguments( term_id = &term->src; } else if (arg == 1) { term_id = &term->second; + } else { + term_id = &extra_args[arg - 2]; } /* If token is a colon, the token is an identifier followed by a @@ -553,7 +564,9 @@ const char* flecs_parse_arguments( if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); - term->id_flags = ECS_PAIR; + if (term) { + term->id_flags = ECS_PAIR; + } } else if (ptr[0] == TOK_PAREN_CLOSE) { ptr = ecs_parse_ws(ptr + 1); @@ -599,7 +612,8 @@ const char* flecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, - ecs_term_t *term_out) + ecs_term_t *term_out, + ecs_term_id_t *extra_args) { const char *ptr = expr; char token[ECS_MAX_TOKEN_SIZE] = {0}; @@ -747,7 +761,7 @@ const char* flecs_parse_term( ptr = ecs_parse_ws(ptr); } else { ptr = flecs_parse_arguments( - world, name, expr, (ptr - expr), ptr, token, &term); + world, name, expr, (ptr - expr), ptr, token, &term, extra_args); } goto parse_done; @@ -865,8 +879,7 @@ const char* flecs_parse_term( } } - if (ptr[0] == TOK_PAREN_CLOSE) { - ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND) { goto parse_pair_object; } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); @@ -893,6 +906,17 @@ const char* flecs_parse_term( term.id_flags = ECS_PAIR; } + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_arguments( + world, name, expr, (ptr - expr), ptr, token, NULL, extra_args); + if (!ptr) { + goto error; + } + } else { + ptr ++; + } + ptr = ecs_parse_ws(ptr); goto parse_done; @@ -930,7 +954,8 @@ char* ecs_parse_term( const char *name, const char *expr, const char *ptr, - ecs_term_t *term) + ecs_term_t *term, + ecs_term_id_t *extra_args) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); @@ -966,7 +991,7 @@ char* ecs_parse_term( } /* Parse next element */ - ptr = flecs_parse_term(world, name, ptr, term); + ptr = flecs_parse_term(world, name, ptr, term, extra_args); if (!ptr) { goto error; } diff --git a/src/addons/plecs.c b/src/addons/plecs.c index a426701d11..856135122a 100644 --- a/src/addons/plecs.c +++ b/src/addons/plecs.c @@ -1711,7 +1711,7 @@ const char *plecs_parse_plecs_term( decl_id = state->last_predicate; } - ptr = ecs_parse_term(world, name, expr, ptr, &term); + ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL); if (!ptr) { return NULL; } diff --git a/src/entity.c b/src/entity.c index ed32041b47..088d0b5ceb 100644 --- a/src/entity.c +++ b/src/entity.c @@ -1377,7 +1377,7 @@ ecs_table_t *flecs_traverse_from_expr( const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))){ if (!ecs_term_is_initialized(&term)) { break; } @@ -1440,7 +1440,7 @@ void flecs_defer_from_expr( const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))) { if (!ecs_term_is_initialized(&term)) { break; } diff --git a/src/filter.c b/src/filter.c index 0ef4c2aae6..808d3fd6c8 100644 --- a/src/filter.c +++ b/src/filter.c @@ -1432,23 +1432,48 @@ ecs_filter_t* ecs_filter_init( const char *name = NULL; const char *ptr = desc->expr; ecs_term_t term = {0}; + ecs_term_id_t extra_args[ECS_PARSER_MAX_ARGS] = {0}; int32_t expr_size = 0; if (entity) { name = ecs_get_name(world, entity); } - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + while (ptr[0] && + (ptr = ecs_parse_term(world, name, expr, ptr, &term, extra_args))) + { if (!ecs_term_is_initialized(&term)) { break; } - if (expr_count == expr_size) { - expr_size = expr_size ? expr_size * 2 : 8; - expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); - } + int32_t arg = 0; + + do { + ecs_assert(arg <= ECS_PARSER_MAX_ARGS, ECS_INTERNAL_ERROR, NULL); + + if (expr_count == expr_size) { + expr_size = expr_size ? expr_size * 2 : 8; + expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); + } + + ecs_term_t *expr_term = &expr_terms[expr_count ++]; + *expr_term = term; + + if (arg) { + expr_term->src = expr_term[-1].second; + expr_term->second = extra_args[arg - 1]; + + if (expr_term->first.name != NULL) { + expr_term->first.name = ecs_os_strdup( + expr_term->first.name); + } + if (expr_term->src.name != NULL) { + expr_term->src.name = ecs_os_strdup( + expr_term->src.name); + } + } + } while (ecs_term_id_is_set(&extra_args[arg ++])); - expr_terms[expr_count ++] = term; if (ptr[0] == '\n') { break; } diff --git a/test/addons/project.json b/test/addons/project.json index 6139d980eb..93d6fa3b0b 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -238,7 +238,12 @@ "query_not_scope", "query_empty_scope", "override_tag", - "override_pair" + "override_pair", + "pair_3_args", + "pair_3_args_implicit_this", + "pair_4_args", + "pair_4_args_implicit_this", + "pair_3_args_2_terms" ] }, { "id": "Plecs", diff --git a/test/addons/src/Parser.c b/test/addons/src/Parser.c index eb015c21b5..149b0632aa 100644 --- a/test/addons/src/Parser.c +++ b/test/addons/src/Parser.c @@ -5339,3 +5339,169 @@ void Parser_override_pair(void) { ecs_fini(world); } + +void Parser_pair_3_args(void) { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "ChildOf($this, $parent, $grandparent)" + })); + + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second_var(terms[0], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_first(terms[1], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[1], 0, EcsSelf|EcsIsVariable, "parent"); + test_second_var(terms[1], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_3_args_implicit_this(void) { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "(ChildOf, $parent, $grandparent)" + })); + + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second_var(terms[0], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_first(terms[1], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[1], 0, EcsSelf|EcsIsVariable, "parent"); + test_second_var(terms[1], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_4_args(void) { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "ChildOf($this, $parent, $grandparent, $greatgrandparent)" + })); + + test_int(filter_count(&f), 3); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second_var(terms[0], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_first(terms[1], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[1], 0, EcsSelf|EcsIsVariable, "parent"); + test_second_var(terms[1], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_first(terms[2], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[2], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_second_var(terms[2], 0, EcsSelf|EcsIsVariable, "greatgrandparent"); + test_int(terms[2].oper, EcsAnd); + test_int(terms[2].inout, EcsInOutDefault); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_4_args_implicit_this(void) { + ecs_world_t *world = ecs_mini(); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "(ChildOf, $parent, $grandparent, $greatgrandparent)" + })); + + test_int(filter_count(&f), 3); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second_var(terms[0], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_first(terms[1], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[1], 0, EcsSelf|EcsIsVariable, "parent"); + test_second_var(terms[1], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_first(terms[2], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[2], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_second_var(terms[2], 0, EcsSelf|EcsIsVariable, "greatgrandparent"); + test_int(terms[2].oper, EcsAnd); + test_int(terms[2].inout, EcsInOutDefault); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_3_args_2_terms(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Tgt); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "ChildOf($this, $parent, $grandparent), Rel($this, $parent)" + })); + + test_int(filter_count(&f), 3); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable); + test_second_var(terms[0], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_first(terms[1], EcsChildOf, EcsSelf|EcsIsEntity); + test_src_var(terms[1], 0, EcsSelf|EcsIsVariable, "parent"); + test_second_var(terms[1], 0, EcsSelf|EcsIsVariable, "grandparent"); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_first(terms[2], Rel, EcsSelf|EcsIsEntity); + test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable); + test_second_var(terms[2], 0, EcsSelf|EcsIsVariable, "parent"); + test_int(terms[2].oper, EcsAnd); + test_int(terms[2].inout, EcsInOutDefault); + + ecs_filter_fini(&f); + + ecs_fini(world); +} diff --git a/test/addons/src/main.c b/test/addons/src/main.c index ba29f30d5b..a7dd42d1a4 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -234,6 +234,11 @@ void Parser_query_not_scope(void); void Parser_query_empty_scope(void); void Parser_override_tag(void); void Parser_override_pair(void); +void Parser_pair_3_args(void); +void Parser_pair_3_args_implicit_this(void); +void Parser_pair_4_args(void); +void Parser_pair_4_args_implicit_this(void); +void Parser_pair_3_args_2_terms(void); // Testsuite 'Plecs' void Plecs_null(void); @@ -2453,6 +2458,26 @@ bake_test_case Parser_testcases[] = { { "override_pair", Parser_override_pair + }, + { + "pair_3_args", + Parser_pair_3_args + }, + { + "pair_3_args_implicit_this", + Parser_pair_3_args_implicit_this + }, + { + "pair_4_args", + Parser_pair_4_args + }, + { + "pair_4_args_implicit_this", + Parser_pair_4_args_implicit_this + }, + { + "pair_3_args_2_terms", + Parser_pair_3_args_2_terms } }; @@ -7531,7 +7556,7 @@ static bake_test_suite suites[] = { "Parser", NULL, NULL, - 225, + 230, Parser_testcases }, {