zoukankan      html  css  js  c++  java
  • 2018-01-12 Antlr4添加中文变量赋求值,括号,各种问题

    中文编程知乎专栏原文地址

    例程(更多测试用例在):

    基数=100
    基数×(基数+1)÷2
    => 求值为5050
    

    续上文Antlr4实现数学四则运算, 修改的语法规则部分:

    程序: 声明+;
    
    声明: 表达式 T新行 			#求值
      | T变量名 '=' 表达式 T新行	#赋值
      | T新行					#空行
      ;
    
    表达式: 表达式 运算符=('*'|'/'|'×'|'÷') 表达式 	#乘除
      | 表达式 运算符=('+'|'-') 表达式 		#加減
      | T数					#数
      | T变量名				#变量
      | '(' 表达式 ')'		#括号
      ;
    
    T变量名: ('a' .. 'z' | 'A' .. 'Z' | 'u4E00'..'u9FA5' | 'uF900'..'uFA2D')+;
    T新行: '
    '?'
    ';
    

    很明显, 变量名的范围仍需扩展, 比如数字就不支持, 而且这个字符范围应该有些过大(详见Validate a JavaScript function name), 待修正(变量字符范围 · Issue #1 · program-in-chinese/quan5).

    定制访问器添加的部分:

    private static Map<String, 节点> 变量值表 = new HashMap<>();
    
      // 以下为声明部分
    
      @Override
      public 节点 visit赋值(赋值Context 上下文) {
        String 变量名 = 上下文.T变量名().getText();
        变量值表.put(变量名, visit(上下文.表达式()));
        return null;
      }
    
      @Override
      public 节点 visit求值(求值Context 上下文) {
        return visit(上下文.表达式());
      }
    
      // 以下为表达式部分
    
      @Override
      public 节点 visit变量(变量Context 上下文) {
        String 变量名 = 上下文.T变量名().getText();
        
        // TODO: 添加变量检查
        return 变量值表.get(变量名);
      }
    
      @Override
      public 节点 visit括号(括号Context 上下文) {
        return visit(上下文.表达式());
      }
    

    变量值表采用变量名到节点的映射, 也就是在对包含这个变量的表达式求值时才对变量对应的表达式进行求值. 这里没有对变量赋值表达式进行语法树构建 · Issue #2 · program-in-chinese/quan5, 还需更多工作. 另外一个问题, 最后的表达式求值也会对变量值重复计算. 举例:

    利率=1
    年增长率=1+利率
    1000×年增长率×年增长率
    

    最后语法树如下:

    ![2018-01-12-antlr4赋值quan2]({{ "/assets/2018-01-12-antlr4赋值_quan2.png" | absolute_url }})

    "年增长率"应该提前求值, 以省去最后的多次计算(避免对变量重复求值 · Issue #3 · program-in-chinese/quan5)


    后两个问题已初步解决, 通过在"运行器"中保存变量表, 以及将各种节点的求值方法都集中到其中. 想起来在其他有些语言实现里也看到过类似结构(根据不同类型进行求值):

     public Object 求值(节点 节点) {
        if (节点 instanceof 运算式节点) {
          运算符号 运算符 = ((运算式节点)节点).运算符;
          Object 左结果 = 求值(((运算式节点)节点).左子节点);
          Object 右结果 = 求值(((运算式节点)节点).右子节点);
          switch(运算符) {
            case 加: return (int)左结果 + (int)右结果;
            case 減: return (int)左结果 - (int)右结果;
            case 乘: return (int)左结果 * (int)右结果;
            case 除: return (int)左结果 / (int)右结果;
            case 赋值:
              变量值表.put(((变量节点)((运算式节点)节点).左子节点).取变量名(), 右结果);
              // 顺延
            default:
              return null;
          }
        } else if (节点 instanceof 变量节点) {
          return 变量值表.get(((变量节点)节点).取变量名());
        } else if (节点 instanceof 数节点) {
          return ((数节点)节点).求值();
        } else {
          for(节点 子节点 : 节点.子节点) {
            返回值 = 求值(子节点);
          }
          return 返回值;
        }
      }
    
  • 相关阅读:
    Vue学习Day05-Vue中组件间的通讯(父到子,子到父,兄弟间)
    mysql数据库阻塞事务分析(thread_running突然升高)
    新建npm的nexus本地仓库,npm install报错npm ERR! code E401
    sqlserver数据库可以ping通但是1433端口无法telnet
    ansible使用synchronize模块报Broken pipe
    centos7上vagrant的安装及使用
    centos无法安装后无法识别r6818网卡问题
    手动上传snapshot和第三方jar包到nexus3
    搭建docker私有仓库nexus
    python3的pip安装报错pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
  • 原文地址:https://www.cnblogs.com/program-in-chinese/p/10487210.html
Copyright © 2011-2022 走看看