code-for-vue-3-book icon indicating copy to clipboard operation
code-for-vue-3-book copied to clipboard

code-15.4-2.html 代码错误

Open zengxiaoluan opened this issue 2 years ago • 0 comments

第一处:parent 会丢失 第二次:因为用了 splice,遍历 children 时,索引应该递减

const children = context.currentNode.children
    if (children) {
      for (let i = 0; i < children.length; i++) {
        context.parent = context.currentNode
        context.childIndex = i
        traverseNode(children[i], context)
      }
    }

修正为:

    const children = context.currentNode.children
    if (children) {
      let len = children.length
      let parent = context.currentNode
      for (let i = len - 1; i >= 0; i--) {
        context.parent = parent
        context.childIndex = i
        traverseNode(children[i], context)
      }
    }

修正的代码应该是:

const State = {
    initial: 1,
    tagOpen: 2,
    tagName: 3,
    text: 4,
    tagEnd: 5,
    tagEndName: 6
  }
  
  function isAlpha(char) {
    return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
  }
  
  function tokenize(str) {
    let currentState = State.initial
    const chars = []
    const tokens = []
    while(str) {
      const char = str[0]
      switch (currentState) {
        case State.initial:
          if (char === '<') {
            currentState = State.tagOpen
            str = str.slice(1)
          } else if (isAlpha(char)) {
            currentState = State.text
            chars.push(char)
            str = str.slice(1)
          }
          break
        case State.tagOpen:
          if (isAlpha(char)) {
            currentState = State.tagName
            chars.push(char)
            str = str.slice(1)
          } else if (char === '/') {
            currentState = State.tagEnd
            str = str.slice(1)
          }
          break
        case State.tagName:
          if (isAlpha(char)) {
            chars.push(char)
            str = str.slice(1)
          } else if (char === '>') {
            currentState = State.initial
            tokens.push({
              type: 'tag',
              name: chars.join('')
            })
            chars.length = 0
            str = str.slice(1)
          }
          break
        case State.text:
          if (isAlpha(char)) {
            chars.push(char)
            str = str.slice(1)
          } else if (char === '<') {
            currentState = State.tagOpen
            tokens.push({
              type: 'text',
              content: chars.join('')
            })
            chars.length = 0
            str = str.slice(1)
          }
          break
        case State.tagEnd:
          if (isAlpha(char)) {
            currentState = State.tagEndName
            chars.push(char)
            str = str.slice(1)
          }
          break
        case State.tagEndName:
          if (isAlpha(char)) {
            chars.push(char)
            str = str.slice(1)
          } else if (char === '>') {
            currentState = State.initial
            tokens.push({
              type: 'tagEnd',
              name: chars.join('')
            })
            chars.length = 0
            str = str.slice(1)
          }
          break
      }
    }
  
    return tokens
  }
  
  function parse(str) {
    const tokens = tokenize(str)

    const root = {
      type: 'Root',
      children: []
    }
    const elementStack = [root]

    while (tokens.length) {
      const parent = elementStack[elementStack.length - 1]
      const t = tokens[0]
      switch (t.type) {
        case 'tag':
          const elementNode = {
            type: 'Element',
            tag: t.name,
            children: []
          }
          parent.children.push(elementNode)
          elementStack.push(elementNode)
          break
        case 'text':
          const textNode = {
            type: 'Text',
            content: t.content
          }
          parent.children.push(textNode)
          break
        case 'tagEnd':
          elementStack.pop()
          break
      }
      tokens.shift()
    }

    return root
  }

  function dump(node, indent = 0) {
    const type = node.type
    const desc = node.type === 'Root'
      ? ''
      : node.type === 'Element'
        ? node.tag
        : node.content

    console.log(`${'-'.repeat(indent)}${type}: ${desc}`)

    if (node.children) {
      node.children.forEach(n => dump(n, indent + 2))
    }
  }

  function transformElement(node) {
    if (node.type === 'Element' && node.tag === 'p') {
      node.tag = 'h1'
    }
  }

  function transformText(node, context) {
    if (node.type === 'Text') {
      context.removeNode()
    }
  }
  

  function traverseNode(ast, context) {
    context.currentNode = ast

    const transforms = context.nodeTransforms
    for (let i = 0; i < transforms.length; i++) {
      transforms[i](context.currentNode, context)
      if (!context.currentNode) return
    }

    const children = context.currentNode.children
    if (children) {
      let len = children.length
      let parent = context.currentNode
      for (let i = len - 1; i >= 0; i--) {
        context.parent = parent
        context.childIndex = i
        traverseNode(children[i], context)
      }
    }
  }


  function transform(ast) {
    const context = {
      currentNode: null,
      parent: null,
      replaceNode(node) {
        context.currentNode = node
        context.parent.children[context.childIndex] = node
      },
      removeNode() {
        if (context.parent) {
          context.parent.children.splice(context.childIndex, 1)
          context.currentNode = null
        }
      },
      nodeTransforms: [
        transformElement,
        transformText
      ]
    }
    // 调用 traverseNode 完成转换
    traverseNode(ast, context)
    // 打印 AST 信息
    dump(ast)
  }

  const ast = parse(`<div>u<p>Vue</p><p>Template</p>pop</div>`)
  transform(ast)

zengxiaoluan avatar Oct 21 '22 05:10 zengxiaoluan