Hive代码分析报告(三):语义分析的前序步骤分析②

2021SC@SDUSC

目录

概述

前序分析②:语法分析:生成AST的环节


概述

在上一篇的分析中,用户指令经过如下函数调用历程:

processCmd()—>CommandProcessor get()-->processLocalCmd()-->run-->runInternal()-->compileInternal()-->compile() 

经过上述的层层调用,Driver的run方法最终会执行compile()操作,由Compiler作后续的语法解析和语义分析,这就是HQL语句从CLI进入编译器的简单程序逻辑。

前序分析②:语法分析生成AST的环节

接上一篇的函数调用,我们来分析compile()函数,这个函数包含了编译流程的大部分函数调用。这个函数很长(约3000行),我们知道,编译流程第一步骤的首要目标就是生成AST了,因此我首先快速浏览了整段代码,找到与生成树相关的内容。

// Trigger query hook before compilation

      hookRunner.runBeforeParseHook(command);


      ASTNode tree;

      try {

        tree = ParseUtils.parse(command, ctx);

      } catch (ParseException e) {

        parseError = true;

        throw e;

      } finally {

        hookRunner.runAfterParseHook(command, parseError);

      }

      perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.PARSE);

根据之前的学习和分析,整个编译流程的第一步,是将HQL转化为AST,这个步骤主要由parse组件完成。

很明显,tree = ParseUtils.parse(command, ctx);一句,便是我们想寻找的重点。

ParseUtils封装了ParseDriver 对sql的解析工作,ParseUtils的parse方法,来看parse函数:

public static ASTNode parse(

      String command, Context ctx, String viewFullyQualifiedName) throws ParseException {

    ParseDriver pd = new ParseDriver();

    ASTNode tree = pd.parse(command, ctx, viewFullyQualifiedName);

    tree = findRootNonNullToken(tree);

    handleSetColRefs(tree);

    return tree;

  }

第一句ParseDriver pd = new ParseDriver(),构造一个ParseDriver对象pd,;

紧接着ASTNode tree = pd.parse(command, ctx, viewFullyQualifiedName),用pd对象调用parse函数,这个函数的返回的是一个AST树实例,由此可见,生成抽象语法树的重点就在该函数,下面看parse()函数:

public ASTNode parse(String command, Context ctx, boolean setTokenRewriteStream)

      throws ParseException {

    if (LOG.isDebugEnabled()) {

      LOG.debug("Parsing command: " + command);

    }


    HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command));

    TokenRewriteStream tokens = new TokenRewriteStream(lexer);

      if ( setTokenRewriteStream) {

        ctx.setTokenRewriteStream(tokens);

      }

      lexer.setHiveConf(ctx.getConf());

    }

    HiveParser parser = new HiveParser(tokens);

    if (ctx != null) {

      parser.setHiveConf(ctx.getConf());

    }

    parser.setTreeAdaptor(adaptor);

    HiveParser.statement_return r = null;

    try {

      r = parser.statement();

    } catch (RecognitionException e) {

      e.printStackTrace();

      throw new ParseException(parser.errors);

    }


    if (lexer.getErrors().size() == 0 && parser.errors.size() == 0) {

      LOG.debug("Parse Completed");

    } else if (lexer.getErrors().size() != 0) {

      throw new ParseException(lexer.getErrors());

    } else {

      throw new ParseException(parser.errors);

    }


    ASTNode tree = (ASTNode) r.getTree();

    tree.setUnknownTokenBoundaries();

    return tree;

  }

我注意到下面这句

HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command));

这个new ANTLRNoCaseStringStream(command)是什么东西?

经查阅代码知,ANTLRNoCaseStringStream类继承自ANTLRStringStream类。

它实际上是一个辅助类,ANTLRStringStream是Lexer使用的输入流之一(基于字符串的输入流)。这里定义的辅助类的作用就是,在Lexer使用其LA()(lookahead)从流中查看字符的时候,将字符转换成大写字符,从而实现大小写不敏感的lexer。

在Hive.g定义的语法中,只要识别大写字母就可以了。然而,真正存放在$token.text中的值仍然是原始String中的值(这种值是lexer通过ANTLRStringStream的consume()方法获得的,这个方法没有在辅助类中override)。

那么,这个HiveLexerX又是什么?起什么作用呢?

经过分析和猜测,我认为HiveLexerX的主要作用可能是进行词法分析。什么说它的作用是词法分析呢?

我们知道,词法分析的作用是将语句中的关键字,特殊符号之类的有特殊含义的符号解释为一个token。

而在parse包下有如下几个文件:

HiveLexerX.g

HiveParser.g

SelectClauseParser.g

FromClauseParser.g

IdentifiersParser.g

这些文件大概率与HiveLexerX密切相关。

其中HiveLexerX.g 文件中,对HQL中的关键字进行了声明(见下图),即对词的声明。由此可见HiveLexerX可能主要负责词法分析。

事实上经过查阅一些资料得知,HiveLexerX类继承自生成的HiveLexer;HiveParserX继承自生成的HiveParser。HiveLexerX 和HiveParserX 是实际使用的词法/语法分析类,它们的唯一目的就是更好地显示、记录分析过程中的错误信息。由此印证了我的猜测。

有关词法和语法解析的内容主要由队友进行,在此处,程序将进行一系列复杂的解析过程,这里不再展开分析。

总的来说,ParseDriver负责组织、驱动整个词法、语法分析流程,并得到分析后的AST。该类维护了一个静态成员xlateMap,它将关键字的名字(以KW_开头的名字,与Hive.g中定义的相同)映射到关键字本身。xlateMap也包含内置操作符名字到操作符字符串的映射。该类的一个重要的函数parse(),也就是我们上面提到的那个方法,是分析流程的关键,它的输入是一个HQL String,输出是一个AST。

重点看倒数第三行:

ASTNode tree = (ASTNode) r.getTree();

经过上述词法分析、语法分析,终于此处生成了目标结果AST,并通过函数调用返回。这便是后续语义分析框架所需的输入了。

通过这两篇前序步骤的简要分析,我更加清楚的理解了从HQL语句到进入驱动器再进行编译阶段的语法词法分析的函数调用历程,这也有助于从全局角度更好理解Hive的编译流程。后面,我将进行我的主要工作之一:语义分析框架的代码分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值