精华内容
下载资源
问答
  • PL/0语言编译程序采用以语法分析为核心、一遍扫描的编译方法。 一.PL/0语言建立一个词法分程序GETSYM(函数) 二.PL/0语言建立一个语法分析程序BLOCK(函数) 三.建立一个解释执行目标程序的函数 内含所有源码和...
  • 编译原理PL/0 语言编译器分析实验报告
  • PL/0编译程序和目标程序的解释执行程序可以采用C、C++、Java等高级语言书写。 其编译过程采用一趟扫描方式,以语法分析程序为核心,词法分析和代码生成程序都作为一个独立的过程,当语法分析需要读单词时就调用...
  • 编译原理pl0项目

    千次阅读 2018-12-20 19:33:32
    文章目录编译原理pl0修改项目要求环境设置part1修改文件流的修改符号的修改变量名的替换goto语句运算符的修改part2修改参考链接github地址 编译原理pl0修改 项目要求 除了文档里面的要求,还有一些隐藏要求已经被...

    编译原理pl0修改

    项目要求

    除了文档里面的要求,还有一些隐藏要求已经被确认了。

    • 可以修改pl0源代码,一般是认为修改pl0编译程序,使得可以运行pl0源代码,但是实际上可以一起修改pl0源代码和pl0编译程序,使得最后可以运行。
    • read和write语句,能够实现读入integer数据和输出integer数据即可,而且从键盘或文件输入输出只要完成一项即可,没必要键盘和文件流都完成,此项目完成的是键盘输入和输出。

    环境设置

    我是用的Free Pascal IDE,以及Vscode,Vscode可以下载pascal的编译和格式插件,然后在user setting里面设置变量,格式化之后代码格式就会很整齐。
    环境设置
    {[^}]+}正则表达式替换空,可以去掉所有的注释。
    ^\s*(?=\r?$)\n正则表达式替换空,可以去掉所有的空行。

    part1修改

    part1的要求就是可以运行就行了。

    文件流的修改

    首先是program PL0 ( input, output);,第一行参数可要可不要,可以随后在后面设置,其中有一行代码page(output);代表着输出文件到output里面,可以直接删掉,因为page没有定义。然后在一开始var申明里面加上fin,fout: text;,然后直接全局替换掉input即可,顺便加上sfile,dfile: string;用于表示文件名。在主程序里面加上下面代码,打开文件流。

     writeln('please input source program file name : ');
     readln(sfile);
     assign(fin,sfile);
     reset(fin);
     writeln('please input the file name to save result : ');
     readln(dfile);
     assign(fout,dfile);
     rewrite(fout);
    

    这样在每个writeln后面都加上一个文件流输入语句就可以了,比如像下面一样。

    writeln( '****', ' ':cc-1, '^', n:2 );
    writeln(fout,'****', ' ':cc-1, '^', n:2);
    

    符号的修改

    主要是引号和减号,全部替换成英文的就可以了。

    变量名的替换

    主要是两个,都在这行里object = (constant, variable, procedure);objectprocedure,这两个都是pascal语言的保留字,所以用这个两个变量名肯定会报错,所以改成objecttypprosedure,以防冲突,另外代码中的其他部分也需要替换,但是不能全局替换,因为有的是使用pascalprocedure,所以这部分改起来很麻烦。

    goto语句

    直接删掉就好了,一共三条语句。

    运算符的修改

    代码中有的符号很不友好,主要是下面三个符号

      ssym[‘≠’] := neq;
      ssym[‘≤’] := leq;      
      ssym[‘≥’] := geq;
    

    具体使用是将改成<>,将改成<=,将改成>=。然后这三行直接删掉就可以了。相应的在pl0源程序里面要把符号改掉。改掉之后要增加符号的识别语句,可以在识别:=的语句后面模仿写下如下代码,这样可以识别新加的三个符号。

    Else If ch = '<'
             Then
             Begin
               getch;
               If ch = '='
                 Then
                 Begin
                   sym := leq;
                   getch
                 End
               Else If ch = '>'
                      Then
                      Begin
                        sym := neq;
                        getch
                      End
               Else sym := lss
             End
      Else If ch = '>'
             Then
             Begin
               getch;
               If ch = '='
                 Then
                 Begin
                   sym := geq;
                   getch
                 End
               Else sym := gtr
             End
    

    语法修改

    将代码以前内容改成小写

      word[1] := 'begin        ';
      word[2] := 'call         ';
      word[3] := 'const        ';
      word[4] := 'do           ';
      word[5] := 'end          ';
      word[6] := 'if           ';
      word[7] := 'odd          ';
      word[8] := 'procedure    ';
      word[9] := 'then         ';
      word[10] := 'var          ';
      word[11] := 'while        ';
    

    这句话原本是没有Not的,所以只读一句话就结束了。

    While Not eoln(fin) Do
    

    这个处理程序原本也是没有Not

    Procedure test( s1,s2 :symset; n: integer );
    Begin
      If Not ( sym In s1 )
        Then
        Begin
          error(n);
          s1 := s1+s2;
          While Not( sym In s1) Do
            getsym
        End
    End;
    

    这行原本也没有Not

    Until Not (sym In declbegsys);
    

    这行原本也没有Not

    If Not (sym In [eql, neq, lss, leq, gtr, geq]) Then
    

    part2修改

    主要是加write和read语句

    pl0源程序修改为一下内容,并且用这个文件测试

    procedure test;
      var input;
      begin
        read(input);
        write(input);
      end;
    
    begin
      call test;
    end.
    
    

    首先Const norw = 11;改成Const norw = 13;,因为要新加两个保留字。
    然后修改

      word[1] := 'begin        ';
      word[2] := 'call         ';
      word[3] := 'const        ';
      word[4] := 'do           ';
      word[5] := 'end          ';
      word[6] := 'if           ';
      word[7] := 'odd          ';
      word[8] := 'procedure    ';
      word[9] := 'read         ';
      word[10] := 'then         ';
      word[11] := 'var          ';
      word[12] := 'while        ';
      word[13] := 'write        ';
      wsym[1] := beginsym;
      wsym[2] := callsym;
      wsym[3] := constsym;
      wsym[4] := dosym;
      wsym[5] := endsym;
      wsym[6] := ifsym;
      wsym[7] := oddsym;
      wsym[8] := procsym;
      wsym[9] := readsym;
      wsym[10] := thensym;
      wsym[11] := varsym;
      wsym[12] := whilesym;
      wsym[13] := writesym;
    
      mnemonic[lit] := 'LIT  ';
      mnemonic[opr] := 'OPR  ';
      mnemonic[lod] := 'LOD  ';
      mnemonic[sto] := 'STO  ';
      mnemonic[cal] := 'CAL  ';
      mnemonic[int] := 'INT  ';
      mnemonic[jmp] := 'JMP  ';
      mnemonic[jpc] := 'JPC  ';
      mnemonic[red] := 'RED  ';
      mnemonic[wrt] := 'WRT  ';
    

    在解释程序里面加

    red :
                  Begin
                    writeln('Input a integer:');
                    writeln(fout,'Input a integer:');
                    readln(s[base(l)+a]);
                    writeln(fout,s[base(l)+a]);
                  End;
            wrt :
                  Begin
                    writeln('Here is the integer:');
                    writeln(s[t]);
                    writeln(fout,'Here is the integer:');
                    writeln(fout,s[t]);
                    t := t+1
                  End
    

    condition里面加

    Else If sym = readsym
             Then
             Begin
               getsym;
               If sym = lparen
                 Then
                 Repeat
                   getsym;
                   If sym = ident
                     Then
                     Begin
                       i := position(id);
                       If i = 0
                         Then error(11)
                       Else If table[i].kind <> variable
                              Then
                              Begin
                                error(12);
                                i := 0
                              End
                       Else With table[i] Do
                              gen(red,lev-level,adr)
                     End
                   Else error(4);
                   getsym;
                 Until sym <> comma
               Else error(40);
               If sym <> rparen
                 Then error(22);
               getsym
             End
      Else If sym = writesym
             Then
             Begin
               getsym;
               If sym = lparen
                 Then
                 Begin
                   Repeat
                     getsym;
                     expression([rparen,comma]+fsys);
                     gen(wrt,0,0);
                   Until sym <> comma;
                   If sym <> rparen
                     Then error(22);
                   getsym
                 End
               Else error(40)
             End;
    

    参考链接

    参考博客

    github地址

    项目地址

    展开全文
  • pl/0语言是pascal语言的一个子集,我们这里分析的pl/0编译程序包括了对pl/0语言源程序进行分析处理、编译生成类pcode代码,并在虚拟机上解释运行生成的类pcode代码的功能。  pl/0语言编译程序采用以语法分析为...
  • 编译原理PL0实验报告

    2020-07-15 09:17:08
    本资源系山东大学计算机科学与技术学院《程序设计语言编译原理》课程实验内容报告,内含详细实验过程,实验最后附C++实现编译器的源码,欢迎下载。
  • 编译原理 PL0源程序

    2011-11-21 21:21:10
    编译原理课程 源代码 课后PL0语言 源代码
  • 编译原理 编译原理 课程实践报告书 学员姓名 张冠宇 学 号 201306042020 专 业 软件工程 年 级 2013 2015 年 12 月 25 日 课程实践报告书 1课程实践目的 通过读懂源程序全面掌握编译原理的基本实现过程对 PL 编译...
  • 大三《编译原理PL0语言编译器 C++实现
  • 编译原理课程设计,改进书后附录的pl0程序,c语言版: 1.扩充语句for(<语句>;<条件>;<语句>)<语句> 2.扩充语句if <条件> then <语句> else <语句> 3.扩充语句repeat <语句>;until <条件> 4.增加自增自减运算++和—...
  • 编译原理Pl/0语言 简单编译器思路

    千次阅读 2016-12-21 16:46:27
    基于山东大学编译原理实习题,实现的简单的编译器。对编译过程代码的解读,相关代码请联系作者
    词法分析
    变量声明:
    length 保存一行字符的长度
    point 输入一行的指针
    构造函数:初始化mySYM,myID,myNUM,length;point指针初始=-1
    调用词法分析函数Compile。
    Compile:将文件(inputfile)保存到reader(字符 缓冲区BufferReader)。将缓冲区的一行存入Buffer以供操作,将该行长度保存到length中。当Buffer中数据没有处理完时进行循环调用调用GETSYS处理一个字符
    GETSYS:
    code存储种别码,strToken暂存字符串(可以拼接字母、数字)
    调用GetChar()取得一个字符,调用GetBC(去掉空格);

    处理完毕后检测ch中的字符
    字母:检测到第一个字符为字母表示接下来应该是字母或数字组成的一个标识符。就将所有的后来的字母或数字与第一个字符连接起来。直到遇到一个非字母或数字输入,回退一个字符(因为前面向前查看了)
    调用Reserve检查保留字表查看是否是保留字,将该标识符所属类加入到SYM表。

    数字:检测到第一个字符为数字的时候检测后续是否都是数字,如果都是数字则将数字连接起来,直到遇到下一个不为数字的字符。将所有的数字连接起来插入到常量表,向SYM表加入一个INT表示一个常量。

    是其他的标点符号:对不同的标点符号作相应的处理,把相应的符号加入到SYM。

    GetChar:根据point指向位置从Buffer中取一个字符,当point指针小于length-1(Buffer从0开始,最后一个字符是length-1),指针后移。将point指向的Buffer中的字符保存到ch中,输出字符和其位置。如果此时point指向length-1,则表明该行将处理结束,isEnd=true。或者point指针>=length-1
    也代表该行处理 结束,isEnd=true。
    GetBC:如果当前ch中是换行则表明此行处理结束,isEnd=true;如果ch中是空格或制表符时再调用GetChar取得一个字符,直到ch中不再是制表符或空格。

    语法分析
    变量声明:
    Vector<OrderFormat> Code; //用于存储中间代码的数组
    index 记录Code的索引值;
    myIndex记录主程序的入口;
    offset 记录一开始 空出的前两条指令的偏移量;
    currentSYM 记录当前取得单词的种别码;
    number 保存常量值;
    tx 记录小表在大表中的索引;

    构造函数:
    1.调用词法分析器 将源文件进行词法分析,得到各个有效数组,并将其存储到实例变量中:
    存放变量标识符的 myID、存放常数的 myNUM、存放种别码的 mySYM
    2.得到各个数组的遍历器 SYMiterator、IDiterator、NUMiterator
    3.符号说明表 Table声明为数组的数组(大表)来存放子过程的小表、 中间代码数组 Code 存放指令。
    4.初始化Code的索引值 index=0(Code 表的索引),offset=1(给跳转到Main的指令留位置)
    5.调用递归下降语法分析器

    递归下降语法分析器:
    调用getNextSYM从mySYM中取得下一个种别码
    调用语法分析过程Block(0),表明开始构造Main层

    Block(层号)
    说明部分的处理任务就是对每个过程(包括主程序,可以看成是一个主过程)的说明对象 造名字表。填写所在层次(主程序是0层,在主程序中定义的过程是1层,随着嵌套的深度增加而层次数增大。PL/0最多允许3层),标识符的属性和分配的相对地址等。标识符的属性不同则填写的信息不同。
    所造的表放在全程量一维数组TABLE中,TX为指针,数组元素为结构体类型数据。LEV给出层次,DX给出每层的局部量的相对地址,每说明完一个变量后DX加1。

    1.声明一个 符号表类型的数组 存储每一个符号(常量或变量)的状态。将此符号表类型数组加入到总的表中。
    2.addr 记录偏移量、localVarCount 记录局部变量的个数、currenttx记录当前说明表在Table表中索引。在该过程中将变量、常量、过程形成项加入到myTable表中。
    3.说明部分处理造表完成执行 生成目标代码 调用GEN(操作码,层次差,偏移量), 偏移量为本层局部变量的个数+3(有三个预处理信息)

    处理常数:handleConst(需要传入符号表、所属层号)
    1.从常数表中取得一个数值,如果不是-1则表明是常量。
    2.创建一个新的表项,用来保存这个常数。表项含有参数(id<变量名>,kind<类别>,level<层号>,number<常数值>,tx<表内索引值>)
    3.判断常量是否已经被定义过,返回false则表明该变量已经定义过
    getTableItem(name,level):遍历总的符号表数组存储数组,取得每一个符号说明数组的遍历器。
    在该过程及其父过程中,所有声明的常量、变量和过程都可见
    4.将此表项加入到当前层的表中
    5.如果下一个种别码是逗号,说明还有常量声明 继续处理。
    6.如果是分号则说明定义完毕

    处理变量:
    1.count 记录声明的变量的个数
    2.创建一个新的表项,用来保存一个变量,先将变量存入再将本层偏移量++。因为本层的偏移量只是会在变量声明中用到所以不需要将外面的偏移量更新。
    3.如果变量没有在前面被定义,则将该变量加入到当前的表中。

    处理过程:
    如果当前的种别码是_procedure则表明是一个过程声明。
    查看下一个种别码,改变currentSYM的指向。
    pro(currenttx,level)
    currenttx当前操作的小表在大表中的索引,level当前的层号
    1.调用处理过程的首部,即将声明过程 的标识符 加入到小表中
    prepro(currenttx,level)过程处理的首部,用于将声明过程的一项加入到声明层的table中。其中,过程的一项的addr偏移位index+offset(当前Code表的指针+给跳转给Main的指令),将tx++即当前的大表指针应后移一位,处理新的变量声明。
    2.调用Block(level+1)构造新的level+1的一层。
    3.子过程调用完成后,最后应该是一个分号。否则就是缺少分号。
    如果还有并列的过程说明则再次重复过程,调用pro。

    生成代码:
    1.如果是主程序层的生成代码,则将myIndex赋值
    2.在运行栈中开辟 3+局部变量个空间。 为什么要给个3???
    3.调用Sentence处理过程的所有语句。(用begin,end包围可以有很多句,否则只有一句)
    4.退出该层数据区(OPR,0,0)
    GEN()生成目标代码,加入到Code(中间代码数组中),(Code索引值)index++

    Sentence():
    处理过程(执行操作) 如果没有begin,end包围表明只有一个语句
    1.如果currentSYM=begin ->很 多语句,不为end且是分号递归调用下一层的Sentence
    2.如果currentSYM=ID->是赋 值语句。查表,根据标识符(id)从表中得到一项,该项的类型应该是变量。向前查看,如果不是赋值符号则出错。后面应该是表达式,识别合法的表达式Expression(),将表达式计算完毕后将栈顶的内容送到某变量内存单元中(id) (STO,调用层和说明层的层差,变量所在说明层中的相对位置)。
    3.如果currentSYM=if->是 条件判断语句,先处理条件Condition()。给jpc条件跳转语句留一个位置,接收then语句,处理语句的内容。 将跳转指令插入到保留的位置,跳转目的地址为index+offset,当前所有then语句执行结束的语句。
    4.如果currentSYM=while->是while循环语句,记录while循环开始的地方,处理条件Condition(),记录jpc语句插入的地方,占用一个Code一个位置。处理while循环的内容:应该以do开头,处理语句。将无条件跳转语句跳转到判断语句加入代码数组Code;将条件为非时跳转加入到保留位置(表明当while不成功跳转到所有的do语句之后)
    5.如果currentSYM=write/read是读写语句。读写语句应被括号包围,且标识符应该在变量声明中存在。读操作,从控制台读一个数到栈顶,将栈顶的数送到指定的标识符中;写操作,将标识符内的值放到栈顶,将栈顶数值输出到控制台。
    Condition(level,currenttx)
    处理条件表达式:1.一元运算(odd)奇偶判断
    2.其他运算 处理表达式Expression(level,currenttx)将代码加入Code,接收关系运算符,接收并处理另一个表达式,将代码加入Code。将运算符代码加入Code
    Expression()识别合法的表达式
    加号:正数。查看后面的项:Item()
    减号:负数。查看后面的项:并将其取反
    都不是:直接处理项
    循环:判断后面是否有加、减,如果有则 继续查看下一项,将下一项的结果作为一项,将操作添加到栈的顶部。
    Item() 处理出一项数出来(乘法、除法优先运算,检测时将它们先运算作为一个项)
    调用处理因数 factor()。
    判断是否是乘除运算符,如果是乘除运算符,则再调用处理一个因数。 将 运算加入到Code表中,如果后续没有乘法或除法操作则返回。
    factor():处理因数
    左因数只可能是 标识符、常数、左括号。
    标识符:从当前小表中拿出声明过的标识符。检测其类型,是变量,则向代码数组中加入
    (LOD,调用层和说明层的层差,变量所在说明层中的相对位置);是常量,向代码数组中加入
    (LIT,0,常量的值)
    常数:将常数放到运算栈顶 (LIT,0,getNextNum());
    左括号:括号内有完整的表达式 调用Expression()处理, 且应以右括号结束
    展开全文
  • 编译原理 PL0详细分析

    2010-06-11 19:42:25
    编译原理学习的顶好资料 PL/0语言词法分析、语法分析、语义分析、中间代码生成、执行的详细描述
  • 大学编译原理课程实验课源代码,C++文件,扩充pl语言,添加for,else,repeat,while,until等关键字
  • 编译原理实验 词义与语义分析 还PL0语言的扩充 代码运用于C/C++
  • 处理用户提交的符合上述文法的源代码序列,生成元式中间代码;原版资料,很全,很详细,希望能够给你提供帮助
  • PL/0语言建立一个词法分程序GETSYM(函数) 为PL/0语言建立一个语法分析程序BLOCK(函数) 建立一个解释执行目标程序的函数
  • 山东大学计算机学院的编译原理实验,用java写的,是对PL/0语言的编译程序
  • 编译原理PL/O语言的语法分析过程BLOCK,详细的语法分析程序
  • 编译原理PL/0实验

    2011-01-13 17:52:50
    (1)增加单词:保留字 ELSE,FOR,TO, DOWNTO;...(3)教学型编译程序:PL/0 二、 设计方案 (1) 概述 源语言PL0 目标语言:类 pcode 实现工具:C++ 运行平台: Windows XP (2) 结构设计说明:
  • 编译原理PL0课程设计

    2010-01-02 13:16:27
    基本内容(成绩范围:“中”、“及格”或“不及格”) (1)扩充赋值运算:+= 和 -= (2)扩充语句 REPEAT <语句序列> UNTIL <条件> 其中,<条件>是循环条件,即条件不成立时,重复执行...(5)其他典型语言设施。
  • (Python实现,详细注释)通过实现PL/0语言(一种示例小语言)的词法分析器,理解词法分析过程,掌握程序各部分之间的接口安排。 PL/0语言的词法分析器将要完成以下工作: (1)跳过分隔符(如空格,回车,制表符...
  • 编译原理PL0功能扩充

    2009-04-03 21:40:07
    实现了+=和-=的扩充赋值运算 实现了循环语句REPEAT<语句> DOWHILE<条件>;的扩充 实现了自加“++”和自减“--”运算 实现对字符类型的CHAR的识别和一些字符操作 实现了数组类型ARRAY的识别和一些对数组元素的操作
  • PL0-Compiler ...中山大学编译原理项目:PL0语言的编译程序 找到PASCAL编译系统; 在PASCAL系统上运行PL0编译程序,需要对PL0编译程序作一些修改﹑调试; 在PASCAL系统中,为PL0的编译程序建立输入文件和...

    PL0-Compiler

    代码传送门

    ps:吐槽一下,老师给的代码实在是太烂了。又没缩进又多编译错误,除此之外还有很多细节漏掉关键字,总之就很多坑,所以这里发一个无坑带缩进版,方便大家学习。

    中山大学编译原理项目:PL0语言的编译程序

    • 找到PASCAL编译系统;
    • 在PASCAL系统上运行PL0编译程序,需要对PL0编译程序作一些修改﹑调试;
    • 在PASCAL系统中,为PL0的编译程序建立输入文件和输出文件;
      • 在输入文件中存放PL0源程序;
      • 在输出文件中存放PL0源程序被编译后产生的中间代码和运行数据;
    • PL0的编译程序运行时, 通过输入文件输入PL0源程序, 在输出文件中产生源程序的中间代码, 然后运行该中间代码, 在输出文件中产生运行数据;
    • 如果上述工作成功, 则第一项实习任务完成.再做以下工作:
    • 在PL0语言中增加Read和Write语句;
    • 修改PL0编译程序, 使得PL0源程序可以使用Read和Write语句, 从文件(或键盘)输入数据,并可以向文件(或屏幕)写数据.

    使用方法

    测试PL0代码样例

    • PL0-sourse.txt( 不带read与write命令)
    • PL0-sourse-rw.txt(带read与write命令)

    测试方法

    • 在命令行PL0文件夹目录下

      • 编译

        fpc PL0-compiler.pas
        
      • 执行

        PL0-compiler.exe
        
      • 输入PL0代码文件(PL0-sourse.txt)

      • 输入结果输出文件(PL0-dest.txt)

    • 最终结果显示在PL0-dest.txt中,包括栈中数据,最终结果,中间代码等

    1

    源代码

    源代码跟测试代码都放到GITHUB仓库了,可以直接上仓库看,或者在这里看也可以。不过700多行的编译程序看起来还是有点痛苦的,csdn没有pascal高亮。

    {PL0编译程序注释}
    
    program  PL0 ( input, output);
    {带有代码生成的PL0编译程序}
    const
      norw = 13; {保留字的个数}
      txmax = 100; {标识符表长度}
      nmax = 14; {数字的最大位数}
      al = 10; {标识符的长度}
      amax = 2047; {最大地址}
      levmax = 3; {程序体嵌套的最大深度}
      cxmax = 200; {代码数组的大小}
    type
      symbol = (nul, ident, number, plus, minus, times, slash, oddsym,
      eql, neq, lss, leq, gtr, geq, lparen, rparen, comma, semicolon,
      period, becomes, beginsym, endsym, ifsym, thensym,
      whilesym, dosym, callsym, constsym, varsym, procsym,
      readsym, writesym);
      alfa = packed array [1..al] of char; 
      object2 = (constant, variable, procedure2);
      symset = set of symbol;
      fct = (lit, opr, lod, sto, cal, int, jmp, jpc, red, wrt); {functions}
      instruction = packed record
        f : fct;  {功能码}
        l : 0..levmax; {相对层数}
        a : 0..amax; {相对地址}
    end;
      {LIT 0,a : 取常数a
      OPR 0,a : 执行运算a
      LOD l,a : 取层差为l的层﹑相对地址为a的变量
      STO l,a : 存到层差为l的层﹑相对地址为a的变量
      CAL l,a : 调用层差为l的过程
      INT 0,a : t寄存器增加a
      JMP 0,a : 转移到指令地址a处
      JPC 0,a : 条件转移到指令地址a处 }
    var
      ch : char; {最近读到的字符}
      sym : symbol; {最近读到的符号}
      id : alfa; {最近读到的标识符}
      num : integer; {最近读到的数}
      cc : integer; {当前行的字符计数}
      ll : integer; {当前行的长度}
      kk, err : integer;
      cx : integer; {代码数组的当前下标}
      line : array [1..81] of char; {当前行}
      a : alfa; {当前标识符的字符串}
      code : array [0..cxmax] of instruction; {中间代码数组}
      word : array [1..norw] of alfa; {存放保留字的字符串}
      wsym : array [1..norw] of symbol; {存放保留字的记号}
      ssym : array [char] of symbol; {存放算符和标点符号的记号}
      mnemonic : array [fct] of packed array [1..5] of char;
      {中间代码算符的字符串}
      declbegsys, statbegsys, facbegsys : symset;
      table : array [0..txmax] of record {符号表}
              name : alfa;
              case kind : object2 of constant : (val : integer);
      variable, procedure2 : (level, adr : integer)
              end;
      fin, fout : text; {定义fin, fout为文本文件变量}
      sfile, dfile : string;
    
    procedure error (n : integer);
      begin 
        writeln('****', ' ' : cc-1, '^', n : 2);  
        err := err + 1
        {cc为当前行已读的字符数, n为错误号}{错误数err加1}
      end {error};
    procedure getsym;
      var  i, j, k : integer;
      procedure  getch ; {取下一字符}
      begin if cc = ll then {如果cc指向行末}
        begin if eof(fin) then {如果已到文件尾}
            begin 
              write('PROGRAM INCOMPLETE'); 
              writeln(fout, 'PROGRAM INCOMPLETE');
              close(fin);
              close(fout);
              exit(); {实现goto 99 功能}
            end;
          {读新的一行}
          ll := 0; 
          cc := 0;
          write(cx : 5, ' ');
          write(fout, cx : 5, ' ');{cx : 5位数}
          while not eoln(fin) do {如果不是行末}
            begin
                ll := ll + 1; 
                read(fin, ch); 
                write(ch);
                write(fout, ch);
                line[ll] := ch  {一次读一行入line}
            end;
          writeln; 
          writeln(fout);
          readln(fin);
          ll := ll + 1;
          line[ll] := ' ' {line[ll]中是行末符}
        end;
      cc := cc + 1; 
      ch := line[cc]  {ch取line中下一个字符}
      end; {getch}
    
      begin {getsym} 
        while ch = ' ' do 
          getch; {跳过无用空白}
        if ch in ['a'..'z'] then 
        begin {标识符或保留字} 
          k := 0;
          repeat {处理字母开头的字母﹑数字串}
            if k < al then
              begin 
                k:= k + 1; 
                a[k] := ch
              end;
            getch
          until not(ch in ['a'..'z', '0'..'9']);
          if k >= kk  
          then kk := k 
          else repeat 
            a[kk] := ' '; 
            kk := kk-1 {如果标识符长度不是最大长度, 后面补空白}
          until kk = k;                
          id := a;  
          i := 1;  
          j := norw;
          {id中存放当前标识符或保留字的字符串}
          repeat  
            k := (i+j) div 2; {用二分查找法在保留字表中找当前的标识符id}
            if id <= word[k] then j := k-1; 
            if id >= word[k] then i := k+1
          until i > j;
          if i-1 > j then sym := wsym[k] else sym := ident
          {如果找到, 当前记号sym为保留字, 否则sym为标识符}
          end 
    
        else if ch in ['0'..'9'] then
          begin {数字} 
          k := 0;  num := 0;  sym := number; {当前记号sym为数字}
          repeat {计算数字串的值}
            num := 10*num + (ord(ch)-ord('0'));
            {ord(ch)和ord(0)是ch和0ASCII码中的序号}
            k := k + 1;  
            getch;
          until not (ch in ['0'..'9']); {直到输入的不再是数字}
          if k > nmax then  error(30)
          {当前数字串的长度超过上界,则报告错误}
          end 
        else if ch = ':' then {处理赋值号}
          begin  
            getch;
            if ch = '=' then
              begin  
                sym := becomes; 
                getch 
              end
            else  
              sym := nul;
          end
        else {处理其它算符或标点符号}
          begin
            sym := ssym[ch];
            getch
          end
      end {getsym};
    
    procedure gen(x : fct; y, z : integer); 
      begin 
        if cx > cxmax then {如果当前指令序号>代码的最大长度}
        begin 
          write('PROGRAM TOO LONG');
          writeln(fout);
          close(fin);
          exit(); {实现goto 99 功能}
        end;
        with code[cx] do {在代码数组cx位置生成一条新代码}
          begin  
            f := x; {功能码} l := y; {层号} a := z {地址}
          end;
        cx := cx + 1 {指令序号加1}
      end {gen};
    
    procedure test(s1, s2 : symset; n : integer);
      begin
        if not (sym in s1) then 
      {如果当前记号不属于集合S1,则报告错误n}
          begin  
            error(n);  
            s1 := s1 + s2;
            while not(sym in s1) do 
              getsym 
            end
      {跳过一些记号, 直到当前记号属于S1S2}
      end {test};
    
    procedure block(lev, tx : integer; fsys : symset);
      var dx : integer; {本过程数据空间分配下标}
          tx0 : integer; {本过程标识表起始下标}
          cx0 : integer; {本过程代码起始下标}
      
      procedure enter(k : object2);
        begin {把object2填入符号表中}
          tx := tx +1; {符号表指针加1}
          with table[tx] do{在符号表中增加新的一个条目}
          begin  
            name := id; {当前标识符的名字} 
            kind := k; {当前标识符的种类}
            case k of
              constant : 
                begin {当前标识符是常数名}
                  if num > amax then {当前常数值大于上界,则出错}
                    begin error(30); num := 0 end;
                    val := num
                end;
              variable : 
                begin {当前标识符是变量名}
                  level := lev; {定义该变量的过程的嵌套层数} 
                  adr := dx; {变量地址为当前过程数据空间栈顶} 
                  dx := dx +1; {栈顶指针加1}
                end;
              procedure2 : level := lev {本过程的嵌套层数}
            end
          end
        end {enter};
    
      function  position(id : alfa) : integer; {返回id在符号表的入口}
        var  i : integer; 
        begin {在标识符表中查标识符id}
          table[0].name := id; {在符号表栈的最下方预填标识符id} 
          i := tx; {符号表栈顶指针}
          while table[i].name <> id do i := i-1;
          {从符号表栈顶往下查标识符id}
          position := i {若查到,i为id的入口,否则i=0 } 
        end {position};
    
      procedure constdeclaration;
        begin
          if sym = ident then {当前记号是常数名}
            begin  
              getsym;
              if sym in [eql, becomes] then {当前记号是等号或赋值号}
                begin
                  if sym = becomes then error(1);
                    {如果当前记号是赋值号,则出错}
                  getsym;
                  if sym = number then {等号后面是常数}
                    begin  
                      enter(constant); {将常数名加入符号表}
                      getsym
                    end
                  else error(2) {等号后面不是常数出错}
                end 
              else error(3) {标识符后不是等号或赋值号出错}
            end 
          else error(4) {常数说明中没有常数名标识符}
        end; {constdeclaration}
    
      procedure vardeclaration;
        begin
          if sym = ident then {如果当前记号是标识符}
          begin  
            enter(variable); {将该变量名加入符号表的下一条目} 
            getsym
          end 
          else error(4) {如果变量说明未出现标识符,则出错}
        end; {vardeclaration}
    
      procedure listcode;
        var i : integer;
        begin  {列出本程序体生成的代码}
          for i := cx0 to cx-1 do 
          {cx0: 本过程第一个代码的序号,cx-1: 本过程最后一个代码的序号}
            with code[i] do {打印第i条代码}
              writeln(fout, i:3,mnemonic[f] : 5, l : 3, a : 5)
              {writeln(i, mnemonic[f] : 5, l : 3, a : 5)}
       {i: 代码序号; 
        mnemonic[f]: 功能码的字符串;
        l: 相对层号(层差);
        a: 相对地址或运算号码}
        end {listcode};
    
      procedure  statement(fsys : symset);
        var i, cx1, cx2 : integer;
        procedure  expression(fsys : symset);
          var  addop : symbol;
          procedure  term(fsys : symset);
            var  mulop : symbol;
            procedure  factor(fsys : symset);
              var i : integer;
              begin  
                test(facbegsys, fsys, 24); 
                {测试当前的记号是否因子的开始符号, 
                  否则出错, 跳过一些记号}
                while sym in facbegsys do 
                {如果当前的记号是否因子的开始符号}
                  begin
                    if sym = ident then {当前记号是标识符}
                    begin
                      i := position(id); {查符号表,返回id的入口}
                      if i = 0 
                        then error(11) 
                      else
                      {若在符号表中查不到id, 则出错, 否则,做以下工作}
                        with table[i] do
                          case kind of 
                            constant : gen(lit, 0, val); 
                            {若id是常数, 生成指令,将常数val取到栈顶}
                            variable : gen(lod, lev-level, adr);
                            {若id是变量, 生成指令,将该变量取到栈顶;
                              lev: 当前语句所在过程的层号;
                              level: 定义该变量的过程层号;
                              adr: 变量在其过程的数据空间的相对地址}
                            procedure2 : error(21)
                          {若id是过程名, 则出错}
                          end;
                        getsym {取下一记号}
                      end 
                    else if sym = number then {当前记号是数字}
                      begin
                        if num > amax then {若数值越界,则出错}
                        begin 
                          error(30); 
                          num := 0 
                        end;
                        gen(lit, 0, num); 
                        {生成一条指令, 将常数num取到栈顶}
                        getsym {取下一记号}
                      end 
                    else if sym = lparen then {如果当前记号是左括号}
                      begin 
                        getsym; {取下一记号}
                        expression([rparen]+fsys); {处理表达式}
                        if sym = rparen then getsym
                        {如果当前记号是右括号, 则取下一记号,否则出错}
                        else error(22)
                     end;
                test(fsys, [lparen], 23) 
                {测试当前记号是否同步, 否则出错, 跳过一些记号}
                end {while}
              end {factor};
    
            begin {term}
              factor(fsys+[times, slash]); {处理项中第一个因子}
              while sym in [times, slash] do 
              {当前记号是“乘”或“除”号}
              begin
                mulop := sym; {运算符存入mulop} 
                getsym; {取下一记号}
                factor(fsys+[times, slash]); {处理一个因子}
                if mulop = times 
                then gen(opr, 0, 4)
                {若mulop是“乘”号,生成一条乘法指令}
                else gen(opr, 0, 5)
                {否则, mulop是除号, 生成一条除法指令}
              end
            end {term};
    
          begin {expression}
            if sym in [plus, minus] then {若第一个记号是加号或减号}
            begin 
              addop := sym;  {+”或“-”存入addop}
              getsym; 
              term(fsys+[plus, minus]); {处理一个项}
              if addop = minus then gen(opr, 0, 1)
              {若第一个项前是负号, 生成一条“负运算”指令}
            end 
            else 
              term(fsys+[plus, minus]);
              {第一个记号不是加号或减号, 则处理一个项}
              while sym in [plus, minus] do {若当前记号是加号或减号}
                begin
                  addop := sym; {当前算符存入addop} 
                  getsym; {取下一记号}
                  term(fsys+[plus, minus]); {处理一个项}
                  if addop = plus 
                  then gen(opr, 0, 2)
                  {若addop是加号, 生成一条加法指令}
                  else gen(opr, 0, 3)
                  {否则, addop是减号, 生成一条减法指令}
                end
            end {expression};
    
          procedure condition(fsys : symset);
            var relop : symbol;
            begin
              if sym = oddsym then {如果当前记号是“odd”}
              begin
                getsym;  {取下一记号}
                expression(fsys); {处理算术表达式}
                gen(opr, 0, 6) {生成指令,判定表达式的值是否为奇数,,则取“真”;不是, 则取“假”}
              end 
              else {如果当前记号不是“odd”}
                begin
                  expression([eql, neq, lss, gtr, leq, geq] + fsys); 
                  {处理算术表达式}
                if not (sym in [eql, neq, lss, leq, gtr, geq]) 
                {如果当前记号不是关系符, 则出错; 否则,做以下工作}
                then error(20)  
                else
                  begin
                    relop := sym; {关系符存入relop} 
                    getsym; {取下一记号} 
                    expression(fsys); {处理关系符右边的算术表达式}
                    case relop of
                      eql : gen(opr, 0, 8); 
                      {生成指令, 判定两个表达式的值是否相等}
                      neq : gen(opr, 0, 9);
                      {生成指令, 判定两个表达式的值是否不等}
                      lss : gen(opr, 0, 10);
                      {生成指令,判定前一表达式是否小于后一表达式}
                      geq : gen(opr, 0, 11);
                      {生成指令,判定前一表达式是否大于等于后一表达式}
                      gtr : gen(opr, 0, 12);
                      {生成指令,判定前一表达式是否大于后一表达式}
                      leq : gen(opr, 0, 13);
                      {生成指令,判定前一表达式是否小于等于后一表达式}
                    end
                  end
                end
            end; {condition}
    
        begin {statement}
          if sym = ident then {处理赋值语句}
          begin  
            i := position(id); 
            {在符号表中查id, 返回id在符号表中的入口}
            if i = 0 
              then error(11) 
            {若在符号表中查不到id, 则出错, 否则做以下工作}
            else if table[i].kind <> variable then
            {若标识符id不是变量, 则出错}
            begin {对非变量赋值} 
              error(12); 
              i := 0; 
            end;
            getsym; {取下一记号}
            if sym = becomes 
            then getsym 
            else error(13);
            {若当前是赋值号, 取下一记号, 否则出错}
            expression(fsys); {处理表达式}
            if i <> 0 then {若赋值号左边的变量id有定义}
              with table[i] do gen(sto, lev-level, adr)
              {生成一条存数指令, 将栈顶(表达式)的值存入变量id中;
               lev: 当前语句所在过程的层号;
               level: 定义变量id的过程的层号;
               adr: 变量id在其过程的数据空间的相对地址}
            end 
            else if sym = callsym then {处理过程调用语句}
            begin  
              getsym; {取下一记号}
              if sym <> ident 
              then error(14) 
              else
              {如果下一记号不是标识符(过程名),则出错,
               否则做以下工作}
              begin 
                i := position(id); {查符号表,返回id在表中的位置}
                if i = 0 
                  then error(11) 
                else
                  {如果在符号表中查不到, 则出错; 否则,做以下工作}
                  with table[i] do
                    if kind = procedure2 then 
                      {如果在符号表中id是过程名}
                      gen(cal, lev-level, adr)
                      {生成一条过程调用指令;
                       lev: 当前语句所在过程的层号
                       level: 定义过程名id的层号;
                       adr: 过程id的代码中第一条指令的地址}
                    else error(15); {若id不是过程名,则出错}
                  getsym {取下一记号}
                end
              end 
              else if sym = ifsym then {处理条件语句}
              begin
                getsym; {取下一记号} 
                condition([thensym, dosym]+fsys); {处理条件表达式}
                if sym = thensym 
                  then getsym 
                else error(16);
                {如果当前记号是“then”,则取下一记号; 否则出错}
                cx1 := cx; {cx1记录下一代码的地址} 
                gen(jpc, 0, 0); {生成指令,表达式为“假”转到某地址(待填),
                                  否则顺序执行}
                statement(fsys); {处理一个语句}
                code[cx1].a := cx 
                {将下一个指令的地址回填到上面的jpc指令地址栏}
              end 
              else if sym = beginsym then {处理语句序列}
              begin
                getsym;  
                statement([semicolon, endsym]+fsys);
                {取下一记号, 处理第一个语句}
                while sym in ([semicolon]+statbegsys) do 
                {如果当前记号是分号或语句的开始符号,则做以下工作}
                begin
                  if sym = semicolon 
                    then getsym 
                  else error(10);
                  {如果当前记号是分号,则取下一记号, 否则出错}
                  statement([semicolon, endsym]+fsys) {处理下一个语句}
                end;
                if sym = endsym 
                  then getsym 
                else error(17)
              {如果当前记号是“end”,则取下一记号,否则出错}
              end
              else if sym = whilesym then {处理循环语句}
              begin
                cx1 := cx; {cx1记录下一指令地址,即条件表达式的
                            第一条代码的地址} 
                getsym; {取下一记号}
                condition([dosym]+fsys); {处理条件表达式}
                cx2 := cx; {记录下一指令的地址} 
                gen(jpc, 0, 0); {生成一条指令,表达式为“假”转到某地
                                  (待回填), 否则顺序执行}
                if sym = dosym 
                  then getsym 
                else error(18);
                {如果当前记号是“do,则取下一记号, 否则出错}
                statement(fsys); {处理“do”后面的语句}
                gen(jmp, 0, cx1); {生成无条件转移指令, 转移到“while”后的
                条件表达式的代码的第一条指令处} 
                code[cx2].a := cx 
                {把下一指令地址回填到前面生成的jpc指令的地址栏}
              end
              else if sym = readsym then {处理read关键字}
              begin
                getsym;
                if sym = lparen then{如果read后是左括号}
                  repeat
                    getsym;
                    if sym = ident
                    then begin
                      i := position(id);
                      if i = 0
                        then error(11)
                      else if table[i].kind <> variable
                      then begin
                        error(12);
                        i := 0
                      end
                      else with table[i] do
                        gen(red,lev-level,adr)
                    end
                    else  error(4);
                  getsym;
                  until sym <> comma
                else
                  error(40);
                if sym <> rparen
                then error(22);
                getsym
              end
              else if sym = writesym
                then begin
                  getsym;
                if sym = lparen
                then begin
                  repeat
                    getsym;
                    expression([rparen,comma]+fsys);
                    gen(wrt,0,0);
                  until sym <> comma;
                  if sym <> rparen
                  then error(22);
                getsym
                end
                else
                  error(40)
              end;
            test(fsys, [ ], 19) 
          {测试下一记号是否正常, 否则出错, 跳过一些记号}
          end {statement};
      begin {block}
        dx := 3; {本过程数据空间栈顶指针} 
        tx0 := tx; {标识符表的长度(当前指针)} 
        table[tx].adr := cx; {本过程名的地址, 即下一条指令的序号}
        gen(jmp, 0, 0); {生成一条转移指令}
        if lev > levmax then error(32);
        {如果当前过程层号>最大层数, 则出错}
        repeat
          if sym = constsym then {处理常数说明语句}
          begin  
            getsym;
            repeat 
              constdeclaration; {处理一个常数说明}
              while sym = comma do {如果当前记号是逗号}
              begin 
                getsym; 
                constdeclaration 
              end; {处理下一个常数说明}
              if sym = semicolon 
                then getsym 
              else error(5)
                {如果当前记号是分号,则常数说明已处理完, 否则出错}
            until sym <> ident 
            {跳过一些记号, 直到当前记号不是标识符(出错时才用到)}
          end;
    
        if sym = varsym then {当前记号是变量说明语句开始符号}
          begin getsym;
            repeat 
              vardeclaration; {处理一个变量说明}
              while sym = comma do {如果当前记号是逗号}
              begin  
                getsym;  
                vardeclaration 
              end; 
              {处理下一个变量说明}
              if sym = semicolon 
                then getsym 
              else error(5)
              {如果当前记号是分号,则变量说明已处理完, 否则出错}
            until sym <> ident; 
            {跳过一些记号, 直到当前记号不是标识符(出错时才用到)}
          end;
        while sym = procsym do {处理过程说明}
        begin  
          getsym;
          if sym = ident then {如果当前记号是过程名}
          begin  
            enter(procedure2);  
            getsym  
          end 
          {把过程名填入符号表}
          else 
            error(4); {否则, 缺少过程名出错}
          if sym = semicolon 
            then getsym 
          else error(5);
          {当前记号是分号, 则取下一记号,否则,过程名后漏掉分号出错}
          block(lev+1, tx, [semicolon]+fsys); {处理过程体}
          {lev+1: 过程嵌套层数加1; tx: 符号表当前栈顶指针,
          也是新过程符号表起始位置; [semicolon]+fsys: 过程体开始和末尾符号集}
          if sym = semicolon then {如果当前记号是分号}
          begin 
            getsym; {取下一记号}
            test(statbegsys+[ident, procsym], fsys, 6)
            {测试当前记号是否语句开始符号或过程说明开始符号,
             否则报告错误6, 并跳过一些记号}
          end
          else error(5) {如果当前记号不是分号,则出错}
        end; {while}
      test(statbegsys+[ident], declbegsys, 7)
      {检测当前记号是否语句开始符号, 否则出错, 并跳过一些
      记号}
      until not (sym in declbegsys); 
      {回到说明语句的处理(出错时才用),直到当前记号不是说明语句
       的开始符号}
      code[table[tx0].adr].a := cx;  {table[tx0].adr是本过程名的第1代码(jmp, 0, 0)的地址,本语句即是将下一代码(本过程语句的第
    1条代码)的地址回填到该jmp指令中,(jmp, 0, cx)}
      with table[tx0] do {本过程名的第1条代码的地址改为下一指令地址cx}
      begin  
        adr := cx; {代码开始地址}
      end;
      cx0 := cx; {cx0记录起始代码地址}
      gen(int, 0, dx); {生成一条指令, 在栈顶为本过程留出数据空间}
      statement([semicolon, endsym]+fsys); {处理一个语句}
      gen(opr, 0, 0); {生成返回指令}
      test(fsys, [ ], 8); {测试过程体语句后的符号是否正常,否则出错}
      listcode; {打印本过程的中间代码序列}
    end  {block};
    
    procedure interpret;
      const stacksize = 500; {运行时数据空间()的上界}
      var p, b, t : integer; {程序地址寄存器, 基地址寄存器,栈顶地址寄存器}
          i : instruction; {指令寄存器}
          s : array [1..stacksize] of integer; {数据存储栈}
    
      function  base(l : integer) : integer;
      var  b1 : integer;
      begin
        b1 := b; {顺静态链求层差为l的外层的基地址}
        while l > 0 do
        begin  
          b1 := s[b1];  
          l := l-1 
        end;
        base := b1
      end {base};
      begin  
        writeln('START PL/0');
        writeln(fout, 'START PL/0');
        t := 0; {栈顶地址寄存器}
        b := 1; {基地址寄存器}
        p := 0; {程序地址寄存器}
        s[1] := 0;  
        s[2] := 0;  
        s[3] := 0; 
        {最外层主程序数据空间栈最下面预留三个单元}
        {每个过程运行时的数据空间的前三个单元是:SL, DL, RA;
        SL: 指向本过程静态直接外层过程的SL单元;
        DL: 指向调用本过程的过程的最新数据空间的第一个单元;
        RA: 返回地址 }
        repeat
          i := code[p]; {i取程序地址寄存器p指示的当前指令}
          p := p+1; {程序地址寄存器p加1,指向下一条指令}
          with i do
            case f of
              lit : 
              begin {当前指令是取常数指令(lit, 0, a)}
                t := t+1;  
                s[t] := a
              end;
              {栈顶指针加1, 把常数a取到栈顶}
              opr : case a of {当前指令是运算指令(opr, 0, a)}
                0 : 
                  begin {a=0,是返回调用过程指令}
                    t := b-1; {恢复调用过程栈顶} 
                    p := s[t+3]; {程序地址寄存器p取返回地址} 
                    b := s[t+2]; 
                    {基地址寄存器b指向调用过程的基地址}
                  end;
                1 : s[t] := -s[t]; {一元负运算, 栈顶元素的值反号}
                2 : begin {加法}
                      t := t-1;  
                      s[t] := s[t] + s[t+1] 
                    end;
                3 : begin {减法}
                      t := t-1;  
                      s[t] := s[t]-s[t+1]
                    end;
                4 : begin {乘法}
                      t := t-1;  
                      s[t] := s[t] * s[t+1]
                    end;
                5 : begin {整数除法}
                      t := t-1;  
                      s[t] := s[t] div s[t+1]
                    end;
                6 : s[t] := ord(odd(s[t])); 
                {算s[t]是否奇数, 是则s[t]=1, 否则s[t]=0}
                8 : begin  
                      t := t-1;
                      s[t] := ord(s[t] = s[t+1])
                    end; {判两个表达式的值是否相等,
                         是则s[t]=1, 否则s[t]=0}
                9: begin  
                      t := t-1;
                      s[t] := ord(s[t] <> s[t+1])
                    end; {判两个表达式的值是否不等,
                         是则s[t]=1, 否则s[t]=0}
                10 : begin  
                      t := t-1;
                      s[t] := ord(s[t] < s[t+1])
                    end; {判前一表达式是否小于后一表达式,
                         是则s[t]=1, 否则s[t]=0}
                11: begin  
                      t := t-1;
                      s[t] := ord(s[t] >= s[t+1])
                    end; {判前一表达式是否大于或等于后一表达式,
                       是则s[t]=1, 否则s[t]=0}
                12 : begin  
                      t := t-1;
                      s[t] := ord(s[t] > s[t+1])
                    end; {判前一表达式是否大于后一表达式,
                         是则s[t]=1, 否则s[t]=0}
                13 : begin  
                      t := t-1;
                      s[t] := ord(s[t] <= s[t+1])
                    end; {判前一表达式是否小于或等于后一表达式,
                        是则s[t]=1, 否则s[t]=0}
              end;
              lod : 
              begin {当前指令是取变量指令(lod, l, a)}
                t := t + 1;  
                s[t] := s[base(l) + a]
                {栈顶指针加1, 根据静态链SL,将层差为l,相对地址
                 为a的变量值取到栈顶}
              end;
              sto : 
              begin {当前指令是保存变量值(sto, l, a)指令}
                s[base(l) + a] := s[t];  
                writeln(s[t]); 
                writeln(fout, s[t]);
                {根据静态链SL,将栈顶的值存入层差为l,相对地址
                  为a的变量中}
                t := t-1 {栈顶指针减1}
              end;
              cal : 
              begin {当前指令是(cal, l, a)}
              {为被调用过程数据空间建立连接数据}
                s[t+1] := base( l ); 
                {根据层差l找到本过程的静态直接外层过程的数据空间的SL单元,
                  将其地址存入本过程新的数据空间的SL单元} 
                s[t+2] := b; 
                {调用过程的数据空间的起始地址存入本过程DL单元}
                s[t+3] := p;
                {调用过程cal指令的下一条的地址存入本过程RA单元}
                b := t+1; {b指向被调用过程新的数据空间起始地址} 
                p := a {指令地址寄存储器指向被调用过程的地址a}
              end;
              int : t := t + a; 
              {若当前指令是(int, 0, a), 则数据空间栈顶留出a大小的空间}
              jmp : p := a; 
              {若当前指令是(jmp, 0, a), 则程序转到地址a执行}
              jpc : 
              begin {当前指令是(jpc, 0, a)}
                if s[t] = 0 then p := a;
                {如果当前运算结果为“假”(0), 程序转到地址a
                 执行, 否则顺序执行}
                t := t-1 {数据栈顶指针减1}
              end;
              red : 
              begin {对red指令}
                writeln('Please input your number:'); 
                readln(s[base(l)+a]); {读一行数据,读入到相差l层,层内偏移为a的数据栈中的数据的信息}
              end;
              wrt : 
              begin {对wrt指令}
                writeln('your output: ', s[t]);
                writeln(fout, 'your output: ', s[t]);
                t := t+1  {栈顶上移}
              end
            end {with, case}
        until p = 0; 
        {程序一直执行到p取最外层主程序的返回地址0时为止}
        write('END PL/0');
        write(fout, 'END PL/0');
      end; {interpret}
    
    
    begin  {主程序}
      writeln('please input source program file name : ');
      readln(sfile);
      assign(fin,sfile);
      reset(fin);
      writeln('please input the file name to save result : ');
      readln(dfile);
      assign(fout,dfile);
      rewrite(fout);
    
      for ch := 'a' to ';' do  ssym[ch] := nul; 
      {ASCII码的顺序}
      word[1] := 'begin     '; word[2] := 'call      ';
      word[3] := 'const     '; word[4] := 'do        ';
      word[5] := 'end       '; word[6] := 'if        ';
      word[7] := 'odd       '; word[8] := 'procedure ';
      word[9] := 'read      '; word[10]:= 'then      ';
      word[11]:= 'var       '; word[12]:= 'while     ';
      word[13]:= 'write     ';
      {保留字}
      wsym[1] := beginsym;   wsym[2] := callsym;
      wsym[3] := constsym;   wsym[4] := dosym;
      wsym[5] := endsym;     wsym[6] := ifsym;
      wsym[7] := oddsym;     wsym[8] := procsym;
      wsym[9] := readsym;    wsym[10] := thensym;
      wsym[11] := varsym;  wsym[12] := whilesym;
      wsym[13] := writesym;
      {保留字的记号}
      ssym['+'] := plus;      ssym['-'] := minus;
      ssym['*'] := times;     ssym['/'] := slash;
      ssym['('] := lparen;     ssym[')'] := rparen;
      ssym['='] := eql;       ssym[','] := comma;
      ssym['.'] := period;     ssym['!'] := neq; {不等于 用!表示}
      ssym['<'] := lss;       ssym['>'] := gtr;
      ssym['@'] := leq;      ssym['#'] := geq; {小于等于 用@表示, 大于等于 用#表示}
      ssym[';'] := semicolon;
      {算符和标点符号的记号}
      mnemonic[lit] := '  LIT  ';    mnemonic[opr] := '  OPR  ';
      mnemonic[lod] := '  LOD  ';    mnemonic[sto] := '  STO  ';
      mnemonic[cal] := '  CAL  ';    mnemonic[int] := '  INT  ';
      mnemonic[jmp] := '  JMP  ';    mnemonic[jpc] := '  JPC  ';
      mnemonic[red] := '  RED  ';    mnemonic[wrt] := '  WRT  ';
      {中间代码指令的字符串}
      declbegsys := [constsym, varsym, procsym];
      {说明语句的开始符号}
      statbegsys := [beginsym, callsym, ifsym, whilesym];
      {语句的开始符号}
      facbegsys := [ident, number, lparen];
      {因子的开始符号}
      {page(output);} {为找到id page标识符}
      err := 0; {发现错误的个数}
      cc := 0; {当前行中输入字符的指针} 
      cx := 0; {代码数组的当前指针} 
      ll := 0; {输入当前行的长度} 
      ch := ' '; {当前输入的字符}
      kk := al; {标识符的长度}
      getsym; {取下一个记号}
      block(0, 0, [period]+declbegsys+statbegsys); {处理程序体}
      if sym <> period then error(9);
      {如果当前记号不是句号, 则出错}
      if err = 0 then interpret
      {如果编译无错误, 则解释执行中间代码}
      else 
        begin
          write('ERRORS IN PL/0 PROGRAM');
          write(fout, 'ERRORS IN PL/0 PROGRAM');
        end;
      writeln;
      close(fin);
      readln(sfile);
      close(fout);
    end.
    
    展开全文
  • 编译原理PL/0实验报告

    2010-06-26 12:07:00
    这是一份详细的编译原理实验,内容是修改PL/0的代码,增加else语句和一些其它的小功能。
  • 编译原理的大作业,pl语言的扩充,包括多种语句的扩充和数据类型的添加
  • 编译原理第2版 张素琴 pl0语言 C语言实现源代码
  • 编译原理大作业-PL0语言编译器

    千次阅读 2021-02-11 22:27:02
    编译原理大作业-PL0语言编译器一、实验目的二、源码说明1、头文件pl0.h(1 词法分析主要数据结构(通过enum symbol类实现)1、保留字(13+4个):2、运算符及界符(16+2个):3、标识符4、无符号整数5、字符串(2 中间...

    编译原理大作业-PL0语言编译器

    一、实验目的

    根据所学的编译原理理论知识,在符合 PL/0 语言基本词法、语法规则的前提下,以原PL/0 编译程序C语言版本代码为基础,对 PL/0 语言的功能进行扩充。使理论与实践相结合,加深对编译原理的理解。

    二、源码说明

    源码一共有两个版本。
    在这里插入图片描述

    一个是融合了编译器与解释器的主体代码pl0.cpp,实现编译执行的功能。另一个则是将主体代码拆分为编译器、解释器两部分,分别执行编译、执行的功能,这些代码都用到同一个pl0.h文件。因为逻辑相同,这里主要以主体代码为例进行说明。

    1、头文件pl0.h

    该文件主要定义了程序中用到的各数据结构,说明了程序中出现的函数。
    

    (1 词法分析主要数据结构(通过enum symbol类实现)

    PL/0单词种类一共5类(含新增)

    1、保留字(13+4个):

    begin、end、if、then、while、do、var、const、odd、procedure、call、read、write、break、 for、step、until

    2、运算符及界符(16+2个):

    := + - * / < <= = >= > # ( ) , ; . [ ]

    3、标识符

    4、无符号整数

    5、字符串

    在这里插入图片描述

    (2 中间代码生成与解释执行数据结构

    在这里插入图片描述

    类Pcode由F L A三部分构成,实现结构为struct instruction

    (3 符号表管理数据结构

    类型通过object进行分类,所有符号管理为内存中的table数组
    在这里插入图片描述

    (4 错误处理数据结构
    在这里插入图片描述
    开始、后跟符号集用于错误恢复,错误数组用于存储、输出错误信息
    在这里插入图片描述
    预定义的替换字串使得语法分析在出错时能够直接停止

    2、源文件pl0.cpp

    源程序可以分为初始化、主程序、词法分析、语法(+语义)分析、中间代码生成、解释执行、集合运算、出错处理、符号表管理9个模块,接下来逐一进行说明。

    在这里插入图片描述

    (1 初始化init()

    在这里插入图片描述

    进行运行前初始化,对保留字表 (word)、保留字表中每一个保留字对应的 symbol 类型 ( wsym )、部分符号对应的 symbol 类型表 ( ssym )、类 PCODE 指令助记符表 ( mnemonic )、开始以及后跟符号集合 ( declbegsys、statbegsys等)以及一些全局变量的初始化

    (2 main函数

    在这里插入图片描述
    为了方便从EditPlus执行,设定为从命令行读取参数,没有则手动输入。函数在打开pl0源程序文件后,会条用block进行分析,生成fa1.tmp、out.tmp等文件,其中fa.pcode文件输出虚拟机代码,若无错误则会调用解释器进行解释。

    (3 词法分析

    getch()函数

    在这里插入图片描述

    主要功能为读取一个字符放在全局变量ch里面,同时输出源代码以及行号。该函数读取pl0文件中一行,存入line缓冲区,line被getsym取空后再读一行。每次从line缓冲区读出一个字符放在全局变量ch里面,被函数getsym调用。

    getsym()函数

    在这里插入图片描述
    在这里插入图片描述

    词法分析从源文件中读出若干有效字符,组成一个 token 串,识别它的类型为保留字、标识符、数字或是其它符号。如果是保留字,把 sym 置成相应的保留字类型,如果是标识符,把 sym 置成 ident 表示是标识符,于此同时,id 变量中存放的即为保留字字符串或标识符名字。如果是数字,把 sym 置为 number,同时 num 变量中存放该数字的值。如果是字符串,把 sym 置为 OutStr,同时OutString 变量中存放该字符串的值。如果是其它的操作符,则直接把 sym 置成相应类型。经过本函数后ch 变量中存放的是下一个即将被识别的字符

    (4 语法分析+语义分析

    语法分析主要根据PL/0的EBNF,使用递归子程序法对每个语法单位编写分析程序,实现语法分析。在语法分析的过程中,通过testdo、nxtlev符号集实现错误处理,通过gendo实现语义分析的中间代码生成

    block()函数

    在这里插入图片描述

    此函数为编译程序主模块,lev参数为当前分程序所在层,tx为符号表当前尾指针,fsys为当前模块后跟符号集。

    statement ()函数

    在这里插入图片描述
    在这里插入图片描述

    constdeclaration ()函数

    在这里插入图片描述

    vardeclaration ()函数

    在这里插入图片描述

    expression ()函数

    在这里插入图片描述

    term ()函数

    在这里插入图片描述

    factor ()函数

    在这里插入图片描述
    在这里插入图片描述

    condition ()函数

    在这里插入图片描述

    (5 中间代码生成

    gen ()函数

    在这里插入图片描述

    依据传入的参数,在code数组中生成一行新Pcode并增加其尾指针

    listcode ()函数

    在这里插入图片描述

    依据传入的code数组指针,将code数组中存储的Pcode打印到控制台以及fa.pcode文件中

    (6 解释执行

    base ()函数

    在这里插入图片描述

    求出定义该过程的过程基址,l为层次差,b为上一过程(调用该过程的过程)基址,s为栈

    interpret ()函数

    在这里插入图片描述
    在这里插入图片描述

    该函数根据code数组存储的指令一个个执行,直到遇到opr 0,0。其指令运行的逻辑结构为一个栈s与三个寄存器b,p,t,b为基地址,p为下一指令地址,t为栈顶指针

    (7 集合运算

    在这里插入图片描述

    (8 出错处理

    在这里插入图片描述
    函数error()打印出错位置和错误编码

    函数test ()采用短语层恢复思想测试当前符号是否合法
    在这里插入图片描述

    在某一部分(如一条语句,一个表达式)将要结束时,test负责检测下一个符号属于该部分的后跟符号集合和补救用的集合,检测不通过时报错错误号为n。

    (9 符号表管理

    函数enter ()

    在这里插入图片描述该函数依据传入的符号类型不同采取不同的填表策略。

    函数position ()查找符号的位置,从后往前,保证先看此过程局部变量再看其他的。找到则返回在名字表中的位置,否则返回0.

    在这里插入图片描述

    函数ListTable ()输出符号表

    在这里插入图片描述

    三、PL/0 编译器功能的扩展

    (1)I/O 功能扩展

    思路:增加“字符串”语法单位仿C++的输出格式,可输入数字,直接输出字符串以及数字,主要修改write()

    原EBNF:
    在这里插入图片描述
    修改后:
    在这里插入图片描述

    步骤:

    1、增加数据结构

    在这里插入图片描述

    2、修改getsym()

    在这里插入图片描述

    3、修改statement中对write的分析

    在这里插入图片描述

    4、增加一条Pcode指令,修改解释器,使其能将栈顶ASCII码输出为字符

    (2)增加数组类型(数据结构+计算功能上的支持)

    思路:增加“[”、“]”语法单位,仿C、C++数组格式,可存储整数,顺序存放,以首地址+偏移量进行存取,数组索引可以是表达式以方便批量操作。
    由于存取需要两个数,需修改类Pcode指令,新增数组存取指令sta,lda。
    read、write语句需要对数组输入输出进行支持。
    输入数字,直接输出字符串以及数字,主要修改write()

    原EBNF

    在这里插入图片描述

    修改后:
    在这里插入图片描述

    步骤:
    1、增加数据结构
    在这里插入图片描述

    2、修改初始化参数
    在这里插入图片描述

    3、修改vardeclare()

    在这里插入图片描述

    4、修改statement() 标识符、read语句的解析
    在这里插入图片描述

    5、修改factor()

    在这里插入图片描述

    6、修改符号表填写enter(),将block函数尾部输出符号表的动作归纳,新增符号表输出ListTable 函数,在其中增加数组的输出。

    在这里插入图片描述

    (3)增加for、break语句

    思路:for语句实现类似于while语句,参照while语句的实现进行实现,与while语句对照如下:

    在这里插入图片描述

    因此只需要在while的基础上,添加两条i变量的赋值语句即可
    对于break,只需要生成跳出循环的类Pcode即可,但涉及到跳出哪个循环的问题。

    EBNF新增
    <语句> ::= break
    | for <语句> step <表达式> until <表达式> do <语句>
    步骤
    1、增加数据结构
    在这里插入图片描述

    2、增加初始化参数
    在这里插入图片描述

    3、statement()函数增加for、break语句判断,其中用到全局变量jp来填写跳出循环地址,判断跳出的循环是哪一层

    在这里插入图片描述

    (4)新增功能的错误处理

    思路:
    PL/0 采用短语层恢复的思想。test函数能实现一个语法单位的合法性测试。如果该单位中出错,则有节制地跳读一部分代码。test的参数S1为合法符号集合,S2为停止符号集合,n为错误编码。调用test时,程序会不断跳读直到得到S1或S2中的符号。
    同时,在pl0编译系统中有行号计数器,可以记录错误发生的位置,错误发生后,可以查看错误代码位置。因此,只需要将新增功能的First集与Follow集添加到原有的集合中,再增加新的错误提示代码即可。
    原PL/0程序只有错误编号,关于错误编号具体对应的错误,没有说明,也就是说,原PL0程序出错只能知道错误的位置而不能知道错误类型。因此还需重新整理编号错误类型,增加一个数组存储错误信息并输出。(这里整理的错误编号见附录。)
    步骤
    1、新增数据结构

    在这里插入图片描述

    2、增加数据初始化

    在这里插入图片描述

    3、整理错误号,对所有错误信息汇总(见附录)
    4、对新增功能增加错误判断(部分)
    在这里插入图片描述

    5、使error函数能够输出错误信息

    (5)IDE图形化界面设计(EditPlus可在官网下载,配置文件见压缩包)

    思路:可以在已有的各文本编辑器里面添加插件或者配置文件,以使其支持PL/0语言。由于VScode、Sublime配置过于复杂,不考虑。而UltraEdit等又过于臃肿,相比之下,小巧轻便的EditPlus可以很方便的配置PL/0语言编译环境

    1、配置代码高亮(.stx文件)

    在这里插入图片描述

    编辑好后按.stx文件保存即可

    2、配置自动补全(.acp文件)

    在这里插入图片描述

    3、将acp、stx文件放入EditPlus中

    在这里插入图片描述

    4、配置运行文件,使得EditPlus能够对PL/0源程序进行编译、执行
    注意这些exe文件需要有对命令行输入的处理,也就是在main函数中有arg等参数。

    在这里插入图片描述

    四、成果展示

    (1)输入输出(猜大小,值为2)

    (具体代码见T1格式化输入输出.pl0文件)

    在这里插入图片描述

    (2)数组结构的运算

    (具体代码见 T2数组结构运算.pl0文件)
    在这里插入图片描述

    经过运算比较,得出结果正确
    

    (3)for循环,break语句

    (具体代码见 T3for与break.pl0文件)

    在这里插入图片描述

    在for循环中把lkz[i]数组前0-4项赋值为2020,在while循环中把lkz数组前0-6项取相反数。

    可以看到break使循环停止
    在这里插入图片描述

    (4)错误处理

    (错误检测的验证代码见 E22-E29.pl0文件)
    新加入功能:若错误超过一定值则不往下生成Pcode,自动停止
    E22:
    在这里插入图片描述

    E25:
    在这里插入图片描述

    E27:

    在这里插入图片描述

    E28:
    在这里插入图片描述

    E29:

    在这里插入图片描述

    (5)IDE功能(自动补全、代码折叠、编译运行等)

    打开.pl0文件,按下图所示快捷键即可编译、运行、编译运行。

    在这里插入图片描述

    敲出begin、if等关键字时,按下空格键自动补全:
    在这里插入图片描述

    代码高亮

    在这里插入图片描述

    代码折叠
    在这里插入图片描述

    五、总结

    本次实验对PL/0语言的编译程序进行了功能上的扩充。在原理上,深刻理解了一个语言编译器的运作过程,对其逻辑上7大部分的模型体会更深。
    值得一提的是,PL/0语言编译器因仅用于研究编译器原理,没有代码优化、目标代码生成部分其生成的中间代码直接通过C语言编译器生成的解释器程序运行在机器上,本质上是应用的C语言的跨平台性。
    一个优秀的编译器并不是随随便便就能写出来的。即便是对一个简单的编译器做修改,本次实验仍在实现过程中遇到了相当多的问题。可想而知做一门强大、复杂语言的编译器是一个多复杂、困难的事情。
    代码仍可能存在一些 bug,虽然在自己的测试代码下没遇到问题,但仍无法保证其对所有pl0代码都能正确编译执行。

    六、参考

    1、https://www.allhuo.com/2009/04/01/%E5%A6%82%E4%BD%95%E5%9C%A8-editplus-%E4%B8%AD%E5%88%9B%E5%BB%BA%E8%AF%AD%E6%B3%95%E6%96%87%E4%BB%B6%E8%87%AA%E5%AE%9A%E4%B9%89%E8%AF%AD%E6%B3%95%E9%AB%98%E4%BA%AE/
    2、https://www.cnblogs.com/picaso/archive/2012/03/07/2383620.html
    3、《编译原理》第3版 王生原 清华大学出版社

    七、附录

    1、PL/0 EBNF描述(新增修改的EBNF已标黄)

    在这里插入图片描述
    在这里插入图片描述

    2、PL/0 语法图

    在这里插入图片描述

    3、类P-code表(新增指令已标红)

    在这里插入图片描述
    在这里插入图片描述

    4、错误编号汇总(新增已标红)

    在这里插入图片描述
    在这里插入图片描述

    5、代码

    pl0.h

    #pragma once
    
    #include <stdio.h>
    
    #define norw 13+4     /* 保留字个数 */
    #define txmax 100   /* 名字表容量 */
    #define nmax 14     /* 数字的最大位数 */
    #define al 10       /* 标识符的最大长度 */
    #define amax 2047   /* 常量最大值 */
    #define levmax 3    /* 最大允许过程嵌套声明层数 [0,  levmax]*/
    #define cxmax 1000   /* 最多的虚拟机代码数 */
    #define stacksize 500    /* 解释执行时使用的栈 */
    
    //由16+2个运算符及界符,13+4个保留字还有数字、标识符+字符串构成
    enum symbol {
        nul, ident, number,
        becomes, plus, minus, times, slash,
        eql, neq, lss, leq, gtr, geq,
        lparen, rparen, comma, semicolon, period,
        beginsym, endsym, ifsym, thensym, whilesym,
        dosym, varsym, constsym, oddsym, procsym, callsym,
        readsym, writesym,
        //在此开始增加单词
        OutStr, //单引号括起来的输出字符串
        Lsquare, Rsquare, //[ ]
        breaksym,//break关键字
        forsym, stepsym, untilsym,//for,step,until关键字
    };
    #define symnum 32+7	/* 符号数 */
    #define StrLen 20 //符号串最大长度
    char OutString[StrLen];//write用于输出的字符串
    
    /* 虚拟机代码 */
    enum fct {
        lit, opr, lod,
        sto, cal, inte,
        jmp, jpc,
        //新增数组存取
        sta, lda,
    };
    #define fctnum 8+2	/* 虚拟机代码数 */
    
    /* 虚拟机代码结构 */
    struct instruction
    {
        enum fct f; /* 虚拟机代码指令 */
        int l;      /* 引用层与声明层的层次差 */
        int a;      /* 根据f的不同而不同 */
    };
    
    FILE* fas;  /* 输出名字表 */
    FILE* fa;   /* 输出虚拟机代码 */
    FILE* fa1;  /* 输出源文件及其各行对应的首地址 */
    FILE* fa2;  /* 输出结果 */
    char ch;            //用于词法分析器,存放最近一次从文件中读出的字符,getch使用
    enum symbol sym;    // 词法分析器输出结果之用,存放最近一次识别出来的 符号token 的类型
    char id[al + 1];    // 词法分析器输出结果之用,当前ident, 多出的一个字节用于存放0
    int num;            // 词法分析器输出结果之用,当前number
    int cc, ll;          // getch使用的计数器,cc表示当前字符(ch)的位置,即行缓冲区的列指针,ll为行缓冲区长度
    int cx;             // 虚拟机代码指针, 取值范围[0, cxmax-1],代码生成模块总在 cx 所指位置生成新的代码
    char line[81];      //行缓冲区,用于从文件读出一行,供词法分析获取单词时之用
    char a[al + 1];       /* 词法分析器中用于临时存放正在分析的词, 多出的一个字节用于存放0 */
    struct instruction code[cxmax]; /* 存放编译得到的类 PCODE虚拟机代码的数组 */
    char word[norw][al];        /* 保留字 */
    enum symbol wsym[norw];     /* 保留字对应的符号值 */
    enum symbol ssym[256];      /* 单字符的符号值 */
    char mnemonic[fctnum][5];   //类 PCODE 指令助记符表
    
    bool declbegsys[symnum];    /* 表示声明开始的符号集合 */
    bool statbegsys[symnum];    /* 表示语句开始的符号集合 */
    bool facbegsys[symnum];     /* 表示因子开始的符号集合 */
    bool ExpressionFollow[symnum]; //表达式后跟符号集
    
    /* 符号表中的类型 */
    enum object {
        constant,
        variable,
        procedur,
        array       //lkz增加部分
    };
    /* 符号表结构 */
    struct tablestruct
    {
        char name[al];      /* 名字 */
        enum object kind;   /* 类型:const, var, array or procedure */
        int val;            /* 数值,仅const使用 */
        int level;          /* 如果是变量名或过程名,存放层差、偏移地址和大小*/
        int adr;            /* 地址,仅const不使用 */
        int size;           /* 需要分配的数据区空间, 仅procedure,array使用 */
    };
    
    struct tablestruct table[txmax]; /* 符号表 */
    
    FILE* fin;
    FILE* fout;
    char fname[al];
    int err; //错误计数器
    
    #pragma region 就是函数调用与错误处理放在一起
    
    
    /* 当函数中会发生fatal error时,返回-1告知调用它的函数,最终退出程序 */
    #define getsymdo                      if(-1 == getsym()) return -1
    #define getchdo                       if(-1 == getch()) return -1
    #define testdo(a, b, c)               if(-1 == test(a, b, c)) return -1
    #define gendo(a, b, c)                if(-1 == gen(a, b, c)) return -1
    #define expressiondo(a, b, c)         if(-1 == expression(a, b, c)) return -1
    #define factordo(a, b, c)             if(-1 == factor(a, b, c)) return -1
    #define termdo(a, b, c)               if(-1 == term(a, b, c)) return -1
    #define conditiondo(a, b, c)          if(-1 == condition(a, b, c)) return -1
    #define statementdo(a, b, c)          if(-1 == statement(a, b, c)) return -1
    #define constdeclarationdo(a, b, c)   if(-1 == constdeclaration(a, b, c)) return -1
    #define vardeclarationdo(a, b, c)     if(-1 == vardeclaration(a, b, c)) return -1
    #define errorDo(a)                    if(-1 == error(a)) return -1
    #pragma endregion
    
    #pragma region 各函数
    int error(int n);
    int getsym();
    int getch();
    void init();
    int gen(enum fct x, int y, int z);
    int test(bool* s1, bool* s2, int n);
    int inset(int e, bool* s);
    int addset(bool* sr, bool* s1, bool* s2, int n);
    int subset(bool* sr, bool* s1, bool* s2, int n);
    int mulset(bool* sr, bool* s1, bool* s2, int n);
    int block(int lev, int tx, bool* fsys);
    void interpret();
    int factor(bool* fsys, int* ptx, int lev);
    int term(bool* fsys, int* ptx, int lev);
    int condition(bool* fsys, int* ptx, int lev);
    int expression(bool* fsys, int* ptx, int lev);
    int statement(bool* fsys, int* ptx, int lev);
    void listcode(int cx0);
    int vardeclaration(int* ptx, int lev, int* pdx);
    int constdeclaration(int* ptx, int lev, int* pdx);
    int position(char* idt, int tx);
    void enter(enum object k, int* ptx, int lev, int* pdx);
    int base(int l, int* s, int b);
    void ListTable(int tx0, int tx);
    #pragma endregion
    
    
    //错误信息数组
    const char* err_msg[] = {
        "",//0
        "'='写成了':='",//1
        "'='后面要跟一个数字",//2
        "标识符后面要跟一个‘=’",//3
        "在const,var,procedure后面要有一个标识符",//4
        "缺少','或者';'",//5
        "过程名错误!",//6
        "需要声明",//7
        "声明后边是一个不正确的符号",//8
        "少了'.',程序无法正常结束",//9
        "少了';'",//10
        "发现未声明的标识符!",//11
        "非法赋值",//12
        "少了':='",//13
        "call之后缺少标识符!",//14
        "call之后标识符不是过程!",//15
        "少了then",//16
        "缺少';'或者end",//17
        "少了do",//18
        "符号错误",//19
        "条件语句中未发现操作符(“#,>”等)",//20
        "不能把过程的标识符放在表达式里!",//21
        "单引号后未跟单引号,词法分析出错!",//22
        "符号后面不能跟着<因子>",//23
        "符号不能作为<表达式>的开始!",//24
        "数组声明有误",//25
        "write里面不是表达式或字符串!",//26
        "break未写在循环中!",//27
        "for语句缺少step或until !",//28
        "for 语句循环变量类型错误!",//29
        "数字过大!",//30
        "常量超过可定义的最大值!",//31
        "超过允许的最大嵌套层数,层数太多啦!",//32
        "格式错误,应是右括号')'",//33
        "格式错误,应是左括号'('",//34
        "read里不是标识符ID,或该标识符未声明",//35
    };
    
    /* 用于循环语句跳出的辅助表结构 */
    struct JumpOut
    {
        int Addr; //生成的jmp虚拟机代码地址
        int CycleNum; //外层循环数
    };
    
    struct JumpOut jp;//全局变量,用于循环语句跳出
    
    

    pl0.cpp

    #define _CRT_SECURE_NO_WARNINGS
    
    /*
     * PL/0 编译器
     *
     * 使用方法:
     * 运行后输入PL/0源程序文件?
     * 回答是否输出虚拟机代码
     * 回答是否输出名字表
     * fa.pcode输出虚拟机代码
     * fa1.tmp输出源文件及其各行对应的首地址
     * out.tmp输出运行结果
     * fas.tmp输出名字表
     */
    
    #include "pl0.h"
    #include "string.h"
    
     //运行前初始化,对保留字表 (word)、保留字表中每一个保留字对应的 symbol 类型 ( wsym )、
     //部分符号对应的 symbol 类型表 ( ssym )、类 PCODE 指令助记符表 ( mnemonic )、
     //声明开始集合 ( declbegsys )、表达式开始集合 ( statbegsys )、
     //项开始符号集合 ( facbegsys ) 以及一些全局变量的初始化
    void init()
    {
    	jp.CycleNum = 0;
    	jp.Addr = 0;
    	int i;
    	// ASCII 范围(0–31 控制字符, 32–126 分配给了能在键盘上找到的字符
    	//数字 127 代表 DELETE 命令, 后 128 个是扩展 ASCII 打印字符) 因此共 256 个
    	/* 设置单字符符号 */
    	for (i = 0; i <= 255; i++)
    	{
    		ssym[i] = nul;
    	}
    	ssym['+'] = plus;
    	ssym['-'] = minus;
    	ssym['*'] = times;
    	ssym['/'] = slash;
    	ssym['('] = lparen;
    	ssym[')'] = rparen;
    	ssym['='] = eql;
    	ssym[','] = comma;
    	ssym['.'] = period;
    	ssym['#'] = neq;
    	ssym[';'] = semicolon;
    	ssym['['] = Lsquare;
    	ssym[']'] = Rsquare;
    
    
    	/* 设置保留字名字,按照字母顺序,便于折半查找 */
    	strcpy(&(word[0][0]), "begin");
    
    	strcpy(&(word[1][0]), "break");
    
    	strcpy(&(word[2][0]), "call");
    	strcpy(&(word[3][0]), "const");
    	strcpy(&(word[4][0]), "do");
    	strcpy(&(word[5][0]), "end");
    
    	strcpy(&(word[6][0]), "for");
    
    	strcpy(&(word[7][0]), "if");
    	strcpy(&(word[8][0]), "odd");
    	strcpy(&(word[9][0]), "procedure");
    	strcpy(&(word[10][0]), "read");
    
    	strcpy(&(word[11][0]), "step");
    
    	strcpy(&(word[12][0]), "then");
    
    	strcpy(&(word[13][0]), "until");
    
    	strcpy(&(word[14][0]), "var");
    	strcpy(&(word[15][0]), "while");
    	strcpy(&(word[16][0]), "write");
    
    	/* 设置保留字符号 */
    	wsym[0] = beginsym;
    
    	wsym[1] = breaksym;
    
    	wsym[2] = callsym;
    	wsym[3] = constsym;
    	wsym[4] = dosym;
    	wsym[5] = endsym;
    
    	wsym[6] = forsym;
    
    	wsym[7] = ifsym;
    	wsym[8] = oddsym;
    	wsym[9] = procsym;
    	wsym[10] = readsym;
    
    	wsym[11] = stepsym;
    
    	wsym[12] = thensym;
    
    	wsym[13] = untilsym;
    
    	wsym[14] = varsym;
    	wsym[15] = whilesym;
    	wsym[16] = writesym;
    
    	/* 设置指令名称 */
    	strcpy(&(mnemonic[lit][0]), "lit");
    	strcpy(&(mnemonic[opr][0]), "opr");
    	strcpy(&(mnemonic[lod][0]), "lod");
    	strcpy(&(mnemonic[sto][0]), "sto");
    	strcpy(&(mnemonic[cal][0]), "cal");
    	strcpy(&(mnemonic[inte][0]), "int");
    	strcpy(&(mnemonic[jmp][0]), "jmp");
    	strcpy(&(mnemonic[jpc][0]), "jpc");
    	//数组
    	strcpy(&(mnemonic[sta][0]), "sta");
    	strcpy(&(mnemonic[lda][0]), "lda");
    
    	/* 设置符号集 */
    	for (i = 0; i < symnum; i++)
    	{
    		declbegsys[i] = false;
    		statbegsys[i] = false;
    		facbegsys[i] = false;
    		ExpressionFollow[i] = false;
    	}
    
    	/* 设置声明开始符号集 */
    	declbegsys[constsym] = true;
    	declbegsys[varsym] = true;
    	declbegsys[procsym] = true;
    
    	/* 设置语句开始符号集 */
    	statbegsys[beginsym] = true;
    	statbegsys[callsym] = true;
    	statbegsys[ifsym] = true;
    	statbegsys[whilesym] = true;
    	statbegsys[readsym] = true;
    	statbegsys[writesym] = true;
    	statbegsys[ident] = true;
    
    	/* 设置因子开始符号集 */
    	facbegsys[ident] = true;
    	facbegsys[number] = true;
    	facbegsys[lparen] = true;
    
    	/* 表达式后跟符号集 */
    	ExpressionFollow[semicolon] = true;
    	ExpressionFollow[period] = true;
    	ExpressionFollow[comma] = true;
    	ExpressionFollow[rparen] = true;
    	ExpressionFollow[Rsquare] = true;
    	ExpressionFollow[eql] = true;
    	ExpressionFollow[neq] = true;
    	ExpressionFollow[geq] = true;
    	ExpressionFollow[leq] = true;
    	ExpressionFollow[gtr] = true;
    	ExpressionFollow[lss] = true;
    	ExpressionFollow[endsym] = true;
    	ExpressionFollow[thensym] = true;
    	ExpressionFollow[dosym] = true;
    	ExpressionFollow[stepsym] = true;
    	ExpressionFollow[untilsym] = true;
    }
    
    //主函数,主要命令行询问
    int main(int argc, char* argv[])
    {
    	bool nxtlev[symnum];
    
    	if (argc > 1) {
    		strcpy(fname, argv[1]);
    	}
    	else
    	{
    		printf("请输入pl0文件");
    		scanf("%s", &fname);
    		//return 0;
    	}
    
    	fin = fopen(fname, "r");
    
    	if (fin)
    	{
    		fa1 = fopen("fa1.tmp", "w");
    		fprintf(fa1, "pl/0 file:   ");
    		fprintf(fa1, "%s\n", fname);
    
    		init();     /* 初始化 */
    
    		err = 0;
    		cc = cx = ll = 0;
    		ch = ' ';
    
    		//看能否取出第一个单词
    		if (-1 != getsym())
    		{
    			fa = fopen("fa.pcode", "w");
    			fas = fopen("fas.tmp", "w");
    			addset(nxtlev, declbegsys, statbegsys, symnum);
    			nxtlev[semicolon] = true;
    			nxtlev[period] = true;
    			if (-1 == block(0, 0, nxtlev))   /* 调用编译程序 */
    			{
    				fclose(fin);
    				printf("\n");
    			}
    			fclose(fa);
    			fclose(fas);
    			//当前单词是否为.
    			if (sym != period && err == 0)
    			{
    				error(9);
    			}
    			//要等所有错误处理完才能关闭错误打印文件
    			fclose(fa1);
    			//源程序没有错误
    			if (err != 0) {
    				printf("pl/0程序中存在错误!");
    			}
    			else {
    				printf("恭喜,程序正确!\n编译完成(*^▽^*)\n");
    				fa2 = fopen("out.tmp", "w");
    				interpret();    /* 调用解释执行程序 */
    				fclose(fa2);
    			}
    		}
    		fclose(fin);
    	}
    	else
    	{
    		printf("Can't open file!\n");
    	}
    
    	printf("\n");
    	getchar();
    	getchar();
    	getchar();
    	return 0;
    }
    
    #pragma region 词法分析
    
    
    /*
    * 漏掉换行符,读取一个字符。
    *
    * 读一行,存入line缓冲区,line被getsym取空后再读一行
    *
    * 每次从line缓冲区读出一个字符放在全局变量ch里面,输出代码行号
    *
    * 被函数getsym调用。
    */
    int getch()
    {
    	//读完一行
    	if (cc == ll)
    	{
    		if (feof(fin))  // 文件结束
    		{
    			printf("program incomplete");
    			return -1;
    		}
    		ll = 0;
    		cc = 0;
    		printf("%d ", cx);
    		fprintf(fa1, "%d ", cx);
    		ch = ' ';
    		//如果不是换行符
    		while (ch != 10)
    		{
    			//读入一个字符到ch
    			if (EOF == fscanf(fin, "%c", &ch))
    			{
    				line[ll] = 0;
    				break;
    			}
    			printf("%c", ch);
    			fprintf(fa1, "%c", ch);
    			//往后再读一个字符
    			line[ll] = ch;
    			ll++;
    		}
    		printf("\n");
    		fprintf(fa1, "\n");
    	}
    	ch = line[cc];
    	cc++;
    	return 0;
    }
    
    //词法分析,获取一个符号
    //从源文件中读出若干有效字符,组成一个 token 串,识别它的类型为保留字/标识
    //符/数字或是其它符号。如果是保留字,把 sym 置成相应的保留字类型,如果是标
    //识符,把 sym 置成 ident 表示是标识符,于此同时,id 变量中存放的即为保留
    //字字符串或标识符名字。如果是数字,把 sym 置为 number,同时 num 变量中存
    //放该数字的值。如果是其它的操作符,则直接把 sym 置成相应类型。经过本过程后
    //ch 变量中存放的是下一个即将被识别的字符
    
    int getsym()
    {
    	int i, j, k;
    
    	/* the original version lacks "\r", thanks to foolevery */
    	while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t')  /* 忽略空格、换行、回车和TAB */
    	{
    		getchdo;
    	}
    
    	//名字或保留字(以A..z开头)
    	if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
    	{
    		k = 0;
    		do {
    			if (k < al)
    			{
    				a[k] = ch;
    				k++;
    			}
    			getchdo;
    		} while (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'Z');
    		a[k] = 0;
    		strcpy(id, a);
    		i = 0;
    		j = norw - 1;
    		do {    /* 二分查找,搜索当前符号是否为保留字 */
    			k = (i + j) / 2;
    			if (strcmp(id, word[k]) <= 0)
    			{
    				j = k - 1;
    			}
    			if (strcmp(id, word[k]) >= 0)
    			{
    				i = k + 1;
    			}
    		} while (i <= j);
    		if (i - 1 > j)
    		{
    			sym = wsym[k]; // 找到则标记保留字
    		}
    		else
    		{
    			sym = ident; // 否则标记为标识符
    		}
    	}
    	// 为数字:以0..9开头 
    	else if (ch >= '0' && ch <= '9')
    	{
    		k = 0;
    		num = 0;
    		sym = number;
    		do {
    			num = 10 * num + ch - '0';
    			k++;
    			getchdo;
    		} while (ch >= '0' && ch <= '9'); /* 获取数字的值 */
    		k--;
    		if (k > nmax)
    		{
    			errorDo(30);
    		}
    	}
    	//占两个字符的运算符及界符
    	else
    	{
    		if (ch == ':')      /* 检测赋值符号 */
    		{
    			getchdo;
    			if (ch == '=')
    			{
    				sym = becomes;
    				getchdo;
    			}
    			else
    			{
    				sym = nul;  /* 不能识别的符号 */
    			}
    		}
    		else if (ch == '<')      /* 检测小于或小于等于符号 */
    		{
    			getchdo;
    			if (ch == '=')
    			{
    				sym = leq;
    				getchdo;
    			}
    			else
    			{
    				sym = lss;
    			}
    		}
    		else if (ch == '>')        /* 检测大于或大于等于符号 */
    		{
    			getchdo;
    			if (ch == '=')
    			{
    				sym = geq;
    				getchdo;
    			}
    			else
    			{
    				sym = gtr;
    			}
    		}
    		else if (ch == '\'')        /* 检测单引号框起来的输出字符 */
    		{
    			sym = OutStr;
    			int index = 0;
    			getchdo;
    			while (ch != '\'' && index < StrLen)
    			{
    				OutString[index] = ch;
    				index++;
    				getchdo;
    			}
    			if (ch != '\'' && index == StrLen) {
    				errorDo(22);
    			}
    			getchdo;
    			OutString[index] = 0;
    		}
    		else            /* 当符号不满足上述条件时,全部按照单字符符号处理 */
    		{
    			sym = ssym[ch];
    			if (sym != period)
    			{
    				getchdo;
    			}
    		}
    	}
    	return 0;
    }
    
    #pragma endregion
    
    #pragma region 语法分析
    
    /*
    * 编译程序主模块
    *
    * lev:    当前分程序所在层
    * tx:     名字表当前尾指针
    * fsys:   当前模块后跟符号集
    */
    int block(int lev, int tx, bool* fsys)
    {
    	int i;
    
    	int dx;                 /* 名字分配到的相对地址 */
    	int tx0;                /* 保留初始tx */
    	int cx0;                /* 保留初始cx */
    	bool nxtlev[symnum];    /* 在下级函数的参数中,符号集合均为值参,但由于使用数组实现,
    							传递进来的是指针,为防止下级函数改变上级函数的集合,开辟新的空间
    							传递给下级函数*/
    
    	dx = 3;
    	tx0 = tx;               /* 记录本层符号的初始位置 */
    	table[tx].adr = cx;
    
    	gendo(jmp, 0, 0);
    
    	if (lev > levmax)
    	{
    		errorDo(32);
    	}
    
    	do {
    
    		if (sym == constsym)    /* 收到常量声明符号,开始处理常量声明 */
    		{
    			getsymdo;
    			constdeclarationdo(&tx, lev, &dx);  /* dx的值会被constdeclaration改变,使用指针 */
    			while (sym == comma)
    			{
    				getsymdo;
    				constdeclarationdo(&tx, lev, &dx);
    			}
    			if (sym == semicolon)
    			{
    				getsymdo;
    			}
    			else
    			{
    				errorDo(5);   /*漏掉了逗号或者分号*/
    			}
    		}
    
    		if (sym == varsym)      /* 收到变量声明符号,开始处理变量声明 */
    		{
    			getsymdo;
    
    			vardeclarationdo(&tx, lev, &dx);
    			while (sym == comma)
    			{
    				getsymdo;
    				vardeclarationdo(&tx, lev, &dx);
    			}
    			if (sym == semicolon)
    			{
    				getsymdo;
    			}
    			else
    			{
    				errorDo(5);
    			}
    		}
    
    		while (sym == procsym) /* 收到过程声明符号,开始处理过程声明 */
    		{
    			getsymdo;
    
    			if (sym == ident)
    			{
    				enter(procedur, &tx, lev, &dx); /* 记录过程名字 */
    				getsymdo;
    			}
    			else
    			{
    				errorDo(4);   /* procedure后应为标识符 */
    			}
    
    			if (sym == semicolon)
    			{
    				getsymdo;
    			}
    			else
    			{
    				errorDo(5);   /* 漏掉了分号 */
    			}
    
    			memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    			nxtlev[semicolon] = true;
    			if (-1 == block(lev + 1, tx, nxtlev))
    			{
    				return -1;  /* 递归调用 */
    			}
    
    			if (sym == semicolon)
    			{
    				getsymdo;
    				memcpy(nxtlev, statbegsys, sizeof(bool) * symnum);
    				nxtlev[procsym] = true;
    				testdo(nxtlev, fsys, 6);
    			}
    			else
    			{
    				errorDo(5);   /* 漏掉了分号 */
    			}
    		}
    		memcpy(nxtlev, statbegsys, sizeof(bool) * symnum);
    		testdo(nxtlev, declbegsys, 7);
    	} while (inset(sym, declbegsys));   /* 直到没有声明符号 */
    
    	code[table[tx0].adr].a = cx;    /* 开始生成当前过程代码 */
    	table[tx0].adr = cx;            /* 当前过程代码地址 */
    	table[tx0].size = dx;           /* 声明部分中每增加一条声明都会给dx增加1,声明部分已经结束,dx就是当前过程数据的size */
    	cx0 = cx;
    	gendo(inte, 0, dx);             /* 生成分配内存代码 */
    
    	ListTable(tx0, tx);        //输出符号表
    
    	/* 语句后跟符号为分号或end */
    	memcpy(nxtlev, fsys, sizeof(bool) * symnum);  /* 每个后跟符号集和都包含上层后跟符号集和,以便补救 */
    	nxtlev[semicolon] = true;
    	nxtlev[endsym] = true;
    	statementdo(nxtlev, &tx, lev);
    	gendo(opr, 0, 0);                       /* 每个过程出口都要使用的释放数据段指令 */
    	memset(nxtlev, 0, sizeof(bool) * symnum); /*分程序没有补救集合 */
    	testdo(fsys, nxtlev, 8);                /* 检测后跟符号正确性 */
    	listcode(cx0);                          /* 输出代码 */
    	return 0;
    }
    /*
    * 语句处理
    */
    int statement(bool* fsys, int* ptx, int lev)
    {
    	int i, cx1, cx2, ArrayNo = -1;
    	bool nxtlev[symnum];
    
    	if (sym == ident)   /* 准备按照赋值语句处理 */
    	{
    		i = position(id, *ptx);
    		if (i == 0)
    		{
    			errorDo(11);  /* 变量未找到 */
    		}
    		else
    		{
    			if (table[i].kind != variable && table[i].kind != array)
    			{
    				errorDo(12);  /* 赋值语句格式错误 */
    				i = 0;
    			}
    			else
    			{
    				if (table[i].kind == array) {
    					getsymdo;
    					if (sym == Lsquare) { getsymdo; }
    					else error(25);
    					expressiondo(nxtlev, ptx, lev);//数组序号保存在栈顶
    					if (sym != Rsquare) error(25);
    				}
    				getsymdo;
    				if (sym == becomes)
    				{
    					getsymdo;
    				}
    				else
    				{
    					errorDo(13);  /* 没有检测到赋值符号 */
    				}
    				memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    				expressiondo(nxtlev, ptx, lev); /* 处理赋值符号右侧表达式 */
    				if (i != 0)
    				{
    					/* expression将执行一系列指令,但最终结果将会保存在栈顶,执行sto命令完成赋值 */
    					if (table[i].kind == array)
    					{
    						gendo(sta, lev - table[i].level, table[i].adr);
    					}
    					else { gendo(sto, lev - table[i].level, table[i].adr); }
    				}
    			}
    		}//if (i == 0)
    	}
    	else if (sym == readsym) /* 准备按照read语句处理 */
    	{
    		getsymdo;
    		if (sym != lparen)
    		{
    			errorDo(34);  /* 格式错误,应是左括号 */
    		}
    		else
    		{
    			do {
    				getsymdo;
    				if (sym == ident || sym == array)
    				{
    					i = position(id, *ptx); /* 查找要读的变量 */
    				}
    				else
    				{
    					i = 0;
    				}
    
    				if (i == 0)
    				{
    					errorDo(35);  /* read()中应是声明过的变量名 */
    				}
    				else if (table[i].kind != variable && table[i].kind != array)
    				{
    					errorDo(35);	/* read()参数表的标识符不是变量 */
    				}
    				else
    				{
    					if (table[i].kind == array) {
    						getsymdo;
    						if (sym == Lsquare) { getsymdo; }
    						else errorDo(25);
    						expressiondo(nxtlev, ptx, lev);
    						if (sym != Rsquare) error(25);
    					}
    					gendo(opr, 0, 16);  /* 生成输入指令,读取值到栈顶 */
    					if (table[i].kind == array)
    					{
    						gendo(sta, lev - table[i].level, table[i].adr);
    					}
    					else { gendo(sto, lev - table[i].level, table[i].adr);/* 储存到变量 */ }
    				}
    				getsymdo;
    
    			} while (sym == comma); /* 一条read语句可读多个变量 */
    		}
    		if (sym != rparen)
    		{
    			errorDo(33);  /* 格式错误,应是右括号 */
    			while (!inset(sym, fsys))   /* 出错补救,直到收到上层函数的后跟符号 */
    			{
    				getsymdo;
    			}
    		}
    		else
    		{
    			getsymdo;
    		}
    	}
    	else if (sym == writesym)    /* 准备按照write语句处理,与read类似 */
    	{
    		getsymdo;
    		if (sym == lparen)
    		{
    			do {
    				getsymdo;
    				memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    				nxtlev[rparen] = true;
    				nxtlev[comma] = true;       /* write的后跟符号为) or , */
    				if (sym == OutStr)//符号串
    				{
    					int index = 0;
    					while (OutString[index] != 0)
    					{
    						//将字符串的ASCII码放入栈顶,再按照字符格式输出
    						gen(lit, 0, OutString[index]);
    						gen(opr, 0, 17);
    						index++;
    					}
    					getsymdo;
    				}
    				else //表达式
    				{
    					expressiondo(nxtlev, ptx, lev); /* 调用表达式处理,此处与read不同,read为给变量赋值 */
    					gendo(opr, 0, 14);  /* 生成输出指令,输出栈顶的值 */
    				}
    			} while (sym == comma);
    			if (sym != rparen)
    			{
    				errorDo(33);  /* write()中应为完整表达式 */
    			}
    			else
    			{
    				getsymdo;
    			}
    		}
    		gendo(opr, 0, 15);  /* 输出换行 */
    	}
    	else if (sym == callsym) /* 准备按照call语句处理 */
    	{
    		getsymdo;
    		if (sym != ident)
    		{
    			errorDo(14);  /* call后应为标识符 */
    		}
    		else
    		{
    			i = position(id, *ptx);
    			if (i == 0)
    			{
    				errorDo(11);  /* 过程未找到 */
    			}
    			else
    			{
    				if (table[i].kind == procedur)
    				{
    					gendo(cal, lev - table[i].level, table[i].adr);   /* 生成call指令 */
    				}
    				else
    				{
    					errorDo(15);  /* call后标识符应为过程 */
    				}
    			}
    			getsymdo;
    		}
    	}
    	else if (sym == ifsym)   /* 准备按照if语句处理 */
    	{
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[thensym] = true;
    		nxtlev[dosym] = true;   /* 后跟符号为then或do */
    		conditiondo(nxtlev, ptx, lev); /* 调用条件处理(逻辑运算)函数 */
    		if (sym == thensym)
    		{
    			getsymdo;
    		}
    		else
    		{
    			errorDo(16);  /* 缺少then */
    		}
    		cx1 = cx;   /* 保存当前指令地址 */
    		gendo(jpc, 0, 0);   /* 生成条件跳转指令,跳转地址未知,暂时写0 */
    		statementdo(fsys, ptx, lev);    /* 处理then后的语句 */
    		code[cx1].a = cx;   /* 经statement处理后,cx为then后语句执行完的位置,它正是前面未定的跳转地址 */
    	}
    	else if (sym == beginsym)    /* 准备按照复合语句处理 */
    	{
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[semicolon] = true;
    		nxtlev[endsym] = true;  /* 后跟符号为分号或end */
    		/* 循环调用语句处理函数,直到下一个符号不是语句开始符号或收到end */
    		statementdo(nxtlev, ptx, lev);
    
    		while (inset(sym, statbegsys) || sym == semicolon)
    		{
    			if (sym == semicolon)
    			{
    				getsymdo;
    			}
    			else
    			{
    				errorDo(10);  /* 缺少分号 */
    			}
    			statementdo(nxtlev, ptx, lev);
    		}
    		if (sym == endsym)
    		{
    			getsymdo;
    		}
    		else
    		{
    			errorDo(17);  /* 缺少end或分号 */
    		}
    	}
    	else if (sym == whilesym)    /* 准备按照while语句处理 */
    	{
    		jp.CycleNum += 1;
    		cx1 = cx;   /* 保存判断条件操作的位置 */
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[dosym] = true;   /* 后跟符号为do */
    		conditiondo(nxtlev, ptx, lev);  /* 调用条件处理 */
    		cx2 = cx;   /* 保存jpc指令的位置 */
    		gendo(jpc, 0, 0);   /* 生成条件跳转,但跳出循环的地址未知 */
    		if (sym == dosym)
    		{
    			getsymdo;
    		}
    		else
    		{
    			errorDo(18);  /* 缺少do */
    		}
    		statementdo(fsys, ptx, lev);    /* 循环体 */
    		gendo(jmp, 0, cx1); /* 回头重新判断条件 */
    		code[cx2].a = cx;   /* 反填跳出循环的地址,与if类似 */
    		//若有break或continue语句
    		if (jp.Addr != 0) {
    			code[jp.Addr].a = cx;   //反填跳出循环的地址
    			jp.Addr = 0;
    		}
    		jp.CycleNum -= 1;
    	}
    	else if (sym == forsym)    /* 准备按照for语句处理 */
    	{
    		jp.CycleNum += 1;
    
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[dosym] = true;   /* 后跟符号为do */
    		i = position(id, *ptx); //存储标识符在符号表中的位置
    		statementdo(nxtlev, ptx, lev);
    		cx1 = cx;   /* 保存判断条件操作的位置 */
    		if (sym == stepsym) {
    			getsymdo;
    			expressiondo(nxtlev, ptx, lev);//每次要增加的值存在栈顶,设为t
    		}
    		else
    		{
    			errorDo(28);
    		}
    		if (sym == untilsym) {
    			getsymdo;
    			expressiondo(nxtlev, ptx, lev);//停止值存在栈顶,设为t+1
    		}
    		else
    		{
    			errorDo(28);
    		}
    		//变量的值存在栈顶,设为t+2
    		if (table[i].kind == variable) {
    			gendo(lod, lev - table[i].level, table[i].adr);   /* 找到变量地址并将其值入栈 */
    		}
    		else
    		{
    			errorDo(29);
    		}
    		gendo(opr, 0, 9);//变量未达到设定停止值?
    		cx2 = cx;   /* 保存jpc指令的位置 */
    		gendo(jpc, 0, 0);   /* 生成条件跳转,但跳出循环的地址未知 */
    
    		if (sym == dosym)
    		{// 找到变量地址并将其值入栈   t+1
    			gendo(lod, lev - table[i].level, table[i].adr);
    			gendo(opr, 0, 2);//变量+每次要增加的值 t
    			gendo(sto, lev - table[i].level, table[i].adr);
    			getsymdo;
    		}
    		else
    		{
    			errorDo(18);  /* 缺少do */
    		}
    
    		statementdo(fsys, ptx, lev);    /* 循环体 */
    		gendo(jmp, 0, cx1); /* 回头重新判断条件 */
    		code[cx2].a = cx;   // 反填跳出循环的地址
    		//若有break或continue语句
    		if (jp.Addr != 0) {
    			code[jp.Addr].a = cx;   //反填跳出循环的地址
    			jp.Addr = 0;
    		}
    		jp.CycleNum -= 1;
    	}
    	else if (sym == breaksym)
    	{
    		getsymdo;
    		if (jp.CycleNum == 0) {
    			errorDo(27);
    		}
    		else
    		{
    			jp.Addr = cx;
    			gendo(jmp, 0, 0); /* 跳出循环地址未知 */
    		}
    	}
    	else
    	{
    		memset(nxtlev, 0, sizeof(bool) * symnum); /* 语句结束无补救集合 */
    		testdo(fsys, nxtlev, 19);   /* 检测语句结束的正确性 */
    	}
    
    	return 0;
    }
    /*
    * 常量声明处理
    */
    int constdeclaration(int* ptx, int lev, int* pdx)
    {
    	if (sym == ident)
    	{
    		getsymdo;
    		if (sym == eql || sym == becomes)
    		{
    			if (sym == becomes)
    			{
    				errorDo(1);   /* 把=写成了:= */
    			}
    			getsymdo;
    			if (sym == number)
    			{
    				enter(constant, ptx, lev, pdx);
    				getsymdo;
    			}
    			else
    			{
    				errorDo(2);   /* 常量说明=后应是数字 */
    			}
    		}
    		else
    		{
    			errorDo(3);   /* 常量说明标识后应是= */
    		}
    	}
    	else
    	{
    		error(4);   /* const后应是标识 */
    	}
    	return 0;
    }
    
    /*
    * 变量声明处理
    ptx为符号表尾位置
    lev为当前层
    pdx为为在当前层的偏移量
    */
    int vardeclaration(int* ptx, int lev, int* pdx)
    {
    	if (sym == ident)
    	{
    		getsymdo;
    		//数组
    		if (sym == Lsquare) {
    			getsymdo;
    			if (sym == number) {
    				getsymdo;
    				enter(array, ptx, lev, pdx);
    			}
    			else error(25);
    			if (sym == Rsquare) {
    				getsymdo;
    			}
    			else error(25);
    		}
    		else enter(variable, ptx, lev, pdx); // 填写名字表
    	}
    	else
    	{
    		errorDo(4);   /* var后应是标识 */
    	}
    	return 0;
    }
    /*
    * 表达式处理
    */
    int expression(bool* fsys, int* ptx, int lev)
    {
    	enum symbol addop;  /* 用于保存正负号 */
    	bool nxtlev[symnum];
    
    	if (sym == plus || sym == minus) /* 开头的正负号,此时当前表达式被看作一个正的或负的项 */
    	{
    		addop = sym;    /* 保存开头的正负号 */
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[plus] = true;
    		nxtlev[minus] = true;
    		termdo(nxtlev, ptx, lev);   /* 处理项 */
    		if (addop == minus)
    		{
    			gendo(opr, 0, 1); /* 如果开头为负号生成取负指令 */
    		}
    	}
    	else    /* 此时表达式被看作项的加减 */
    	{
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[plus] = true;
    		nxtlev[minus] = true;
    		termdo(nxtlev, ptx, lev);   /* 处理项 */
    	}
    	while (sym == plus || sym == minus)
    	{
    		addop = sym;
    		getsymdo;
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[plus] = true;
    		nxtlev[minus] = true;
    		termdo(nxtlev, ptx, lev);   /* 处理项 */
    		if (addop == plus)
    		{
    			gendo(opr, 0, 2);   /* 生成加法指令 */
    		}
    		else
    		{
    			gendo(opr, 0, 3);   /* 生成减法指令 */
    		}
    	}
    	return 0;
    }
    
    /*
    * 项处理
    */
    int term(bool* fsys, int* ptx, int lev)
    {
    	enum symbol mulop;  /* 用于保存乘除法符号 */
    	bool nxtlev[symnum];
    
    	memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    	nxtlev[times] = true;
    	nxtlev[slash] = true;
    	factordo(nxtlev, ptx, lev); /* 处理因子 */
    	while (sym == times || sym == slash)
    	{
    		mulop = sym;
    		getsymdo;
    		factordo(nxtlev, ptx, lev);
    		if (mulop == times)
    		{
    			gendo(opr, 0, 4);   /* 生成乘法指令 */
    		}
    		else
    		{
    			gendo(opr, 0, 5);   /* 生成除法指令 */
    		}
    	}
    	return 0;
    }
    
    /*
    * 因子处理
    */
    int factor(bool* fsys, int* ptx, int lev)
    {
    	int i;
    	bool nxtlev[symnum];
    	testdo(facbegsys, fsys, 24);    /* 检测因子的开始符号 */
    	if (inset(sym, facbegsys))    /* BUG: 原来的方法var1(var2+var3)会被错误识别为因子 */
    	{
    		if (sym == ident)    /* 因子为常量或变量 */
    		{
    			i = position(id, *ptx); /* 查找名字 */
    			if (i == 0)
    			{
    				errorDo(11);  /* 标识符未声明 */
    			}
    			else
    			{
    				switch (table[i].kind)
    				{
    				case constant:  /* 名字为常量 */
    					gendo(lit, 0, table[i].val);    /* 直接把常量的值入栈 */
    					break;
    				case variable:  /* 名字为变量 */
    					gendo(lod, lev - table[i].level, table[i].adr);   /* 找到变量地址并将其值入栈 */
    					break;
    				case array:  /* 名字为数组 */
    					getsymdo;
    					if (sym == Lsquare) { getsymdo; }
    					else error(25);
    					memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    					nxtlev[rparen] = true;
    					expressiondo(nxtlev, ptx, lev);
    					if (sym != Rsquare) error(25);
    					gendo(lda, lev - table[i].level, table[i].adr);   /* 找到数组元素地址并将其值入栈 */
    					break;
    				case procedur:  /* 名字为过程 */
    					errorDo(21);  /* 不能为过程 */
    					break;
    				}
    			}
    			getsymdo;
    		}
    		else if (sym == number)   /* 因子为数 */
    		{
    			if (num > amax)
    			{
    				errorDo(31);
    				num = 0;
    			}
    			gendo(lit, 0, num);
    			getsymdo;
    		}
    		else
    		{
    			if (sym == lparen)  /* 因子为表达式 */
    			{
    				getsymdo;
    				memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    				nxtlev[rparen] = true;
    				expressiondo(nxtlev, ptx, lev);
    				if (sym == rparen)
    				{
    					getsymdo;
    				}
    				else
    				{
    					errorDo(33);  /* 缺少右括号 */
    				}
    			}
    			testdo(fsys, facbegsys, 23);    /* 因子后有非法符号 */
    		}
    	}
    	return 0;
    }
    
    /*
    * 条件处理
    */
    int condition(bool* fsys, int* ptx, int lev)
    {
    	enum symbol relop;
    	bool nxtlev[symnum];
    
    	if (sym == oddsym)   /* 准备按照odd运算处理 */
    	{
    		getsymdo;
    		expressiondo(fsys, ptx, lev);
    		gendo(opr, 0, 6);   /* 生成odd指令 */
    	}
    	else
    	{
    		/* 逻辑表达式处理 */
    		memcpy(nxtlev, fsys, sizeof(bool) * symnum);
    		nxtlev[eql] = true;
    		nxtlev[neq] = true;
    		nxtlev[lss] = true;
    		nxtlev[leq] = true;
    		nxtlev[gtr] = true;
    		nxtlev[geq] = true;
    		expressiondo(nxtlev, ptx, lev);
    		if (sym != eql && sym != neq && sym != lss && sym != leq && sym != gtr && sym != geq)
    		{
    			errorDo(20);
    		}
    		else
    		{
    			relop = sym;
    			getsymdo;
    			expressiondo(fsys, ptx, lev);
    			switch (relop)
    			{
    			case eql:
    				gendo(opr, 0, 8);
    				break;
    			case neq:
    				gendo(opr, 0, 9);
    				break;
    			case lss:
    				gendo(opr, 0, 10);
    				break;
    			case geq:
    				gendo(opr, 0, 11);
    				break;
    			case gtr:
    				gendo(opr, 0, 12);
    				break;
    			case leq:
    				gendo(opr, 0, 13);
    				break;
    			}
    		}
    	}
    	return 0;
    }
    
    #pragma endregion
    
    #pragma region 中间代码生成
    /*
    * 生成虚拟机代码
    *
    * x: instruction.f;
    * y: instruction.l;
    * z: instruction.a;
    */
    int gen(enum fct x, int y, int z)
    {
    	if (cx >= cxmax)
    	{
    		printf("程序过长!");
    		return -1;
    	}
    	code[cx].f = x;
    	code[cx].l = y;
    	code[cx].a = z;
    	cx++;
    	return 0;
    }
    
    
    /*
    * 输出目标代码清单
    */
    void listcode(int cx0)
    {
    	int i;
    	for (i = cx0; i < cx; i++)
    	{
    		printf("%d %s %d %d\n", i, mnemonic[code[i].f], code[i].l, code[i].a);
    		fprintf(fa, "%d %s %d %d\n", i, mnemonic[code[i].f], code[i].l, code[i].a);
    	}
    }
    
    #pragma endregion
    
    
    #pragma region 解释器
    /*
    * 解释程序,仅执行一遍
    */
    void interpret()
    {
    	int p, b, t;    /* 指令指针p PC,指令基址base,栈顶指针top */
    	struct instruction i;   /* 存放当前指令 */
    	int s[stacksize];   /* 栈 */
    
    	printf("start pl0\n");
    	t = 0;
    	b = 0;
    	p = 1;
    	s[0] = s[1] = s[2] = 0;
    	do {
    		//printf("%d %s %d %d\n", p, mnemonic[code[p].f], code[p].l, code[p].a);
    
    		i = code[p];
    		p++;
    		switch (i.f)
    		{
    		case lit:   /* 将a的值取到栈顶 */
    			s[t] = i.a;
    			t++;
    			break;
    		case opr:   /* 数学、逻辑运算 */
    			switch (i.a)
    			{
    			case 0://过程返回
    				t = b;
    				p = s[t + 2];//RA
    				b = s[t + 1];//DL
    				break;
    			case 1://求相反数
    				s[t - 1] = -s[t - 1];
    				break;
    			case 2://次栈顶值与栈顶相加,存入次栈顶
    				t--;
    				s[t - 1] = s[t - 1] + s[t];
    				break;
    			case 3://次栈顶值-栈顶,存入次栈顶
    				t--;
    				s[t - 1] = s[t - 1] - s[t];
    				break;
    			case 4://次栈顶值*栈顶,存入次栈顶
    				t--;
    				s[t - 1] = s[t - 1] * s[t];
    				break;
    			case 5://次栈顶值/栈顶,存入次栈顶
    				t--;
    				s[t - 1] = s[t - 1] / s[t];
    				break;
    			case 6://栈顶元素求mod2,存入栈顶
    				s[t - 1] = s[t - 1] % 2;
    				break;
    			case 8://次栈顶值==栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] == s[t]);
    				break;
    			case 9://次栈顶值!=栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] != s[t]);
    				break;
    			case 10://次栈顶值<栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] < s[t]);
    				break;
    			case 11://次栈顶值>=栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] >= s[t]);
    				break;
    			case 12://次栈顶值>栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] > s[t]);
    				break;
    			case 13://次栈顶值<=栈顶?,存入次栈顶
    				t--;
    				s[t - 1] = (s[t - 1] <= s[t]);
    				break;
    			case 14://输出栈顶值,栈顶指针--
    				printf("%d", s[t - 1]);
    				fprintf(fa2, "%d", s[t - 1]);
    				t--;
    				break;
    			case 15://输出\n
    				printf("\n");
    				fprintf(fa2, "\n");
    				break;
    			case 16://输出?,并从控制台读入一个数字到栈顶
    				printf("?");
    				fprintf(fa2, "?");
    				scanf("%d", &(s[t]));
    				fprintf(fa2, "%d\n", s[t]);
    				t++;
    				break;
    			case 17://输出字符栈顶值,栈顶指针--
    				printf("%c", s[t - 1]);
    				fprintf(fa2, "%c", s[t - 1]);
    				t--;
    				break;
    			}
    			break;
    		case lod:   /* 取相对当前过程层差为l,偏移量为A的单元的值到栈顶 */
    			s[t] = s[base(i.l, s, b) + i.a];
    			t++;
    			break;
    		case sto:   /* 栈顶的值存到相对当前过程层差为l,偏移量为A的单元 */
    			t--;
    			s[base(i.l, s, b) + i.a] = s[t];
    			break;
    		case lda:   /* 取相对当前过程层差为l,偏移量为(A+次栈顶)的单元的值到次栈顶 */
    			s[t - 1] = s[base(i.l, s, b) + i.a + s[t - 1]];
    			break;
    		case sta:   /* 栈顶的值存到相对当前过程层差为l,偏移量为(A+次栈顶)的单元 */
    			t--;
    			s[base(i.l, s, b) + i.a + s[t - 1]] = s[t];
    			t--;
    			break;
    		case cal:   /* 调用子过程 */
    			s[t] = base(i.l, s, b); /* 将父过程基地址入栈,SL */
    			s[t + 1] = b; /* DL,调用该过程的过程基地址入栈,此两项用于base函数 */
    			s[t + 2] = p; /* RA,将当前指令指针入栈,用于之后过程完了返回 */
    			b = t;  /* 改变基地址指针值为新过程的基地址 */
    			p = i.a;    /* 跳转 */
    			break;
    		case inte:  /* 分配内存 */
    			t += i.a;
    			break;
    		case jmp:   /* 直接跳转 */
    			p = i.a;
    			break;
    		case jpc:   /* 条件跳转 */
    			t--;
    			if (s[t] == 0)
    			{
    				p = i.a;
    			}
    			break;
    		}
    	} while (p != 0);
    }
    
    /* 求出定义该过程的过程基址
    *l为层次差,b为上一过程(调用该过程的过程)基址,s为栈
    */
    int base(int l, int* s, int b)
    {
    	int b1;
    	b1 = b;
    	while (l > 0)
    	{
    		b1 = s[b1];
    		l--;
    	}
    	return b1;
    }
    
    #pragma endregion
    
    
    #pragma region 用数组实现集合的集合运算,n是集合大小
    
    
    //用数组实现集合的集合运算
    
    int inset(int e, bool* s)
    {
    	return s[e];
    }
    
    //s1并s2
    int addset(bool* sr, bool* s1, bool* s2, int n)
    {
    	int i;
    	for (i = 0; i < n; i++)
    	{
    		sr[i] = s1[i] || s2[i];
    	}
    	return 0;
    }
    //s1-s2
    int subset(bool* sr, bool* s1, bool* s2, int n)
    {
    	int i;
    	for (i = 0; i < n; i++)
    	{
    		sr[i] = s1[i] && (!s2[i]);
    	}
    	return 0;
    }
    //s1交s2
    int mulset(bool* sr, bool* s1, bool* s2, int n)
    {
    	int i;
    	for (i = 0; i < n; i++)
    	{
    		sr[i] = s1[i] && s2[i];
    	}
    	return 0;
    }
    #pragma endregion
    
    #pragma region 出错处理
    //出错处理,打印出错位置和错误编码
    int error(int n)
    {
    	//打印的空格
    	char space[81];
    	memset(space, 32, 81);
    	space[cc - 1] = 0; //出错时当前符号已经读完,所以cc-1,0表示'\0'结束
    
    	printf("Error=>%s! %d:%s\n", space, n, err_msg[n]);
    	fprintf(fa1, "****%s!%d\n", space, n);
    
    	err++;
    	return -1;
    }
    
    /*
    * 测试当前符号是否合法,短语层恢复思想
    *
    * 在某一部分(如一条语句,一个表达式)将要结束时时我们希望下一个符号属于某集?
    * (该部分的后跟符号),test负责这项检测,并且负责当检测不通过时的补救措施,
    * 程序在需要检测时指定当前需要的符号集合和补救用的集合(如之前未完成部分的后跟
    * 符号),以及检测不通过时的错误号。
    *
    * s1:   我们需要的符号
    * s2:   如果不是我们需要的,则需要一个补救用的集合
    * n:    错误号
    */
    int test(bool* s1, bool* s2, int n)
    {
    	if (!inset(sym, s1))
    	{
    		error(n);
    		/* 当检测不通过时,不停获取符号,直到它属于需要的集合或补救的集合 */
    		while ((!inset(sym, s1)) && (!inset(sym, s2)))
    		{
    			getsymdo;
    		}
    	}
    	return 0;
    }
    
    #pragma endregion
    
    #pragma region 符号表管理
    
    /*
    * 在符号表中加入一项
    *
    * k:      名字种类const,var or procedure
    * ptx:    名字表尾指针的指针,为了可以改变名字表尾指针的值
    * lev:    名字所在的层次,,以后所有的lev都是这样
    * pdx:    dx为当前应分配的变量的相对地址,分配后要增加1
    为什么变量会使指针++,因为变量需要分配空间
    */
    void enter(enum object k, int* ptx, int lev, int* pdx)
    {
    	(*ptx)++;
    	strcpy(table[(*ptx)].name, id); /* 全局变量id中已存有当前名字的名字 */
    	table[(*ptx)].kind = k;
    	switch (k)
    	{
    	case constant:  /* 常量名字 */
    		if (num > amax)
    		{
    			error(31);  /* 数越界 */
    			num = 0;
    		}
    		table[(*ptx)].val = num;
    		break;
    	case variable:  /* 变量名字 */
    		table[(*ptx)].level = lev;
    		table[(*ptx)].adr = (*pdx);
    		(*pdx)++;
    		break;
    	case array:  /* 数组名字 */
    		table[(*ptx)].level = lev;
    		table[(*ptx)].adr = (*pdx);
    		table[(*ptx)].size = num;
    		(*pdx) += num;
    		break;
    	case procedur:  /* 过程名字 */
    		table[(*ptx)].level = lev;
    		break;
    	}
    }
    
    /*
    * 查找名字的位置,从后往前,保证先看此过程局部变量再看其他的
    * 找到则返回在名字表中的位置,否则返回0.
    *
    * idt:    要查找的名字
    * tx:     当前名字表尾指针
    */
    int position(char* idt, int tx)
    {
    	int i;
    	strcpy(table[0].name, idt);
    	i = tx;
    	while (strcmp(table[i].name, idt) != 0)
    	{
    		i--;
    	}
    	return i;
    }
    
    /*
    * 输出符号表
    */
    void ListTable(int tx0, int tx)
    {
    	int i;
    	i = tx;
    
    	while (table[i].kind != procedur && i > 0) i--;
    
    	if (i != tx && i != 0) printf("The Table at procedure %s:\n", table[i].name);
    	else  printf("The Table at main program:\n");
    
    	if (tx0 + 1 > tx)
    	{
    		printf("    NULL\n");
    	}
    	else {
    		printf("%-8s %-8s %-8s %-8s %-8s %-8s \n", "number", "kind", "name", "val/lev", "addr", "size");
    		for (i = tx0 + 1; i <= tx; i++)
    		{
    			switch (table[i].kind)
    			{
    			case constant:
    				printf("%-8d const    %-8s ", i, table[i].name);
    				printf("val=%-4d\n", table[i].val);
    				fprintf(fas, "%-8d const    %-8s ", i, table[i].name);
    				fprintf(fas, "val=%-4d\n", table[i].val);
    				break;
    			case variable:
    				printf("%-8d var      %-8s ", i, table[i].name);
    				printf("lev=%-4d addr=%-3d\n", table[i].level, table[i].adr);
    				fprintf(fas, "%-8d var      %-8s ", i, table[i].name);
    				fprintf(fas, "lev=%-4d addr=%-3d\n", table[i].level, table[i].adr);
    				break;
    			case procedur:
    				printf("%-8d proc     %-8s ", i, table[i].name);
    				printf("lev=%-4d addr=%-3d size=%-3d\n", table[i].level, table[i].adr, table[i].size);
    				fprintf(fas, "%-8d proc     %-8s ", i, table[i].name);
    				fprintf(fas, "lev=%-4d addr=%-3d size=%-3d\n", table[i].level, table[i].adr, table[i].size);
    				break;
    			case array:
    				printf("%-8d array    %-8s ", i, table[i].name);
    				printf("lev=%-4d addr=%-3d size=%-3d\n", table[i].level, table[i].adr, table[i].size);
    				fprintf(fas, "%-8d array    %-8s ", i, table[i].name);
    				fprintf(fas, "lev=%-4d addr=%-3d size=%-3d\n", table[i].level, table[i].adr, table[i].size);
    				break;
    			}
    		}
    	}
    
    	printf("\n");
    }
    #pragma endregion
    
    
    展开全文
  • PL/0作以下修改扩充: (1)增加单词:保留字 ELSE,FOR,TO,DOWNTO,RETURN 运算符 +=,-=,++,--, (2)修改单词:不等号# 改为 (3)增加条件语句的ELSE子句,要求:写出相关文法,语法图,语义规则。
  • 山东大学编译原理实验,Java版,PL0,完全是自己写的然后给大家福利,具体实现了: 一. PL/0语言建立一个词法分程序GETSYM(函数) 二. PL/0语言建立一个语法分析程序BLOCK(函数) 三. 建立一个解释执行目标程序...
  • (5)其他典型语言设施。 ———————————————————————————————————————— 本人在课程设计中已实现的功能 (1)增加单词:保留字 ELSE,FOR,TO,DOWNTO,RETURN 运算符 +=...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,552
精华内容 2,220
关键字:

编译原理pl0语言