/* * test_emit_rust_skeleton.c * * v0.8 feat/rust-output: skeleton emitter regression. Verifies * `lime --rust grammar.lime` produces a .rs file that: * * 1. Exists at the expected path. * 2. Contains the expected token-constant block with FIRST_TOKEN * matching the grammar's %first_token directive. * 3. Contains the placeholder action-table statics. * 4. Contains the parser struct + impl block. * * Does NOT verify rustc-compiles (that requires rustc on PATH; the * skeleton's compilability is checked manually + by CI when rustc * is available). Subsequent commits on this branch add a separate * test fixture that runs rustc and asserts the parse-engine logic * is equivalent to the C output. * * Skipped at runtime when LIME_BIN isn't reachable. */ #include #include #include #include #include #include #include #include #include static int test_count = 0; static int pass_count = 0; static int skip_count = 0; #define TEST(name) do { \ printf("[TEST %d] %s\n", ++test_count, name); fflush(stdout); \ } while (0) #define ASSERT(cond, msg) do { \ if (!(cond)) { fprintf(stderr, " FAIL: %s\n", msg); return; } \ } while (0) #define PASS() do { printf(" PASS\n"); pass_count++; } while (0) #define SKIP(reason) do { \ printf(" SKIP: %s\n", reason); skip_count++; \ } while (0) static const char *g_lime_bin; static const char *g_template_path; static int subprocess_available(void) { g_lime_bin = getenv("LIME_BIN"); return g_lime_bin && access(g_lime_bin, X_OK) == 0; } static int resolve_template(void) { static char buf[1024]; const char *t = getenv("LIME_TEMPLATE"); if (t && access(t, R_OK) == 0) { g_template_path = t; return 1; } const char *root = getenv("LIME_PROJECT_ROOT"); if (root) { snprintf(buf, sizeof(buf), "%s/limpar.c", root); if (access(buf, R_OK) == 0) { g_template_path = buf; return 1; } } return 0; } static char *make_tmpdir(void) { static char tmp[256]; const char *base = getenv("TMPDIR"); if (!base || !*base) base = "/tmp"; snprintf(tmp, sizeof(tmp), "%s/lime_rust_emit_XXXXXX", base); return mkdtemp(tmp); } static void rm_dir(const char *dir) { char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); int rc = system(cmd); (void)rc; } static int write_file(const char *path, const char *contents) { FILE *fp = fopen(path, "wb"); if (!fp) return -1; size_t n = strlen(contents); int ok = fwrite(contents, 1, n, fp) == n; fclose(fp); return ok ? 0 : -1; } static char *slurp(const char *path, size_t *out_len) { FILE *fp = fopen(path, "rb"); if (!fp) return NULL; fseek(fp, 0, SEEK_END); long n = ftell(fp); rewind(fp); char *buf = malloc((size_t)n + 1); if (!buf) { fclose(fp); return NULL; } fread(buf, 1, (size_t)n, fp); fclose(fp); buf[n] = 0; if (out_len) *out_len = (size_t)n; return buf; } static int run_lime_rust(const char *tmpdir, const char *file) { pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { if (chdir(tmpdir) != 0) _exit(127); int devnull = open("/dev/null", O_WRONLY); if (devnull >= 0) { dup2(devnull, STDOUT_FILENO); dup2(devnull, STDERR_FILENO); close(devnull); } char tflag[1024]; snprintf(tflag, sizeof(tflag), "-T%s", g_template_path); execlp(g_lime_bin, "lime", tflag, "--rust", file, (char *)NULL); _exit(127); } int status; waitpid(pid, &status, 0); return WIFEXITED(status) ? WEXITSTATUS(status) : -1; } static const char *G_RUST_TEST = "%name TR\n" "%token A B C.\n" "%first_token 257\n" "%start_symbol s\n" "s ::= A.\n" "s ::= B.\n" "s ::= C.\n"; static void test_rust_emit_skeleton(void) { TEST("--rust emits .rs with header + token constants + struct"); char *tmpdir = make_tmpdir(); if (!tmpdir) { SKIP("mkdtemp failed"); return; } char gpath[512]; snprintf(gpath, sizeof(gpath), "%s/g.lime", tmpdir); if (write_file(gpath, G_RUST_TEST) != 0) { rm_dir(tmpdir); ASSERT(0, "write grammar failed"); } int rc = run_lime_rust(tmpdir, "g.lime"); if (rc != 0) { rm_dir(tmpdir); fprintf(stderr, " lime --rust exited %d\n", rc); ASSERT(0, "lime --rust failed"); } char rspath[512]; snprintf(rspath, sizeof(rspath), "%s/g.rs", tmpdir); size_t n; char *content = slurp(rspath, &n); if (!content) { rm_dir(tmpdir); ASSERT(0, "g.rs not produced"); } /* Expected markers: */ int has_header = strstr(content, "Generated by lime") != NULL; int has_first_tok = strstr(content, "FIRST_TOKEN: u16 = 257") != NULL; int has_token_a = strstr(content, "pub const A:") != NULL; int has_struct = strstr(content, "pub struct TRParser") != NULL; int has_impl = strstr(content, "impl TRParser") != NULL; int has_new_method = strstr(content, "fn new()") != NULL; int has_push = strstr(content, "fn push") != NULL; int has_finalize = strstr(content, "fn finalize") != NULL; int has_error_enum = strstr(content, "enum ParseError") != NULL; int has_yy_action = strstr(content, "YY_ACTION: &[u16]") != NULL; int has_dispatch = strstr(content, "YY_RULE_REDUCE_FN") != NULL; int has_reduce_fn = strstr(content, "fn yy_rule_0") != NULL; free(content); rm_dir(tmpdir); ASSERT(has_header, "missing 'Generated by lime' header"); ASSERT(has_first_tok, "missing FIRST_TOKEN: u16 = 257"); ASSERT(has_token_a, "missing pub const A:"); ASSERT(has_struct, "missing pub struct TRParser"); ASSERT(has_impl, "missing impl TRParser"); ASSERT(has_new_method, "missing fn new"); ASSERT(has_push, "missing fn push"); ASSERT(has_finalize, "missing fn finalize"); ASSERT(has_error_enum, "missing ParseError enum"); ASSERT(has_yy_action, "missing YY_ACTION static slice"); ASSERT(has_dispatch, "missing YY_RULE_REDUCE_FN dispatch table"); ASSERT(has_reduce_fn, "missing fn yy_rule_0 callback"); PASS(); } int main(void) { printf("=== test_emit_rust_skeleton ===\n"); if (!subprocess_available()) { printf("SKIP: LIME_BIN unset / unreachable\n"); return 77; } if (!resolve_template()) { printf("SKIP: limpar.c template not found\n"); return 77; } test_rust_emit_skeleton(); int effective = test_count - skip_count; printf("\n=== Results: %d/%d passed (%d skipped) ===\n", pass_count, effective, skip_count); return (pass_count == effective) ? 0 : 1; }