Skip to content

Leniency and Strict Mode

Lanexio™ Parser is a lenient parser by default. Every grammar entry point recovers from malformed input instead of throwing, producing LexError nodes in the tree. Four grammar surfaces also expose a strict parse option that surfaces spec-level rejections as flags on the tree: CSV/TSV, TOML, and XML via mode, and the tsql dialect of SQL via clientBatch. This page is the policy reference; the per-grammar guides show the option in each entry point’s shape.

The mode option takes "lenient" (the default) or "strict". Since ADR 0044, the { strict: true } alias is accepted as a convenience spelling: it maps onto mode: "strict" and never replaces it. The two spellings resolve to the same value through one resolver, so they cannot behave differently. Both spellings are accepted in two shapes each:

// Direct shape on the grammar entry point
parseCsv(bytes, { mode: 'strict' });
parseXml(bytes, { mode: 'strict' });
parseToml(bytes, { mode: 'strict' });
parseCsv(bytes, { strict: true });
parseXml(bytes, { strict: true });
parseToml(bytes, { strict: true });
// Unified shape through the registry dispatcher
parse(src, { language: 'csv', grammarOptions: { mode: 'strict' } });
parse(src, { language: 'xml', grammarOptions: { mode: 'strict' } });
parse(src, { language: 'toml', grammarOptions: { mode: 'strict' } });
parse(src, { language: 'csv', strict: true });
parse(src, { language: 'xml', strict: true });
parse(src, { language: 'toml', strict: true });

Precedence is mode > strict > default lenient. An explicit mode always wins, including over a contradictory strict: { mode: "lenient", strict: true } parses lenient and { mode: "strict", strict: false } parses strict. strict: false is the explicit lenient spelling, so callers who want to be explicit about leniency can say so. An absent or non-boolean strict falls back to the default lenient mode.

ParseMode ("lenient" | "strict") is a shared primitive in parser-core (ADR 0033), and pickParseMode reads the options structurally, so an unknown mode value is treated exactly like lenient.

Beyond mode and strict, the unified parse() entry point accepts three more grammar pass-through convenience keys, all resolved by the same merge rule (ADR 0048): validate (XML DTD validation), clientBatch (the tsql boundary modes in the table below), and spec (TOML version alias). An explicit grammarOptions.<key> always wins over the top-level key of the same name. When grammarOptions is absent, a set top-level key synthesizes { grammarOptions: { key } }. When grammarOptions is present, the merged bag is a shallow copy and never mutates the caller’s options object. The SQL grammar’s dialect key follows the same rule, and an explicit grammarOptions.mode still wins over the strict convenience key at pickParseMode.

Strict mode never throws. A rejected construct is a flag on the tree (hasError on the node, and on the document root where the grammar documents it), never an exception. Lenient output is byte-identical to previous releases.

GrammarLenient accepts (recovers)Strict rejects (flags)
CSV / TSVunclosed quotes, uneven column countsunclosed quotes and records whose field count differs from the reference row (header row when header is true, otherwise the first record)
XMLunbound namespace prefixes, DTD validity gapsunbound element/attribute prefixes (unconditional namespace-prefix well-formedness) and internal-subset DTD violations (VC Root Element Type, element content models, attribute constraints) when an internal subset is present
TOMLTOML v1.1.0 constructs (the v1.1 default grammar)TOML 1.1-only constructs (seconds-less datetimes/times, inline-table newlines and trailing commas, \xHH byte escapes) by defaulting to the 1.0.0 rule set when no explicit version is given; an explicit version always wins
SQL (tsql)a batch-boundary GO and a sqlcmd :directive line, accepted by defaultclientBatch: "reject" flags each boundary with an Error node while later statements still materialize; clientBatch: "split" emits first-class SqlKind.BatchBoundary and SqlKind.ClientDirective leaves so a loader can slice one file into batches. The surface is tsql-only: the other dialects ignore clientBatch and strict, and strict: true maps to "reject" when clientBatch is absent

Grammars without a strict profile (HTML, Markdown, MDX, JSON/JSONC/JSON5, YAML, CSS, GraphQL) run in lenient mode only: malformed input produces error leaves, and the parse path never throws. SQL is deliberately absent from that list: the tsql clientBatch boundary is a strict surface (see the table above), so SQL is not lenient-only. Duplicate-key strictness for YAML, external-DTD resolution and content-model/standalone-VC coverage for XML, and a strict-mode profile for the remaining grammars are roadmap candidates, not 1.0 commitments (ADR 0043).

Leniency is the recovery contract that keeps editors and tools responsive: malformed input becomes a tree with error nodes instead of a crash or a partial result. The cost is that some invalid documents parse clean. Strict mode is the conformance lens: it trades some recovery for spec-level flagging when a consumer (a linter, a validator, a test harness) needs to reject marginal input. XML’s conformance contract in particular is the mode: "strict" profile (ADR 0041).

The mode design is recorded in ADR 0033 (per-grammar strict / lenient modes), the v1.0 niche and roadmap in ADR 0043, the { strict: true } alias in ADR 0044, and the top-level convenience-key merge convention in ADR 0048, all under docs/decisions/.