gram.c (24063B)
1 #include <kit/core.h> 2 #include <kit/gram.h> 3 #include <stddef.h> 4 #include <stdint.h> 5 #include <stdlib.h> 6 #include <string.h> 7 8 #include "driver.h" 9 #include "env.h" 10 11 /* `kit gram` — EBNF grammar compiler. Reads an EBNF grammar (FILE or stdin via 12 * "-") and emits a C parser/lexer (`-o`/`--header`), dumps the parsed syntax 13 * tree (`--dump-sexpr`), or synthesizes sample token streams / %machine traces. 14 * Drives the public <kit/gram.h> API over a DriverEnv (heap + stderr diag sink 15 * + file I/O); the library reports compile/validation errors through ctx->diag, 16 * so this command only sets the exit status for those. */ 17 18 #define GRAM_TOOL "gram" 19 20 typedef struct { 21 const char* grammar; /* input path, or "-" for stdin */ 22 const char* out_c; /* -o (default: <grammar>.c) */ 23 const char* out_h; /* --header (default: <grammar>.h) */ 24 const char* prefix; /* --prefix (default: from filename) */ 25 const char* sample_traces; /* --sample-traces NAME (a %machine) */ 26 int sample_tokens; 27 int have_stop_prob; 28 double stop_prob; 29 size_t samples; 30 uint64_t seed; 31 size_t max_depth; 32 size_t max_repeat; 33 size_t max_tokens; 34 int dump_sexpr; 35 int multiline; 36 int lexer_standalone; 37 int fold_keywords; 38 int position_lazy; 39 int parser_codegen; 40 int parser_recover; 41 } GramArgs; 42 43 typedef enum GramArgErrorCategory { 44 GRAM_ARG_OK = 0, 45 GRAM_ARG_UNKNOWN_OPTION, 46 GRAM_ARG_MISSING_OPTION_VALUE, 47 GRAM_ARG_INVALID_NUMERIC_VALUE, 48 GRAM_ARG_DUPLICATE_GRAMMAR, 49 GRAM_ARG_MISSING_GRAMMAR, 50 } GramArgErrorCategory; 51 52 typedef struct GramArgError { 53 GramArgErrorCategory category; 54 const char* option; 55 const char* value; 56 const char* previous; 57 } GramArgError; 58 59 void driver_help_gram(void) { 60 driver_printf( 61 "%.*s", 62 KIT_SLICE_ARG(KIT_SLICE_LIT( 63 "kit gram — EBNF parser / lexer generator\n" 64 "\n" 65 "USAGE\n" 66 " kit gram [options] grammar.ebnf\n" 67 " kit gram [options] - (read grammar from stdin)\n" 68 "\n" 69 "OUTPUT\n" 70 " -o FILE output .c (default: <grammar>.c)\n" 71 " --header FILE output .h (default: <grammar>.h)\n" 72 " --prefix NAME identifier prefix (default: from " 73 "filename)\n" 74 " --dump-sexpr print the parsed EBNF syntax tree to stdout\n" 75 "\n" 76 "GRAMMAR\n" 77 " A rule has the form `name = expression;`. Quoted text is a literal\n" 78 " token and a bare name references another rule. The smallest\n" 79 " accepted grammar is:\n" 80 "\n" 81 " start = \"hello\";\n" 82 "\n" 83 " EBNF expressions may compose rule/literal sequences, alternatives,\n" 84 " grouping, optional terms, and repetition. Directive families used\n" 85 " by the release include `%lex NAME { ... }` lexer blocks, `%skip`\n" 86 " lexer handling, `%keywords` extraction, and `%machine NAME` token\n" 87 " automata. --sample-traces NAME selects a declared %machine. Use\n" 88 " --dump-sexpr to inspect how an input grammar was parsed.\n" 89 "\n" 90 "LEXER\n" 91 " --multiline bake newline-aware ^/$ into the lexer\n" 92 " --lexer-standalone emit a self-contained re2c-style scanner " 93 "(C-only)\n" 94 " --fold-keywords standalone: fold %keywords into the DFA " 95 "(default: MPH)\n" 96 " --position-lazy standalone: omit per-token line/col tracking\n" 97 "\n" 98 "PARSER\n" 99 " --parser-codegen also emit a pull-based recursive-descent " 100 "parser\n" 101 " --parser-recover emit error-recovery machinery in the RD " 102 "parser\n" 103 "\n" 104 "SYNTHESIS\n" 105 " --sample-tokens print sample token streams\n" 106 " --sample-traces NAME print traces of a %machine\n" 107 " --samples N --seed N --stop-prob P\n" 108 " --max-depth N --max-repeat N --max-tokens N\n" 109 "\n" 110 "GENERATED C CONTRACT\n" 111 " Normal generation writes <grammar>.c and <grammar>.h unless -o\n" 112 " and --header override them. Grammar `-` reads stdin; syntax dumps\n" 113 " and samples write stdout, while diagnostics use stderr. Generated\n" 114 " parser headers include <kit/gram_parse.h>; compile with the shipped\n" 115 " include directory and link the shipped lib/libkit.a runtime.\n" 116 "\n" 117 " With --parser-codegen, PREFIXstack_bounds sizes caller-owned\n" 118 " control/value stacks, PREFIXgenerate_scratch_count reports sample\n" 119 " scratch storage, and PREFIXparser_init initializes a caller-owned\n" 120 " KitGramParser. The generated header is the authority for exact\n" 121 " enum and symbol names.\n" 122 "\n" 123 "PATHS\n" 124 " Use -- before a leading-dash grammar file name.\n" 125 "\n" 126 "GETTING HELP\n" 127 " -h, --help Show this help and exit\n" 128 " --version Show Kit version and exit\n" 129 "\n" 130 "EXAMPLES\n" 131 " printf '%s\\n' 'start = \"hello\";' > grammar.ebnf\n" 132 " kit gram --dump-sexpr grammar.ebnf\n" 133 "\n" 134 " kit gram -o lexer.c --header lexer.h --prefix demo grammar.ebnf\n" 135 " kit gram --lexer-standalone --parser-codegen \\\n" 136 " -o parser.c --header parser.h --prefix demop grammar.ebnf\n" 137 "\n" 138 "EXIT CODES\n" 139 " 0 success 1 compile / I/O error 2 bad usage\n"))); 140 } 141 142 /* ---- small parsers (driver helpers + rt strtod/strtoull) ------------------ */ 143 144 static int gram_parse_size(const char* s, size_t* out) { 145 uint64_t v; 146 if (driver_parse_u64(s, &v) != 0 || v > SIZE_MAX) return 0; 147 *out = (size_t)v; 148 return 1; 149 } 150 151 static int gram_parse_double(const char* s, double* out) { 152 char* end = NULL; 153 double v; 154 if (!s || !*s) return 0; 155 v = strtod(s, &end); 156 if (*end || v < 0.0 || v > 1.0) return 0; 157 *out = v; 158 return 1; 159 } 160 161 /* opt-with-value: matches "--name VALUE" (consuming argv[i+1]) or "--name=VALUE" 162 * (prefix len `plen` covers "--name="). Returns the value or NULL on no match; 163 * on a match that needs the next arg but argc runs out, sets *err. */ 164 static const char* opt_val(int argc, char** argv, int* i, const char* name, 165 const char* eqname, size_t plen, 166 GramArgError* error) { 167 if (driver_streq(argv[*i], name)) { 168 if (++*i >= argc) { 169 error->category = GRAM_ARG_MISSING_OPTION_VALUE; 170 error->option = name; 171 return NULL; 172 } 173 return argv[*i]; 174 } 175 if (driver_strneq(argv[*i], eqname, plen)) return argv[*i] + plen; 176 return NULL; 177 } 178 179 static int gram_invalid_numeric(GramArgError* error, const char* option, 180 const char* value) { 181 error->category = GRAM_ARG_INVALID_NUMERIC_VALUE; 182 error->option = option; 183 error->value = value; 184 return 0; 185 } 186 187 static int gram_set_grammar(GramArgs* a, GramArgError* error, 188 const char* value) { 189 if (a->grammar) { 190 error->category = GRAM_ARG_DUPLICATE_GRAMMAR; 191 error->previous = a->grammar; 192 error->value = value; 193 return 0; 194 } 195 a->grammar = value; 196 return 1; 197 } 198 199 static int parse_args(int argc, char** argv, GramArgs* a, 200 GramArgError* error) { 201 int options = 1; 202 memset(a, 0, sizeof *a); 203 memset(error, 0, sizeof *error); 204 for (int i = 1; i < argc; i++) { 205 const char* arg = argv[i]; 206 const char* v; 207 if (options && driver_streq(arg, "--")) { 208 options = 0; 209 } else if (!options) { 210 if (!gram_set_grammar(a, error, arg)) return 0; 211 } else if (driver_streq(arg, "--dump-sexpr")) { 212 a->dump_sexpr = 1; 213 } else if (driver_streq(arg, "--multiline")) { 214 a->multiline = 1; 215 } else if (driver_streq(arg, "--lexer-standalone")) { 216 a->lexer_standalone = 1; 217 } else if (driver_streq(arg, "--fold-keywords")) { 218 a->fold_keywords = 1; 219 } else if (driver_streq(arg, "--position-lazy")) { 220 a->position_lazy = 1; 221 } else if (driver_streq(arg, "--parser-codegen")) { 222 a->parser_codegen = 1; 223 } else if (driver_streq(arg, "--parser-recover")) { 224 a->parser_recover = 1; 225 } else if (driver_streq(arg, "--sample-tokens")) { 226 a->sample_tokens = 1; 227 } else if ((v = opt_val(argc, argv, &i, "--sample-traces", 228 "--sample-traces=", 16, error))) { 229 a->sample_traces = v; 230 } else if (error->category) { 231 return 0; 232 } else if ((v = opt_val(argc, argv, &i, "--stop-prob", "--stop-prob=", 12, 233 error))) { 234 if (!gram_parse_double(v, &a->stop_prob)) 235 return gram_invalid_numeric(error, "--stop-prob", v); 236 a->have_stop_prob = 1; 237 } else if (error->category) { 238 return 0; 239 } else if ((v = opt_val(argc, argv, &i, "--samples", "--samples=", 10, 240 error))) { 241 if (!gram_parse_size(v, &a->samples)) 242 return gram_invalid_numeric(error, "--samples", v); 243 } else if (error->category) { 244 return 0; 245 } else if ((v = opt_val(argc, argv, &i, "--seed", "--seed=", 7, error))) { 246 if (driver_parse_u64(v, &a->seed) != 0) 247 return gram_invalid_numeric(error, "--seed", v); 248 } else if (error->category) { 249 return 0; 250 } else if ((v = opt_val(argc, argv, &i, "--max-depth", "--max-depth=", 12, 251 error))) { 252 if (!gram_parse_size(v, &a->max_depth)) 253 return gram_invalid_numeric(error, "--max-depth", v); 254 } else if (error->category) { 255 return 0; 256 } else if ((v = opt_val(argc, argv, &i, "--max-repeat", "--max-repeat=", 13, 257 error))) { 258 if (!gram_parse_size(v, &a->max_repeat)) 259 return gram_invalid_numeric(error, "--max-repeat", v); 260 } else if (error->category) { 261 return 0; 262 } else if ((v = opt_val(argc, argv, &i, "--max-tokens", "--max-tokens=", 13, 263 error))) { 264 if (!gram_parse_size(v, &a->max_tokens)) 265 return gram_invalid_numeric(error, "--max-tokens", v); 266 } else if (error->category) { 267 return 0; 268 } else if ((v = opt_val(argc, argv, &i, "-o", "-o", 2, error))) { 269 a->out_c = v; 270 } else if (error->category) { 271 return 0; 272 } else if ((v = opt_val(argc, argv, &i, "--header", "--header=", 9, 273 error))) { 274 a->out_h = v; 275 } else if (error->category) { 276 return 0; 277 } else if ((v = opt_val(argc, argv, &i, "--prefix", "--prefix=", 9, 278 error))) { 279 a->prefix = v; 280 } else if (error->category) { 281 return 0; 282 } else if (driver_streq(arg, "-")) { 283 if (!gram_set_grammar(a, error, arg)) return 0; 284 } else if (arg[0] == '-' && arg[1]) { 285 error->category = GRAM_ARG_UNKNOWN_OPTION; 286 error->option = arg; 287 return 0; 288 } else { 289 if (!gram_set_grammar(a, error, arg)) return 0; 290 } 291 } 292 if (!a->grammar) { 293 error->category = GRAM_ARG_MISSING_GRAMMAR; 294 return 0; 295 } 296 return 1; 297 } 298 299 static void gram_print_arg_error(const GramArgError* error) { 300 switch (error->category) { 301 case GRAM_ARG_UNKNOWN_OPTION: { 302 const char* const options[] = { 303 "--dump-sexpr", "--multiline", "--lexer-standalone", 304 "--fold-keywords", "--position-lazy", "--parser-codegen", 305 "--parser-recover", "--sample-tokens", "--sample-traces", 306 "--stop-prob", "--samples", "--seed", 307 "--max-depth", "--max-repeat", "--max-tokens", 308 "-o", "--header", "--prefix", 309 "-h", "--help", "--version", 310 }; 311 DriverSuggestion suggestions[3]; 312 size_t n = driver_suggest_values( 313 error->option, options, sizeof options / sizeof options[0], 314 suggestions, 3); 315 if (n) 316 driver_errf(GRAM_TOOL, "unknown option: %s; did you mean '%s'?", 317 error->option, suggestions[0].value); 318 else 319 driver_errf(GRAM_TOOL, "unknown option: %s", error->option); 320 break; 321 } 322 case GRAM_ARG_MISSING_OPTION_VALUE: 323 driver_errf(GRAM_TOOL, "option requires an argument: %s", error->option); 324 break; 325 case GRAM_ARG_INVALID_NUMERIC_VALUE: 326 driver_errf(GRAM_TOOL, "invalid numeric value '%s' for %s", error->value, 327 error->option); 328 break; 329 case GRAM_ARG_DUPLICATE_GRAMMAR: 330 driver_errf(GRAM_TOOL, "multiple grammar inputs: %s and %s", 331 error->previous, error->value); 332 break; 333 case GRAM_ARG_MISSING_GRAMMAR: 334 driver_errf(GRAM_TOOL, "missing grammar input"); 335 break; 336 case GRAM_ARG_OK: 337 driver_errf(GRAM_TOOL, "invalid command line"); 338 break; 339 } 340 driver_errf(GRAM_TOOL, 341 "usage: kit gram [options] grammar.ebnf (see --help)"); 342 } 343 344 /* ---- owned-string helpers (env heap; freed via gram_free_str) ------------- */ 345 346 static char* gram_dup_n(DriverEnv* env, const char* s, size_t n) { 347 char* out = driver_alloc(env, n + 1); 348 if (!out) return NULL; 349 driver_memcpy(out, s, n); 350 out[n] = '\0'; 351 return out; 352 } 353 354 static char* gram_dup(DriverEnv* env, const char* s) { 355 return gram_dup_n(env, s, driver_strlen(s)); 356 } 357 358 static void gram_free_str(DriverEnv* env, char* s) { 359 if (s) driver_free(env, s, driver_strlen(s) + 1); 360 } 361 362 /* "<dir>/foo.ebnf" -> "foo" (basename without the final extension). */ 363 static char* gram_path_stem(DriverEnv* env, const char* path) { 364 const char* base = driver_basename(path); 365 const char* dot = NULL; 366 for (const char* p = base; *p; p++) 367 if (*p == '.') dot = p; 368 size_t n = (dot && dot > base) ? (size_t)(dot - base) : driver_strlen(base); 369 return gram_dup_n(env, base, n); 370 } 371 372 /* Replace the final extension of `path` with `suffix` (e.g. ".c"). */ 373 static char* gram_replace_suffix(DriverEnv* env, const char* path, 374 const char* suffix) { 375 const char* dot = NULL; 376 for (const char* p = path; *p; p++) 377 if (*p == '.') dot = p; 378 size_t n = dot ? (size_t)(dot - path) : driver_strlen(path); 379 size_t slen = driver_strlen(suffix); 380 char* out = driver_alloc(env, n + slen + 1); 381 if (!out) return NULL; 382 driver_memcpy(out, path, n); 383 driver_memcpy(out + n, suffix, slen + 1); 384 return out; 385 } 386 387 /* Sanitized identifier prefix derived from the grammar filename + '_'. */ 388 static char* gram_default_prefix(DriverEnv* env, const char* path) { 389 char* stem = gram_path_stem(env, path); 390 if (!stem) return NULL; 391 size_t slen = driver_strlen(stem); 392 char* out = driver_alloc(env, slen + 3); 393 if (!out) { 394 gram_free_str(env, stem); 395 return NULL; 396 } 397 size_t len = 0; 398 if (!stem[0] || (stem[0] >= '0' && stem[0] <= '9')) out[len++] = '_'; 399 for (size_t i = 0; stem[i]; i++) { 400 char c = stem[i]; 401 int ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 402 (c >= '0' && c <= '9') || c == '_'; 403 out[len++] = ok ? c : '_'; 404 } 405 out[len++] = '_'; 406 out[len] = '\0'; 407 gram_free_str(env, stem); 408 return out; 409 } 410 411 /* Best-effort mkdir -p of `path`'s parent directory (posix_open_writer needs the 412 * directory to exist for its temp+rename). A path with no '/' lands in cwd. */ 413 static void gram_mkparent(DriverEnv* env, const char* path) { 414 const char* slash = NULL; 415 for (const char* p = path; *p; p++) 416 if (*p == '/') slash = p; 417 if (!slash || slash == path) return; 418 char* dir = gram_dup_n(env, path, (size_t)(slash - path)); 419 if (!dir) return; 420 (void)driver_mkdir_p(env, dir); 421 gram_free_str(env, dir); 422 } 423 424 /* ---- token-stream / machine-trace synthesis ------------------------------- */ 425 426 static int sample_tokens(DriverEnv* env, const KitGramCompiled* compiled, 427 const GramArgs* a, const char* diag_path) { 428 const KitGramGrammar* g = kit_gram_parser_grammar(compiled); 429 if (!g) { 430 driver_errf(GRAM_TOOL, "%s: grammar has no parser to sample", diag_path); 431 return 0; 432 } 433 size_t depth = a->max_depth ? a->max_depth : 8; 434 size_t ctl_cap = 0, val_cap = 0; 435 kit_gram_stack_bounds(g, depth + 4, &ctl_cap, &val_cap); 436 if (!ctl_cap) ctl_cap = 1; 437 if (!val_cap) val_cap = 1; 438 size_t scratch_cap = kit_gram_parser_generate_scratch_count(g); 439 if (!scratch_cap) scratch_cap = 1; 440 441 KitGramSlot* ctl = driver_alloc(env, ctl_cap * sizeof *ctl); 442 KitGramSem* vals = driver_alloc(env, val_cap * sizeof *vals); 443 size_t* scratch = driver_alloc(env, scratch_cap * sizeof *scratch); 444 int rc = 1; 445 if (!ctl || !vals || !scratch) { 446 driver_errf(GRAM_TOOL, "out of memory"); 447 if (scratch) driver_free(env, scratch, scratch_cap * sizeof *scratch); 448 if (vals) driver_free(env, vals, val_cap * sizeof *vals); 449 if (ctl) driver_free(env, ctl, ctl_cap * sizeof *ctl); 450 return 0; 451 } 452 size_t samples = a->samples ? a->samples : 1; 453 for (size_t i = 0; i < samples && rc; i++) { 454 KitGramParser ps; 455 KitGramConfig pcfg = {.ctl_stack = ctl, 456 .ctl_cap = ctl_cap, 457 .val_stack = vals, 458 .val_cap = val_cap}; 459 kit_gram_parser_init(&ps, g, &pcfg); 460 KitGramGenConfig gcfg = {.seed = a->seed + i, 461 .max_depth = a->max_depth, 462 .max_repeat = a->max_repeat, 463 .max_tokens = a->max_tokens, 464 .scratch = scratch, 465 .scratch_cap = scratch_cap}; 466 int first = 1; 467 for (;;) { 468 KitGramToken tok; 469 KitGramGenStatus st = kit_gram_parser_generate_next(&ps, &gcfg, &tok); 470 if (st == KIT_GRAM_GEN_TOKEN) { 471 const char* name = kit_gram_token_name(compiled, tok.kind); 472 driver_printf("%s%s", first ? "" : " ", name ? name : "?"); 473 first = 0; 474 continue; 475 } 476 if (st == KIT_GRAM_GEN_DONE) { 477 driver_printf("\n"); 478 break; 479 } 480 if (st == KIT_GRAM_GEN_LIMIT) 481 driver_errf(GRAM_TOOL, "%s:0:0: synthesis token budget too small", 482 diag_path); 483 else 484 driver_errf(GRAM_TOOL, "%s:0:0: token generation failed", diag_path); 485 rc = 0; 486 } 487 } 488 driver_free(env, scratch, scratch_cap * sizeof *scratch); 489 driver_free(env, vals, val_cap * sizeof *vals); 490 driver_free(env, ctl, ctl_cap * sizeof *ctl); 491 return rc; 492 } 493 494 static int sample_traces(DriverEnv* env, const KitGramCompiled* compiled, 495 const GramArgs* a, const char* diag_path) { 496 size_t mi; 497 if (!kit_gram_find_machine(compiled, a->sample_traces, &mi)) { 498 driver_errf(GRAM_TOOL, "%s:0:0: no %%machine named '%s'", diag_path, 499 a->sample_traces); 500 return 0; 501 } 502 double stop_prob = a->have_stop_prob ? a->stop_prob : 0.5; 503 size_t max_repeat = a->max_repeat ? a->max_repeat : 2; 504 size_t max_tokens = a->max_tokens ? a->max_tokens : 128; 505 size_t samples = a->samples ? a->samples : 1; 506 size_t cap = max_tokens ? max_tokens : 1; 507 const char** buf = driver_alloc(env, cap * sizeof *buf); 508 if (!buf) { 509 driver_errf(GRAM_TOOL, "out of memory"); 510 return 0; 511 } 512 uint64_t seed = a->seed; 513 int rc = 1; 514 for (size_t i = 0; i < samples; i++) { 515 size_t n = 0; 516 KitGramGenStatus st = 517 kit_gram_machine_generate(compiled, mi, 0, &seed, stop_prob, max_repeat, 518 max_tokens, buf, cap, &n); 519 if (st == KIT_GRAM_GEN_ERROR) { 520 driver_errf(GRAM_TOOL, "%s:0:0: trace generation failed", diag_path); 521 rc = 0; 522 break; 523 } 524 size_t shown = n < cap ? n : cap; 525 for (size_t j = 0; j < shown; j++) 526 driver_printf("%s%s", j ? " " : "", buf[j]); 527 driver_printf("\n"); 528 if (st == KIT_GRAM_GEN_LIMIT) 529 driver_errf(GRAM_TOOL, "%s:0:0: note: trace truncated at max-tokens=%zu", 530 diag_path, max_tokens); 531 } 532 driver_free(env, buf, cap * sizeof *buf); 533 return rc; 534 } 535 536 /* ---- entry ---------------------------------------------------------------- */ 537 538 int driver_gram(int argc, char** argv) { 539 DriverEnv env; 540 KitContext ctx; 541 GramArgs a; 542 GramArgError arg_error; 543 DriverLoad ld = {0}; 544 uint8_t* sbuf = NULL; 545 size_t sbuf_len = 0; 546 KitSlice text = KIT_SLICE_NULL; 547 KitGramCompiled* compiled = NULL; 548 char* out_c = NULL; 549 char* out_h = NULL; 550 char* prefix = NULL; 551 int loaded = 0, rc = 2; 552 553 if (driver_argv_wants_help(argc, argv, 1)) { 554 driver_help_gram(); 555 return 0; 556 } 557 if (!parse_args(argc, argv, &a, &arg_error)) { 558 gram_print_arg_error(&arg_error); 559 return 2; 560 } 561 562 driver_env_init(&env); 563 ctx = driver_env_to_context(&env); 564 565 int from_stdin = driver_streq(a.grammar, "-"); 566 const char* diag_path = from_stdin ? "<stdin>" : a.grammar; 567 const char* derive_from = from_stdin ? "stdin" : a.grammar; 568 569 if (from_stdin) { 570 if (!driver_read_stdin(&env, &sbuf, &sbuf_len)) { 571 driver_errf(GRAM_TOOL, "cannot read stdin"); 572 rc = 1; 573 goto done; 574 } 575 text.data = sbuf; 576 text.len = sbuf_len; 577 } else { 578 if (driver_load_bytes(&env.file_io, GRAM_TOOL, a.grammar, &ld, &text) != 0) { 579 rc = 1; 580 goto done; 581 } 582 loaded = 1; 583 } 584 585 KitGramOptions opts = {.multiline = a.multiline != 0, 586 .lexer_standalone = a.lexer_standalone != 0, 587 .fold_keywords = a.fold_keywords != 0, 588 .position_lazy = a.position_lazy != 0, 589 .parser_codegen = a.parser_codegen != 0, 590 .parser_recover = a.parser_recover != 0}; 591 KitSlice path = kit_slice_cstr(diag_path); 592 593 /* --dump-sexpr: stream the parsed syntax tree to stdout (+ trailing \n). */ 594 if (a.dump_sexpr) { 595 KitWriter* w = driver_stdout_writer(&env); 596 if (!w) { 597 driver_errf(GRAM_TOOL, "cannot open stdout"); 598 rc = 1; 599 goto done; 600 } 601 KitStatus st = kit_gram_dump_sexpr(&ctx, text, path, &opts, w); 602 if (st == KIT_OK) kit_writer_write(w, "\n", 1); 603 kit_writer_close(w); 604 rc = st == KIT_OK ? 0 : 1; 605 goto done; 606 } 607 608 if (kit_gram_compile_text(&ctx, text, path, &opts, &compiled) != KIT_OK) { 609 rc = 1; 610 goto done; 611 } 612 613 if (a.sample_tokens) { 614 rc = sample_tokens(&env, compiled, &a, diag_path) ? 0 : 1; 615 goto done; 616 } 617 if (a.sample_traces) { 618 rc = sample_traces(&env, compiled, &a, diag_path) ? 0 : 1; 619 goto done; 620 } 621 622 /* Default: emit the generated C header + source. */ 623 out_c = a.out_c ? gram_dup(&env, a.out_c) 624 : gram_replace_suffix(&env, derive_from, ".c"); 625 out_h = a.out_h ? gram_dup(&env, a.out_h) 626 : gram_replace_suffix(&env, derive_from, ".h"); 627 prefix = a.prefix ? gram_dup(&env, a.prefix) 628 : gram_default_prefix(&env, derive_from); 629 if (!out_c || !out_h || !prefix) { 630 driver_errf(GRAM_TOOL, "out of memory"); 631 rc = 1; 632 goto done; 633 } 634 635 gram_mkparent(&env, out_h); 636 gram_mkparent(&env, out_c); 637 KitWriter* hw = NULL; 638 KitWriter* sw = NULL; 639 if (ctx.file_io->open_writer(ctx.file_io->user, out_h, &hw) != KIT_OK) { 640 driver_errf(GRAM_TOOL, "cannot open output: %s", out_h); 641 rc = 1; 642 goto done; 643 } 644 if (ctx.file_io->open_writer(ctx.file_io->user, out_c, &sw) != KIT_OK) { 645 driver_errf(GRAM_TOOL, "cannot open output: %s", out_c); 646 driver_writer_abort(hw); 647 kit_writer_close(hw); 648 rc = 1; 649 goto done; 650 } 651 KitGramEmitOptions emit = { 652 .header_path = out_h, .source_path = out_c, .prefix = prefix}; 653 KitStatus est = kit_gram_emit_c(compiled, &emit, hw, sw); 654 if (est != KIT_OK) { 655 driver_writer_abort(hw); 656 driver_writer_abort(sw); 657 } 658 kit_writer_close(hw); 659 kit_writer_close(sw); 660 rc = est == KIT_OK ? 0 : 1; 661 662 done: 663 if (compiled) kit_gram_free(compiled); 664 gram_free_str(&env, out_c); 665 gram_free_str(&env, out_h); 666 gram_free_str(&env, prefix); 667 if (sbuf) driver_free(&env, sbuf, sbuf_len); 668 if (loaded) driver_release_bytes(&env.file_io, &ld); 669 driver_env_fini(&env); 670 return rc; 671 }