{"_id":"@dexravel/kindling","_rev":"2-2b65b02a1ceaa018f5642a2d84f281e0","name":"@dexravel/kindling","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@dexravel/kindling","version":"0.1.0","keywords":["interpreter","language","compiler","toy-language"],"license":"MIT","_id":"@dexravel/kindling@0.1.0","maintainers":[{"name":"dexravel","email":"Mohitbansal25082006@gmail.com"}],"bin":{"kindling":"dist/cli.js"},"dist":{"shasum":"755800103442be589de53eea88f43470fec8c22f","tarball":"https://registry.npmjs.org/@dexravel/kindling/-/kindling-0.1.0.tgz","fileCount":37,"integrity":"sha512-Ipc1Dli7XN8gC+MxSEWNByvs9wePt1HO2WWEQQTHT0wj+GYZOJtOsA9nRttEW7wLnb3CFTWuAAb1sxiHHLiLjw==","signatures":[{"sig":"MEUCICrpWuaidMIJ2k5ZZDywkxc9rvlz+/5nj1pP4aU908oCAiEA0LQQEsEFgpgb9hwVo2YPA61mddXr4qA310s8/pMv1hk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":158729},"type":"module","scripts":{"dev":"tsx src/cli.ts","run":"tsx src/cli.ts run","repl":"tsx src/cli.ts repl","test":"vitest run","build":"tsc","postbuild":"node scripts/fix-shebang.js","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"dexravel","email":"Mohitbansal25082006@gmail.com"},"_npmVersion":"11.8.0","description":"A from-scratch interpreter for Kindling, a small dynamically-typed, C-like scripting language.","directories":{},"_nodeVersion":"24.13.1","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.20.1"},"_npmOperationalInternal":{"tmp":"tmp/kindling_0.1.0_1786432814710_0.4494288468769905","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dexravel/kindling","version":"0.1.1","description":"A from-scratch interpreter for Kindling, a small dynamically-typed, C-like scripting language.","type":"module","bin":{"kindling":"dist/cli.js"},"scripts":{"build":"tsc","postbuild":"node scripts/fix-shebang.js","dev":"tsx src/cli.ts","run":"tsx src/cli.ts run","repl":"tsx src/cli.ts repl","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"keywords":["interpreter","language","compiler","toy-language"],"license":"MIT","devDependencies":{"@types/node":"^22.20.1","tsx":"^4.19.2","typescript":"^5.7.2","vitest":"^2.1.8"},"gitHead":"5eb1be60a99c5e1337b0e7cc6bc9fee4cdb83435","_id":"@dexravel/kindling@0.1.1","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-nRh45xBjI+XVlH6H7oN9FNXPk1Hw+Od9goerVT2xSnV5DkzDUNtn+Oq++lVMr1L5P8FMeGYWTqn+8TX8ayWXZg==","shasum":"5bdde977eb116a5d014449124842d9c8b233052f","tarball":"https://registry.npmjs.org/@dexravel/kindling/-/kindling-0.1.1.tgz","fileCount":37,"unpackedSize":159619,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIG5n1wPl9GLEVYN/mP+MLVq8BuFfpBwjXC4AigBzSHkBAiEAskPDfanFRLpLPUEbpUAo3epnaqkJgXaJxr0H4QdHOKs="}]},"_npmUser":{"name":"dexravel","email":"Mohitbansal25082006@gmail.com"},"directories":{},"maintainers":[{"name":"dexravel","email":"Mohitbansal25082006@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/kindling_0.1.1_1786434672657_0.8032459659730844"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-11T07:20:14.521Z","modified":"2026-08-11T07:51:13.104Z","0.1.0":"2026-08-11T07:20:14.846Z","0.1.1":"2026-08-11T07:51:12.824Z"},"license":"MIT","keywords":["interpreter","language","compiler","toy-language"],"description":"A from-scratch interpreter for Kindling, a small dynamically-typed, C-like scripting language.","maintainers":[{"name":"dexravel","email":"Mohitbansal25082006@gmail.com"}],"readme":"# Kindling\n\nA from-scratch interpreter for **Kindling**, a small, dynamically-typed,\nC-like scripting language — built in TypeScript, with no parser\ngenerator and no external runtime dependencies. Variables, arithmetic,\ncontrol flow, functions, closures, a real CLI, and an interactive REPL.\n\n```kindling\nfunction makeCounter() {\n  let count = 0;\n  function increment() {\n    count = count + 1;\n    return count;\n  }\n  return increment;\n}\n\nlet counter = makeCounter();\nprint(counter()); // 1\nprint(counter()); // 2\nprint(counter()); // 3\n```\n\n**Install the CLI globally** (published on npm as\n[`@dexravel/kindling`](https://www.npmjs.com/package/@dexravel/kindling)):\n\n```powershell\nnpm install -g @dexravel/kindling\nkindling run examples/fibonacci.kdl\nkindling repl\n```\n\nThe `-g` flag matters — without it, `kindling` installs into whatever\nfolder you're standing in instead of becoming a command available\nanywhere, and running `kindling` afterward will fail with\n\"not recognized.\"\n\n---\n\n## Table of contents\n\n- [Quick start](#quick-start)\n- [Part 1 — Front End (Lexer, AST, Parser)](#part-1--front-end-lexer-ast-parser)\n- [Part 2 — Back End (Evaluator, Control Flow, Functions & Closures)](#part-2--back-end-evaluator-control-flow-functions--closures)\n- [Part 3 — Interface, Reliability, and Proof of Correctness](#part-3--interface-reliability-and-proof-of-correctness)\n- [Language reference](#language-reference)\n- [Project layout](#project-layout)\n- [Test suite](#test-suite)\n- [Design notes](#design-notes)\n\n---\n\n## Quick start\n\n**Just want to use the CLI?** Install it from npm — see the top of\nthis README. This section is for working on the interpreter itself\n(a cloned copy of this repo).\n\n**Fresh clone, zero extra setup:**\n\n```powershell\nnpm install\nnpm run build\nnode dist/cli.js run examples/fibonacci.kdl\n```\n\n**During development** (runs TypeScript directly via `tsx`, no build step):\n\n```powershell\nnpm run dev -- run examples/fibonacci.kdl\nnpm run dev -- repl\n\n# equivalently:\nnpx tsx src/cli.ts run examples/fibonacci.kdl\nnpx tsx src/cli.ts repl\n```\n\n**To test the `kindling` command itself against your local changes**\n(without publishing), use `npm link` instead of installing from npm:\n\n```powershell\nnpm link\nkindling run examples/fibonacci.kdl\nkindling repl\n```\n\n`npm link` makes the global `kindling` command point at your local\nclone instead of the published package — useful while developing, but\nrun `npm uninstall -g kindling` before installing the real published\nversion, or the two will conflict (an `EEXIST` error on the shim\nscripts is the usual symptom).\n\n**Run the tests:**\n\n```powershell\nnpm test\n```\n\n---\n\n## Part 1 — Front End (Lexer, AST, Parser)\n\n**Goal:** turn raw Kindling source text into a correct, precedence-aware\nAST. No execution — this part only proves the grammar is right.\n\n### What it does\n\n- **`src/token.ts`** — every token type the lexer can produce, plus the\n  `Token` shape (`type`, `lexeme`, `literal`, `line`). Keywords are\n  centralized in a `KEYWORDS` map rather than scattered string\n  comparisons.\n- **`src/lexer.ts`** — a single-pass tokenizer. Walks the source once,\n  character by character, tracking line numbers as it goes so every\n  downstream error can report exactly where it happened. Handles\n  multi-character operators (`==`, `!=`, `<=`, `>=`) by peeking one\n  character ahead, `//` line comments, string literals with proper\n  unterminated-string detection, and number/identifier scanning.\n- **`src/ast.ts`** — the AST as two tagged unions, `Expr` and `Stmt`,\n  rather than a class hierarchy. Every node has a `kind` string-literal\n  field so TypeScript can narrow on it — the evaluator's switch\n  statement gets full type-checking on every case with zero casts.\n  Every node also carries `line`, propagated from the token(s) that\n  produced it.\n- **`src/parser.ts`** — builds the AST from the token stream.\n  - **Statements** use classic recursive descent: one method per\n    grammar rule (`letStatement`, `ifStatement`, `whileStatement`, …).\n  - **Expressions** use **Pratt parsing** (precedence climbing) instead\n    of one function per precedence level — see\n    [Design notes](#why-pratt-parsing-for-expressions) below.\n- **`src/errors.ts`** — `KindlingError` base class plus `LexError`,\n  `ParseError`, `RuntimeError` subclasses. Every error carries a `line`\n  number; \"syntax error\" with no location is useless.\n- **`src/inspect.ts`** — a debug/demo CLI for exercising the pipeline\n  before Part 3's real CLI existed. Still useful for dumping raw\n  tokens/AST.\n\n### Commands run\n\n```powershell\nnpm install\nnpx tsc --noEmit\nnpm test\nnpx tsx src/inspect.ts examples/hello.kdl\nnpx tsx src/inspect.ts examples/hello.kdl --tokens-only\nnpx tsx src/inspect.ts examples/hello.kdl --ast-only\n```\n\n### Files created in Part 1\n\n```\npackage.json\ntsconfig.json\nvitest.config.ts\n.gitignore\nsrc/token.ts\nsrc/errors.ts\nsrc/lexer.ts\nsrc/ast.ts\nsrc/parser.ts\nsrc/inspect.ts\ntests/lexer.test.ts\ntests/parser.test.ts\nexamples/hello.kdl\nREADME.md\n```\n\n---\n\n## Part 2 — Back End (Evaluator, Control Flow, Functions & Closures)\n\n**Goal:** make the AST actually execute — variables, arithmetic,\ncontrol flow, functions, and closures. This is where `print` actually\nprints, and where the closure-counter proof runs correctly.\n\n### What it does\n\n- **`src/environment.ts`** — an `Environment` is a variable map chained\n  to an optional enclosing environment. This chain **is** how scoping\n  works:\n  - `define` introduces a new variable in *this* scope only (used by\n    `let` and function-parameter binding).\n  - `get` reads a variable, searching outward through the chain.\n  - `assign` writes to an *existing* variable, searching outward to\n    find which scope owns it (different from `define` — `x = 5`\n    updates the `x` that already exists somewhere in the chain rather\n    than shadowing it).\n- **`src/interpreter.ts`** — the evaluator. Two entry points:\n  `evaluate(expr, env)` returns a JS value; `execute(stmt, env)` runs\n  for effect. Both are single `switch` statements over the node's\n  `kind`, fully narrowed by TypeScript.\n  - **Truthiness:** only `false` and `nil` are falsy — `0` and `\"\"`\n    are truthy. A deliberate, explicit design choice rather than\n    inheriting JavaScript's coercion rules.\n  - **Closures:** `MiniFunction` captures the `Environment` active at\n    *definition* time (not call time) as `closure`. Every call creates\n    a fresh `Environment` chained to that closure — this one fact is\n    the entire closures mechanism. See\n    [Design notes](#proof-closures-capture-their-defining-environment-not-the-call-site).\n  - **Return:** implemented as a thrown-and-caught `ReturnSignal`, not\n    a value threaded through every `execute` call — the simplest way\n    to unwind out of deeply nested ifs/whiles/blocks.\n  - **Native functions:** `print` and `clock` are registered as\n    `KindlingCallable`s in the global environment.\n- **`src/inspect.ts`** — updated with a `--run` flag to actually\n  execute a `.kdl` file end to end.\n\n### Commands run\n\n```powershell\nnpm install\nnpx tsc --noEmit\nnpm test\nnpx tsx src/inspect.ts examples/hello.kdl --run\nnpx tsx src/inspect.ts examples/closures.kdl --run\nnpx tsx src/inspect.ts examples/fibonacci.kdl --run\n```\n\n### Files created in Part 2\n\n```\nsrc/environment.ts\nsrc/interpreter.ts\ntests/interpreter.test.ts\nexamples/closures.kdl\nexamples/fibonacci.kdl\n```\n\n### Files updated in Part 2\n\n```\nsrc/inspect.ts   (added --run flag to actually execute a .kdl file)\n```\n\n### The closure proof\n\n```kindling\nfunction makeCounter() {\n  let count = 0;\n  function increment() {\n    count = count + 1;\n    return count;\n  }\n  return increment;\n}\nlet counter = makeCounter();\nprint(counter()); // 1\nprint(counter()); // 2\nprint(counter()); // 3\n```\n\nIf this prints `1, 2, 3` (not `1, 1, 1`), closures are correctly\ncapturing their defining environment rather than the call site.\n\n---\n\n## Part 3 — Interface, Reliability, and Proof of Correctness\n\n**Goal:** make the interpreter usable from the command line, make its\nerrors trustworthy, and back all of it with tests — this is what turns\na working interpreter into a credible, reviewable project.\n\n### What it does\n\n- **`src/repl.ts`** — an interactive shell. The `Repl` class creates\n  **one** `Interpreter` (and therefore one `globals` `Environment`) for\n  the entire life of a session, reusing it across every line — this is\n  the entire mechanism behind \"variables persist across lines.\"\n  `Repl.run(line)` returns `null` on success or a formatted error\n  string on failure, deliberately decoupled from any actual terminal\n  I/O so it's directly unit-testable. `startInteractive()` is the thin\n  wrapper hooking a `Repl` up to a real terminal via Node's `readline`.\n  Catches `LexError` / `ParseError` / `RuntimeError` per line and\n  prints a clean message instead of a raw stack trace — one bad line\n  doesn't end the session.\n- **`src/cli.ts`** — the real, user-facing entry point (what\n  `package.json`'s `bin` field points at). Two subcommands:\n  - `kindling run <file.kdl>` — full lex → parse → evaluate pipeline.\n  - `kindling repl` — launches the interactive shell.\n- **Parser error recovery** (`src/parser.ts`) — added `synchronize()`\n  and a new `parseProgram()` method. Previously, `parse()` threw on the\n  first `ParseError` and stopped dead. `parseProgram()` catches each\n  error, skips forward to the next plausible statement boundary (a `;`\n  or a keyword like `let`/`if`/`function`), and keeps going — so a file\n  with three unrelated typos reports all three in one pass.\n  `parse()` itself is **unchanged** (still throws on the first error),\n  so existing single-error callers/tests are unaffected.\n- **Test suite additions:**\n  - `tests/repl.test.ts` — variable/closure state persists across\n    separate `Repl.run()` calls; errors return a formatted string\n    without throwing or ending the session; blank input is a no-op.\n  - `tests/parser-recovery.test.ts` — `parseProgram()` collects\n    multiple errors with correct line numbers; `parse()`'s legacy\n    single-error behavior is unchanged; recovery still successfully\n    parses the valid statements around/after broken ones.\n  - `tests/cli.test.ts` — end-to-end smoke tests that spawn the real\n    CLI as a child process and check exit codes and stdout/stderr:\n    running fibonacci successfully, a missing file, no subcommand,\n    a runtime error with the correct line number, and multi-error\n    reporting on `examples/broken.kdl`. (Spawns `node <tsx-cli-path>\n    <our-cli-path>` directly rather than `npx tsx`, since `npx` resolves\n    to `npx.cmd` on Windows and `spawnSync` won't launch `.cmd` files\n    without `shell: true` — this keeps the tests OS-agnostic.)\n- **`examples/broken.kdl`** — a runnable example with two deliberate,\n  unrelated syntax errors, used to demonstrate multi-error recovery.\n\n### Commands run\n\n```powershell\nnpm install\nnpx tsc --noEmit\nnpm test\nnpm run build\nnode dist/cli.js run examples/fibonacci.kdl\nnode dist/cli.js run examples/broken.kdl\nnpx tsx src/cli.ts repl\nnpm link\nkindling run examples/fibonacci.kdl\nkindling repl\n```\n\n### Files created in Part 3\n\n```\nsrc/repl.ts\nsrc/cli.ts\ntests/repl.test.ts\ntests/parser-recovery.test.ts\ntests/cli.test.ts\nexamples/broken.kdl\n```\n\n### Files updated in Part 3\n\n```\nsrc/parser.ts    (added synchronize() and parseProgram() for multi-error\n                  recovery; parse() unchanged — still throws on first error)\npackage.json     (added `run` / `repl` npm script shortcuts;\n                  `bin` entry point unchanged — was already correct)\n```\n\n### `kindling run <file.kdl>`\n\n```powershell\nnode dist/cli.js run examples/broken.kdl\n```\n\n```\nParseError [line 6]: Expected ';' after variable declaration (got 'let')\nParseError [line 8]: Expected ')' after parameters (got '{')\n\n2 syntax errors found. Not running.\n```\n\nLex errors (unterminated strings, unknown characters) still stop at\nthe first one — a broken token stream makes \"keep scanning past it\"\nproduce misleading cascading garbage, so single-shot reporting is more\nhonest there. Runtime errors also stop execution immediately — there's\nno meaningful way to \"recover\" from a program that's already halfway\nthrough running.\n\n### `kindling repl`\n\n```\n$ kindling repl\nKindling REPL — type Kindling code, or .exit to quit.\nVariables persist across lines. Ctrl+D also exits.\n\n> let x = 5;\n> print(x);\n5\n> x = x + 1;\n> print(x);\n6\n> let = broken syntax !!;\nParseError [line 1]: Expected variable name after 'let' (got '=')\n> print(x);\n6\n> .exit\n\nGoodbye.\n```\n\n### Checkpoints (all verified)\n\n- ✅ Fresh clone → `npm install` → `npm run build` →\n  `node dist/cli.js run examples/fibonacci.kdl` works with no extra setup\n- ✅ Full test suite passes (`npm test`)\n- ✅ REPL correctly persists variables across lines and reports clean\n  errors on bad input without ending the session\n\n---\n\n## Language reference\n\nA quick tour of Kindling's syntax, all of it demonstrated across\n`examples/*.kdl`.\n\n```kindling\n// Variables\nlet x = 5;\nlet name = \"Kindling\";\nlet flag = true;\nlet nothing = nil;\n\n// Arithmetic: + - * / %  (+ also concatenates strings)\nlet sum = 1 + 2 * 3;\n\n// Comparison & equality: < > <= >= == !=\n// Logical (short-circuiting): and, or, unary !\nif (x > 0 and x < 10) {\n  print(\"in range\");\n}\n\n// Loops\nwhile (x > 0) {\n  x = x - 1;\n}\nfor (let i = 0; i < 5; i = i + 1) {\n  print(i);\n}\n\n// Functions & closures\nfunction add(a, b) {\n  return a + b;\n}\nprint(add(2, 3));\n\n// Blocks are their own scope\n{\n  let scoped = \"only visible in here\";\n}\n```\n\n**Built-in (native) functions:** `print(value)`, `clock()` (seconds\nsince epoch, useful for timing).\n\n**Truthiness:** only `false` and `nil` are falsy. Everything else —\nincluding `0` and `\"\"` — is truthy.\n\n---\n\n## Project layout\n\n```\npackage.json\ntsconfig.json\nvitest.config.ts\n.gitignore\nREADME.md\nPROGRESS.md\n\nsrc/\n  token.ts          Token types + keyword table\n  errors.ts          KindlingError / LexError / ParseError / RuntimeError\n  lexer.ts           Source text -> tokens\n  ast.ts             Expr / Stmt tagged unions\n  parser.ts          Tokens -> AST (recursive descent + Pratt parsing,\n                      plus Part 3 error recovery)\n  environment.ts      Scope-chain variable storage\n  interpreter.ts      AST -> execution (evaluator, closures, natives)\n  inspect.ts          Debug CLI: dump tokens/AST, or --run a file\n  repl.ts             Interactive shell (Part 3)\n  cli.ts              Real entry point: `kindling run` / `kindling repl`\n\ntests/\n  lexer.test.ts\n  parser.test.ts\n  interpreter.test.ts\n  repl.test.ts               (Part 3)\n  parser-recovery.test.ts    (Part 3)\n  cli.test.ts                (Part 3)\n\nexamples/\n  hello.kdl\n  closures.kdl\n  fibonacci.kdl\n  broken.kdl          (Part 3 — demonstrates multi-error recovery)\n```\n\n---\n\n## Test suite\n\n```powershell\nnpm test              # run once\nnpm run test:watch    # watch mode\n```\n\n79 tests across 6 files, covering:\n\n- **Lexer** — multi-char operators, string escapes, unterminated-string\n  errors, number/identifier scanning.\n- **Parser** — operator precedence tree shape, unary minus, nested\n  calls, and (Part 3) multi-error recovery with correct line numbers.\n- **Evaluator** — arithmetic, block scoping/shadowing, the closure\n  counter proof, recursion.\n- **Errors** — undefined variable → `RuntimeError` with correct line\n  number; unterminated string → `LexError`.\n- **REPL** (Part 3) — variable/closure persistence across separate\n  lines; errors don't end the session.\n- **CLI** (Part 3) — end-to-end child-process smoke tests: successful\n  run, missing file, no subcommand, runtime error with line number,\n  multi-error syntax reporting.\n\n---\n\n## Design notes\n\n### Why recursive descent, not a parser generator\n\nThe statement grammar (`if`, `while`, `for`, `function`, `let`, …) is\nhand-written recursive descent: one method per grammar rule, named\nafter the production it parses. For a small, fixed, C-like grammar,\nthis is more transparent than a generator-produced parser — every rule\nis a readable TypeScript function you can step through in a debugger,\nand adding a new statement type is \"add one more `if (this.match(...))`\nbranch and one more method,\" no build step or generated-code layer\ninvolved.\n\n### Why Pratt parsing for expressions\n\nExpressions use Pratt parsing (precedence climbing) instead of one\nrecursive-descent function per precedence level. The naive alternative\n— `parseEquality` calling `parseComparison` calling `parseTerm` calling\n`parseFactor` calling `parseUnary`, one function per tier — works, but\nadding an operator means adding a function and threading it by hand\ninto that call chain.\n\nPratt parsing collapses this into one function,\n`parseExpression(minPrecedence)`, driven by a single precedence table.\nAdding an operator means adding one table entry. The core loop:\n\n1. Parse a prefix expression (literal, unary op, parenthesized group,\n   variable) to get a left-hand side.\n2. Peek at the next token. If it's a binary operator whose precedence\n   clears the current `minPrecedence`, consume it, recursively parse\n   its right-hand side at `precedence + 1`, and fold the result into a\n   new `left`.\n3. Repeat until the next operator's precedence is too low (or there\n   isn't one) — then return `left`.\n\nThis is exactly why `2 + 3 * 4` correctly nests `3 * 4` as the right\nchild of `+`: `*` binds tighter, so when the loop is sitting on `+`\nwith a `minPrecedence` that `*` clears, it recurses in and consumes\n`3 * 4` as one unit before control returns to the outer `+`.\n\n### Proof: closures capture their defining environment, not the call site\n\nSee the closure proof in [Part 2](#the-closure-proof) above. The\nmechanism lives in `MiniFunction` (`src/interpreter.ts`): `this.closure\n= closure` is set once, at construction, to whatever `Environment` was\nactive when the `function` statement was evaluated. Every call creates\na new `Environment` chained to that captured closure — not to the\nenvironment of wherever the call happens to occur.\n\n### Why `return` is a thrown signal\n\n`ReturnSignal` (`src/interpreter.ts`) is an internal class thrown by\n`execute()` on a `Return` statement, caught in exactly one place:\n`MiniFunction.call`. A `return` inside deeply nested `if`/`while`/block\nstatements needs to unwind all the way back to the function-call\nboundary; throwing is the simplest way to do that without every\nstatement-executing function manually checking \"did the last thing I\nran signal a return?\" and propagating that check up by hand.\n\n### Why only `parseProgram()` recovers, not `parse()`\n\n`parse()` keeps its exact Part 1/2 behavior — throw on the first\n`ParseError` — so nothing that already depends on that contract (the\nPart 1/2 test suite, `inspect.ts`) needed to change. `parseProgram()`\nis strictly additive: opt in when you want every syntax error in one\npass (which is what `cli.ts`'s `kindling run` does).","readmeFilename":"README.md"}