【编译原理】语法分析器代码分析报告


一、语法分析器设计思路说明

该语法分析器基于LL(1)文法和递归下降分析方法进行设计。

LL(1)文法

       LL(1)文法是进行不带回溯的确定的自上而下分析所必须满足的文法条件。其包含三方面的要求:
1、文法不含左递归
2、对于每个非终结符A的各个候选式的终结首符号集两两不相交。即,若
                                                       A→a1|a2|…|an

                               FIRST ( ai ) ∩ FIRST ( aj )=∅,其中1≤i, j≤n,且i≠j
3、对文法的每个非终结符A,若它的候选式的首终结符集包含ε,则
                                               FIRST ( A ) ∩ FOLLOW( A ) = ∅
       如果一个文法G满足上述三个条件,就称文法G是LL(1)文法。
判断LL(1)文法的步骤
1、求储能推出ε的非终结符
2、计算FIRST集
3、计算FOLLOW集
4、计算SELECT集
       SELECT交集为空则为LL(1)文法。

递归下降分析方法

       递归下降分析法是一种自顶向下的分析方法,文法的每个非终结符对应一个递归过程(函数)。分析过程就是从文法开始符出发执行一组递归过程(函数),这样向下推导直到推出句子;或者说从根节点出发,自顶向下为输入串寻找一个最左匹配序列,建立一棵语法树。
       递归下降分析方法的基本思想是:为每个非终结符构造一个子程序,每个子程序的函数体按非终结符的候选式分情况展开,遇到终结符就进行比较,看是否与输入符号串匹配;遇到非终结符就调用相应的非终结符的子程序。分析过程从调用文法开始符号对应的子程序开始,直到所有非终结符都展开为终结符并得到匹配为止。如果分析过程中达到这一步则表明分析成功,否则表明输入符号串有语法错误。由于文法是递归定义的,因此子程序也是递归的。
对应于每个非终结符U(假定U的候选式是U→U1|U2|…|Un)的函数需要完成两项任务:
       1.根据输入符号决定使用U的哪个候选式进行分析。如果当前面临的输入符号token在候选式U的 First集中,即 token∈ First(Ui),则选择使用Ui进行分析。如果当前输人符号token不在任何一个First(Ui)中,但有ε∈First(U,),则判断该输入符号是否在Follow(U)中,如果在,ε将被使用。
函数原型:

void U( ){
token = GetNextToken( );//从输入的 token串中读取一个符号到token中,输入指针下移
if (token ∈ First (U1) )U1( );
else if ( token∈ First (U2))U2();
....
else if ( (ε∈ First(U)) && ( token∈ Follow(U)))
....else error();//没有找到匹配项,出错处理

       2.对应于某个候选式U,(假定U是形如X1X2…Xn的候选式)的处理Ui(),就是通过顺序处理该候选式Ui的每个符号Xi来完成其功能的。每个Xi的处理要根据Xi是终结符或非终结符来确定;若Xi是非终结符,就调用该非终结符对应的函数Xi();若Xi是终结符,就直接调用match()函数进行比较,如果Xi和读入的符号匹配,就读入下一个输入符号,如果不匹配,则报告错误。
函数原型:

void Ui()
{
  if(X1∈ Vn) X1();//处理X1
  else match(token);
  if(X2∈ Vn)  X2();//处理X2
  else match(token);
            ...
  if(Xn∈ Vn)  Xn();//处理Xn
  else match(token);
}

       3.match()的功能是判断当前输入符号token是否与文法推导中出现的符号Xi相等,若相等,读取下一个输入符号,否则出错。
函数原型:

void match (char * token)
{
       if(Xi == token){  //与当前输入符号相同,即匹配
             token = GetNextToken( );//取下一个符号到token中
             return;    //匹配时直接返回
        }
              else error ( );   //不匹配,进行出错处理   
}

二、TinyScript的语法要求

1、TinyScript语言中的表达式

       根据运算符的不同,表达式可分为算术表达式,关系表达式,布尔表达式和赋值表达式。这四种类型表达式的优先级,算术表达式优先级比关系表达式高,关系表达式比布尔表达式高,布尔表达式比赋值表达式高。

算术表达式

       算术表达式对数据进行运算,算数运算符包括+、-、*、/、%,由于优先级和结合性的存在,表达式一般由递归规则定义,文法定义如下:

<算术表达式><算术表达式> + <><算术表达式> - <><>
<><> * <因子><> / <因子><因子>
<因子><算术量>- <因子>
<算术量><整数><标识符>│( <算术表达式>

关系表达式

       关系表达式的运算对象是算术表达式,运算符有>,<,>=,<=,==,!=,六种
关系表达式的文法定义如下:

<算术表达式><算术表达式><关系运算符><算术表达式>
<关系运算符>>|<|>=|<=|==|!=

布尔表达式

       布尔表达式本质是数值运算,运算对象是关系表达式,运算符有,非(!)、与(&&)、或(||),布尔表达式文法定义如下:

<布尔表达式><布尔表达式> || <布尔项><布尔项>
<布尔项><布尔项> && <布因子><布因子>
<布因子><布尔量>│! <布因子>
<布尔量><布尔常量><标识符>│( <布尔表达式> )│

赋值表达式

       赋值表达式含义是把赋值号右边的表达式的值赋值给左边的一个标识符。赋值表达式的文法如下:

<赋值表达式><标识符>=<表达式>

相关代码如下:

var source = "1+2+3+4".chars().mapToObj(x->(char)x);
var lexer = new Lexer();
var it = new PeekTokenIterator(lexer.analyse(source).stream());
var expr = SimpleParser.parse(it);

2、TinyScript语言中的语句

       TinyScript语言的语句分为声明语句和执行语句。声明语句又分为变量声明、常量声明和函数声明。执行语句包括数据处理语句、控制语句和复合语句。其文法如下:

<语句><赋值句><if><while><repeat句><复合句>
<赋值句><标识符> := <算术表达式>
<if>if <布尔表达式> then <语句>if <布尔表达式> then <语句> else <语句>
<while>while <布尔表达式> do <语句>
<repeat句> → repeat <语句> until <布尔表达式>
<复合句> → begin <语句表> end
<语句表><语句><语句表><语句>

相关代码如下:

if(token.isVariable() && lookahead != null && lookahead.getValue().equals("=")) {
            return AssignStmt.parse(it);
        } else if(token.getValue().equals("var")) {
            return DeclareStmt.parse(it);
        } else if(token.getValue().equals("func")) {
            return FunctionDeclareStmt.parse( it);
        } else if(token.getValue().equals("return")) {
            return ReturnStmt.parse(it);
        } else if(token.getValue().equals("if")) {
            return IfStmt.parse(it);
        } else if(token.getValue().equals("{")) {
            return Block.parse(it);
        }else {
            return Expr.parse(it);
        }

3、TinyScript语言中的函数

       TinyScript语言中,函数分为函数声明、函数定义、和函数调用。函数定义包括函数返回值,函数名和行参列表以及对应的语句表。其文法如下:

<函数定义><函数类型><标识符><复合语句>
<函数定义行参列表><函数定义形参>│ε

相关代码如下:

public void test_endToken(){
        var source = "abcdefg";
        var it = new PeekIterator<Character>(source.chars().mapToObj(c -> (char)c), (char)0);
        var i = 0;
        while(it.hasNext()) {
            if(i == 7) {
                assertEquals((char)0, it.next());
            } else {
                assertEquals(source.charAt(i++), it.next());
            }
        }
    }

4、TinyScript语言中的程序

       程序用来定义一个合法的TinyScript语言的结构,由零到多个声明语句,main()函数,零到多个函数顺序构成,每个程序必须有一个main()函数,其文法定义为:

<程序><声明语句>main()<复合语句><函数块>
<函数块><函数定义>│ε

三、语法分析器代码分析

       语法分析器在词法分析器的commom和lexer包的基础上,新增了用于语法分析的parser包,其中最关键的是parser.ast包,把语法规则抽象为不同的类,通过这些类完成递归下降的语法分析。
抽象语法树的继承关系如下图
继承关系图

ASTNode:抽象语法树
Expr:表达式
Factor:因子**所谓因子就是操作符两边可以计算的东西,变量或者数字
Scalar:标量
Variable:变量
Stmt:语句
Block:语句块
IfStmtif语句
AssignStmt:赋值语句
DeclareStmt:声明语句
ForStmt:for循环语句
FunctionDeclareStmt:声明函数语句

对AST类的子类进行分析:

      1.Stmt类的parseStmt(PeekTokenIterator it)方法对文法进行了抽象,Stmt类是语句类,是包含IfStmt、AssignStmt、DeclareStmt、ForStmt、FunctionDeclareStmt的父类,通过parseStmt(PeekTokenIteratorit)方法来判断并返回对应的子类语句,首先定义lookhead,接下来判断lookahead是否为空,且loohead.getValue()是否与“=”匹配,若匹配,则返回赋值语句的语法分析;若不满足条件,则进行后面的判断:若token.getValue()与“var”匹配,则返回声明语句;与“func”匹配返回声明函数语句…以此类推,若上述都不满足,则返回Expr.parse(it)匹配的一个表达式来完成对Stmt语句的具体子类的判断及返回。


      2.DeclareStmt类的parse(PeekTokenIterator it)方法对文法进行了抽象,首先通过it.nextMatch(“var”)匹配固定的关键字var,然后通过Factor.parse(it)从输入流中匹配一个元素(变量名)。如果匹配成功,则继续通过it.nextMatch(“=”)匹配声明语句中的语法单元“=”,并通过Expr.parse(it)匹配一个表达式以完成声明语句的识别分析。


       3.AssignStmt类的parse(PeekTokenIterator it)方法对文法进行了抽象,首先通过Factor.parse(it)从输入流中匹配一个元素(变量名)。如果匹配成功,则继续通过it.nextMatch(“=”)匹配声明语句中的语法单元“=”,并通过Expr.parse(it)匹配一个表达式以完成赋值语句的识别分析。


      4.IfStmt类的parseIF(PeekTokenIterator it)方法对文法进行了抽象,首先通过it.nextMatch(“if”)匹配固定的关键字if ,然后通过it.nextMatch(“(”)匹配if后的左括号,并通过Expr.parse(it)匹配一个表达式,接下来通过it.nextMatch(“)”)匹配if后的右括号,然后通过Block.parse(it)匹配将要执行的程序语句块,最后通过parseTail方法来完成对else的匹配以完成IF判断语句的识别分析。

      IfStmt类parseTail(PeekTokenIterator it)方法对文法进行了抽象,首先通过it.nextMatch(“else”)匹配固定的关键字else,然后通过判断语句,如果lookhead获取的值与“{”匹配,则返回通过Block.parse(it)匹配将要执行的程序语句块,如果lookhead获取的值与“if”匹配,则返回通过parseIF(it)函数来执行IF判断语句的识别分析,再者就返回空值。


       5.FunctionDeclareStmt类的parseIF(PeekTokenIterator it)方法对文法进行了抽象,首先通过it.nextMatch(“func”)匹配固定的关键字func,然后通过it.nextMatch(“(”)匹配func后的左括号,并通过FunctionArgs.parse( it)匹配一个字符串变量名,接下来通过it.nextMatch(“)”)匹配func后的右括号,然后通过Block.parse(it)匹配将要执行的程序语句块,将匹配到的程序语句块Block使用addChild方法加入func函数中以完成对func声明函数语句的识别分析。


       6.ReturnStmt类的parse(PeekTokenIterator it)方法对文法进行了抽象,首先通过it.nextMatch(“return”)匹配固定的关键字return,然后通过Expr.parse(it)匹配一个表达式。如果表达式不为空值,则将表达式加入stmt语句以完成返回语句的识别分析。


      7.CallExpr类的parse(ASTNode factor, PeekTokenIterator it)方法对文法进行了抽象,传入了语法树类的变量。首先将Factor因子通过addChild()方法加入expr表达式中,接下来通过it.nextMatch(“(”)匹配表达式的左括号,接下来通过Expr.parse(it)匹配一个表达式,如果该表达式不为空值,则将表达式p加入expr表达式中,在while语句中嵌套了一个if判断语句,若it.peek().getValue()不与右括号匹配,则通过it.nextMatch(“)”)匹配表达式的右括号,while语句结束后再次通过it.nextMatch(“)”)匹配表达式的右括号,最后返回expr语句以完成Expr表达式语句的识别分析。


      8.ASTNode类是AST节点的抽象超类。由于AST节点有非终结(非叶子)节点和终结节点(叶子节点),其中叶子节点是由token直接生成的,非叶子节点是由文法条目规约时产生的。因此,不妨首先创建一个抽象的ASTNode类,再创建AST终结节点与非终结节点类,继承这个抽象的ASTNode类。ASTNode中的getChild(int
index)方法是获取子节点的方法,如果传入的index的值大于children.size()则返回空值,否则返回children.get(index)来获取子节点的标记值。addChild(ASTNode
node)方法主要是对子节点加入父节点进行了程序编写。


       9.ASTNodeTypes类列举了各种类型的枚举定义,其中各个类型的定义如下:
BLOCK//代码块
BINARY_EXPR//二项表达式
1+1 UNARY_EXPR//一元表达式
++i VARIABLE//变量
SCALAR//值类型
IF_STMT//if语句
WHILE_STMT//while语句
FOR_STMT//for语句
ASSIGN_STMT//赋值语句
FUNCTION_DECLARE_STMT//定义函数的语句
DECLARE_STMT //声明语句


      10.Program类是继承Block语句块的解析入口类,首先是定义一个名为block的Program类变量,接下来定义一个ASTNode语法树类的名为stmt的变量,接下来通过解析stmt的语句,不为空后,继续来解析{}中的语句,以完成解析语句的入口类的编写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢热型网友.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值