AST抽象语法树

抽象语法树是什么

抽象语法树本质上是一个JS对象,模板语法直接编译成正常的HTML语法是非常困难的,这就要借助抽象语法树过渡,让编译工作变得简单

抽象语法树就服务于模板编译,只要涉及到翻译,一种语法翻译成另一种语法,就要借助抽象语法树

手写实现AST抽象语法树

定义两个栈和指针来辅助我们来实现AST抽象语法树

  • 检测到开始标记,将标记tag放入栈1,将ast语法形式的对象{'tag':tag,'children':[]}放入栈2

  • 抓取结束标记之前的文字,文字不能全为空,改变栈2的栈顶内容为抓取到的文字

  • 检测到结束标记,如果tag和栈1 的顶部内容相同,栈1出栈,栈2出栈,让栈2的children加入栈2出栈的内容

// parse函数,主函数
export default function parse(templateString){
  // 指针
  var index = 0;
  // 剩余字符串
  var rest = ''
  // 开始标记
  var startRegExp = /^\<([a-z]+[0-6]?)\>/;
  // 结束标记
  var endRegExp = /^\<\/([a-z]+[0-6]?)\>/
  // 抓取结束标记之前的文字
  var wordRegExp = /^([^\<]+)\<\/[a-z]+[0-6]?\>/;
  // 两个栈
  var stack1 = [];
  // 到最后stack2出栈,输出结果会为空,可以补一项
  var stack2 = [{'children':[]}];
  while(index < templateString.length-1){
    rest = templateString.substring(index);
    if(startRegExp.test(rest)){
      let tag = rest.match(startRegExp)[1]
      //将开始标记存入栈1中
      stack1.push(tag);
      // 将对象存入栈2中
      stack2.push({'tag':tag,'children':[]})
      // 指针移动标签的长度加2,因为<>占两位
      index += tag.length +2;
      // console.log('开始标记',tag)
    }else if(endRegExp.test(rest)){
      let tag = rest.match(endRegExp)[1];
      //此时,tag一定是和栈1顶部的内容相同
      let pop_tag = stack1.pop();
      if(tag === pop_tag){
        let pop_arr = stack2.pop();
        if(stack2.length > 0 ){
          stack2[stack2.length - 1].children.push(pop_arr);
        }
      }else{
        throw new Error(pop_tag + '标签没有封闭')
      }
      // console.log('结束标记' +tag)
      // 指针移动标签的长度加2,因为</>占3位
      index += tag.length +3;
    }else if(wordRegExp.test(rest)){
      // 识别是不是文字,不能全为空
      let word = rest.match(wordRegExp)[1];
      if(!/^\s+$/.test(word)){
        // 文字不全是空
        // console.log('检查到文字'+word)
        // 改变此时stack2栈顶元素中
        stack2[stack2.length-1].children.push({"text":word,"type":3})
      }
      index += word.length;
      // console.log(stack1,JSON.stringify(stack2))
    }else{
      index++;
    }
    
  }
  // 之前默认放置一项了,就要去第0项输出
  return stack2[0].children[0]
}

测试

import parse from './parse'
var templateString = `
  <div>
    <h3>你好</h3>
    <ul>
      <li>A</li>
      <li>B</li>
      <li>C</li>
    </ul>
  </div>
`

const ast  = parse(templateString)
console.log(ast)

输出结果

 接下来识别attrs,当在div放入class时,上面的代码是识别不了的,但是标签和class之间肯定有一个空格,我们就要在开始标记上识别空格

var startRegExp = /^\<([a-z]+[0-6]?)(\s[^\<]+)?\>/;

 指针需要移动attrsString的位数,并在对象中加入attrs属性

 

 接下来定义parseAtrrsString函数将attrs处理成数组,并在attrs属性中调用函数

stack2.push({ 'tag': tag, 'children': [] ,'attrs':parseAtrrsString(attrsString)})

// 让attrsString变成数组返回
export default function (attrsString) {
  if (attrsString === undefined) return []
  // 当前是否在引号内
  var isYinhao = false;
  // 断点
  var point = 0;
  // 结果数组
  var result = [];
  // 遍历attrsString,而不是用split(),因为用split(' ')会导致class="aa bb cc"这种情况出现错误
  for (let i = 0; i < attrsString.length; i++) {
    let char = attrsString[i];
    if (char === '"') {
      isYinhao = !isYinhao;
      console.log(isYinhao)
    } else if (char === ' ' && !isYinhao) {
      // 遇见了空格并且不再引号中
      // 不全是空格才push进结果数组
      if (!/^\s*$/.test(attrsString.substring(point, i))) {
        // 去掉头尾空格
        result.push(attrsString.substring(point, i).trim());
        point = i;
      }
      
    }
  }
  // 循环结束之后还剩一个属性
  result.push(attrsString.substring(point).trim())
  // 返回 name=name,value=value这种形式,需要映射
  result = result.map(item =>{
    //根据等号拆分
    const o = item.match(/^(.+)=(.+)$/);
    return {
      name:o[1],
      value:o[2]
    }
  })
  return result
}

 输出结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值