zh CN Interpret - chiba233/yume-dsl-token-walker GitHub Wiki

解释 API

结构查询 | Lint | 首页

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 成纯文本)

30 秒上手

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>!"

interpretTextparser.parse(input) + interpretTokens(...) 的语法糖。 如果你已经有 TextToken[],直接用 interpretTokens


你的 interpret 函数返回什么

每次你的 interpret 被调用,你需要返回一个 InterpretResult。一共五种:

返回值 意思 什么时候用
{ type: "nodes", nodes: [...] } yield 这些节点 大多数情况——包裹子节点、加标签
{ type: "text", text: "..." } yield 一个文本节点 输出一段特定文本,不递归子节点
{ type: "flatten" } 把 token.value 展平成纯文本后 yield 搜索索引、aria label、纯文本预览
{ type: "drop" } 什么都不 yield 注释、元数据等不产生输出的标签
{ type: "unhandled" } 交给 onUnhandled 策略 你不认识这个标签

nodes 里用 helpers.interpretChildren 递归

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"};
},

flattenhelpers.flattenText 的区别

  • { 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}]`};
}

Demo:三种输出类型

Demo 1:输出 HTML 字符串

最常见的场景——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>'

Demo 2:输出自定义 AST 节点

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));

Demo 3:输出纯文本(搜索索引 / 预览)

不需要 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:注入运行时上下文

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 是只读的运行时配置。如果你需要累加器或计数器,用闭包外部的变量。


onUnhandled 策略

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 收集成数组

异步 API

handler 里需要 await?请看独立的 异步解释 API 页面——完整 demo、设计要点、辅助函数、类型参考。


推荐项目结构

小项目——写在一个文件里

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("");

中型项目——handler 分文件

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

完整签名参考

interpretText

function* interpretText<TNode, TEnv>(
    input: string,
    parser: ParserLike,
    ruleset: InterpretRuleset<TNode, TEnv>,
    env: TEnv,
): Generator<TNode>;

ParserLike 是任何有 parse(input, overrides?) 方法的对象。createParser(...) 返回的就是。

interpretTokens

function* interpretTokens<TNode, TEnv>(
    tokens: TextToken[],
    ruleset: InterpretRuleset<TNode, TEnv>,
    env: TEnv,
): Generator<TNode>;

InterpretRuleset

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;
}

InterpretHelpers

interface InterpretHelpers<TNode, TEnv = unknown> {
    interpretChildren: (value: string | TextToken[]) => Iterable<TNode>;
    flattenText: (value: string | TextToken[]) => string;
    env: TEnv;
}
⚠️ **GitHub.com Fallback** ⚠️