en Error Handling - chiba233/yumeDSL GitHub Wiki
Error Handling
Source Position Tracking | Vue 3 Rendering
The parser never throws. Broken tags? Degraded to plain text, output continues.
The public error callback belongs to the render-facing APIs: parseRichText / stripRichText.
parseStructural intentionally does not expose error semantics on its public surface.
How errors flow
DSL source text
│
▼
Parser scans
│
├─ Tag OK → TextToken (normal output)
│
└─ Tag broken → degrade to plain text
│
├─ onError provided? → call it with ParseError
└─ not provided? → silently discard, keep parsing
Core principle: no matter how bad the input, never crash — just degrade.
onError Callback
onError is a public capability of the render pipeline APIs, used to collect final error semantics:
const errors: ParseError[] = [];
parseRichText("$$bold(unclosed", {
onError: (error) => errors.push(error),
});
// errors[0]:
// {
// code: "INLINE_NOT_CLOSED",
// message: "(L1:C1) Inline tag not closed: >>>$$bold(<<< unclosed",
// line: 1,
// column: 1,
// snippet: " >>>$$bold(<<< unclosed"
// }
If onError omitted: errors silently discarded. Parser never throws.
stripRichText("$$bold(unclosed", {
onError: (error) => console.log(error.code),
});
// INLINE_NOT_CLOSED
Why parseStructural has no public onError
parseStructural is the structure view, not the final error-semantics API.
parseStructuralanswers "what raw structure is present in the source?"parseRichText/stripRichTextanswer "how should this be interpreted, degraded, and reported?"
So parseStructural intentionally keeps onError out of StructuralParseOptions.
There is still an internal scan-time error channel for upper-layer APIs to reuse, but that is an implementation detail,
not public contract.
ParseError Interface
interface ParseError {
code: ErrorCode;
message: string;
line: number;
column: number;
snippet: string;
}
| Field | What it is |
|---|---|
code |
Error code (ErrorCode union type). Use code for programmatic branching — exhaustive switch possible |
message |
Human-readable error description with (L1:C1) prefix and >>>...<<< markers. Intended for display, not parsing — the format may change between versions |
line / column |
1-indexed. When trackPositions is active, reuses the same line-offset table |
snippet |
Context around the error, >>> <<< mark the problematic span |
Error Codes
type ErrorCode =
| "DEPTH_LIMIT"
| "UNEXPECTED_CLOSE"
| "INLINE_NOT_CLOSED"
| "SHORTHAND_NOT_CLOSED"
| "BLOCK_NOT_CLOSED"
| "BLOCK_CLOSE_MALFORMED"
| "RAW_NOT_CLOSED"
| "RAW_CLOSE_MALFORMED";
| Code | What happened | Example trigger |
|---|---|---|
DEPTH_LIMIT |
Nesting too deep, exceeded depthLimit | 51 levels of $$a($$b(... |
UNEXPECTED_CLOSE |
Stray close tag with no matching open. Suppressed when )$$ is immediately followed by a valid tag head (since 1.3.7) |
lone )$$ |
INLINE_NOT_CLOSED |
Inline tag opened but never closed | $$bold(unclosed |
SHORTHAND_NOT_CLOSED |
Implicit inline shorthand opened but never closed (since 1.3) | bold(unclosed with implicitInlineShorthand enabled |
BLOCK_NOT_CLOSED |
Block close marker missing | $$info()* content but no *end$$ |
BLOCK_CLOSE_MALFORMED |
Block close exists but wrong format | *end$$ not on its own line |
RAW_NOT_CLOSED |
Raw close marker missing | $$code()% content but no %end$$ |
RAW_CLOSE_MALFORMED |
Raw close exists but wrong format | %end$$ not on its own line |
Graceful Degradation
The parser NEVER throws on malformed input. Different scenarios degrade differently:
Unregistered tags
Degradation depends on the tag form:
- Inline
$$unknown(hi)$$→ outputs only the inner contenthi(delimiters stripped, content kept as plain text) - Raw/Block
$$unknown()%\nstuff\n%end$$→ entire raw markup output as literal text (parser doesn't recognize the tag at all, so all delimiters are preserved)
// inline: content unwrapped
dsl.parse("$$unknown(hello)$$");
// → [{ type: "text", value: "hello" }]
// raw/block: entire markup preserved
dsl.parse("$$unknown()%\nstuff\n%end$$");
// → [{ type: "text", value: "$$unknown()%\nstuff\n%end$$" }]
Unsupported form
When a handler exists but doesn't support the form the user wrote (e.g., handler only has inline, user writes raw form), the entire raw markup is output as literal text:
// code handler only has raw, user writes inline
dsl.parse("$$code(ts)$$");
// → [{ type: "text", value: "$$code(ts)$$" }]
allowForms restriction
Forms disabled by allowForms are treated as unsupported — same behavior as unsupported form, entire raw markup output as literal text.
Unclosed tags
Parser reports error (via onError), opening markup recovered as plain text. No exception thrown.
Error-semantics cleanup after 1.1.1
4 false-positive errors removed from parseRichText
parseRichText previously emitted a spurious INLINE_NOT_CLOSED when encountering
complex-form tags (raw/block syntax) in certain degradation scenarios. These 4 false
positives are removed — the parser no longer reports an error for tags that are correctly
degrading through the complex-form path.
By 1.1.3 and the current version, the complex-form error codes are stable. For example:
$$raw-code(js)%\nconst x = 1→RAW_NOT_CLOSED$$info(T)*\nhello→BLOCK_NOT_CLOSED$$raw-code(ts)%\nconst x = 1\n%end$$ trailing→RAW_CLOSE_MALFORMED$$info()*\nhello\n*end$$ trailing→BLOCK_CLOSE_MALFORMED