zh CN Structural Slice - chiba233/yume-dsl-token-walker GitHub Wiki
只重新解析大文档中的一个区域,而不是全文。parseStructural 给你地图;parseSlice 让你跳到地图上的任何一点,拿到带完整位置信息的
TextToken[]。
什么时候用:
- 用户在 200 KB 文档里编辑了一个标签——只有那个标签需要重解析
- 你想渲染特定区域而不需要为全文解析付代价
- 你需要
TextToken[]的位置信息指向原始文档
当上游启用 implicit inline shorthand 时,parseSlice 具备 shorthand 感知回退:
若直接按 span 解析在隐式简写 inline 区间退化为纯文本回显,会在可用时回退重解析其父级包围标签 span。
完整管线:结构扫描 → 找到编辑区域 → 只重解析那个区域 → 解释:
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 | 有 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 的开销随切片大小增长,不随文档大小增长。
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 派生。
interface ParserLike {
parse: (input: string, overrides?: ParseOverrides) => TextToken[];
structural?: (input: string, overrides?: ParseOverrides) => StructuralNode[];
}yume-dsl-rich-text 的 createParser(...) 返回的对象满足这个接口。
interface ParseOverrides {
trackPositions?: boolean;
baseOffset?: number;
tracker?: PositionTracker;
}