#include <addons.h>

static
int filter_count(ecs_filter_t *f) {
    test_assert(f != NULL);
    return f->term_count;
}

static
ecs_term_t* filter_terms(ecs_filter_t *f) {
    test_assert(f != NULL);
    return f->terms;
}

#define test_first(column, e, flgs)\
    test_int(column.first.id, e);\
    test_int(column.first.flags, flgs);

#define test_src(column, e, flgs)\
    test_int(column.src.id, e);\
    test_int(column.src.flags, flgs);

#define test_second(column, e, flgs)\
    test_int(column.second.id, e);\
    test_int(column.second.flags, flgs);

#define test_first_var(column, e, flgs, str)\
    test_first(column, e, flgs|EcsIsVariable);\
    test_str(column.first.name, str);

#define test_src_var(column, e, flgs, str)\
    test_src(column, e, flgs|EcsIsVariable);\
    test_str(column.src.name, str);

#define test_second_var(column, e, flgs, str)\
    test_second(column, e, flgs|EcsIsVariable);\
    test_str(column.second.name, str);

void Parser_resolve_this(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_lookup_fullpath(world, ".");
    test_assert(e != 0);
    test_assert(e == EcsThis);

    ecs_fini(world);
}

void Parser_resolve_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_lookup_fullpath(world, "*");
    test_assert(e != 0);
    test_assert(e == EcsWildcard);

    ecs_fini(world);
}

void Parser_resolve_any(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_lookup_fullpath(world, "_");
    test_assert(e != 0);
    test_assert(e == EcsAny);

    ecs_fini(world);
}

void Parser_resolve_is_a(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_lookup_fullpath(world, "IsA");
    test_assert(e != 0);
    test_assert(e == EcsIsA);

    ecs_fini(world);
}

void Parser_0(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "0"
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_this(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_this_by_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(this)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_this_by_var_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(*)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsWildcard, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_any(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(_)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsAny, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_subject_0(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(0)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_as_predicate(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "this(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_var_as_predicate(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$This(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_lowercase_var_as_predicate(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$this(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_as_object(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, this)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], EcsThis, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_var_as_object(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, $This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_wildcard_pred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(*, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsWildcard, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_wildcard_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, *)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], EcsWildcard, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_any_pred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(_, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsAny, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_any_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, _)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], EcsAny, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_this_pred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_this_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, $This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_pred_w_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred:self, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_obj_w_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj:self)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_pred_w_up(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred:up, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsUp|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_obj_w_up(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj:up)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_pred_w_self_up(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred:self|up, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_obj_w_self_up(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj:self|up)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_pred_w_up_trav(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred:up(ChildOf), Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsUp|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    
    test_assert(terms[0].first.trav == EcsChildOf);
    test_assert(terms[0].second.trav == 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_obj_w_up_trav(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj:up(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_assert(terms[0].first.trav == 0);
    test_assert(terms[0].second.trav == EcsChildOf);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_pred_w_invalid_flags(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred:, Obj)"
    }));

    ecs_fini(world);
}

void Parser_pair_implicit_subject_obj_w_invalid_flags(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj:)"
    }));

    ecs_fini(world);
}

void Parser_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_this(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_this_by_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(this, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_this_by_var_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_wildcard_pred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "*($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsWildcard, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_wildcard_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(*, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsWildcard, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_wildcard_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This, *)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], EcsWildcard, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_0_object(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, 0)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_second(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id, ecs_pair(EcsChildOf, 0));

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_0_object(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, 0)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsChildOf, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_second(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id, ecs_pair(EcsChildOf, 0));

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_0(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(0, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_in_component_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[in] Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsIn);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_in_component_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[in] Pred(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsIn);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_in_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[in] (Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsIn);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_in_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[in] Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsIn);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_inout_component_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_inout_component_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] Pred(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_inout_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] (Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_inout_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_out_component_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[out] Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_out_component_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[out] Pred(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_out_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[out] (Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_out_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[out] Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsOut);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_inout_filter_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[none] Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_singleton(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_singleton(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$This($)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_implicit_no_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred()"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_explicit_no_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(0)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_no_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(0, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_variable_single_char(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, X);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($X)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf|EcsUp, "X");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_variable_multi_char(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, XYZ);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($XYZ)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf|EcsUp, "XYZ");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_variable_multi_char_w_underscore(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, XY_Z);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($XY_Z)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf|EcsUp, "XY_Z");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_variable_multi_char_w_number(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, XY_1);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($XY_1)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf|EcsUp, "XY_1");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_variable_multi_char_not_allcaps(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, XYZ);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($xyZ)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf|EcsUp, "xyZ");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_var(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "($Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first_var(terms[0], 0, EcsSelf, "Pred");
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_obj_var(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, $Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second_var(terms[0], 0, EcsSelf, "Obj");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_not(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "!Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_not(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "!(Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_not(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "!Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_component_not(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "!Pred_1, !Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsNot);
    test_int(terms[1].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_component_not_no_space(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "!Pred_1,!Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsNot);
    test_int(terms[1].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_component_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_component_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?Pred_1, ?Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsOptional);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_component_optional_no_space(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?Pred_1,?Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsOptional);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_from_and(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "AND | Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAndFrom);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_from_or(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "OR | Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOrFrom);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_from_not(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "NOT | Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsNotFrom);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_implicit_subject_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?(Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pair_explicit_subject_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?Pred(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_implicit_subject_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TOGGLE | Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);    
}

void Parser_pred_explicit_subject_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TOGGLE | Pred($This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);    
}

void Parser_pred_no_subject_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TOGGLE | Pred()"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_pair_implicit_subject_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_PAIR);

    ecs_filter_fini(&f);

    ecs_fini(world);   
}

void Parser_pair_explicit_subject_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_PAIR);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_inout_role_pred_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] TOGGLE | Pred"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_inout_role_pred_no_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] TOGGLE | Pred()"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_inout_role_pred_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] TOGGLE | Pred($This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_inout_role_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] (Pred, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);
    test_int(terms[0].id_flags, ECS_PAIR);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_inout_role_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] Pred($This, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOut);
    test_int(terms[0].id_flags, ECS_PAIR);

    ecs_filter_fini(&f);

    ecs_fini(world);  
}

void Parser_2_pred_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1, Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);    


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pred_no_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1(), Pred_2()"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);    


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pred_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1($This), Pred_2($This)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);    


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pair_implicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred_1, Obj_1), (Pred_2, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);    


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pair_explicit_subject(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1($This, Obj_1), Pred_2($This, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);    


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pred_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TOGGLE | Pred_1, TOGGLE | Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_TOGGLE);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault); 
    test_int(terms[1].id_flags, ECS_TOGGLE);   


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pair_implicit_subj_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred_1, Obj_1), (Pred_2, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_PAIR);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault); 
    test_int(terms[1].id_flags, ECS_PAIR);   


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_pair_explicit_subj_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1($This, Obj_1), Pred_2($This, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_PAIR);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault); 
    test_int(terms[1].id_flags, ECS_PAIR);   


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_or_pred_implicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1 || Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOr);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_or_pred_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1($This) || Pred_2($This)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOr);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_or_pair_implicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred_1, Obj_1) || (Pred_2, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);    
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsOr);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);    
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_or_pair_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);
    ECS_TAG(world, Obj_1);
    ECS_TAG(world, Obj_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred_1($This, Obj_1) || Pred_2($This, Obj_2)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);    
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj_1, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsOr);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);    
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], Obj_2, EcsSelf|EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_2_or_pred_inout(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "[inout] Pred_1 || Pred_2"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred_1, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsOr);
    test_int(terms[0].inout, EcsInOut);

    test_first(terms[1], Pred_2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_1_digit_pred_implicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_ensure(world, 100);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "100"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], 100, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_1_digit_pred_no_subj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_ensure(world, 100);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "100()"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], 100, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_1_digit_pred_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_ensure(world, 100);
    ecs_ensure(world, 200);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "100(200)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], 100, EcsSelf|EcsIsEntity);
    test_src(terms[0], 200, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_1_digit_pair_implicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_ensure(world, 100);
    ecs_ensure(world, 300);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(100, 300)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], 100, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], 300, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_1_digit_pair_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred_1);
    ECS_TAG(world, Pred_2);

    ecs_ensure(world, 100);
    ecs_ensure(world, 200);
    ecs_ensure(world, 300);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "100(200, 300)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], 100, EcsSelf|EcsIsEntity);
    test_src(terms[0], 200, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], 300, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(self)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(up)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_subset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(down)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsDown|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_inclusive(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(self|up)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_subset_inclusive(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(self|down)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsDown|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(up|cascade)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_subset_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(down|cascade)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsDown|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_inclusive_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(up|cascade|self)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_subset_inclusive_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(down|cascade|self)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsDown|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_implicit_superset_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(cascade)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(self|cascade)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsSelf|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_implicit_superset_cascade_w_rel(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_ENTITY(world, Rel, Traversable);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(cascade(Rel))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, Rel);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_ENTITY(world, Rel, Traversable);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(self|cascade(Rel))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsSelf|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, Rel);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(up(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, EcsChildOf);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_cascade_superset_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(cascade|up(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, EcsChildOf);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_cascade_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(up|cascade(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, EcsChildOf);

    ecs_filter_fini(&f);

    ecs_fini(world); 
}

void Parser_pred_implicit_subject_superset_cascade_childof_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "?Pred(up|cascade(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsIsVariable);
    test_int(terms[0].oper, EcsOptional);
    test_int(terms[0].inout, EcsInOutDefault);  
    test_int(terms[0].src.trav, EcsChildOf);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_expr_w_symbol(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_entity_t comp = ecs_component_init(world, &(ecs_component_desc_t){
        .entity = ecs_entity(world, {
            .name = "Foo",
            .symbol = "FooBar"
        })
    });

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "FooBar"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], comp, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);  


    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_expr_w_newline(void) {
    ecs_world_t *world = ecs_mini();

    ecs_new_entity(world, "Foo");

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Foo\n"
    }));

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_subj_entity_w_explicit_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj:self)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_subj_entity_w_explicit_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj:self|up)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_subj_entity_w_explicit_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj:up(ChildOf))"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsUp|EcsIsEntity);
    test_int(terms[0].src.trav, EcsChildOf);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_subj_entity_w_explicit_self_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj:self|up(ChildOf))"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].src.trav, EcsChildOf);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_obj_entity_w_explicit_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, Obj:self)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_obj_entity_w_explicit_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, Obj:self|up)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_obj_entity_w_explicit_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, Obj:up(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_obj_entity_w_explicit_self_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, Obj:self|up(ChildOf))"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_w_explicit_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self:(Subj)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_w_explicit_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up:(Subj)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_w_explicit_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:up(ChildOf):(Subj)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_w_explicit_self_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up(ChildOf):(Subj)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_w_explicit_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_w_explicit_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_w_explicit_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:up(ChildOf)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_w_explicit_self_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up(ChildOf)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_2_terms_w_explicit_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Pred2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self, Pred2"
    }));

    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_2_terms_w_explicit_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Pred2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up, Pred2"
    }));

    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_2_terms_w_explicit_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Pred2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:up(ChildOf), Pred2"
    }));

    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_pred_entity_no_args_2_terms_w_explicit_self_superset_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Pred2);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred:self|up(ChildOf), Pred2"
    }));

    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].first.trav, EcsChildOf);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], Pred2, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_newline(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 = "\n"
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_newlines(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 = "\n\n"
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_3_newlines(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 = "\n\n"
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_space(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 = " "
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_spaces(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 = "  "
    }));

    test_int(filter_count(&f), 0);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_trailing_newline(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred\n"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_trailing_newlines(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred\n\n"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_trailing_space(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred "
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_2_trailing_spaces(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred  "
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_template_type(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t Pred = ecs_entity_init(world, &(ecs_entity_desc_t){
        .name = "Template<int>"
    });
    test_assert(Pred != 0);
    test_str(ecs_get_name(world, Pred), "Template<int>");

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Template<int>"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_predicate_w_parens(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Pred)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_not_alive_pred(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    test_assert(!ecs_is_alive(world, 5000));

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "5000"
    }));

    ecs_fini(world);
}

void Parser_not_alive_subj(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    test_assert(!ecs_is_alive(world, 5000));

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(5000)"
    }));

    ecs_fini(world);
}

void Parser_not_alive_obj(void) {
    ecs_log_set_level(-4);
    
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    test_assert(!ecs_is_alive(world, 5000));

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(Subj, 5000)"
    }));

    ecs_fini(world);
}

void Parser_this_subj_var_kind(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, TagB($This)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_obj_var_kind(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, TagB(Subj, $This)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[1], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[1], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_subj_obj_var_kind(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, TagB($This, $This)"
    }));
    test_int(filter_count(&f), 2);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[1], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[1], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_var_w_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA($Var)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], 0, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);    
    test_str(terms[0].src.name, "Var");

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_entity_pred_no_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_str(terms[0].first.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_entity_subj_no_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA(Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_str(terms[0].src.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_entity_obj_no_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, Subj);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA(Subj, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_str(terms[0].second.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_pred_no_name(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 = "$This"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    test_str(terms[0].first.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_subj_no_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA($This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_str(terms[0].src.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_this_obj_no_name(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA(Subj, $This)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_str(terms[0].second.name, NULL);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_auto_object_variable(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$(Pred)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second_var(terms[0], 0, EcsSelf, "Pred");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_auto_object_variable_w_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$(Pred, Subj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], Subj, EcsSelf|EcsUp|EcsIsEntity);
    test_second_var(terms[0], 0, EcsSelf, "Pred");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_auto_scoped_variable(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t Pred = ecs_entity(world, {
        .name = "foo.Pred"
    });

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$(foo.Pred)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second_var(terms[0], 0, EcsSelf, "Pred");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_invalid_variable_only(void) {
    ecs_log_set_level(-4);
    
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Subj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$"
    }));

    ecs_fini(world);
}

void Parser_oneof_self_pred_w_relative_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, OneOf);
    ECS_ENTITY(world, Obj, (ChildOf, Rel));

    test_assert( ecs_lookup_child(world, 0, "Obj") == 0 ); // sanity check

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Rel, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Rel, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);
 
    ecs_fini(world);
}

void Parser_oneof_other_pred_w_relative_obj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Parent);
    ECS_ENTITY(world, Rel, (OneOf, Parent));
    ECS_ENTITY(world, Obj, (ChildOf, Parent));

    test_assert( ecs_lookup_child(world, 0, "Obj") == 0 ); // sanity check

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Rel, Obj)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Rel, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Obj, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    ecs_filter_fini(&f);
 
    ecs_fini(world);
}

void Parser_oneof_self_pred_w_invalid_obj(void) {
    ecs_log_set_level(-4);
    
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, OneOf);
    ECS_TAG(world, Obj);

    test_assert( ecs_lookup_child(world, 0, "Obj") != 0 ); // sanity check

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Rel, Obj)"
    }));
 
    ecs_fini(world);
}

void Parser_oneof_other_pred_w_invalid_obj(void) {
    ecs_log_set_level(-4);
    
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Parent);
    ECS_ENTITY(world, Rel, (OneOf, Parent));
    ECS_TAG(world, Obj);

    test_assert( ecs_lookup_child(world, 0, "Obj") != 0 ); // sanity check

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Rel, Obj)"
    }));
 
    ecs_fini(world);
}

void Parser_pair_implicit_src_missing_rel(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(, Obj)"
    }));

    ecs_fini(world);
}

void Parser_pair_implicit_src_missing_obj(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "(Rel, )"
    }));

    ecs_fini(world);
}

void Parser_pair_explicit_src_missing_src(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Rel(, Obj)"
    }));

    ecs_fini(world);
}

void Parser_pair_explicit_src_missing_obj(void) {
    ecs_log_set_level(-4);

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Rel($this, )"
    }));

    ecs_fini(world);
}

void Parser_eq_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, First);
    ECS_TAG(world, Second);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "First == Second"
    }));

    ecs_fini(world);
}

void Parser_eq_id_var(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Second);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$this == Second"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_second(terms[0], Second, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_eq_var_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, First);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "First == $this"
    }));

    ecs_fini(world);
}

void Parser_eq_var(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, First);
    ECS_TAG(world, Second);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$x == $y"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf, "x");
    test_second_var(terms[0], 0, EcsSelf, "y");
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_neq_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, First);
    ECS_TAG(world, Second);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "First != Second"
    }));

    ecs_fini(world);
}

void Parser_neq_id_var(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Second);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$this != Second"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsIsVariable);
    test_second(terms[0], Second, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_neq_var_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, First);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "First != $this"
    }));

    ecs_fini(world);
}

void Parser_neq_var(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 = "$x != $y"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity);
    test_src_var(terms[0], 0, EcsSelf, "x");
    test_second_var(terms[0], 0, EcsSelf, "y");
    test_int(terms[0].oper, EcsNot);
    test_int(terms[0].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_eq_name(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" == \"Second\""
    }));

    ecs_fini(world);
}

void Parser_eq_name_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" == $y"
    }));

    ecs_fini(world);
}

void Parser_eq_var_name(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "$x == \"Second\""
    }));

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_neq_name(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" != \"Second\""
    }));

    ecs_fini(world);
}

void Parser_neq_name_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" != $y"
    }));

    ecs_fini(world);
}

void Parser_neq_var_name(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 = "$x != \"Second\""
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredEq, EcsSelf|EcsIsEntity);
    test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable));
    test_str(terms[0].src.name, "x");
    test_uint(terms[0].src.id, 0);
    test_uint(terms[0].second.flags, (EcsSelf|EcsIsName));
    test_str(terms[0].second.name, "Second");
    test_uint(terms[0].second.id, 0);
    test_int(terms[0].oper, EcsNot);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_match_name(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" ~= \"Second\""
    }));

    ecs_fini(world);
}

void Parser_match_name_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" ~= $y"
    }));

    ecs_fini(world);
}

void Parser_match_var_name(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 = "$x ~= \"Second\""
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredMatch, EcsSelf|EcsIsEntity);
    test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable));
    test_str(terms[0].src.name, "x");
    test_uint(terms[0].src.id, 0);
    test_uint(terms[0].second.flags, (EcsSelf|EcsIsName));
    test_str(terms[0].second.name, "Second");
    test_uint(terms[0].second.id, 0);
    test_int(terms[0].oper, EcsAnd);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_match_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "$x ~= $y"
    }));

    ecs_fini(world);
}

void Parser_nmatch_name(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"First\" ~= \"!Second\""
    }));

    ecs_fini(world);
}

void Parser_nmatch_name_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "\"!First\" ~= $y"
    }));

    ecs_fini(world);
}

void Parser_nmatch_var_name(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 = "$x ~= \"!Second\""
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], EcsPredMatch, EcsSelf|EcsIsEntity);
    test_uint(terms[0].src.flags, (EcsSelf|EcsIsVariable));
    test_str(terms[0].src.name, "x");
    test_uint(terms[0].src.id, 0);
    test_uint(terms[0].second.flags, (EcsSelf|EcsIsName));
    test_str(terms[0].second.name, "Second");
    test_uint(terms[0].second.id, 0);
    test_int(terms[0].oper, EcsNot);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_eq_same_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "$x == $x"
    }));

    ecs_fini(world);
}

void Parser_neq_same_var(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "$x != $x"
    }));

    ecs_fini(world);
}

void Parser_eq_same_var_this(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "$this == $this"
    }));

    ecs_fini(world);
}

void Parser_neq_same_var_this(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "$this != $this"
    }));

    ecs_fini(world);
}

void Parser_eq_w_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Foo);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "?$this == Foo"
    }));

    ecs_fini(world);
}

void Parser_neq_w_optional(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Foo);

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "?$this != Foo"
    }));

    ecs_fini(world);
}

void Parser_match_w_optional(void) {
    ecs_world_t *world = ecs_mini();

    ecs_log_set_level(-4);
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .expr = "?$this ~= \"Foo\""
    }));

    ecs_fini(world);
}

void Parser_query_scope_1_term(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, {TagB}"
    }));
    test_int(filter_count(&f), 4);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[3], 0, EcsIsEntity);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_scope_1_term_spaces(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, { TagB }"
    }));
    test_int(filter_count(&f), 4);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[3], 0, EcsIsEntity);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_scope_2_terms(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, TagC);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, { TagB, TagC }"
    }));
    test_int(filter_count(&f), 5);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], TagC, EcsSelf|EcsIsEntity);
    test_src(terms[3], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutDefault);

    test_first(terms[4], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[4], 0, EcsIsEntity);
    test_int(terms[4].oper, EcsAnd);
    test_int(terms[4].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_nested_scope(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, TagC);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, {TagB, {TagC}}"
    }));
    test_int(filter_count(&f), 7);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[3], 0, EcsIsEntity);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutNone);

    test_first(terms[4], TagC, EcsSelf|EcsIsEntity);
    test_src(terms[4], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[4].oper, EcsAnd);
    test_int(terms[4].inout, EcsInOutDefault);

    test_first(terms[5], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[5], 0, EcsIsEntity);
    test_int(terms[5].oper, EcsAnd);
    test_int(terms[5].inout, EcsInOutNone);

    test_first(terms[6], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[6], 0, EcsIsEntity);
    test_int(terms[6].oper, EcsAnd);
    test_int(terms[6].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_nested_scope_spaces(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, TagC);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, { TagB, { TagC } }"
    }));
    test_int(filter_count(&f), 7);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsAnd);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[3], 0, EcsIsEntity);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutNone);

    test_first(terms[4], TagC, EcsSelf|EcsIsEntity);
    test_src(terms[4], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[4].oper, EcsAnd);
    test_int(terms[4].inout, EcsInOutDefault);

    test_first(terms[5], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[5], 0, EcsIsEntity);
    test_int(terms[5].oper, EcsAnd);
    test_int(terms[5].inout, EcsInOutNone);

    test_first(terms[6], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[6], 0, EcsIsEntity);
    test_int(terms[6].oper, EcsAnd);
    test_int(terms[6].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_scope_unbalanced(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_log_set_level(-4);
    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, { TagB"
    }));

    ecs_fini(world);
}

void Parser_query_not_scope(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, !{TagB}"
    }));
    test_int(filter_count(&f), 4);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], TagA, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);

    test_first(terms[1], EcsScopeOpen, EcsSelf|EcsIsEntity);
    test_src(terms[1], 0, EcsIsEntity);
    test_int(terms[1].oper, EcsNot);
    test_int(terms[1].inout, EcsInOutNone);

    test_first(terms[2], TagB, EcsSelf|EcsIsEntity);
    test_src(terms[2], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[2].oper, EcsAnd);
    test_int(terms[2].inout, EcsInOutDefault);

    test_first(terms[3], EcsScopeClose, EcsSelf|EcsIsEntity);
    test_src(terms[3], 0, EcsIsEntity);
    test_int(terms[3].oper, EcsAnd);
    test_int(terms[3].inout, EcsInOutNone);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_query_empty_scope(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_log_set_level(-4);
    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL == ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "TagA, !{}"
    }));

    ecs_fini(world);
}

void Parser_override_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "OVERRIDE|Tag"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Tag, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_OVERRIDE);

    ecs_filter_fini(&f);

    ecs_fini(world);
}

void Parser_override_pair(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 = "OVERRIDE|(Rel, Tgt)"
    }));

    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Rel, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsSelf|EcsUp|EcsIsVariable);
    test_second(terms[0], Tgt, EcsSelf|EcsIsEntity);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].id_flags, ECS_PAIR|ECS_OVERRIDE);

    ecs_filter_fini(&f);

    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);
}

void Parser_cascade_desc(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);

    ecs_filter_t f = ECS_FILTER_INIT;
    test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){
        .storage = &f,
        .expr = "Pred(cascade|desc)"
    }));
    test_int(filter_count(&f), 1);

    ecs_term_t *terms = filter_terms(&f);
    test_first(terms[0], Pred, EcsSelf|EcsIsEntity);
    test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsDesc|EcsIsVariable);
    test_int(terms[0].oper, EcsAnd);
    test_int(terms[0].inout, EcsInOutDefault);
    test_int(terms[0].src.trav, EcsIsA);

    ecs_filter_fini(&f);

    ecs_fini(world);
}
