ast编译原理-2-语法分析-简单的加乘分析

经过上一篇,通过有限状态机的概念编写了tokenzier,实现了简单的jsx语法分词。接着需要进行语法分析阶段。

语法分析

了解语法分析的原理和递归下降算法

递归下降算法
  • 左边是一个非终结符
  • 右边是产生式
  • 在语法解析过程中,左边会被右边替代。如果替代之后还有非终结符,那么继续这个替代过程,直到最后全部都是终结符,也就是token。
  • 只有终结符才可以称为AST的叶子节点,这个过程,就叫推导过程
  • 上级文法嵌套下级文法,上级的算法调用下级的算法,表现在生成AST中,上级算法生成上级节点,下级算法生成下级节点。这就是下降的含义。
上下文无关文法
  • 上下文无关的意识是,无论在任何而情况下,文法的推到规则都是一样的。
  • 规则分为两级,第一级是加法规则,第二级是乘法规则。把乘法规则作为加法规则的子规则。
  • 解析形成AST时,乘法节点一定是加法节点的子节点,从而被优先计算。
  • 加法规则中还递归的引用了加法规则。

例子

  • 算法表达式
2+3*4
  • 分tokens
[
{type: "NUMBER", value: "2"},
{type: "PLUS", value: "+"},
{type: "NUMBER", value: "3"},
{type: "MULTIPLE", value: "*"},
{type: "NUMBER", value: "2"},
]
  • 如上面所说,语法解析过程中,终结符就是tokens,遇到tokens就不可再分了。
  • 语法规则
	add -> mulitple | multiple + add  // 加法规则
	multuple -> Number | Number * multuple  	// 乘法规则 , Number就是一个token,用来终结
  • 左边,add, muiluple就是左边,非终结符,它会被右边替代。如果替代之后还有终结符,就会继续替代。(递归下降算法)
  • 比如add被multiple+add替代,替代之后又有mupltiple和add,所以又被替代,依次反复,直到没有非终结符。
以2+3*4为例子

通过tokens

  • 1 取第一个tokens,Number 2,开始匹配语法规则,先计算优先级低的,加法优先级肯定低。先推导加法规则 add
  • 2 取2匹配add,add被multiple或者multiple+add替代。而multiple又是一个非终结符,所以他又要被替代。
  • 3 最终2被匹配到Number或者是Number * multiple,而2就匹配到了Number,如果这里两个都不匹配,会报错。
  • 4 但是2不知道是匹配 Number还是Number * multiple。所以这时候通过peek(偷瞄)操作,看一下下一个tokens是谁。
  • 5 发现,2后面不是*号,那就匹配不上了Number * multiple了。所以multiple就匹配到了Number 2 。tokens中的第一个2就被消费掉了。
  • 6 回到第一步,add到底是匹配multiple还是匹配multiple+add呢?multiple我们已经知道匹配2了,这时候继续peek偷瞄下一个token。发现是+,刚刚好是multuple + add。
  • 7 所以add会被2 + add替换。tokens中的+号token也被消耗掉。下一个token就是3了,所以3就要匹配2+add中的add了。递归遍历
  • 8 add被替换成multuple或者是multiple+add。这时候还不知道是哪个。先替换multiple,multiple不知道匹配Number还是Number * multiple,所以peek后一个token,发现是 *号,刚刚好匹配Number*multiple。
  • 9 add->multiple->Number * multiple -> 3 * multeiple,所以3和*的token中被消费 ,下一个是4,刚刚好匹配mulitple,而multiple又会被Number或者Number+multiple替换,而peek之后,发现只剩下4了,所以4匹配到了Number。

在这里插入图片描述
拿tokens流中的每一个token进行一一匹配,讲token按照特定的语法规则,解析成AST。如上图,就是AST的格式
最后经过优化,效果应该如下
在这里插入图片描述
第一层:
在这里插入图片描述
第二层:
在这里插入图片描述
转化后的ast就如上。
语法规则是自己定的,不同的语法规则转换的AST不一样

再如2+3+4

tokens:

[
{type: "NUMBER", value: "2"},
{type: "PLUS", value: "+"},
{type: "NUMBER", value: "3"},
{type: "PLUS", value: "+"},
{type: "NUMBER", value: "4"},
]

顺序:

token 2 匹配到add,add被multiple+add替换,multiple被Numebr 2 替换。tokens中的2和+被消费。
token 3 匹配到add,add被multiple+add替换,multiple被Number 3 替换,tokens中的3和+被消费。
剩一个token4,匹配到add,add被multiple替换,multiple被Numebr 4i替换。结束,

图应该是如下:
在这里插入图片描述
精简后就是:
在这里插入图片描述

开始编写parse代码,先实现2+3*4的语法分析
  • 进行词法分析,解析成tokens,上次词法分析,也就是分词阶段使用了有限状态机,这次使用正则分词。
// 只匹配0-9或者是+或者是X
const RegExpObject = /([0-9]+)|(\+)|((\*))/g;
const tokenTypes = {
  NUMBER: "NUMBER",
  PLUS: "PLUS",
  MULTIPLE: "MULTIPLE",
};
const tokensName = [tokenTypes.NUMBER, tokenTypes.PLUS, tokenTypes.MULTIPLE];

const token = { type: "", value: "" };
/**生成器函数 */
function* tokenizer(script) {
  while (true) {
    // 匹配过一次后继续往下匹配,第一次[2,2,undefined,undefined,...],
    //第二次[+,undefined,+,undefined....],第三次[3,3,undefined,undefined...]
    // 第四次 [*, undefined, undefined, *, ...]
    const result = RegExpObject.exec(script);
    if (!result) {
      break;
    }
    const index = result.findIndex((item, index) => index > 0 && item);
    const token = { type: tokensName[index - 1], value: result[0] };
    token.type = tokensName[index - 1];
    //没执行一次函数,返回一个token
    yield token;
  }
}

/**正则分词 */
function tokenize(script) {
  const tokens = [];
  const it = tokenizer(script);
  let isContinue = true;
  while (isContinue) {
    const { value, done } = it.next();
    console.log(value, done);
    if (value) {
      tokens.push(value);
    }
    isContinue = !done;
  }

  return tokens;
}

/**
 * 将jsx转成AST语法树
 */
function parse(code) {
  // 分词处理
  const tokens = tokenize(code);
  console.log("tokens", tokens);
}

parse("2+3*4");

通过generator生成器函数,解析token。

进行词法推导
  • 新增peek功能,在语法分析中,我们需要通过peek功能知道下一个token,才能匹配正确的语法规则
  • 还有支持消耗token功能,所以封装一个类。
class TokenReader {
  constructor(tokens){
    this.tokens = tokens
    this.pos = 0
  }

  //读取消耗token
  read(){
    if(this.pos < this.tokens.length){
      return this.tokens[this.pos++]
    }
    return null
  }

  peek(){
    if(this.pos < this.tokens.length){
      return this.tokens[this.pos]
    }
    return null
  }
  //恢复
  unread(){
    if(this.pos > 0){
      this.pos--
    }
  }
}
接着进行词法推导
  • 定义每个AST节点的类型
//定义节点的类型
const ASTTypes = {
  Program: "Program",
  Numeric: "Numeric",
  Additive: "Additive",
  Multiplicative: "Multiplicative",
};
// 定义ast每个节点的类型
class ASTNode {
  constructor(type, value) {
    this.type = type;
    if (value) {
      this.value = value;
    }
    this.children = [];
  }
  //添加在节点
  appendChild(childNode) {
    this.children.push(childNode);
  }
}


代码:

/**
 * 将jsx转成AST语法树
 */
function parse(code) {
  // 分词处理
  const tokenReader = tokenize(code);
  const ast = toAST(tokenReader);
  console.dir(ast, { depth: null });
}

parse("2+3*4");

/**
 * 转换AST,实现语法分析推导过程
 * 规则就两条,加法和乘法
add -> mulitple | multiple + add  // 加法规则
multuple -> Number | Number * multuple  	// 乘法规则 , Number就是一个token,用来终结
 */

function toAST(tokenReader) {
  //根节点
  const rootNode = new ASTNode(ASTTypes.Program);
  /**
   * 开始推导,加法,乘法,先推导加法
   * 实现的时候,函数additive对应加法规则
   */
  const child = additive(tokenReader);
  if (child) {
    rootNode.children.push(child);
  }
  return rootNode;
}

//递归下降算法,从加法,到乘法,到number

//加法
function additive(tokenReader) {
  // 匹配Multiple,但是还没结束,还需要判断下一个token
  const child1 = multiple(tokenReader);
  const token = tokenReader.peek();
  let node = child1; //如果没匹配到+号,就是唯一的节点
  if (child1 && token) {
    if (token.type === tokenTypes.PLUS) {
      //后一个字符是+号,那么Add要被Multiple+Add替换
      tokenReader.read(); //消耗+号token
      //准备递归,去匹配下一个
      const child2 = additive(tokenReader);
      if (child2) {
        //匹配到了+号,那么2+3就是同一层的节点
        node = new ASTNode(ASTTypes.Additive);
        node.appendChild(child1);
        node.appendChild(child2);
      }
    }
  }

  return node;
}

//乘法
function multiple(tokenReader) {
  // 判断multiple是被Number还是被Number+multiple替换
  // 先匹配Numer,但是乘法匹配规则还没结束,得判断下一个token
  const child1 = number(tokenReader);
  let node = child1; //没匹配*号,chld1就是单独的节点
  //Number匹配成功
  const token = tokenReader.peek();
  if (child1 && token) {
    if (token.type === tokenTypes.MULTIPLE) {
      // TODO 匹配到了×号,所以Multiple要被Number*Mlutiple替换,所以3*4都是子节点了
      tokenReader.read(); //消耗X号
      //递归调用
      const child2 = multiple(tokenReader);
      if (child2) {
        // 匹配到了*号,那么child1和chil2就是同一层
        node = new ASTNode(ASTTypes.Multiplicative);
        node.appendChild(child1);
        node.appendChild(child2);
      }
    }
  }
  return node;
}

//NUMBER
function number(tokenReader) {
  let node = null;
  //看看当前的token
  let token = tokenReader.peek();
  //匹配到数字
  if (token !== null && token.type === tokenTypes.NUMBER) {
    token = tokenReader.read(); //读取并消耗该token
    //创建一个新的语法树节点
    node = new ASTNode(ASTTypes.Numeric, token.value);
  }
  return node;
}

打印结果:

ASTNode {
  type: 'Program',
  children: [
    ASTNode {
      type: 'Additive',
      children: [
        ASTNode { type: 'Numeric', value: '2', children: [] },
        ASTNode {
          type: 'Multiplicative',
          children: [
            ASTNode { type: 'Numeric', value: '3', children: [] },
            ASTNode { type: 'Numeric', value: '4', children: [] }
          ]
        }
      ]
    }
  ]
}

可以看到,最关键的就是,additive,multuple和number方法的互相调用和递归(递归下降算法),通过判断token,决定怎么处理tokens流的数据。
通过ast计算结果

/**计算 */
function evaluate(node) {
  let result;
  switch (node.type) {
    case ASTTypes.Program: {
      //根节点
      for (let child of node.children) {
        result = evaluate(child);
      }
      break;
    }
    case ASTTypes.Additive: {
      // 加法节点,有两个children
      result = evaluate(node.children[0]) + evaluate(node.children[1]);
      break;
    }
    case ASTTypes.Numeric: {
      //如果是number
      result = parseFloat(node.value);
      break;
    }
    case ASTTypes.Multiplicative: {
      //乘法节点
      result = evaluate(node.children[0]) * evaluate(node.children[1]);
      break;
    }
    default:
      break;
  }

  return result;
}

递归调用。在这里插入图片描述
在这里插入图片描述
2+3*4正确。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值