• 北航 编译实践 PL/0文法


     

    编译实践-PL编译系统实现

     

     

     

    姓名:

     

    专业:

    计算机科学与技术

    学院:

    软件学院

    提交时间:

    20131225

     

     

     

    北京航空航天大学·软件学院

    编译实践-PL编译系统实现

    1. 实验要求
    • 以个人为单位进行开发,不得多人合作完成。
    • 32个学时。个人无计算机者可以申请上机机时。
    • 细节要求:
      • 输入:符合PL/0文法的源程序(自己要有5个测试用例,包含出错的情况,还要用老师提供的测试用例进行测试)
      • 输出:P-Code
      • 错误信息:参见教材第316页表14.4
      • P-Code指令集:参见教材第316页表14.5
      • 语法分析部分要求统一使用递归下降子程序法实现。
      • 编程语言使用CC++C#Java等。
      • 上交材料中不但要包括源代码(含注释)和可执行程序,还应有完整文档。
    1. PL/0语言描述

    PL/0语言是一种类PASCAL语言,是教学用程序设计语言,它比PASCAL语言简单,作了一些限制。PL/0的程序结构比较完全,赋值语句作为基本结构,构造概念有

    • 顺序执行、条件执行和重复执行,分别由begin/end,if then elsewhile do语句表示。
    • PL0还具有子程序概念,包括过程说明和过程调用语句。
    • 在数据类型方面,PL0只包含唯一的整型,可以说明这种类型的常量和变量。
    • 运算符有+-*/=<><><=>=()
    • 说明部分包括常量说明、变量说明和过程说明。
    1. PL/0语言文法的EBNF表示

    <程序> ::= <分程序>.

    <分程序> ::= [<常量说明部分>][变量说明部分>]{<过程说明部分>}<语句>

    <常量说明部分> ::= const<常量定义>{,<常量定义>};

    <常量定义> ::= <标识符>=<无符号整数>

    <无符号整数> ::= <数字>{<数字>}

    <标识符> ::= <字母>{<字母>|<数字>}

    <变量说明部分>::= var<标识符>{,<标识符>};

    <过程说明部分> ::= <过程首部><分程序>

    <过程首部> ::= procedure<标识符>;

    <语句> ::= <赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<重复语句>|<>

    <赋值语句> ::= <标识符>:=<表达式>

    <表达式> ::= [+|-]<>{<加法运算符><>}

    <> ::= <因子>{<乘法运算符><因子>}

    <因子> ::= <标识符>|<无符号整数>|'('<表达式>')'

    <加法运算符> ::= +|-

    <乘法运算符> ::= *|/

    <条件> ::= <表达式><关系运算符><表达式>|odd<表达式>

    <关系运算符> ::= =|<>|<|<=|>|>=

    <条件语句> ::= if<条件>then<语句>[else<语句>]

    <当型循环语句> ::= while<条件>do<语句>

    <过程调用语句> ::= call<标识符>

    <复合语句> ::= begin<语句>{;<语句>}end

    <重复语句> ::= repeat<语句>{;<语句>}until<条件>

    <读语句> ::= read'('<标识符>{,<标识符>}')'

    <写语句> ::= write'('<标识符>{,<标识符>}')'

    <字母> ::= a|b|...|X|Y|Z

    <数字> ::= 0|1|2|...|8|9

    注意:

    数据类型:无符号整数

    标识符类型:简单变量(var)和常数(const)

    数字位数:小于14

    标识符的有效长度:小于10

    过程嵌套:小于3

    1. PL/0语言的语法图描述

    1-1 程序语法描述图

    1-2 分程序语法描述图

     

    1-6 项语法描述图

    1-7 因子语法描述图

     

    1. PL/0编译系统结构

      1-8 PL/0编译程序和解释执行过程

       

      PL/0编译程序函数定义层次结构:

      pl0

          error

          getsym

              getch

          gen

          test

          block

              enter

              position

              constdeclaration

              vardeclaration

              listcode

              statement

                  expression

                      term

                          factor

                  condition

          interpret

              base           

    下面介绍这些过程(函数)的作用。

    pl0

    主程序

    error

    出错处理,打印出错位置和错误代码

    getsym

    词法分析,读取一个单词

    getch

    取字符

    gen

    生成P-code指令,送入目标程序区

    test

    测试当前单词符号是否合法

    block

    分程序分析处理

    enter

    登记符号表

    position

    查找标识符在符号表中的位置

    constdeclaration

    常量定义处理

    vardeclaration

    变量定义处理

    listcode

    列出p-code指令清单

    statement

    语句部分分析处理

    expression

    表达式分析处理

    term

    项分析处理

    factor

    因子分析处理

    condition

    条件分析处理

    interpret

    P-code解释执行程序

    base

    通过静态链求出数据区的基地址

    1. PL/0编译程序的词法分析

      PL/0编译系统中所有的字符,字符串的类型为,如下表格:

      保留字

      begin, end, if,then, else, const,procedure,

      var,do,while, call,read, write, repeat, until

      算数运算符

      + ,—,*/

      比较运算符

        <> , < ,<= , >, >= ,=

      赋值符

      := , =

      标识符

      变量名,过程名,常数名

      常数

      10,25等整数

      界符

      ',','.',';','(',')'

      PL/0的词法分析程序Scanner.getsym()由语法分析程序调用,主要功能为:

    • 跳过空格字符。
    • 识别单词符号,返回单词类型(按照在Symbol.java中定义的编译系统的字符编号,返回类型码)
    • 特别的,对于编译系统的保留字符(例如:const, if, then等)需要查找系统的保留字符表word[],为了加快查找速度,调用系统的二分搜索法Arrays.binarySearch().
    • 另外,如果读取的字符为数字,需要将该字符转换成整数值(调用公式num = 10 * num + (ch - '0');),再存入符号表的Value区域.

      Scanner.getsym()是调用扫描输入的源程序。主要功能如下:

    • 优化读取字符效率,每次读取一行源程序,存入缓冲区line,因此设置lineLength为源程序当前行的长度,chCount标志当前正在读取的字符位置
    • 采用"单符号先行"技术,在识别完每个符号的类型后,必须再度入下一个字符,以保证下一次再调用getsym()时,curCh保存的是该符号的首字符

    图1- 词法分析程序的状态转换图

    1. PL/0编译程序的符号表管理
      • 符号表结构
    • 符号表中每一条记录所对应的结构:

      public class Item {

      public static final int constant = 0;

      public static final int variable = 1;

      public static final int procedure = 2;

      String name; //名字

      int type; //类型,const var or procedure

      int value; //数值,const使用

      int level; //所处层,varprocedure使用

      int addr; //地址,varprocedure使用

      int size; //需要分配的数据区空间,仅procedure使用

      }

    符号表类SymbolTable中用数组存储符号表,再分配一个指针tablePtr指向当前符号表的末尾。

    public class SymbolTable {

    //有效的符号表大小

    public int tablePtr = 0;

        //名字表

        public Item[] table = new Item[tableMax];

        ... ...

    }

    举例:

    PL/0代码样例:

    CONST A=35B=49
    VAR C
    DE
    PROCEDURE P

    VAR G
    XYZ

    此时的符号表内容:

    NAMEA

    KIND:CONSTANT

    VAL:35

       

    NAMEB

    KIND:CONSTANT

    VAL:49

       

    NAMEC

    KIND:VARIABLE

    LEVEL:LEV

    ADDR:DX

     

    NAMED

    KIND:VARIABLE

    LEVEL:LEV

    ADDR:DX+1

     

    NAMEE

    KIND:VARIABLE

    LEVEL:LEV

    ADDR:DX+2

     

    NAMEP

    KIND:PROCEDURE

    LEVEL:LEV

    ADDR:

    SIZE:7

    NAMEG

    KIND:VARIABLE

    LEVEL:LEV+1

    ADDR:DX

     

     

        

    • 符号表管理
      • 登记(在符号表中插入一项)

    /**

    * 把某个符号登录到名字表中,从1开始填,0表示不存在该项符号

    * @param sym 要登记到名字表的符号

    * @param k 该符号的类型:const, var ,procedure

    * @param lev 名字所在的层次

    * @param dx 当前应分配的变量的相对地址,注意dx要加一

    */

    public void enter(Symbol sym,int type,int lev, int dx)

     

    • 查询

    /**

    * 在名字表中查找某个名字的位置

    *从后往前查,这样符合嵌套分程序名字定义和作用域的规定

    * @param idt 要查找的名字

    * @return 如果找到则返回名字项的下标,否则返回0

    */

    public int position(String idt)

    1. PL/0编译程序的语法分析

    1- 语法调用关系图

    采用不带回溯的递归子程序法,对于语言的文法要求:

    1. 该文法必须是非左递归。
    2. 文法的非终结符,其规则右部所生成的first集合两两不相交
      1. 若文法具有形如,则

    递归子程序设计实例

    • <expression>::=[+|-]<term>{(+|-)<>}

    void expression(BitSet fsys, int lev) {

    if (symtype == plus || symtype == minus) {

        int adop = symtype;

        nextsym();

        term(nxtlev, lev);

        if (adop == minus)

        gen(OPR, 0, 1);

    } else

        term(nxtlev, lev);

        //分析{<加法运算符><>}

    while (symtype == plus || symtype == minus) {

       int adop = symtype;

       nextsym();

       term(nxtlev, lev);

       gen(OPR, 0, adop);

      }

    }

    • <term>::=<factor>{(*|/)<term>} 

    void term(BitSet fsys, int lev) {

      factor(nxtlev, lev);

      //分析{<乘法运算符><因子>}

      while (symtype == mul || symtype == div) {

        int mop = sym.symtype;

        nextsym();

        factor(nxtlev, lev);

        gen(OPR, 0, mop);

      }

    }

    • <factor>::=<ident>|<number>|'('<experssion>')'

    void factor(BitSet fsys, int lev) {

    if (symtype == ident) {

       int index = table.position(sym.id);

       if (index > 0) {

         Item item = table.get(index);

         switch (item.type) {

         case constant:

         gen(LIT, 0, item.value);

         break;

         case variable:

         gen(LOD, lev - item.lev, item.addr);

         break;

        }

      }

      nextsym();

    } else if (symtype == number) {

       gen(LIT, 0, num);

       nextsym();

    } else if (symtype == lparen) {

       nextsym();

       expression(nxtlev, lev);

       if (symtype == rparen)

          nextsym();

    }

    1. PL/0编译程序的目标代码结构和代码生成
    • 代码结构

            P-code 语言:一种栈式机的语言。此类栈式机没有累加器和通用寄存器,有一个栈式存储器,有四个控制寄存器(指令寄存器 I,指令地址寄存器 P,栈顶寄存器 T和基址寄存器 B),算术逻辑运算都在栈顶进行。

    F

    L

    A

            指令格式

           F :操作码

           L :层次差(标识符引用层减去定义层)

           A :不同的指令含义不同

    5 P-code 指令的含义

    指令

    具体含义

    LIT 0,a

    取常量a放到数据栈栈顶

    OPR 0,a

    执行运算,a表示执行何种运算(+ - * /)

    LOD l,a

    取变量放到数据栈栈顶(相对地址为a,层次差为l)

    STO l,a

    将数据栈栈顶内容存入变量(相对地址为a,层次差为l)

    CAL l,a

    调用过程(入口指令地址为a,层次差为l)

    INT 0,a

    数据栈栈顶指针增加a

    JMP 0,a

    无条件转移到指令地址a

    JPC 0,a

    条件转移到指令地址a

    //pcode类的结构

    public class Pcode{

    //虚拟机代码指令

    public int f;

    //引用层与声明层的层次差

    public int l;

    //指令参数

    public int a;

    }

    //存放虚拟机代码的数组

    public Pcode[] pcodeArray;

    //生成虚拟机代码

    public void gen(int f, int l, int a) {

        pcodeArray[arrayPtr++] = new Pcode(f, l, a);

    }

    • 代码生成与地址返填

        对于if then [else],while dorepeat until语句,要生成跳转指令,故采用地址返填技术。

    • if-then-else语句的目标代码生成模式:

    if <condition> then <statement>[else]

     

    <condition>

     

    JPC addr1

     

    <statement>

    addr1:

    [else]

    • while-do语句的目标代码生成模式:

    while <condition> do <statement>

    addr2:

    <condition>

     

    JPC addr3

     

    <statement>

     

    JPC addr2

    addr3:

     
    • repeat-until语句的目标代码生成模式:

    repeat <statement> until <condition>

    addr4:

    <statement>

     

    <condition>

     

    JPC addr4

    注意:由于OPR指令设计复杂,故进一步解释:

    (1).OPR 0 0

    RETUEN

    (stack[sp + 1] ß base(L);

    sp ß bp - 1;

    bp ß stack[sp + 2];

    pc ß stack[sp + 3];)

    (2).OPR 0 1

    NEG

    (- stack[sp] )

    (3).OPR 0 2

    ADD

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] + stack[sp + 1])

    (4).OPR 0 3

    SUB

    (sp ß sp – 1 ;

    stack[sp] ßstack[sp] - stack[sp + 1])

    (5).OPR 0 4

    MUL

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] * stack[sp + 1])

    (6).OPR 0 5

    DIV

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] / stack[sp + 1])

    (7).OPR 0 6

    ODD

    (stack[sp] ß stack % 2)

    (8).OPR 0 7

    MOD

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] % stack[sp + 1])

    (9).OPR 0 8

    EQL

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] == stack[sp + 1])

    (10).OPR 0 9

    NEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] != stack[sp + 1])

    (11).OPR 0 10

    LSS

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] < stack[sp + 1])

    (12).OPR 0 11

    GEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] >= stack[sp + 1])

    (13).OPR 0 12

    GTR

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] > stack[sp + 1])

    (14).OPR 0 13

    LEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] <= stack[sp + 1])

    (15).OPR 0 14

    print (stack[sp]);

    sp ß sp – 1;

    (16).OPR 0 15

    print (' ');

    (17).OPR 0 16

    scan(stack[sp]);

    sp ß sp + 1;

     
    1. PL/0编译程序的语法错误处理

    8.1错误处理的原则

                  尽可能准确指出错误位置和错误属性

                 尽可能进行校正

                 短语层恢复技术

                     在进入某个语法单位时,调用TEST函数, 检查当前符号是否属于该语法单位的开始符号集合.

                     在语法单位分析结束时,调用TEST函数, 检查当前符号是否属于调用该语法单位时应有的后跟符号集合.

    Test()函数的定义:

    /**

    * @param s1 需要的符号

    * @param s2 不需要的符号,添加一个补救集合

    * @param errcode 错误号

    */

    void test(BitSet s1, BitSet s2, int errcode) {

    if (!s1.get(sym.symtype)) {

       Err.report(errcode);

    //当检测不通过时,不停地获取符号,直到它属于需要的集合

      s1.or(s2); //s2集合补充进s1集合

      while (!s1.get(sym.symtype)) {

      nextsym();

      }

    }

    }

    注意:FOLLOW集合随着调用的深度增加,逐层增加,且与调用的位置相关。

    举例:

    write语句的下一层:

    <statement>::=write '('<identity>{,identity}')'

    fsys={[rparen, comma]+fsys};

    factor语句的下一层

    <factor>::=… …|'('<expression>')'

    fsys={[rparen]+fsys};

    1- PL/0文法非终结符的开始符号集与后继符号集

    非终结符

    FIRST(S)

    FOLLOW(S)

    分程序

    const var procedure ident if call begin while read write repeat

    . ;

    语句

    ident call begin if while read write until

    . ; end

    条件

    odd + - ( ident number

    then do

    表达式

    = + - ( ident number

    . ; R end then do

    ident number (

    . ; R + - end then do

    因子

    ident number (

    . ; R + - * / end then do

    PL/0编译系统中,所定义的36种错误类型,如下列举:

     

    PL/0语言的出错信息表

    出错编号

    出错原因

    1

    常数说明中的"="写成"="

    2

    常数说明中的"="后应是数字。

    3

    常数说明中的标识符后应是"="

    4

    const ,var, procedure后应为标识符。

    5

    漏掉了''''

    6

    过程说明后的符号不正确(应是语句开始符,或过程定义符)

    7

    应是语句开始符。

    8

    程序体内语句部分的后跟符不正确。

    9

    程序结尾丢了句号'.'

    10

    语句之间漏了''

    11

    标识符未说明。

    12

    赋值语句中,赋值号左部标识符属性应是变量。

    13

    赋值语句左部标识符后应是赋值号'='

    14

    call后应为标识符。

    15

    call后标识符属性应为过程。

    16

    条件语句中丢了'then'

    17

    丢了'end"''

    18

    while型循环语句中丢了'do'

    19

    语句后的符号不正确。

    20

    应为关系运算符。

    21

    表达式内标识符属性不能是过程。

    22

    表达式中漏掉右括号')'

    23

    因子后的非法符号。

    24

    表达式的开始符不能是此符号。

    31

    数越界。

    32

    read语句括号中的标识符不是变量。

    33

    格式错误,应为右括号

    34

    格式错误,应为左括号

    35

    read()中的变量未声明

    36

    变量字符过长

     

    1. PL/0编译程序的目标代码解释执行和存储分配
    • pcode解释器的结构
    1. . 目标代码存放在数组pcodeArray
    2. . 定义一维整型数组runtimeStack作为运行栈
    3. .栈顶寄存器(指针)sp;
    4. .基址寄存器(指针)bp;
    5. .程序地址寄存器 pc;
    6. .指令寄存器 index.
    • 运行栈的存储分配
    1. .SL:静态链,指向定义该过程的直接外过程(或主程序)运行时最新数据段的基地址。
    2. .DL:动态链,指向调用该过程前正在运行过程的数据段基地址。
    3. .RA:返回地址,记录调用该过程时目标程序的断点,即调用过程指令的下一条指令的地址

                      例如,假定有过程 ABC,其中过程 C 的说明局部于过程 B,而过程 B 说明局部于过程 A,程序运行时,过程 A 调用过程 B,过程 B 则调用过程 C,过 C 又调用过程 B,如下图所示:

    9-1过程说明嵌套图     过程调用图     表示 A 调用 B

                                    从静态链的角度我们可以说A是在第一层说明,B是在第二层说明,C则是在第三层说明。

                                   若在B中存取A中说明的变量a,由于编译程序只知道A,B间的静态层差为1,如果这时沿着动态链下降一步,将导致对C的局部变量的操作。

    为防止这种情况发生,设置第二条链,将各个数据区连接起来。我们称之为动态链(dynamic linkDL这样,编译程序所生成的代码地址,指示着静态层差和数据区的相对修正量。下面是过程 AB C 运行时刻的数据区图示:

    P-code解释执行过程:

    (1).LIT 0 A

    sp ß sp +1;

    stack[sp] ß A;

    (2).LOD L A

    sp ß sp +1;

    stack[sp] ß stack[ base(L) + A];

    (3).STO L A

    stack[ base(L) + A] ß stack[sp];

    sp ß sp -1;

    (4).CAL L A

    stack[sp + 1] ß base(L);

    stack[sp + 2] ß bp;

    stack[sp + 3] ß pc;

    bp ß sp + 1;

    pc ß A;

    (5).INT 0 A

    sp ß sp + A;

    (6).JMP 0 A

    pc = A;

    (7).JPC 0 A

    if stack[sp] == 0

    {

    pc ß A;

    sp ß sp - 1;

    }

    (8).OPR 0 0

    RETUEN

    (stack[sp + 1] ß base(L);

    sp ß bp - 1;

    bp ß stack[sp + 2];

    pc ß stack[sp + 3];)

    (9).OPR 0 1

    NEG

    (- stack[sp] )

    (10).OPR 0 2

    ADD

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] + stack[sp + 1])

    (11).OPR 0 3

    SUB

    (sp ß sp – 1 ;

    stack[sp] ßstack[sp] - stack[sp + 1])

    (12).OPR 0 4

    MUL

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] * stack[sp + 1])

    (13).OPR 0 5

    DIV

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] / stack[sp + 1])

    (14).OPR 0 6

    ODD

    (stack[sp] ß stack % 2)

    (15).OPR 0 7

    MOD

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] % stack[sp + 1])

    (16).OPR 0 8

    EQL

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] == stack[sp + 1])

    (17).OPR 0 9

    NEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] != stack[sp + 1])

    (18).OPR 0 10

    LSS

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] < stack[sp + 1])

    (19).OPR 0 11

    GEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] >= stack[sp + 1])

    (20).OPR 0 12

    GTR

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] > stack[sp + 1])

    (21).OPR 0 13

    LEQ

    (sp ß sp – 1 ;

    stack[sp] ß stack[sp] <= stack[sp + 1])

    (22).OPR 0 14

    print (stack[sp]);

    sp ß sp – 1;

    (23).OPR 0 15

    print (' ');

    (24).OPR 0 16

    scan(stack[sp]);

    sp ß sp + 1;

     

    系统运行环境

                         硬件配置:lenovo-g470

                        软件配置:netbeans-7.4

                        软件运行环境:java JDK-1.7

    附录:

            样例测试

    //test.pl0

    //generated p-code

    const z=0;

    var head,foot,cock,rabbit,n;

    begin

        n := z;

        cock := 1;

        while cock <= head do

        begin

            rabbit :=head-cock;

            if cock*2+rabbit*4=foot then

            begin

                write(cock,rabbit);

                n:=n+1

            end;

            cock:=cock+1

        end;

        if n=0 then write(0,0)

    end.

    0 JMP 0 21

    1 JMP 0 2

    2 INT 0 4

    3 LOD 1 3

    4 STO 0 3

    5 LOD 1 4

    6 STO 1 3

    7 LOD 0 3

    8 STO 1 4

    9 OPR 0 0

    10 JMP 0 11

    11 INT 0 3

    12 LOD 1 3

    13 LOD 1 3

    14 LOD 1 4

    15 OPR 0 5

    16 LOD 1 4

    17 OPR 0 4

    18 OPR 0 3

    19 STO 1 3

    20 OPR 0 0

    21 INT 0 7

    22 LIT 0 45

    23 STO 0 3

    24 LIT 0 27

    25 STO 0 4

    26 CAL 0 11

    27 LOD 0 3

    28 LIT 0 0

    29 OPR 0 9

    30 JPC 0 34

    31 CAL 0 2

    32 CAL 0 11

    33 JMP 0 27

    34 LOD 0 4

    35 STO 0 5

    36 LIT 0 45

    37 LIT 0 27

    38 OPR 0 4

    39 LOD 0 5

    40 OPR 0 5

    41 STO 0 6

    42 LOD 0 5

    43 OPR 0 14

    44 LOD 0 6

    45 OPR 0 14

    46 OPR 0 15

    47 OPR 0 0

    实践报告+源代码链接:

     http://files.cnblogs.com/ZJUT-jiangnan/compiler.rar

  • 相关阅读:
    C# Thread Lambda
    C#中Invoke的用法
    C#抽象类和抽象方法
    SQL Server2005杂谈(2):公用表表达式(CTE)的递归调用
    改变自己的128种方法
    小技巧
    Linux学习第一章作业.zxs
    Linux学习第二、三章作业.zxs
    Linux 系统管理 04—账号管理
    Mysql中的in和find_in_set的区别?
  • 原文地址:https://www.cnblogs.com/ZJUT-jiangnan/p/3494501.html
Copyright © 2020-2023  润新知