en Interpret - chiba233/yume-dsl-token-walker GitHub Wiki
Structural Query | Lint | Home
Interpret 是 token-walker 的核心能力:把 TextToken[] 树变成你想要的任何输出类型。
HTML 字符串、React VNode、Vue render function、游戏引擎指令、纯文本——都行。
你定义 TNode 是什么,interpret 就 yield 什么。
你的 DSL 文本
│
▼
yume-dsl-rich-text 解析
│
▼
TextToken[] ←── 这是你拿到的东西
│
▼
interpretTokens(tokens, ruleset, env)
│
├─ token.type === "text" → ruleset.createText(token.value)
│
└─ token.type === "bold" → ruleset.interpret(token, helpers)
│
▼
你返回 InterpretResult
{ type: "nodes", nodes: [...] }
│
▼
Generator<TNode> ── 逐个 yield,不缓冲
关键设计:
-
interpretTokens是一个Generator——节点逐个 yield,不会一次性塞进数组。你可以流式消费上千 token 而不需要缓冲 -
type: "text"的 token 自动走createText,不经过interpret——你只需要处理非文本标签 - 如果
interpret返回{ type: "unhandled" },走onUnhandled策略(默认:flatten 成纯文本)
import { createParser, createSimpleInlineHandlers } from "yume-dsl-rich-text";
import { interpretText, collectNodes } from "yume-dsl-token-walker";
// 1. 建 parser
const parser = createParser({
handlers: createSimpleInlineHandlers(["bold", "italic"]),
});
// 2. 一行搞定:解析 + 解释 + 收集
const html = collectNodes(
interpretText("Hello $$bold(world)$$!", parser, {
createText: (text) => text,
interpret: (token, helpers) => {
if (token.type === "bold")
return { type: "nodes", nodes: ["<b>", ...helpers.interpretChildren(token.value), "</b>"] };
if (token.type === "italic")
return { type: "nodes", nodes: ["<em>", ...helpers.interpretChildren(token.value), "</em>"] };
return { type: "unhandled" };
},
}, undefined),
).join("");
// → "Hello <b>world</b>!"interpretText 是 parser.parse(input) + interpretTokens(...) 的语法糖。
如果你已经有 TextToken[],直接用 interpretTokens。
每次你的 interpret 被调用,你需要返回一个 InterpretResult。一共五种:
| 返回值 | 意思 | 什么时候用 |
|---|---|---|
{ type: "nodes", nodes: [...] } |
yield 这些节点 | 大多数情况——包裹子节点、加标签 |
{ type: "text", text: "..." } |
yield 一个文本节点 | 输出一段特定文本,不递归子节点 |
{ type: "flatten" } |
把 token.value 展平成纯文本后 yield | 搜索索引、aria label、纯文本预览 |
{ type: "drop" } |
什么都不 yield | 注释、元数据等不产生输出的标签 |
{ type: "unhandled" } |
交给 onUnhandled 策略 | 你不认识这个标签 |
helpers.interpretChildren(token.value) 返回一个 Iterable<TNode>——它会递归解释子 token 树。
用展开运算符 ...helpers.interpretChildren(token.value) 把子节点嵌进你的输出:
interpret: (token, helpers) => {
if (token.type === "bold") {
return {
type: "nodes",
nodes: ["<b>", ...helpers.interpretChildren(token.value), "</b>"],
};
}
return { type: "unhandled" };
},-
{ type: "flatten" }—— 让引擎自动展平token.value,结果经过createText包装 -
helpers.flattenText(token.value)—— 在 handler 里手动拿到纯文本字符串,你自己决定怎么用
// 用 flattenText 做搜索标签
if (token.type === "searchable") {
const plain = helpers.flattenText(token.value);
return { type: "text", text: `[SEARCH:${plain}]` };
}最常见的场景——token 树变成 HTML:
import { createParser, createSimpleInlineHandlers, createPipeHandlers } from "yume-dsl-rich-text";
import { interpretText, collectNodes, createRuleset, fromHandlerMap } from "yume-dsl-token-walker";
const parser = createParser({
handlers: {
...createSimpleInlineHandlers(["bold", "italic", "underline"]),
...createPipeHandlers({
link: {
inline: (args, ctx) => ({
type: "link",
url: args.text(0),
value: args.materializedTailTokens(1),
}),
},
}),
},
});
const ruleset = createRuleset<string, void>({
createText: (text) => text,
interpret: fromHandlerMap({
bold: (token, h) => ({ type: "nodes", nodes: ["<b>", ...h.interpretChildren(token.value), "</b>"] }),
italic: (token, h) => ({ type: "nodes", nodes: ["<em>", ...h.interpretChildren(token.value), "</em>"] }),
underline: (token, h) => ({ type: "nodes", nodes: ["<u>", ...h.interpretChildren(token.value), "</u>"] }),
link: (token, h) => ({
type: "nodes",
nodes: [`<a href="${token.url}">`, ...h.interpretChildren(token.value), "</a>"],
}),
}),
onUnhandled: "flatten",
});
const input = "Hello $$bold($$italic(world)$$)$$ - $$link(https://example.com | click)$$";
const html = collectNodes(interpretText(input, parser, ruleset, undefined)).join("");
// → 'Hello <b><em>world</em></b> - <a href="https://example.com">click</a>'TNode 不必是 string。可以是你自己定义的 AST、VNode、或任何类型:
type HtmlNode =
| { kind: "text"; value: string }
| { kind: "element"; tag: string; attrs?: Record<string, string>; children: HtmlNode[] };
const ruleset = createRuleset<HtmlNode, void>({
createText: (text) => ({ kind: "text", value: text }),
interpret: fromHandlerMap({
bold: (token, h) => ({
type: "nodes",
nodes: [{ kind: "element", tag: "strong", children: Array.from(h.interpretChildren(token.value)) }],
}),
link: (token, h) => ({
type: "nodes",
nodes: [{
kind: "element",
tag: "a",
attrs: { href: (token.url as string) ?? "#" },
children: Array.from(h.interpretChildren(token.value)),
}],
}),
}),
});
// 拿到的是 HtmlNode[],不是字符串——你可以 serialize 成 HTML,也可以交给 React/Vue 渲染
const nodes = collectNodes(interpretTokens(tokens, ruleset, undefined));不需要 ruleset——flattenText 是独立工具函数:
import { flattenText } from "yume-dsl-token-walker";
const tokens = parser.parse("Hello $$bold($$italic(world)$$)$$ - $$link(https://example.com | click)$$");
const plain = flattenText(tokens);
// → "Hello world - click"用于搜索索引、aria label、通知预览、纯文本导出。
env 是你传给 interpretTokens 的第三个参数,在每个 handler 里通过 helpers.env 访问。
用它传入主题、语言、权限、feature flag 等运行时信息:
interface Env {
theme: "light" | "dark";
locale: string;
}
const ruleset = createRuleset<string, Env>({
createText: (text) => text,
interpret: (token, helpers) => {
if (token.type === "bold") {
const color = helpers.env.theme === "dark" ? "#fff" : "#000";
return {
type: "nodes",
nodes: [`<b style="color:${color}">`, ...helpers.interpretChildren(token.value), "</b>"],
};
}
return { type: "unhandled" };
},
});
const html = collectNodes(
interpretTokens(tokens, ruleset, { theme: "dark", locale: "ja" }),
).join("");不要往 env 里塞业务状态。 env 是只读的运行时配置。如果你需要累加器或计数器,用闭包外部的变量。
当 interpret 返回 { type: "unhandled" } 时,引擎按 onUnhandled 策略处理。
默认是 "flatten"——展平成纯文本。
| 策略 | 行为 | 什么时候用 |
|---|---|---|
"flatten" (默认) |
展平成纯文本 | 生产环境——未知标签安静降级 |
"throw" |
直接 throw | 测试环境——漏处理的标签立即暴露 |
"drop" |
什么都不输出 | 需要完全忽略未知标签 |
| function | 自定义处理 | 调试——输出可见占位符 |
// 调试模式:用 debugUnhandled 输出占位符
import { debugUnhandled } from "yume-dsl-token-walker";
const ruleset = createRuleset({
createText: (text) => text,
interpret: fromHandlerMap({ /* 你的 handler */ }),
onUnhandled: process.env.NODE_ENV === "production"
? "flatten"
: debugUnhandled(), // → "[unhandled:someTag]"
});| 函数 | 干什么 | 什么时候用 |
|---|---|---|
createRuleset(ruleset) |
恒等函数,只提供类型推断 | 定义 ruleset 时获得完整 TS 提示 |
fromHandlerMap(handlers) |
把 Record<type, handler> 变成 interpret 函数 |
中大型项目,handler 按标签分文件 |
dropToken |
现成的 { type: "drop" } handler |
注释、元数据等不产生输出的标签 |
unwrapChildren |
现成的"透传子节点"handler | 结构性标签,不需要包装容器 |
wrapHandlers(handlers, wrap) |
给每个 handler 套一层统一的包装 | 所有 block 标签都加 <div> 容器 |
debugUnhandled(format?) |
返回 onUnhandled 函数,输出可见占位符 |
调试模式 |
collectNodes(iterable) |
Array.from 语法糖 |
把 Generator 收集成数组 |
Need await in your handlers? See the dedicated Async Interpret page — full demo, design points, helpers, and type reference.
const html = collectNodes(
interpretTokens(tokens, {
createText: (t) => t,
interpret: (token, h) => {
if (token.type === "bold")
return { type: "nodes", nodes: ["<b>", ...h.interpretChildren(token.value), "</b>"] };
return { type: "unhandled" };
},
}, undefined),
).join("");src/
dsl/
handlers.ts ← handler map
ruleset.ts ← createRuleset + fromHandlerMap
interpret.ts ← 调用 interpretTokens
// handlers.ts
export const handlers = {
bold: (token, h) => ({ type: "nodes", nodes: ["<b>", ...h.interpretChildren(token.value), "</b>"] }),
italic: (token, h) => ({ type: "nodes", nodes: ["<em>", ...h.interpretChildren(token.value), "</em>"] }),
comment: () => ({ type: "drop" }),
};
// ruleset.ts
import { createRuleset, fromHandlerMap, debugUnhandled } from "yume-dsl-token-walker";
import { handlers } from "./handlers";
export const ruleset = createRuleset({
createText: (text) => text,
interpret: fromHandlerMap(handlers),
onUnhandled: process.env.NODE_ENV === "production" ? "flatten" : debugUnhandled(),
});src/
dsl/
parser.ts ← yume-dsl-rich-text 配置
handlers/
inline.ts ← bold, italic, link, ...
block.ts ← info, warning, spoiler, ...
index.ts ← 合并 handler map
ruleset.ts ← createRuleset, env 类型
interpret.ts ← interpretTokens 包装
render/
toHtml.ts ← TNode → HTML string
toPlainText.ts ← flattenText 用于搜索 / 预览
原则:
-
env只放运行时上下文——主题、语言、权限。不放业务状态 - handler 是纯映射——token 进,result 出。副作用放 render 层
- 一种输出格式一个 ruleset——需要 HTML 和纯文本就建两个 ruleset
function* interpretText<TNode, TEnv>(
input: string,
parser: ParserLike,
ruleset: InterpretRuleset<TNode, TEnv>,
env: TEnv,
): Generator<TNode>;ParserLike 是任何有 parse(input, overrides?) 方法的对象。createParser(...) 返回的就是。
function* interpretTokens<TNode, TEnv>(
tokens: TextToken[],
ruleset: InterpretRuleset<TNode, TEnv>,
env: TEnv,
): Generator<TNode>;interface InterpretRuleset<TNode, TEnv = unknown> {
createText: (text: string) => TNode;
interpret: (token: TextToken, helpers: InterpretHelpers<TNode, TEnv>) => InterpretResult<TNode>;
onUnhandled?: UnhandledStrategy<TNode, TEnv>; // 默认 "flatten"
onError?: (context: { error: Error; phase: "interpret" | "flatten" | "traversal" | "internal"; token?: TextToken; position?: SourceSpan; env: TEnv }) => void;
}interface InterpretHelpers<TNode, TEnv = unknown> {
interpretChildren: (value: string | TextToken[]) => Iterable<TNode>;
flattenText: (value: string | TextToken[]) => string;
env: TEnv;
}