2021SC@SDUSC
总览
本篇为对QueryParserDriver类进行分析的第三篇,主要分析的是在生成逻辑数据模型的过程中,对宏文件的初始化以及扩展遍历进行分析。
代码分析
traverse()
- list<>的解释:这是泛型,保证List传入类型跟ArrayList传入类型一致;如果List指定了泛型,那么编译就会检测,如果不定义泛型,编译通过,运行不合理值会报错。
- 泛型(Generic type 或者generics)是对Java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
- 实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
- 内联函数,内联函数可以用来替代宏,内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
- 分析:traverse是一个递归的过程,遍历读取树的每一个节点,macrodef为宏定义,inline为内联函数,判断节点类型将它添加到合适的位置。
static void traverse(Tree t, List<CommonTree> macroNodes,
List<CommonTree> inlineNodes) {
if (t.getText().equals(MACRO_DEF)) {
macroNodes.add((CommonTree) t.getParent());
} else if (t.getText().equals(MACRO_INLINE)) {
inlineNodes.add((CommonTree) t);
}
int n = t.getChildCount();
for (int i = 0; i < n; i++) {
Tree t0 = t.getChild(i);
traverse(t0, macroNodes, inlineNodes);
}
}
makeMacroDef()
该方法为宏定义的构建,首先判断是否为重复的宏名,然后创建宏,按节点读取树,获取参数标记、获取返回别名标记、获取宏体,有时脚本没有文件名,例如将字符串传递到PigServer时,如果如此则创建并添加该文件。设置宏的起始行号,删除此节点。
private PigMacro makeMacroDef(CommonTree t, Map<String, PigMacro> seen)
throws ParserException {
String mn = t.getChild(0).getText();
if (!macroSeen.add(mn)) {
String msg = getErrorMessage(null, t, null,
"Duplicated macro name '" + mn + "'");
throw new ParserException(msg);
}
if (seen != null) {
for (String s : seen.keySet()) {
macroSeen.add(s);
}
}
String fname = ((PigParserNode)t).getFileName();
Tree defNode = t.getChild(1);
// get parameter markers
ArrayList<String> params = new ArrayList<String>();
Tree paramNode = defNode.getChild(0);
int n = paramNode.getChildCount();
for (int i = 0; i < n; i++) {
params.add(paramNode.getChild(i).getText());
}
// get return alias markers
ArrayList<String> returns = new ArrayList<String>();
Tree retNode = defNode.getChild(1);
int m = retNode.getChildCount();
for (int i = 0; i < m; i++) {
returns.add(retNode.getChild(i).getText());
}
// get macro body
Tree bodyNode = defNode.getChild(2);
String body = bodyNode.getChild(0).getText();
body = body.substring(1, body.length() - 1);
// sometimes the script has no filename, like when a string is passed to PigServer for
// example. See PIG-2866.
if (!fname.isEmpty()) {
FetchFileRet localFileRet = getMacroFile(fname);
fname = localFileRet.file.getAbsolutePath();
}
PigMacro pm = new PigMacro(mn, fname, params, returns, body, seen);
try {
pm.validate();
} catch (IOException e) {
String msg = getErrorMessage(null, t,
"Invalid macro definition: ", e.getMessage());
throw new ParserException(msg);
}
// set the starting line number of the macro
PigParserNode pnode = (PigParserNode)bodyNode.getChild(0);
pm.setStartLine(pnode.getStartLine());
seen.put(mn, pm);
// delete this node
Tree defineNode = t.getParent();
Tree stmtNode = defineNode.getParent();
stmtNode.deleteChild(defineNode.getChildIndex());
return pm;
}
macroImport()
导入宏的方法,首先删除引述,已经导入了此文件,所以只需跳过导入语句,读取文件内容,通过一系列转化,将内容做语法转化。
private void macroImport(CommonTree t) throws ParserException {
// remove quote
String fname = t.getChild(0).getText();
fname = QueryParserUtils.removeQuotes(fname);
if (!importSeen.add(fname)) {
// we've already imported this file, so just skip this import statement
LOG.debug("Ignoring duplicated import " + fname);
t.getParent().deleteChild(t.getChildIndex());
return;
}
Tree macroAST = null;
if (pigContext.macros.containsKey(fname)) {
macroAST = pigContext.macros.get(fname);
} else {
FetchFileRet localFileRet = getMacroFile(fname);
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(localFileRet.file));
} catch (FileNotFoundException e) {
String msg = getErrorMessage(fname, t,
"Failed to import file '" + fname + "'", e.getMessage());
throw new ParserException(msg);
}
StringBuilder sb = new StringBuilder();
String line = null;
try {
line = in.readLine();
while (line != null) {
sb.append(line).append("\n");
line = in.readLine();
}
} catch (IOException e) {
String msg = getErrorMessage(fname, t,
"Failed to read file '" + fname + "'", e.getMessage());
throw new ParserException(msg);
}
String macroText = null;
try {
in.close();
in = new BufferedReader(new StringReader(sb.toString()));
macroText = pigContext.doParamSubstitution(in);
} catch (IOException e) {
String msg = getErrorMessage(fname, t,
"Parameter sustitution failed for macro.", e.getMessage());
throw new ParserException(msg);
}
// parse
CommonTokenStream tokenStream = tokenize(macroText, fname);
try {
macroAST = parse( tokenStream );
pigContext.macros.put(fname, macroAST);
} catch(RuntimeException ex) {
throw new ParserException( ex.getMessage() );
}
}
QueryParserUtils.replaceNodeWithNodeList(t, (CommonTree)macroAST, fname);
}
expandMacro()
展开宏的方法,首先对树进行遍历转化,然后验证导入是否已启用/禁用,对抽象语法树的各个节点调用macroImport()方法,实现对宏的导入。
Tree expandMacro(Tree ast) throws ParserException {
LOG.debug("Original macro AST:\n" + ast.toStringTree() + "\n");
// first insert the import files
while (expandImport(ast))
;
LOG.debug("macro AST after import:\n" + ast.toStringTree() + "\n");
List<CommonTree> macroNodes = new ArrayList<CommonTree>();
List<CommonTree> inlineNodes = new ArrayList<CommonTree>();
// find all macro def/inline nodes
traverse(ast, macroNodes, inlineNodes);
Map<String, PigMacro> seen = new HashMap<String, PigMacro>();
List<PigMacro> macroDefs = new ArrayList<PigMacro>();
// gether all the def nodes
for (CommonTree t : macroNodes) {
macroDefs.add(makeMacroDef(t, seen));
}
// inline macros
inlineMacro(inlineNodes, macroDefs);
LOG.debug("Resulting macro AST:\n" + ast.toStringTree() + "\n");
return ast;
}
总结
本次分析为对QueryParserDriver类最后的一篇分析,主要分析的是对宏的调用转换方法,将宏转换为抽象语法树。