zh CN 错误处理 - chiba233/yumeDSL GitHub Wiki

错误处理

源码位置追踪 | Vue 3 渲染

解析器永远不会抛异常。遇到写错的标签?降级成纯文本,照常输出。 对外暴露错误回调的是 parseRichText / stripRichTextparseStructural 的公开 API 故意不承诺错误语义。


错误处理流程

DSL 源文本
    │
    ▼
解析器扫描
    │
    ├─ 标签正常 → TextToken(正常输出)
    │
    └─ 标签有问题 → 降级为纯文本
                    │
                    ├─ 有 onError? → 调用回调,传 ParseError
                    └─ 没有?      → 静默丢弃,继续解析

核心原则:输入再烂也不崩,只是降级。


onError 回调

onError 是渲染层 API 的公开能力,用来收集最终解释后的错误语义:

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"
// }

如果省略 onError:错误被静默丢弃。解析器永远不会抛出异常。

stripRichText("$$bold(unclosed", {
    onError: (error) => console.log(error.code),
});
// INLINE_NOT_CLOSED

为什么 parseStructural 没有公开 onError

parseStructural 的职责是给你结构视图,不是给你最终的错误语义。

  • parseStructural 负责“源码里长什么样”
  • parseRichText / stripRichText 负责“最终怎么解释、怎么降级、该报什么错”

所以 parseStructural 的公开 options 里故意没有 onError
内部扫描阶段确实会经过错误通道,但那是给上层 API 复用的内部实现细节,不是 public contract。


ParseError 接口

interface ParseError {
    code: ErrorCode;
    message: string;
    line: number;
    column: number;
    snippet: string;
}
字段 什么意思
code 错误码(ErrorCode 联合类型)。程序逻辑应该基于 code 分支,适合穷举 switch
message 人类可读的错误描述,带 (L1:C1) 前缀和 >>>...<<< 标记。主要给人看,格式可能在未来版本调整,不要解析它
line / column 从 1 开始。开了 trackPositions 时复用同一张行偏移表
snippet 错误周围的上下文片段,>>> <<< 标出问题范围

错误码

type ErrorCode =
    | "DEPTH_LIMIT"
    | "UNEXPECTED_CLOSE"
    | "INLINE_NOT_CLOSED"
    | "SHORTHAND_NOT_CLOSED"
    | "BLOCK_NOT_CLOSED"
    | "BLOCK_CLOSE_MALFORMED"
    | "RAW_NOT_CLOSED"
    | "RAW_CLOSE_MALFORMED";
错误码 什么情况 触发示例
DEPTH_LIMIT 嵌套太深,超过 depthLimit 51 层 $$a($$b(...
UNEXPECTED_CLOSE 游离的关闭标签,找不到匹配的开标签。若 )$$ 后紧跟合法标签头则不报(1.3.7 起) 单独出现的 )$$
INLINE_NOT_CLOSED inline 标签开了但没关 $$bold(unclosed
SHORTHAND_NOT_CLOSED 隐式 inline 简写开了但没关(1.3 起) 启用 implicitInlineShorthand 时的 bold(unclosed
BLOCK_NOT_CLOSED block 标签缺少关闭标记 $$info()* 有内容但没有 *end$$
BLOCK_CLOSE_MALFORMED block 关闭标记在但格式不对 *end$$ 没有独占一行
RAW_NOT_CLOSED raw 标签缺少关闭标记 $$code()% 有内容但没有 %end$$
RAW_CLOSE_MALFORMED raw 关闭标记在但格式不对 %end$$ 没有独占一行

优雅降级

解析器永远不会在格式错误的输入上抛异常。不同场景的降级方式不同:

未注册标签

降级行为取决于标签形式:

  • Inline $$unknown(hi)$$ → 只输出内部内容 hi(标签定界符被去掉,内容作为纯文本保留)
  • Raw/Block $$unknown()%\nstuff\n%end$$ → 整段原始标记作为字面文本输出(解析器根本不识别这个标签,所以所有定界符原样保留)
// inline:内容被展开
dsl.parse("$$unknown(hello)$$");
// → [{ type: "text", value: "hello" }]

// raw/block:整段标记原样保留
dsl.parse("$$unknown()%\nstuff\n%end$$");
// → [{ type: "text", value: "$$unknown()%\nstuff\n%end$$" }]

不支持的形式

Handler 存在但不支持用户写的形式时(比如 handler 只有 inline,用户写了 raw 形式),整段原始标记作为字面文本输出

// code handler 只有 raw,用户写了 inline
dsl.parse("$$code(ts)$$");
// → [{ type: "text", value: "$$code(ts)$$" }]

allowForms 限制

allowForms 禁用的形式等同于不支持——按不支持形式处理,整段原始标记作为字面文本输出。

未关闭标签

解析器报告错误(通过 onError),开标记作为纯文本恢复。不抛出异常。


1.1.1 之后的错误语义整理

parseRichText 移除 4 个误报

parseRichText 之前在遇到 complex form 标签(raw/block 语法)的某些退化场景时, 会错误地上报 INLINE_NOT_CLOSED。这 4 个误报已移除——正确走 complex form 退化路径的标签不再触发错误。

1.1.3 以及当前版本,这些复杂形态错误码已经稳定下来,例如:

  • $$raw-code(js)%\nconst x = 1RAW_NOT_CLOSED
  • $$info(T)*\nhelloBLOCK_NOT_CLOSED
  • $$raw-code(ts)%\nconst x = 1\n%end$$ trailingRAW_CLOSE_MALFORMED
  • $$info()*\nhello\n*end$$ trailingBLOCK_CLOSE_MALFORMED