第三章 抽象语法树

第三章 抽象语法树
第三章 抽象语法树

第三章抽象语法树

在现代编译器的构造过程中,前端主要实现从源程序到中间形式(Intermediate Representation)的转换,而编译器的后端用来完成从中间形式到具体目标机代码的转换,这是一种广泛采用的编译器构造模型。虽然源程序到目标程序的直接转换是可行的,但是使用独立于具体目标平台的中间形式有以下优点:

(1)使用中间形式可以比较容易地构造面向不同目标平台和不同语言的编译器。在不改动已有编译器前端的情况下,为新的目标平台构造一个生成该平台目标程序的后端,就可以构造出新平台的编译器。同样对于一个新的语言,在不改动已有编译器后端的情况下,为新语言构造一个识别该语言的前端,就可以构造出新语言的编译器。

(2)针对中间形式,可以进行独立于目标平台的代码优化。这样可以生成较高质量的目标代码,在此基础上可以对目标代码进行平台相关的优化,进而生成更高质量的目标代码。

使用中间形式的主要缺点是,产生中间代码的编译过程与不产生中间代码的编译过程相比在效率上会显得有些低。这是因为中间代码还要进行再一次的翻译才能生成目标代码。但是,增加一层中间形式可以使编译器更好地模块化,并且可以在中间形式上做很多优化,这些足以抵消两次翻译所带来的低效率。所以,很多现代的编译器都使用了中间形式,比较常见的中间形式有逆波兰表示,N元表示和树形表示三种。由于,在本系统中,我们使用抽象语法树作为中间形式,所以不再对前两种中间形式做以介绍,感兴趣的读者可以参考《编译原理》(Alfred V.Aho编著)。

3.1 树形表示

树形表示是一种非常流行的中间形式,它与三元式表示有着密切的关系,三元式表示可以看成是树形表示的直接形式。例如,表达式W*X+(Y+Z)的树形表示如图3.1所示:

图3.1 表达式W*X+(Y+Z)的树形表示

在现代编译器的构造过程中,经常使用抽象语法树(Abstract Syntax Tree)作为一种中间形式,这样做可以把翻译过程从语法分析过程中分离出来,使得层次非常清晰。引入抽象语法树是基于多方面原因的,其中的一个原因在于适于语法分析的文法可能并不反映语言成分的自然层次结构。比如Fortran的文法可能把子程序看成是简单的语句序列,而这并不能反映出DO循环的嵌套。如果我们采用能够反映DO循环嵌套的树形表示,那么子程序的语义分析将会更容易一些。因此,很多编译器经常要构造语法树。

在这里我们之所以称其为“抽象”语法树,是因为抽象语法树的结构不依赖于被编译语言的原文法,也就是语法分析阶段所采用的文法。在语法分析过程中,为了能够适应摸中分析方法的需要,有时需要对文法进行等价的转换(比如,消除左递归,避免回溯等),这些转换在采用特定的分析方法进行语法分析时是有必要的,但是这样做同时也会带来一些问题。比如,会引入多余的非终结符和多余的产生式等,所有这些多余的成分都会对下面的编译阶段产生一定的限制,甚至会使各阶段变得非常混乱。按照这些文法进行推导时,每一个正确的输入在理论上都对应一个依赖于这些文法的具体分析树。但是正如上面提到的,这种分析数不大适合直接使用,所以我们引入了抽象语法树。抽象语法树在语法分析和后面的各编译阶段之间建立了一个清晰的接口,它能够反映源程序本身的语法结构。

基于以上原因,抽象语法树在现代编译器的构造中使用得越来越广泛,本系统也构造了抽象语法树。下面我们先简单的介绍一下语法分析树和抽象语法树,并对它们做以简单的比较。

语法分析树也称为具体语法树,因为这种树中的一切都是明示的,完全而又具体,显示了如何根据相应上下文无关文法推导出特定的单词序列。在我们知道了一个单词序列为合法之后,后续的编译阶段就不再需要语法分析树上的许多信息了。因此,语法分析结束以后,一般会将这种语法分析树转换为一棵抽象语法树(又称AST,简称语法树),删除树内部的大部分“人为”结点,并对剩下的结点标注有用的信息。附在特定节点上的标注被称为它的属性。

例如语句if(a>b) a = a-b;的语法分析树和抽象语法树分别可表示成图3.2和图3.3。

图3.2 语句if(a>b) a = a-b的语法分析树

图3.3语句if(a>b) a = a-b的抽象语法树

在许多编译器里,带标注的语法树就是从前端传递到后端的中间形式,而在另一些编译器里,语义分析器最后可能还要遍历这棵树,生成某种另外的中间形式。本系统将这种语法树作为中间表示,不再对它做一步的转换

3.2 系统抽象语法树的节点表示

?While语句节点

产生式:

while-stmt→ while( expression ) statement

while语句的抽象语法树

说明:对于while语句,根节点是while,第一个孩子节点表示条件(expression),第二个孩子节点是条件为真的时候的执行语句(statement)。这些说明部分在下面的抽象语法树图中是很直观的表示,因此以下语句结点语法树将不再赘述。

图3.4while语句的抽象语法树

?If语句节点表示

产生式:if-stmt→ if( expression ) statement [else statement]

图3.5 If语句的抽象语法树

?函数声明节点

产生式:fun-declaration→ ( voi d | int ) ID( params ) compound-stmt

图3.6 函数声明的语法树?表达式语句节点

产生式:

expression→ ID = expression | simple-expression

simple-expression→ additive-expression [ relop additive-expression ] relop → < | <= | > | >= | == | !=

additive-expression→ term [( + | - ) term ]

term→ factor [ ( * | / ) factor ]

factor→ ( expression )| ID | call | NUM

图3.7 表达式语句的语法树?程序节点

产生式:program→ { var-declaration | fun-declaration }

图3.8程序的语法树例:如下程序段代码:int pi;

int add(int x, int y)

return x+y;

}

void main() {…}

语法分析结束后生成的抽象语法树如下图3.9所示:

图3.9 生成的语法树

3.3抽象语法树的设计

在本章前面几节中,我们介绍了抽象语法树的基本概念,与语法分析树的比较以及在系统中抽象语法树的节点表示。在本节中,我们将详细讲解本系统中抽象语法树节点设计,并且对抽象语法树的实现代码做以解释。

抽象语法树的节点类型如下所示:

//抽象语法树的节点类型

public enum NodeType

{

FunDecl, VarDecl, Para,

ADD, SUB, MUL, DIV, REQ, RLT, RGT, RNEQ, RNGT, RNLT,

AssignStm, IfStm, IfElseStm, ElseStm, WhileStm, ReturnStm,

FunCall, ConstID, VarID,

OTHER, ERROR

……

};

在此基础上,我们再给出了抽象语法树的节点定义,见第二章表2.1。

下面,我们给出抽象语法树的实现代码并给出必要的解释。所给出的代码与文件parser/Parser.cs中的源代码略有不同,但是基本思路一致,为了便于读者理解,parser/Parser.cs特改写成以下代码形式:

class Parser

{ Token curtoken;

void parse() {

curtoken = getNextToken(); //取得第一个token

TreeNode root = null;

root = program(); //递规下降分析!构建语法树,并返回根节点}

函数parse():这个函数所做的工作就是从词法分析所生成的单词序列中取得第一个单词Token,然后,调用program()函数,构建抽象语法树,并返回抽象语法树的根节点;

void match(TokenType expected){

if(curtoken.TType == expected) curtoken=getNextToken();

else ParseError();

}

函数match(TokenType expected):用来通过将当前单词类型与语法上应该出现的正确单词类型进行比较,检查是否有语法错误。如果没有语法错误,则从单词序列中取得下一个单词,以便后面继续进行语法分析,构建抽象语法树使用,如果出现语法错误,则调用函数ParseError()进行出错处理;

TreeNode program() {

Token p, q; p = root;

while(curtoken.TType!=TokenType.ENDFILE) {

if(curtoken.TType==TokenType.FunDecl) q=fun_decl();

if(curtoken.TType==TokenType.VarDecl) q =var_decl();

p.Sibling = q; p = p.Sibling;

}

Return root;

}

TreeNode var_decl() {

函数program():此函数的作用是进行递归下降分析,生成抽象语法树,返回抽象语法树的根节点root。这个函数中的变量p用来记录前一次所生成的节点,变量q用来记录当前所生成的节点。

TreeNode retnode, nextnode, curnode; retnode = curnode;

retnode.NType = curnode.NType = NodeType.VarDecl;

while(curtoken.TType!=TokenType.SEMI) {

Switch(curtoken.TType) {

case TokenType.INT:

match(TokenType.INT);

break;

case TokenType.ID:

nextnode.NodeStr = curtoken.str;

curnode.Sibling = nextnode; curnode = nextnode;

match(TokenType.ID);

break;

case https://www.360docs.net/doc/b69265259.html,MA:

match(https://www.360docs.net/doc/b69265259.html,MA); break;

default: ParseError();

break;

} }

Match(TokenType.SEMI); Return retnode;

}

函数var_decl():这个函数实现了一个变量声明语句的抽象语法树节点的构建,并且返回其根节点retnode。这里的变量声明仅限于简单变量,且数据类型为整型。

TreeNode fun_decl() {

TreeNode retnode; retnode.NType = NodeType. FunDecl;

While(curtoken.TType!=TokenType. LPAREN) {

Switch(curtoken.TType) {

case TokenType.INT: case TokenType.VOID:

retnode.retType = curtoken.str;

match(curtoken.TType); break;

case TokenType.ID:

retnode.NodeStr = curtoken.str;

match(TokenType.ID); break;

default: ParseError(); break;

} }

match(TokenType.LPAREN);

if(token.TType != TokenType.RPAREN) { //匹配参数列表,第1个子节点

TreeNode paramsNode = param_list();

retnode.child[0] = paramsNode;

}

match(TokenType.RPAREN);

TreeNode stmtNode = compound_stmt();//函数体,第2个子节点

retode.child[1] = stmtNode;

return retnode;

}

函数fun_decl():此函数实现了函数定义的抽象语法树节点的构建,并且返回其根节点retnode。while循环体用来函数定义节点,即具有函数名和返回类型节点信息的语法节点。if语句体用来生成函数定义节点的第一个子节点,参数列表节点。后面的代码完成函数定义节点的第二个子节点,函数体节点的构建。

至此,我们对本系统抽象语法树生成过程的介绍就结束了,在这一章中,我们先后讲解了抽象语法树的基本概念,作用,并结合本系统说明了抽象语法树的设计与基本构建过程。

安全检查符号表抽象语法树Java字节码字节码分析工具论文

安全检查论文:Java程序安全检查工具前端的设计与实现 【中文摘要】本文在分析程序安全检查工具框架的基础上,根据安全检查的特殊需求,给出了一种基于ASM(一种字节码分析工具)构造Java安全检查器前端的方法,并将此方法应用于实际开发过程中。使用此方法构造的前端通过分析Java字节码文件为后端安全检查提供符号表、抽象语法树。文中重点讨论了符号表和抽象语法树的设计与实现。首先,本文针对字节码文件中符号和作用域的特点,设计了适用于Java字节码文件的符号表。其次,针对如何从字节码文件中恢复出表达式和控制流语句结构的问题,设计了模拟字节码指令执行的方法。该方法通过模拟字节码指令的实际执行过程,提取出建立抽象语法树所需的信息,生成抽象语法树。 【英文摘要】According to the specific requirements for the safety check, a method based on a Java bytecode file analyzer ASM, for constructing the front-end of a Java program safety checker is proposed and implemented in this thesis. The front-end provides symbol table and abstract syntax tree for the back-end of the safety checker by analyzing Java bytecode files of the project.The design and implementation of the symbol table and abstract syntax tree are detailedly discussed in the thesis. Firstly, according to t... 【关键词】安全检查符号表抽象语法树 Java字节码字节码

抽象语法树文献综述_V1

抽象语法树 姓名:刘乐 学号:2101470 日期:2011/10/16

抽象语法树(AST) 1.AST的基本概念 在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式[1],这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构,图一是一段源代码的语法书结构,代码见附录一。所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于if-condition-then这样的条件跳转语句,可以使用带有两个分支的节点来表示。. 图一源代码语法树 和抽象语法树相对的是具体语法树(concrete syntax tree),通常称作分析树(parse tree)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。

2.语法分析和语法树 语法分析指的是将代码扫描到一个容器中,然后对该容器中的字符在词法分析的基础上将字段组合成各类语法短语,在结构上分析判断源程序。使用语法分析可以解决词法分析中较难解决的字段的多重意义的问题。[4] 图二词法分析[5] 语法树是在语法分析的基础上,将代码的结构转化成树的形式,可以解决字段的上下文相关的问题。而语法树可以通过许多词法语法解析器自动生成,也解决了手工识别的效率问题。 3.AST的作用 在现代编译器的构造过程中,前端主要实现从源程序到中间形式(Intermediate Representation)的转换,而编译器的后端用来完成从中间形式到具体目标机代码的转换,这是一种广泛采用的编译器构造模型。虽然源程序到目标程序的直接转换是可行的,但是使用独立于具体目标平台的中间形式有以下优点: (1)使用中间形式可以比较容易地构造面向不同目标平台和不同语言的编译器。在不改动已有编译器前端的情况下,为新的目标平台构造一个生成该平台目标程序的后端,就可以构造出新平台的编译器。同样对于一个新的语言,在不改动已有编译器后端的情况下,为新语言构造一个识别该语言的前端,就可以构造出新语言的编译器。 (2)针对中间形式,可以进行独立于目标平台的代码优化。这样可以生成较高质量的目标代码,在此基础上可以对目标代码进行平台相关的优化,进而生成更高质量的目标代码。 使用中间形式的主要缺点是,产生中间代码的编译过程与不产生中间代码的编译过程相比在效率上会显得有些低。这是因为中间代码还要进行再一次的翻译

抽象语法树(AST)

抽象语法树(AST) 抽象语法树(AST) 最近在做一个类JA V A语言的编译器,整个开发过程,用抽象语法树(Abstract SyntaxTree,AST)作为程序的一种中间表示,所以首先就要学会建立相对应源代码的AST和访问AST。Eclipse AST是Eclipse JDT的一个重要组成部分,定义在包org.eclipse.jdt.core.dom中,用来表示JA V A语言中的所有语法结构。 Eclipse AST的总体结构 1、org.eclipse.jdt.core.dom.AST(AST节点类) Eclipse AST的工厂类,用于创建表示各种语法结构的节点。 2、org.eclipse.jdt.core.dom.ASTNode及其派生类(AST类)用于表示JA V A语言中的所有语法结构,在实际使用中常作为AST上的节点出现。 3、org.eclipse.jdt.core.dom.ASTVisitor(ASTVisitor类)Eclipse AST的访问者类,定义了统一的访问AST中各个节点的方法。 详细介绍: 一、AST节点类

整体结构包括CompilationUnit类(编译单元)、TypeDeclaration类(类型声明)、MethodDeclaration类(方法声明); 语句包括Block类(语句块)、ExpressionStatement类(表达式)、IfStatement(if语句)、WhileStatement类(while语句)、EmptyStatement类(空语句)、BreakStatement类和ContinueStatement类; 表达式包括MethodInvocation类(方法调用)、Assignment 类(赋值表达式)(“=”、“+=”、“-=”、“*=”、“/=”)、InfixExpression类(中缀表达式)(“+”、“-”、“*”、“/”、“%”、“==”、“!=”、“<"、“<=”、“>=”、“&&”、“||”。)、PrefixExpression类(前缀表达式)(“+”PLUS “-”MINUS “!”NOT)、ParenthesizedExpression类(带括号的表达式)、NumberLiteral类(整数)、Name类(simple)、MethodInvocation类(方法调用)。 二、AST类 关键是创建编译单元节点,创建类AST的实例。 AST ast = AST.newAST(JLS3); 三、ASTVisitor类 它提供与节点类有关的visit()方法和endVisit()法,与节点类无关的preVisit()方法和postVisit()方法。 boolean visit( T node):这类方法如果返回true,则接着访问

SUIF 抽象语法树

SUIF抽象语法树 1.概述 本次实验中实验三的目标是通过解析语法分析的输出文件来生成中间代码。至于中间代码的形式,我们要求采用抽象语法树。抽象语法树的结构则是借鉴SUIF的中间表示。 2.SUIF编译系统中间表示 总体上来说,SUIF中间表示由三部分组成:抽象语法树(AST)、符号表、和指令表达式。 2.1.SUIF的层级结构 SUIF中间表示组织成4级层次结构,包括:文件级,过程级,高级结构控制级和指令表达式树级。SUIF中间表示的主要部分是一个混合式的抽象语法树(AST)。从层次结构上来看,包括高级结构控制级和指令表达式树级,即除了传统的指令级别的操作,它还包含三个高级的结构:循环,条件声明和数组访问。循环和条件表示类似于抽象语法树,但却是语言无关的。图2-1展示了SUIF中间表示的层级结构:

图2-1:SUIF中间表示的层次结构 2.2.SUIF的符号表示 SUIF的符号表分为global symbal table,file symbal table,proc symbal table和block symbal table这4种。它们也是高到低,逐级的贯穿在了SUIF中间表示前3个层级中。图 2-2则表明了这个层次结构:

图2-2:SUIF的符号表示 2.3.SUIF的面向对象实现 SUIF系统的内核提供了一个面向对象的SUIF中间表示的实现。SUIF库对每个程序表示元素定义了一个C++类,允许我们对隐藏了细节的数据结构提供接口。在这个系统中,各个类之间存在若干相互联系和共享的属性,这由类的继承和动态邦定功能实现。例如:变量、标号和过程都是符号,他们共享相同的符号接口,但过程除了符号还有函数体。除了基本的SUIF数据结构,SUIF的内核中还定义了很多基本的数据结构,包括哈希表,可扩展数组等。对每个类来说,经常需要用到的函数都被定义为成员函数。图2-3给出了主要的SUIF数据

基于抽象语法树的代码静态自动测试方法研究

第34卷增刊Ⅰ 2007年北京化工大学学报 JOURNAL OF BEI J IN G UN IV ERSIT Y OF CHEMICAL TECHNOLO GY Vol.34,Sup.Ⅰ 2007 基于抽象语法树的代码静态自动测试方法研究 高传平1 谈利群1 宫云战2 (11北京图形研究所,北京 100029;21北京邮电大学网络与交换技术国家重点实验室,北京 100876) 摘 要:软件测试是排除软件故障,提高软件质量和可靠性的重要手段。从是否需要执行被测程序角度考虑,软件测试分为静态测试和动态测试。动态测试通过输入测试数据,动态执行程序来发现软件中存在的错误。尽管动态测试能发现部分软件错误,但对于一些特殊类型错误的检测无效。鉴于此,本文采取了一种特殊的静态分析技术来实现对代码的测试。本文首先讨论了传统软件测试方法的缺点和局限性,给出了软件的故障模型,进而提出了基于抽象语法树的静态分析技术,并给出了故障自动检测算法。依据该算法开发了自动化测试工具,给出了实验结果和对比分析,并指出了下一步的研究方向。关键词:软件测试;静态分析;故障;故障模型;语法树中图分类号:TP30218 收稿日期:2007204205 第一作者:男,1977年生,博士生E 2mail :cpgzgy @https://www.360docs.net/doc/b69265259.html, 引 言 软件测试是排除软件故障,提高软件质量和可靠性的重要手段。按是否需要动态执行被测程序考虑,软件测试分为两类,即静态测试和动态测试[1]。静态测试是指不运行实际被测的源程序,而是通过采用其他手段对程序结构进行静态分析,获得程序结构的相关信息,并完成对软件故障的检测;动态测试通过输入测试数据,动态执行程序来发现软件中存在的错误。动态测试只存在于软件生存期的编码阶段之后。动态测试包括两个基本要素:一是被测程序,二是测试数据,程序一次运行所需要的测试数据称为测试用例,所以测试数据是测试用例的集合。尽管动态测试能发现程序中存在的某些方面的错误,但是对于软件本身存在的一些深层次逻辑结构方面的错误或者一些比较特殊的错误的检测是无能为力的。与静态测试相比,动态测试虽然能在软件运行时发现一些软件故障,但又明显具有一定的局限性。 (1)动态测试的结果依赖于测试数据的选择。好的测试数据往往能发现软件中存在的更多的故障,而不好的测试数据很难发现软件中存在的故障,因此测试数据的选择至关重要。 (2)动态测试在很大程度上受测试人员自身素 质和经验的影响。测试人员的经验和素质将会直接影响测试数据的设计,从而间接对测试结果产生影响。 (3)动态测试的结果有时难以复现。某些故障 的发生具有偶然性,这不仅与测试数据的选择有关系,而且与程序的操作逻辑及执行次数有关。 (4)动态测试对于一些特殊类型故障的检测无效。动态测试对于软件本身存在的一些深层次逻辑结构方面的错误或者一些比较特殊错误的检测是无能为力的。如动态测试对于不可达代码的测试无效。 为了能有效地解决上述问题,本文采取了一种特殊的静态分析方法来实现对程序代码的静态检测,尤其是对于那些用动态方法无法检测的软件问题。 1 故障模型 在传统的软件测试方法中,一个故障能否被检测到,是取决于该故障检测概率的大小,对软件中存在的所有故障的检测而言,只能以故障检测的百分比来衡量。如果一种故障的故障检测率比较高,则此种故障比较容易检测,否则故障就不容易被检测。 目前所存在的软件测试方法,其测试理论的基础并不是基于故障模型,而是基于软件中所有可能的故障。这种测试方法的优点是: (1)能够检测软件中的大部分故障;

第三章 抽象语法树

第三章抽象语法树 在现代编译器的构造过程中,前端主要实现从源程序到中间形式(Intermediate Representation)的转换,而编译器的后端用来完成从中间形式到具体目标机代码的转换,这是一种广泛采用的编译器构造模型。虽然源程序到目标程序的直接转换是可行的,但是使用独立于具体目标平台的中间形式有以下优点: (1)使用中间形式可以比较容易地构造面向不同目标平台和不同语言的编译器。在不改动已有编译器前端的情况下,为新的目标平台构造一个生成该平台目标程序的后端,就可以构造出新平台的编译器。同样对于一个新的语言,在不改动已有编译器后端的情况下,为新语言构造一个识别该语言的前端,就可以构造出新语言的编译器。 (2)针对中间形式,可以进行独立于目标平台的代码优化。这样可以生成较高质量的目标代码,在此基础上可以对目标代码进行平台相关的优化,进而生成更高质量的目标代码。 使用中间形式的主要缺点是,产生中间代码的编译过程与不产生中间代码的编译过程相比在效率上会显得有些低。这是因为中间代码还要进行再一次的翻译才能生成目标代码。但是,增加一层中间形式可以使编译器更好地模块化,并且可以在中间形式上做很多优化,这些足以抵消两次翻译所带来的低效率。所以,很多现代的编译器都使用了中间形式,比较常见的中间形式有逆波兰表示,N元表示和树形表示三种。由于,在本系统中,我们使用抽象语法树作为中间形式,所以不再对前两种中间形式做以介绍,感兴趣的读者可以参考《编译原理》(Alfred V.Aho编著)。 3.1 树形表示 树形表示是一种非常流行的中间形式,它与三元式表示有着密切的关系,三元式表示可以看成是树形表示的直接形式。例如,表达式W*X+(Y+Z)的树形表示如图3.1所示: 图3.1 表达式W*X+(Y+Z)的树形表示

相关主题
相关文档
最新文档