Hive代码分析报告(四): 语义分析①AST相关类解析

2021SC@SDUSC

目录

概述

AST相关类解析:ASTNode类


概述

前面的两篇文章,简要的把用户命令(HQL)如何进入编译器,走完语法分析、词法分析,生成抽象语法树AST的过程了解了一遍。这有助于从整体上把握编译流程的顺序,有助于更好的分析后续的功能框架。接下来,编译流程进入第二个主要阶段:语义分析阶段。

如果把HQL语句比作一句话,那么前面的语法分析、词法分析就是用来分析这句话包含了哪些词、有哪些关键词。而语义分析,顾名思义,就是要读懂这句话。

Hive如何读懂HQL这句话?我们知道了,在前面的编译环节中,HQL提交给编译器,编译器所做的主要工作是:创建ParseDriver对象pd,将hql分析得到AST。

AST,即抽象语法树,AST是编译流程后续过程的输入,所以,它便是语义分析框架“读懂”hql语句的关键信息来源。现在我需要通过阅读源码,主要分析一下AST中都包含了哪些信息,这有更好的理解语义分析的工作原理。

AST相关类解析:ASTNode类

根据前面的分析,我们已经找到了HIVE进行语法分析和词法分析的依据文件。

编译器的语法定义文件是ql/parse/Hive.g,该文件中同时包含了词法定义和语法定义。

经查阅资料和咨询队友,Hive.g在经过程序处理后,会生成两个用于分析的Java类:HiveLexer和HiveParser,顾名思义,分别用于词法分析和语法分析。

这里分析的结果是AST(抽象语法树)且AST中节点的类型是CommonTree类。下面,我们直接来看AST节点类的代码

先看ASTNode类的主要成员数据和构造函数:

public class ASTNode extends CommonTree implements Node,Serializable {

  private static final long serialVersionUID = 1L;

  private transient StringBuilder astStr;

  private transient ASTNodeOrigin origin;

  private transient int startIndx = -1;

  private transient int endIndx = -1;

  private transient ASTNode rootNode;

  private transient boolean isValidASTStr;

  private transient boolean visited = false;


  public ASTNode() {

  }


  /**

   * Constructor.

   *

   * @param t

   *          Token for the CommonTree Node

   */

  public ASTNode(Token t) {

    super(t);

  }


  public ASTNode(ASTNode node) {

    super(node);

    this.origin = node.origin;

  }


  @Override

  public Tree dupNode() {

    return new ASTNode(this);

  }

分析:

我们重点分析ASTNode的主要参数。

代码第一行,可以看出,ASTNode继承自CommonTree,并实现了序列化接口。

ASTNode类主要有下面一些私有成员数据:

private transient ASTNodeOrigin origin;

可知,一个ASTNode对象包含了一个ASTNodeOrigin对象。

private transient int startIndx = -1;

private transient int endIndx = -1;

这是两个私有变量,根据命名可以推测为索引起始点,可能用于树的遍历。

private transient boolean visited = false;

根据名称的含义,知道这是一个标识位,用于标识节点是否被访问过。

private transient ASTNode rootNode;

根据字面意思,很明显,这是根节点对象。

还可以知道,该类有多个的构造函数,第一个构造函数:public ASTNode(Token t) {super(t);}

参数是Token T ,一个toke对象,可知构造树节点所需的信息来自token对象。并且使用了super()函数,即调用了父类的以token对象为参数的构造方法。

下面分析几个ASTNode的主要成员函数

@Override

  public void setUnknownTokenBoundaries() {

    Deque<ASTNode> stack1 = new ArrayDeque<ASTNode>();

    Deque<ASTNode> stack2 = new ArrayDeque<ASTNode>();

    stack1.push(this);


    while (!stack1.isEmpty()) {

      ASTNode next = stack1.pop();

      stack2.push(next);


      if (next.children != null) {

        for (int i = next.children.size() - 1; i >= 0 ; i--) {

          stack1.push((ASTNode)next.children.get(i));

        }

      }

    }


    while (!stack2.isEmpty()) {

      ASTNode next = stack2.pop();


      if (next.children == null) {

        if (next.startIndex < 0 || next.stopIndex < 0) {

          next.startIndex = next.stopIndex = next.token.getTokenIndex();

        }

      } else if (next.startIndex >= 0 && next.stopIndex >= 0) {

        continue;

      } else if (next.children.size() > 0) {

        ASTNode firstChild = (ASTNode)next.children.get(0);

        ASTNode lastChild = (ASTNode)next.children.get(next.children.size()-1);

        next.startIndex = firstChild.getTokenStartIndex();

        next.stopIndex = lastChild.getTokenStopIndex();

      }

    }

  }

这个函数前两句,建立了两个栈,栈的存放对象是AST节点,并且将当前节点压入第一个栈中。

之后,使用迭代,如果第一个栈非空,就从第一个栈中弹出抽象语法树节点,压入第二个栈中,并且,如果next节点存在子孙节点,就把其子孙节点压入栈中

第二个迭代过程的过程是:从第二个栈里弹栈,赋给next节点

如果next节点没有孩子节点且它的开始索引和结束索引之一<0,则将token.getTokenIndex()的值赋给这两个索引。如果next节点的子节点不为空,则分两种情况:如果next的startindex和stopindex均大于零,此时停止当前这层循环,开始下一次循环,否则如果next.children.size() > 0,再进行进一步处理。

经过分析,该函数的作用是,对于子树中的每个节点,确保start/stop index已经设立。先进行深度遍历,自下而上访问。仅更新满足条件:至少一个令牌索引< 0的节点。

紧接着,ASTNode类还有很多其他函数,如:

@Override

  public void setParent(Tree t) {

    super.setParent(t);

    resetRootInformation();

  }


  @Override

  public void addChild(Tree t) {

    super.addChild(t);

    resetRootInformation();

  }

这些函数主要是继承并重写父类的函数,体现了父子类良好的继承性和差异性。

除了上面这些成员函数,下面这两个函数引起了我的注意。

/**
   * @return information about the object from which this ASTNode originated, or
   *         null if this ASTNode was not expanded from an object reference
   */
  public ASTNodeOrigin getOrigin() {
    return origin;
  }

  /**
   * Tag this ASTNode with information about the object from which this node
   * originated.
   */
  public void setOrigin(ASTNodeOrigin origin) {
    this.origin = origin;
  }

还记得上面的ASTNode类的成员数据里,有一个ASTNodeOrigin对象。上面这两个函数,官方对其的功能做出了注释,意思是:

返回有关此 ASTNode 源自的对象的信息,或 null, 如果此 ASTNode 不是从对象引用扩展的

使用有关此节点所在对象的信息,标记此 ASTNode 起源。

关于这个类更详细的信息以及和ASTNode类的联系等,将在之后分析介绍。

总结

ASTNode是Hive自己定义的一个树节点对象,它继承自Antlr的CommonTree类,并实现了Node和Serializable接口。我们在前面grammar options中看到,这个Parser返回的是CommonTree类型的对象,由于ASTNode是CommonTree的子类,所以并不违反协议。Hive之所以要这样做,主要是:

1.实现Node接口,以便可以使用Hive的遍历框架对AST进行遍历;

2.实现Serializable接口,以便可以将AST序列化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值