编译原理2-语法分析-支持减除小括号

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译原理2-语法分析-支持减除小括号相关的知识,希望对你有一定的参考价值。

语法分析-支持减法除法小括号

语法规则 加上减法除法规则就变了

add -> minus | minus + add  // 加法被 减法 | 减法+加法替换
minus -> multiple | mulitple +|- add    减法被 乘法 或者 乘法加减加法
multiple -> primary | primary *|/ multiple  乘法 被 Number 或者 Number 乘除 乘法
primary -> NUMBER | (add)  primary是括号 最下面,最先计算,优先级最高

全部代码:

// 只匹配0-9或者是+或者是X或者-或者/
const RegExpObject = /([0-9]+)|(\\+)|(\\*)|(-)|(\\/)|(\\()|(\\))/g;
const tokenTypes = 
  NUMBER: "NUMBER",
  PLUS: "PLUS",
  MULTIPLE: "MULTIPLE",
  MINUS: "MINUS", //减法
  DIVIDE: "DIVIDE",
  LEFT_PARA: "LEFT_PARA", // (
  RIGHT_PARA: "RIGHT_PARA", // )
;
const tokensName = [
  tokenTypes.NUMBER,
  tokenTypes.PLUS,
  tokenTypes.MULTIPLE,
  tokenTypes.MINUS,
  tokenTypes.DIVIDE,
  tokenTypes.LEFT_PARA,
  tokenTypes.RIGHT_PARA,
];

//定义节点的类型
const ASTTypes = 
  Program: "Program",
  Numeric: "Numeric",
  Additive: "Additive",
  Multiplicative: "Multiplicative",
  Minus: "Minus", //-
  Divide: "Divide", // 除
  LEFT_PARA: "LEFT_PARA",
  RIGHT_PARA: "RIGHT_PARA",
;
// 定义ast每个节点的类型
class ASTNode 
  constructor(type, value) 
    this.type = type;
    if (value) 
      this.value = value;
    
    this.children = [];
  
  //添加在节点
  appendChild(childNode) 
    this.children.push(childNode);
  


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;
  


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--;
    
  


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

  return new TokenReader(tokens);


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


parse("4/(4-2)+3/(3+3)");
// (4/4) - (3+ (3/3))

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

//加上减法除法规则就变了
add -> minus | minus + add  // 加法被 减法 | 减法+加法替换
minus -> multiple | mulitple +|- add    减法被 乘法 或者 乘法加减加法
multiple -> primary | primary *|/ multiple  乘法 被 Number 或者 Number 乘除 乘法
primary -> NUMBER | (add)  primary是括号 最下面,最先计算,优先级最高
 */

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) 
  // 匹配minus,但是还没结束,还需要判断下一个token
  const child1 = minus(tokenReader);
  const token = tokenReader.peek();
  let node = child1; //如果没匹配到+号,就是唯一的节点
  if (child1 && token) 
    // 下一个字符如果是+号,就需要被 minus + add替换
    if (token.type === tokenTypes.PLUS) 
      tokenReader.read(); //消耗+token
      //准备递归,去匹配下一个
      const child2 = additive(tokenReader);
      if (child2) 
        //匹配到了+号,那么2+3就是同一层的节点
        node = new ASTNode(ASTTypes.Additive);
        node.appendChild(child1);
        node.appendChild(child2);
      
    
  

  return node;


//minus =>  multiple | mulitple +|- add
function minus(tokenReader) 
  // 匹配minus,但是还没结束,还需要判断下一个token
  const child1 = multiple(tokenReader);
  const token = tokenReader.peek();
  let node = child1; //如果没匹配到+号,就是唯一的节点
  if (child1 && token) 
    // 下一个字符如果是+|-号,就需要被 mulitple +|- add  替换
    if (token.type === tokenTypes.MINUS || token.type === tokenTypes.PLUS) 
      tokenReader.read(); //消耗-token
      //准备递归,去匹配下一个
      const child2 = additive(tokenReader);
      if (child2) 
        //匹配到了+/-号
        node = new ASTNode(
          token.type === tokenTypes.MINUS ? ASTTypes.Minus : ASTTypes.Additive
        );
        node.appendChild(child1);
        node.appendChild(child2);
      
    
  

  return node;


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


// 括号 primary -> Number | (add)
function primary(tokenReader)
    const child1 = number(tokenReader);
    let node = child1; 
    if(!node)
        const token = tokenReader.peek()
        //匹配到小括号
        if(token!==null && token.type === tokenTypes.LEFT_PARA)
            tokenReader.read(); //去掉左括号
            // 对于4/(4-2),因为(无法匹配number,所以走这个逻辑
           // 去掉(后变成数字,继续走add逻辑,add匹配到4,后面是-,匹配到 muins -> multiple | mulitple +|- add的第二个multiple - add
           // -去掉,继续走add(2),2匹配到了4,后面的右括号无法匹配,结束。所以继续走 tokenReader.read()去掉右括号
           // 而这个primary规则就返回  4-2
            node = additive(tokenReader) //这个node就是 type: "Minuse", children:[..value: 4, ...value: 2]
            tokenReader.read() //去掉右括号
        
    
   
    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;


/**计算 */
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.Minus: 
      result = evaluate(node.children[0]) - evaluate(node.children[1]);
      break;
    
    case ASTTypes.Divide: 
      result = evaluate(node.children[0]) / evaluate(node.children[1]);
      break;
    
    case ASTTypes.Multiplicative: 
      //乘法节点
      result = evaluate(node.children[0]) * evaluate(node.children[1]);
      break;
    
    default:
      break;
  

  return result;



主要还是根据规则去一一匹配,通过递归下降算法,判断匹配的语法规则,调用对应的函数。这里的括号匹配主要的就是,primary这个函数,会将()里面的内容匹配出来,单独处理成一个节点,如(4-2),就处理成

type: MUINS, children: [type: Number, value:4, type: Number, value: 2]

children是优先计算的,所以括号的内容会优先计算,通过debugger可以了解是如何推导的。
例子:parse(“4/(4-2)+3/(3+3)”);;结果

ASTNode 
  type: 'Program',
  children: [
    ASTNode 
      type: 'Additive',
      children: [
        ASTNode 
          type: 'Divide',
          children: [
            ASTNode  type: 'Numeric', value: '4', children: [] ,
            ASTNode 
              type: 'Minus',
              children: [
                ASTNode  type: 'Numeric', value: '4', children: [] ,
                ASTNode  type: 'Numeric', value: '2', children: [] 
              ]
            
          ]
        ,
        ASTNode 
          type: 'Divide',
          children: [
            ASTNode  type: 'Numeric', value: '3', children: [] ,
            ASTNode 
              type: 'Additive',
              children: [
                ASTNode  type: 'Numeric', value: '3', children: [] ,
                ASTNode  type: 'Numeric', value: '3', children: [] 
              ]
            
          ]
        
      ]
    
  ]

result 2.5

以上是关于编译原理2-语法分析-支持减除小括号的主要内容,如果未能解决你的问题,请参考以下文章

四则运算3

编译原理实战入门:用 JavaScript 写一个简单的四则运算编译器语法分析

GCC编译器原理------编译原理三:编译过程(2-2)---编译之语法分析

编译原理 语法分析器

给n个数的加法加括号的方法有多少种

python3简单实现支持括号的加减乘除运算