zh CN Structural Slice - chiba233/yume-dsl-token-walker GitHub Wiki

结构切片

Lint | 导出一览 | 首页

只重新解析大文档中的一个区域,而不是全文。parseStructural 给你地图;parseSlice 让你跳到地图上的任何一点,拿到带完整位置信息的 TextToken[]

什么时候用:

  • 用户在 200 KB 文档里编辑了一个标签——只有那个标签需要重解析
  • 你想渲染特定区域而不需要为全文解析付代价
  • 你需要 TextToken[] 的位置信息指向原始文档

当上游启用 implicit inline shorthand 时,parseSlice 具备 shorthand 感知回退: 若直接按 span 解析在隐式简写 inline 区间退化为纯文本回显,会在可用时回退重解析其父级包围标签 span。


Demo:增量编辑管线

完整管线:结构扫描 → 找到编辑区域 → 只重解析那个区域 → 解释:

import {createParser, createSimpleInlineHandlers, buildPositionTracker} from "yume-dsl-rich-text";
import {parseSlice, interpretTokens, collectNodes} from "yume-dsl-token-walker";

const parser = createParser({
    handlers: createSimpleInlineHandlers(["bold", "italic"]),
});

const fullText = "intro\n$$bold(hello $$italic(world)$$)$$\noutro";

// 1. 预扫描:快速结构化 pass + 位置追踪
const structural = parser.structural(fullText, {trackPositions: true});

// 2. 构建 tracker——只需一次,所有切片复用
const tracker = buildPositionTracker(fullText);

// 3. 选一个节点,只解析那个区域
const boldNode = structural.find((n) => n.type === "inline" && n.tag === "bold");

if (boldNode?.position) {
    const tokens = parseSlice(fullText, boldNode.position, parser, tracker);
    // tokens 的 offset/line/column 都指向 fullText

    // 4. 照常解释
    const html = collectNodes(
        interpretTokens(tokens, {
            createText: (t) => t,
            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("");
}

有 tracker vs. 没 tracker

没 tracker 有 tracker
offset 正确(加了 baseOffset 正确
line 局部的(从 1 开始) 正确——指向原始文档
column 局部的 正确——指向原始文档

buildPositionTracker(fullText) 构建一次就够。不要每次切片都重建——只有文档的换行发生变化(插入或删除行)时才需要重建。


性能

在 ~200 KB 文档上测量(210K 字符,2562 token,1281 个真实标签节点)。 环境:鲲鹏 920 aarch64 / Node v24.14.0——3 轮 x 5 次迭代取平均。

步骤 耗时 备注
全量 parseRichText ~24 ms 200 KB 的完整 handler 管线
全量 parseStructural + 位置追踪 ~31 ms 与 parseRichText 相当;仅结构扫描
nodeAtOffset(定位) ~0.14 ms 遍历缓存的结构树
parseSlice(增量) ~0.025 ms 只解析被编辑的 36 字符节点
buildPositionTracker(重建) ~1.06 ms 只在换行变化时需要

增量路径(定位 + 切片)= ~0.17 ms——只重解析命中的局部区域。 全量 parseRichText 本身已经很快(200 KB ≈ 24 ms);parseSlice 适用于逐键实时编辑场景——只重新解析被编辑的节点,而不是每次都扫描整篇文档。 parseSlice 的开销随切片大小增长,不随文档大小增长。


API 参考

parseSlice(fullText, span, parser, tracker?, fullTree?)

const parseSlice: (
    fullText: string,
    span: SourceSpan,
    parser: ParserLike,
    tracker?: PositionTracker,
    fullTree?: StructuralNode[],
) => TextToken[];
参数 说明
fullText 完整源码文本
span 要解析的区域——通常是 StructuralNode.position
parser parse(input, overrides?) 方法的解析器
tracker 可选。来自 buildPositionTracker(fullText) 的 tracker,用于正确的 line/column
fullTree 可选。用于 shorthand 回退优化的预计算结构树

位置追踪始终启用。baseOffset 自动从 span.start.offset 派生。

ParserLike

interface ParserLike {
    parse: (input: string, overrides?: ParseOverrides) => TextToken[];
    structural?: (input: string, overrides?: ParseOverrides) => StructuralNode[];
}

yume-dsl-rich-textcreateParser(...) 返回的对象满足这个接口。

ParseOverrides

interface ParseOverrides {
    trackPositions?: boolean;
    baseOffset?: number;
    tracker?: PositionTracker;
}
⚠️ **GitHub.com Fallback** ⚠️