/* ** test_runtime_parse.c -- end-to-end test that the runtime ** parse_begin / parse_token / parse_end API actually drives an ** LALR(1) parse against the action tables of a generated parser. ** ** This is the test that proves Claim C3 from test_claims_proof now ** holds: lime_snapshot_create + parse_begin + parse_token works for ** any grammar. We use the same arithmetic grammar from ** bench/bench_arith_grammar.y -- a realistic LALR(1) grammar with ** shift/reduce/precedence dispatch -- and feed token streams through ** the runtime engine, comparing parse outcomes (accept vs syntax ** error) against expectations. ** ** Note: the runtime engine is value-free (it parses syntax only and ** does not run reduce actions). Applications that need semantic ** values call the generator's typed entry point. This test exercises ** the syntax-only API the runtime exposes. */ #include "parser.h" #include "parse_context.h" #include "snapshot.h" #include "snapshot_build.h" #include "bench_arith_grammar.h" #include #include #include static int n_pass = 0; static int n_fail = 0; #define CHECK(cond, name) \ do { \ if (cond) { \ printf(" [PASS] %s\n", name); \ n_pass++; \ } else { \ printf(" [FAIL] %s\n", name); \ n_fail++; \ } \ } while (0) /* ** ArithBuildSnapshot is generated by `lime -n bench_arith_grammar.y` ** into bench_arith_grammar_snapshot.c. It populates a ** ParserSnapshot with the action tables and rule metadata so the ** runtime push parser (parse_token) can drive this grammar. */ extern ParserSnapshot *ArithBuildSnapshot(void); /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ typedef struct { int code; const char *name; } TokSpec; /* ** Feed a sequence of tokens through parse_token; return the final ** outcome: 0 = mid-parse, 1 = accept, -1 = error. ** ** Caller terminates with an EOF token (code 0). */ static int run_parse(ParserSnapshot *snap, const TokSpec *toks, int ntoks) { ParseContext *ctx = parse_begin(snap); if (ctx == NULL) return -1; int last = 0; for (int i = 0; i < ntoks; i++) { last = parse_token(ctx, toks[i].code, NULL, -1); if (last < 0) break; if (last == 1) break; } if (last == 0) { last = parse_token(ctx, 0, NULL, -1); } parse_end(ctx); return last; } /* ------------------------------------------------------------------ */ /* Tests */ /* ------------------------------------------------------------------ */ static void test_well_formed(ParserSnapshot *snap) { /* "1 + 2 * 3" */ TokSpec ok[] = { { ARITH_NUM, "NUM" }, { ARITH_PLUS, "+" }, { ARITH_NUM, "NUM" }, { ARITH_STAR, "*" }, { ARITH_NUM, "NUM" }, }; int rc = run_parse(snap, ok, sizeof(ok) / sizeof(ok[0])); CHECK(rc == 1, "1 + 2 * 3 -- accepts"); } static void test_with_parens(ParserSnapshot *snap) { /* "( 1 + 2 ) * 3" */ TokSpec ok[] = { { ARITH_LP, "(" }, { ARITH_NUM, "NUM" }, { ARITH_PLUS, "+" }, { ARITH_NUM, "NUM" }, { ARITH_RP, ")" }, { ARITH_STAR, "*" }, { ARITH_NUM, "NUM" }, }; int rc = run_parse(snap, ok, sizeof(ok) / sizeof(ok[0])); CHECK(rc == 1, "(1 + 2) * 3 -- accepts"); } static void test_unbalanced_paren(ParserSnapshot *snap) { /* "( 1 + 2" -- missing closing paren. */ TokSpec bad[] = { { ARITH_LP, "(" }, { ARITH_NUM, "NUM" }, { ARITH_PLUS, "+" }, { ARITH_NUM, "NUM" }, }; int rc = run_parse(snap, bad, sizeof(bad) / sizeof(bad[0])); CHECK(rc < 0, "(1 + 2 -- syntax error"); } static void test_double_operator(ParserSnapshot *snap) { /* "1 + + 2" -- two adjacent + tokens. */ TokSpec bad[] = { { ARITH_NUM, "NUM" }, { ARITH_PLUS, "+" }, { ARITH_PLUS, "+" }, { ARITH_NUM, "NUM" }, }; int rc = run_parse(snap, bad, sizeof(bad) / sizeof(bad[0])); CHECK(rc < 0, "1 + + 2 -- syntax error"); } static void test_solo_number(ParserSnapshot *snap) { TokSpec ok[] = { { ARITH_NUM, "NUM" } }; int rc = run_parse(snap, ok, 1); CHECK(rc == 1, "single NUM -- accepts"); } static void test_division(ParserSnapshot *snap) { /* "10 / 2 / 5" -- left-associative */ TokSpec ok[] = { { ARITH_NUM, "NUM" }, { ARITH_SLASH, "/" }, { ARITH_NUM, "NUM" }, { ARITH_SLASH, "/" }, { ARITH_NUM, "NUM" }, }; int rc = run_parse(snap, ok, sizeof(ok) / sizeof(ok[0])); CHECK(rc == 1, "10 / 2 / 5 -- accepts"); } static void test_empty_input(ParserSnapshot *snap) { int rc = run_parse(snap, NULL, 0); /* Empty input: accept reduction depends on grammar. Our arith ** grammar requires at least one expr, so empty input is a syntax ** error. Either outcome (-1 or 1 if grammar allows empty) is ** acceptable; we just want to confirm parse_token doesn't crash ** on empty input. */ CHECK(rc == 1 || rc < 0, "empty input -- terminates without crash"); } /* ------------------------------------------------------------------ */ /* main */ /* ------------------------------------------------------------------ */ int main(void) { printf("Lime runtime parse_token() end-to-end test\n"); printf("==========================================\n"); ParserSnapshot *snap = ArithBuildSnapshot(); if (snap == NULL) { fprintf(stderr, "ArithBuildSnapshot() returned NULL\n"); return 1; } printf("Snapshot built: %u states, %u terminals, %u rules, " "yy_action_count=%u\n", snap->nstate, snap->nterminal, snap->nrule, snap->action_count); printf("Action constants: max_shift=%u min_sr=%u max_sr=%u min_red=%u " "ntoken=%u\n", snap->yy_max_shift, snap->yy_min_shiftreduce, snap->yy_max_shiftreduce, snap->yy_min_reduce, snap->yy_ntoken); test_solo_number(snap); test_well_formed(snap); test_with_parens(snap); test_division(snap); test_unbalanced_paren(snap); test_double_operator(snap); test_empty_input(snap); printf("\n=== Summary ===\n"); printf(" Passed: %d\n", n_pass); printf(" Failed: %d\n", n_fail); snapshot_release(snap); return n_fail == 0 ? 0 : 1; }