You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JavaScript中构造树模式匹配算法

核心实现思路

你要做的模式树匹配本质是带量词的树结构正则匹配,核心设计思路是「返回消耗长度的递归匹配」:每个匹配函数不直接返回布尔值,而是返回{ matched: boolean, consumed: number }结构,matched标识是否匹配成功,consumed标识当前匹配在当前层级消耗了多少个实际节点,这样无需维护全局遍历状态,天然支持回溯,也能同时跟踪模式树和实际树的遍历进度。


前置约定

我们统一使用JSON结构的树做实现,class节点依赖的外部模式统一存在全局patternRegistry对象中,key为class的name属性,value为对应的模式树。


完整可运行代码

入口与通用分发函数

// 全局注册class对应模式树
const patternRegistry = {}

/**
 * 入口函数:校验实际树是否匹配指定模式树
 * @param actualTree 待校验的实际树根节点
 * @param patternTree 模式树根节点
 * @returns 是否匹配成功
 */
function matchTree(actualTree, patternTree) {
  const result = matchNode(patternTree, [actualTree], 0)
  // 要求匹配成功且消耗完所有实际节点
  return result.matched && result.consumed === 1
}

/**
 * 通用匹配分发函数
 * @param patternNode 模式树当前节点
 * @param actualSiblings 实际树当前层级的兄弟节点数组
 * @param startIndex 当前层级开始匹配的下标
 * @returns 匹配结果与消耗节点数
 */
function matchNode(patternNode, actualSiblings, startIndex) {
  switch(patternNode.type) {
    case 'text': return matchText(patternNode, actualSiblings, startIndex)
    case 'sequence': return matchSequence(patternNode, actualSiblings, startIndex)
    case 'optional': return matchOptional(patternNode, actualSiblings, startIndex)
    case 'first': return matchFirst(patternNode, actualSiblings, startIndex)
    case 'interlace': return matchInterlace(patternNode, actualSiblings, startIndex)
    case 'class':
      const delegatePattern = patternRegistry[patternNode.name]
      if (!delegatePattern) throw new Error(`未注册的class模式:${patternNode.name}`)
      return matchNode(delegatePattern, actualSiblings, startIndex)
    default: return { matched: false, consumed: 0 }
  }
}

各类型节点匹配逻辑

1. text节点

匹配当前节点标签,再递归匹配子节点:

function matchText(patternNode, actualSiblings, startIndex) {
  if (startIndex >= actualSiblings.length) return { matched: false, consumed: 0 }
  const actualNode = actualSiblings[startIndex]
  // 有value则校验标签,无value则任意标签都匹配(对应通配<text/>)
  if (patternNode.value && actualNode.tag !== patternNode.value) {
    return { matched: false, consumed: 0 }
  }
  // 匹配子节点
  let childConsumed = 0
  const actualChildren = actualNode.children || []
  const patternChildren = patternNode.children || []
  for (const childPattern of patternChildren) {
    const childRes = matchNode(childPattern, actualChildren, childConsumed)
    if (!childRes.matched) return { matched: false, consumed: 0 }
    childConsumed += childRes.consumed
  }
  // 子节点必须全部消耗完才算匹配成功
  if (childConsumed !== actualChildren.length) return { matched: false, consumed: 0 }
  return { matched: true, consumed: 1 }
}

2. sequence节点

循环匹配子模式序列,直到匹配失败为止,支持匹配0次或多次:

function matchSequence(patternNode, actualSiblings, startIndex) {
  let totalConsumed = 0
  let currentPos = startIndex
  while (currentPos < actualSiblings.length) {
    let roundConsumed = 0
    // 匹配一轮完整的子模式序列
    for (const childPattern of patternNode.children) {
      const res = matchNode(childPattern, actualSiblings, currentPos + roundConsumed)
      if (!res.matched) {
        // 本轮匹配失败,返回之前已匹配的长度
        return { matched: true, consumed: totalConsumed }
      }
      roundConsumed += res.consumed
    }
    totalConsumed += roundConsumed
    currentPos += roundConsumed
  }
  return { matched: true, consumed: totalConsumed }
}

3. optional节点

尝试匹配一次子模式,失败则消耗0个节点,永远匹配成功:

function matchOptional(patternNode, actualSiblings, startIndex) {
  const res = matchNode(patternNode.children[0], actualSiblings, startIndex)
  return res.matched ? res : { matched: true, consumed: 0 }
}

4. first节点

逐个尝试子模式,返回第一个匹配成功的结果,全部失败则匹配失败:

function matchFirst(patternNode, actualSiblings, startIndex) {
  for (const childPattern of patternNode.children) {
    const res = matchNode(childPattern, actualSiblings, startIndex)
    if (res.matched) return res
  }
  return { matched: false, consumed: 0 }
}

5. interlace节点

按任意顺序匹配所有子模式(每个子模式匹配1次,若需要支持重复匹配可移除usedPatterns标记):

function matchInterlace(patternNode, actualSiblings, startIndex) {
  const usedPatterns = new Array(patternNode.children.length).fill(false)
  let currentPos = startIndex
  let matchedCount = 0
  while (matchedCount < patternNode.children.length && currentPos < actualSiblings.length) {
    let found = false
    for (let i = 0; i < patternNode.children.length; i++) {
      if (usedPatterns[i]) continue
      const res = matchNode(patternNode.children[i], actualSiblings, currentPos)
      if (res.matched) {
        usedPatterns[i] = true
        currentPos += res.consumed
        matchedCount++
        found = true
        break
      }
    }
    if (!found) break
  }
  return matchedCount === patternNode.children.length 
    ? { matched: true, consumed: currentPos - startIndex }
    : { matched: false, consumed: 0 }
}

使用示例

// 注册你给出的简单sequence模式
patternRegistry.fooBarSeq = {
  "type": "sequence",
  "children": [
    {
      "type": "text",
      "value": "foo",
      "children": [ { "type": "text", "value": "bar" } ]
    }
  ]
}

// 测试实际树
const testTree = {
  "tag": "foo",
  "children": [ { "tag": "bar" } ]
}

console.log(matchTree(testTree, patternRegistry.fooBarSeq)) // 输出true

内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.22 06:06:02