test_sharp_edges.c (4056B)
1 /* Regression guard for the two sharp edges the realistic suite surfaced and that 2 * were subsequently fixed in the generator: 3 * 4 * 1. Diagnostic source locations. The C generator used to round-trip the parse 5 * through a location-free s-expression and re-derive positions from a node 6 * counter, so every post-parse diagnostic pointed at a bogus line (often 7 * past EOF) with column 1. The generator now builds the AST directly with 8 * real token locations; this asserts the exact line:col for a range of 9 * error classes (matching the Python reference, which was always correct). 10 * 11 * 2. Silent keyword shadowing. Declaring a broad IDENT rule before a keyword 12 * used to silently make the keyword lex as IDENT with no diagnostic. The 13 * generator now rejects any string-literal token a broader recognizer 14 * shadows, suggesting %keywords. 15 * 16 * Drives the in-process compiler API (include/gramgen.h) directly — the exact code 17 * path build/kit_gram_c uses. 18 */ 19 #include <kit/gram.h> 20 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 static void *xrealloc(void *ud, void *ptr, size_t o, size_t n, size_t align) { 26 (void)ud; (void)o; (void)align; 27 if (!n) { free(ptr); return NULL; } 28 return realloc(ptr, n); 29 } 30 31 static int failures = 0; 32 static void check(int ok, const char *id, const char *detail) { 33 printf("%s %-24s %s\n", ok ? "ok " : "FAIL", id, detail); 34 if (!ok) failures++; 35 } 36 37 static KitGramCompiled *compile(const char *text, KitGramDiagnostic *diag) { 38 static KitGramAllocator alloc = { .realloc = xrealloc }; 39 KitGramOptions opts = { .allocator = &alloc }; 40 memset(diag, 0, sizeof *diag); 41 return kit_gram_compile_text(text, strlen(text), "edge", &opts, diag); 42 } 43 44 /* ---- Finding 1: exact diagnostic locations ------------------------------- */ 45 typedef struct { const char *id, *text; uint32_t line, col; } LocCase; 46 47 static void check_location(const LocCase *c) { 48 KitGramDiagnostic diag; 49 KitGramCompiled *cg = compile(c->text, &diag); 50 if (cg) { check(0, c->id, "grammar unexpectedly compiled (fixture is wrong)"); kit_gram_free(cg); return; } 51 char detail[160]; 52 snprintf(detail, sizeof detail, "reported %u:%u (want %u:%u): %s", 53 diag.line, diag.col, c->line, c->col, diag.message); 54 check(diag.line == c->line && diag.col == c->col, c->id, detail); 55 } 56 57 /* ---- Finding 2: keyword shadowing is now a located error ----------------- */ 58 static void check_keyword_shadow(void) { 59 static const char text[] = 60 "%lex {\n" 61 " %skip WS = [ \\t\\r\\n]+;\n" 62 " IDENT = [A-Za-z_] [A-Za-z0-9_]*;\n" 63 "}\n" 64 "%token IF = \"if\";\n" 65 "stmt = \"if\" IDENT | IDENT;\n"; 66 KitGramDiagnostic diag; 67 KitGramCompiled *cg = compile(text, &diag); 68 if (cg) { check(0, "keyword_shadow", "shadowed keyword compiled silently"); kit_gram_free(cg); return; } 69 int ok = strstr(diag.message, "shadowed") && strstr(diag.message, "%keywords") && diag.line == 5; 70 char detail[200]; 71 snprintf(detail, sizeof detail, "%u:%u: %s", diag.line, diag.col, diag.message); 72 check(ok, "keyword_shadow", detail); 73 } 74 75 int main(void) { 76 printf("== realistic-suite regression: diagnostic locations + keyword shadowing ==\n"); 77 78 static const LocCase cases[] = { 79 { "loc_ll1_conflict", "r = \"a\" \"b\" | \"a\" \"c\" ;\n", 1, 15 }, 80 { "loc_group_body", "r = a (\"x\" \"y\")? ;\na = \"z\" ;\n", 1, 8 }, 81 { "loc_charclass_dash","%lex {\n X = [+-] ;\n}\nr = X ;\n", 2, 7 }, 82 { "loc_dangling_else", 83 "%lex {\n %skip WS = [ \\t]+;\n IDENT = [A-Za-z_]+;\n" 84 " %keywords IDENT { IF=\"if\"; ELSE=\"else\"; }\n}\n" 85 "stmt = \"if\" IDENT stmt else_part? | IDENT ;\n" 86 "else_part = \"else\" stmt ;\n", 6, 33 }, 87 }; 88 for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) 89 check_location(&cases[i]); 90 91 check_keyword_shadow(); 92 93 printf("\n%s\n", failures ? "FAILURES" : "all fixes hold"); 94 return failures ? 1 : 0; 95 }