(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

时间:2022-09-09 17:07:48

本章中,你将会完成从第5章开始的解析器开发,以便可以解析程序头(header)、过程和函数申明以及对它们的调用。

方法和目标

我们的目标是解析完整Pascal程序,或者至少能覆盖你所用到的语言特性。如前面章节所做的那样,你将会开发解析子类用来解析:

  • Pascal程序头
  • Pascal过程和函数申明
  • 对已申明过的过程和函数的调用(当然不能调用没有申明过的,因为不存在
  • 对标准过程和函数的调用(标准过程和函数类似于库函数,内置函数等

你将会继续深入的使用符号表堆栈。我们一直在完善的测试实用程序语法检查器(syntax checker),它的最终版即IV版(第五版),将会在本章结束的时候验证你的开发工作。

程序、过程和函数申明

在第9章中你已完成绝大部分申明的解析工作,现在得继续解析Pascal程序申明(即程序头)以及过程和函数的申明,彻底完成这部分工作(解析工作)。

图11-1 展示了一个完整的Pascal程序的语法图,它包含程序头,引用了图形9-1 中块(block)图。

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

前面的章节并没有解析program,而是使用了一个dummyprogramname的标识符,不过这个过程相对简单,仅仅包含一个类似于函数参数的东西


在Pascal语言规范中,头(程序头)可以包含一个用括号围住以逗号分割的参数列表。这些标识符表示在运行期被传入到程序的文件(输入文件)或者被程序写入的文件(输出文件)。如果使用标准的输入输出,则这些参数是可选的,标准IO可以被运行期的执行器(executor)操作。因此或者你解析这些程序参数并验证它们的语法正确性,或者忽略它们。

看下面的程序头例子:

PROGRAM WolfIsland;

PROGRAM XRef (input, output);

如果你从命令行运行程序,标准输入输出默认是控制台。你可以用<或者>分别重定向标准输入和输出到文本文件。例如:

java -classpath classes Pascal execute wolfisland.pas < wolfisland.in

将会编译并执行Pascal程序wolfisland.pas,并且在运行期间,程序将会从文本文件wolfisland.pas中读取输入内容。

图11-2 引入过程和函数申明,完善了语法图9-1,这两个分别以PROCEDURE和FUNCTION关键字开头。

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

可以看到,函数和过程的申明最后必以块(block)申明收尾,它们之间可以以任意顺序出现在程序中。形参表(formal parameter list)是可选的。一个函数申明必须包含一个返回值的类型(即第二个identifier)。每个过程或函数申明必须有一个块申明(说的有点多余),或者跟一个forward标识符(这个有待本节结束时解释一下)。


一个申明了的过程或函数是一个被程序员视为程序一部分而写的程式(routine)。标准过程和函数内置在Pascal中,相当于隐式的申明在全局中。(在嵌套层0)。图11-3 展示了过程和函数申明的形参表语法图。

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

Pascal过程和函数申明可以互相嵌套到任意深度。一个函数申明包含一个冒号:,紧接着是函数返回类型的标识符(比如int、string等),这里匿名类型不允许。

一个形参表的申明是不是让我们想到了语法图9-1中的变量申明?形参表包含一个或多个由分号; 切分的子表(sublists)。每个子表包含一个或多个用作参数名的标识符,每个子表后面跟着一个冒号还有一个指明子表中每个参数类型的类型标识符。匿名类型是禁止的。如果每个子表前面有个VAR,那么每个在子表中的参数是一个VAR参数(比如,通过引用传递);否则是通过值传递。

这里我提醒一下子表的意思 比如 var a,b,c : int ; var d,e : string,那么a,b,c是一个子表,d,e 是另一个子表,a,b,c,d,e都是参数,a,b,c类型为int,而d,e 为string。函数传值有两种类型,值传递和引用传递,在C中都是值传递,在Java中复杂类型都是引用传递,而Pascal是可以表明的,跟C++差不多

这儿举一些过程和函数申明的例子:

   1: PROCEDURE proc (j, k : integer; VAR x, y, z : real; VAR v : arr;
   2:                 VAR p : boolean; ch : char);
   3:     BEGIN
   4:         ...
   5:     END;
   6:  
   7: PROCEDURE SortWords;
   8:     BEGIN
   9:         ...
  10:     END;
  11:  
  12: FUNCTION func (VAR x : real; i, n : integer) : real;
  13:     BEGIN
  14:         ...
  15:         func := ...;
  16:         ...
  17:     END;

请留意过程SortWords没有形参。

Pascal并没有return语句。一个程式在执行到块结束(即end处)自然的退出。从上面的例子可以看到(函数func),在块执行的过程中,可通过赋值给函数名称标识符(func :=)实现返回值。

Pascal程序不能调用没有被申明过的过程和函数,因而如果想用必须用一个前向申明(forward declaration),比如:

FUNCTION forwarded (m : integer; VAR t : real) : real; forward;

前向申明以forward(记住它是一个关键字)代替了块(block),但是后面完整的申明必须要有(这有点类似与C语言的申明和定义,申明之后可以使用,但是如果没有完整的定义,后续链接过程会找不到符号):

   1: FUNCTION forwarded;
   2:     BEGIN
   3:         ...
   4:         forwarded := ...;
   5:         ...
   6:     END;

注意完整申明不重复任何的形参表,还有返回类型。

嵌套作用域和符号表堆栈

因为申明过的过程和函数可以任意嵌套,所以符号表堆栈扮演了一个突出角色。每个过程和函数都有属于自己的符号表用来容纳其定义的标识符。当解析器从头到尾读入源程序时,在解析程式(过程和函数统称为程式)进入和退出其作用域。解析器在碰到一个程式时,在符号表栈中放入一个新表,随后在此程式解析完成后弹出这个新创建的符号表。

图11-4a到11-4f 展示了在解析器解析人造的程序Test是符号表栈的各个阶段情况。这个程序包含一个过程p,且p中还嵌套一个函数f。

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4a 在解析完程序头后的符号表栈。解析器将程序Test的名字放入层级0(即全局)的符号表中。


 

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4b 在解析完过程p名字后的符号表栈。解析器往栈中推入一个新的层级为1(程序级)的符号表,并将程序局部变量i、j、k、n和过程名字p放到这个表中。

按层级可以是全局级->程序级->程式级->块级->XXX)


 

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4c 符号表栈在刚解析完函数f名字后的情况。解析器创建一个新的级别为2的新符号表,放在堆栈中,并将过程p的形参j和局部变量k,还有程序的名字f放在这个表中。


 

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4d 解析函数f的块内容时符号表栈的情况。解析器创建一个新的级别为3的符号表放在堆栈中,并将函数的形参x和局部变量i放到此表中。当解析器碰到赋值语句时,在符号表栈中从顶到底搜索语句中的各个标识符,它发现x, i在局部表中,j和f在层级为2的表中,而n在级别为1的符号表中。


 

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4e 解析过程p中的内容是符号表栈的情况。解析器从栈中脱掉层级为3的符号表。它发现k和f在局部表中,而i和n在层级为1的表中,还有chr在层级为0的符号表中。


 

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-4f 解析程序主体时堆栈的情况。解析器现在脱掉层级为2 的符号表,并发现所有的符号都在局部表中。


 

留意解析器将每个程式的名称放在包含此程式的符号表中。例如将函数名f 放在过程p且层级为2的表中,将过程名字p放在主程序层级为1的符号表中,还有把主程序名字test放在全局表中。这使得p中的赋值语句可以引用函数f,如图11-4e所示。

新申明解析子类

(基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分

图11-5 扩展了类图9-5以便展示新的申明解析子类。类ProgramParser用来解析程序的申明,DeclaredRoutineParser 解析申明过的过程和函数。为配合解析程式,DeclarationsParser及其子类的parse()方法必须返回一个符号表项。这些parse()方法一般返回null,除了DeclaredRoutineParser.parse()方法,它返回刚才解析过的程式名称所对应的符号表项。


清单11-1 展示了类PascalParserTD中的新版parse()方法。它调用programParser.parse()解析整个Pascal程序。(即从program开始的)。

   1: /**
   2:      * Pascal的解析过程,交由ProgramParser
   3:      */
   4:     public void parse() throws Exception {
   5:  
   6:         long startTime = System.currentTimeMillis();
   7:         //ICode iCode = ICodeFactory.createICode();
   8:         //初始化预定义
   9:         Predefined.initialize(symTabStack);
  10:         try {
  11:             Token token = nextToken();
  12:  
  13:             // 解析程序
  14:             ProgramParser programParser = new ProgramParser(this);
  15:             programParser.parse(token, null);
  16:             token = currentToken();
  17:  
  18:             // 发送编译摘要消息
  19:             float elapsedTime = (System.currentTimeMillis() - startTime)/1000f;
  20:             sendMessage(new Message(PARSER_SUMMARY,
  21:                                     new Number[] {token.getLineNumber(),
  22:                                                   getErrorCount(),
  23:                                                   elapsedTime}));
  24:         }
  25:         catch (java.io.IOException ex) {
  26:             errorHandler.abortTranslation(PascalErrorCode.IO_ERROR, this);
  27:         }
  28:     }

现在我们的扩展DeclarationsParser的parse()方法,加上过程和函数申明的解析。参见下面的清单11-2:

   1: public SymTabEntry parse(Token token, SymTabEntry parentId)
   2:         throws Exception
   3:     {
   4:         token = synchronize(DECLARATION_START_SET);
   5:  
   6:         //常量申明?
   7:         if (token.getType() == CONST) {
   8:             token = nextToken(); 
   9:  
  10:             ConstantDefinitionsParser constantDefinitionsParser =
  11:                 new ConstantDefinitionsParser(this);
  12:             constantDefinitionsParser.parse(token);
  13:         }
  14:  
  15:         token = synchronize(TYPE_START_SET);
  16:         //类型申明?
  17:         if (token.getType() == TYPE) {
  18:             token = nextToken(); 
  19:  
  20:             TypeDefinitionsParser typeDefinitionsParser =
  21:                 new TypeDefinitionsParser(this);
  22:             typeDefinitionsParser.parse(token);
  23:         }
  24:  
  25:         token = synchronize(VAR_START_SET);
  26:         //变量申明?
  27:         if (token.getType() == VAR) {
  28:             token = nextToken(); 
  29:  
  30:             VariableDeclarationsParser variableDeclarationsParser =
  31:                 new VariableDeclarationsParser(this);
  32:             variableDeclarationsParser.setDefinition(VARIABLE);
  33:             variableDeclarationsParser.parse(token);
  34:         }
  35:         //程式申明
  36:         token = synchronize(ROUTINE_START_SET);
  37:         TokenType tokenType = token.getType();
  38:         while ((tokenType == PROCEDURE) || (tokenType == FUNCTION)) {
  39:             DeclaredRoutineParser routineParser =
  40:                 new DeclaredRoutineParser(this);
  41:             routineParser.parse(token, parentId);
  42:  
  43:             // 每个定义以分号结束
  44:             token = currentToken();
  45:             if (token.getType() == SEMICOLON) {
  46:                 while (token.getType() == SEMICOLON) {
  47:                     token = nextToken(); //吞掉分号
  48:                 }
  49:             }
  50:             token = synchronize(ROUTINE_START_SET);
  51:             tokenType = token.getType();
  52:         }
  53:         return null;
  54:     }

方法parse()需要参数当前token以及包含待解析程式的程式名称对应的符号表项(程式可以嵌套)。在处理完PROCEDURE或FUNCTION token之后,它调用routineParser.parse()解析程式剩下的部分,接着搜寻表示程式结束的分号并继续下一个过程或函数的解析工作。

解析一个程序申明(Program Declaration)

清单11-3 展示了申明解析子类ProgramParser的parse()方法。在方法吞噬掉PROGRAM token后,它调用routineParser.parse()解析程序剩下的部分。在返回的时候,搜索最终的句号token(句号.表示一个程序申明的结束)。

   1: // 程序开始处的同步集
   2: static final EnumSet<PascalTokenType> PROGRAM_START_SET =
   3:     EnumSet.of(PROGRAM, SEMICOLON);
   4: static {
   5:     PROGRAM_START_SET.addAll(DeclarationsParser.DECLARATION_START_SET);
   6: }
   7:  
   8: /**
   9:  * 解析一个Pascal程序
  10:  * @param token 初始token
  11:  * @param parentId 包含此申明的程式名称符号表项
  12:  * @return null
  13:  * @throws Exception
  14:  */
  15: public SymTabEntry parse(Token token, SymTabEntry parentId)
  16:     throws Exception
  17: {
  18:     token = synchronize(PROGRAM_START_SET);
  19:  
  20:     // 调用routine程式解析器
  21:     DeclaredRoutineParser routineParser = new DeclaredRoutineParser(this);
  22:     routineParser.parse(token, parentId);
  23:  
  24:     //搜寻结束程序的句号点
  25:     token = currentToken();
  26:     if (token.getType() != DOT) {
  27:         errorHandler.flag(token, MISSING_PERIOD, this);
  28:     }
  29:  
  30:     return null;
  31: }

解析过程和函数申明

申明解析子类DeclaredRoutineParser做了差不多与程序,过程和函数相关的所有解析工作。清单11-4 展示了它的parse()方法。

   1: // 程式名字计数器,在解析过程中可能有些程式忘记写名字了,为了不影响解析的进行,
   2:    //需要纠错补上一个模拟名字,以数字代替
   3:    private static int dummyCounter = 0;  
   4:  
   5:    /**
   6:     * 解析一个标准的子程式声明
   7:     * @param token 初始的token
   8:     * @param parentId 父程式对应的符号表项
   9:     * @return 声明程式对应的符号项
  10:     * @throws Exception if an error occurred.
  11:     */
  12:    public SymTabEntry parse(Token token, SymTabEntry parentId)
  13:        throws Exception
  14:    {
  15:        Definition routineDefn = null;
  16:        String dummyName = null;
  17:        SymTabEntry routineId = null;
  18:        TokenType routineType = token.getType();
  19:  
  20:        //判断倒是何种程式
  21:        switch ((PascalTokenType) routineType) {
  22:  
  23:            case PROGRAM: { //程序
  24:                token = nextToken(); 
  25:                routineDefn = DefinitionImpl.PROGRAM;
  26:                dummyName = "DummyProgramName".toLowerCase();
  27:                break;
  28:            }
  29:  
  30:            case PROCEDURE: {//过程
  31:                token = nextToken(); 
  32:                routineDefn = DefinitionImpl.PROCEDURE;
  33:                dummyName = "DummyProcedureName_".toLowerCase() +
  34:                            String.format("%03d", ++dummyCounter);
  35:                break;
  36:            }
  37:  
  38:            case FUNCTION: {//函数
  39:                token = nextToken(); 
  40:                routineDefn = DefinitionImpl.FUNCTION;
  41:                dummyName = "DummyFunctionName_".toLowerCase() +
  42:                            String.format("%03d", ++dummyCounter);
  43:                break;
  44:            }
  45:  
  46:            default: {
  47:                routineDefn = DefinitionImpl.PROGRAM;
  48:                dummyName = "DummyProgramName".toLowerCase();
  49:                break;
  50:            }
  51:        }
  52:  
  53:        // 解析程式的名称
  54:        routineId = parseRoutineName(token, dummyName);
  55:        routineId.setDefinition(routineDefn);
  56:  
  57:        token = currentToken();
  58:  
  59:        //创建此程式的中间码
  60:        ICode iCode = ICodeFactory.createICode();
  61:        routineId.setAttribute(ROUTINE_ICODE, iCode);
  62:        routineId.setAttribute(ROUTINE_ROUTINES, new ArrayList<SymTabEntry>());
  63:  
  64:        // 判断程式是否前向来决定是否创建符号表
  65:        if (routineId.getAttribute(ROUTINE_CODE) == FORWARD) {
  66:            SymTab symTab = (SymTab) routineId.getAttribute(ROUTINE_SYMTAB);
  67:            symTabStack.push(symTab);
  68:        }
  69:        else {
  70:            routineId.setAttribute(ROUTINE_SYMTAB, symTabStack.push());
  71:        }
  72:  
  73:        // 如果是程序,设置堆栈的程序ID
  74:        if (routineDefn == DefinitionImpl.PROGRAM) {
  75:            symTabStack.setProgramId(routineId);
  76:        }else if (routineId.getAttribute(ROUTINE_CODE) != FORWARD) {
  77:            //对于非前向程式申明,将其放入到父程式的子程式列表中
  78:            ArrayList<SymTabEntry> subroutines = (ArrayList<SymTabEntry>)
  79:                                       parentId.getAttribute(ROUTINE_ROUTINES);
  80:            subroutines.add(routineId);
  81:        }
  82:  
  83:        //如果这个是已经前向申明过后“定义”,不能有任何参数和返回值,应该是分号;
  84:        if (routineId.getAttribute(ROUTINE_CODE) == FORWARD) {
  85:            if (token.getType() != SEMICOLON) {
  86:                errorHandler.flag(token, ALREADY_FORWARDED, this);
  87:                parseHeader(token, routineId);
  88:            }
  89:        }else { //正常(非前向)的解析程式头(参数+返回式)
  90:            parseHeader(token, routineId);
  91:        }
  92:  
  93:        // 头完之后是否是分号
  94:        token = currentToken();
  95:        if (token.getType() == SEMICOLON) {
  96:            do {
  97:                token = nextToken();
  98:            } while (token.getType() == SEMICOLON);
  99:        }
 100:        else {
 101:            errorHandler.flag(token, MISSING_SEMICOLON, this);
 102:        }
 103:  
 104:        //解析之后的块或者代替的forward
 105:        if ((token.getType() == IDENTIFIER) &&
 106:            (token.getText().equalsIgnoreCase("forward")))
 107:        {
 108:            //是一个前向申明
 109:            token = nextToken(); 
 110:            routineId.setAttribute(ROUTINE_CODE, FORWARD);
 111:        }else { //正常的调用块解析
 112:            routineId.setAttribute(ROUTINE_CODE, DECLARED);
 113:            BlockParser blockParser = new BlockParser(this);
 114:            ICodeNode rootNode = blockParser.parse(token, routineId);
 115:            iCode.setRoot(rootNode);
 116:        }
 117:  
 118:        // 程式的作用域结束,出栈
 119:        symTabStack.pop();
 120:  
 121:        return routineId;
 122:    }

在处理完PROGRAM、PROCEDURE或FUNCTION token并选择恰当的标识符定义之后( 21-51行),parse()方法调用parseRoutineName()解析程式名称。它为此程式新建一个中间码并以ROUTINE_ICODE属性存储在程式名称对应的符号表项上,除此之外还推入一个新的符号表到栈中并以ROUTINE_SYMTAB属性存放在表项上。不过如果过程或函数之前在前向申明中出现过(也就是一样的名字已经被定义成前向申明),直接推入解析前向申明中创建过的符号表(而不需新建)。

如果是前向申明(此程式名称表示的符号表项),此程式就不能有形参或者函数返回类型,否则就是重复申明。但是为了错误恢复,parse()还是会解析它们(指形参或者返回类型等程式头)。反正不管怎么样,它都得掉用parseHeader()来解析参数和函数返回类型。

在解析完参数和返回类型后,parse()或处理forward标识符(意味这是一个forward前向申明),或调用blockParser.parse()处理程式的块(即主要内容),这解析完之后,方法弹出程式对应的符号表并返回程式名称对应的表项。

设计笔记

在解析器返程程式解析之后,弹出的符号表并没有消失。如清单11-4所示,程式名称的符号表项有个属性ROUTINE_SYMTAB正好指向这个表(不然被垃圾收集了)。类似地,符号表项的ROUTINE_ICODE维护一个指向程式块生成树(即中间码)。后端解释器或编译器在后面需要用到每个程式的符号表和分析树。

清单11-5 展示了parseRoutineName()或parseHeader()等方法。

   1: /**
   2:      * 解析程式的名字
   3:      * @param token 当前token
   4:      * @param dummyName 解析出问题时的假名字
   5:      * @return 申明的程式名字对应的符号表项
   6:      * @throws Exception
   7:      */
   8:     private SymTabEntry parseRoutineName(Token token, String dummyName)
   9:         throws Exception
  10:     {
  11:         SymTabEntry routineId = null;
  12:  
  13:         //解析程式名称标识符
  14:         if (token.getType() == IDENTIFIER) {
  15:             String routineName = token.getText().toLowerCase();
  16:             routineId = symTabStack.lookupLocal(routineName);
  17:  
  18:             // 如果没有定义,放到符号表中
  19:             if (routineId == null) {
  20:                 routineId = symTabStack.enterLocal(routineName);
  21:             }
  22:  
  23:             // 如果已经被定义,则判断是否一个前向申明
  24:             else if (routineId.getAttribute(ROUTINE_CODE) != FORWARD) {
  25:                 routineId = null;
  26:                 errorHandler.flag(token, IDENTIFIER_REDEFINED, this);
  27:             }
  28:  
  29:             token = nextToken(); 
  30:         }
  31:         else { //忘记写名字了
  32:             errorHandler.flag(token, MISSING_IDENTIFIER, this);
  33:         }
  34:  
  35:         //如果routineID没有名字,放个假名字代替
  36:         if (routineId == null) {
  37:             routineId = symTabStack.enterLocal(dummyName);
  38:         }
  39:  
  40:         return routineId;
  41:     }
  42:  
  43:     /**
  44:      * 解析程式的头,包括参数列表和返回
  45:      * @param token 当前Token
  46:      * @param routineId 申明过程式名称对应的符号表项
  47:      * @throws Exception
  48:      */
  49:     private void parseHeader(Token token, SymTabEntry routineId)
  50:         throws Exception
  51:     {
  52:         //先解析形参列表
  53:         parseFormalParameters(token, routineId);
  54:         token = currentToken();
  55:  
  56:         // 如果程式是函数,则要解析它的返回值类型function p : int
  57:         if (routineId.getDefinition() == DefinitionImpl.FUNCTION) {
  58:             VariableDeclarationsParser variableDeclarationsParser =
  59:                 new VariableDeclarationsParser(this);
  60:             variableDeclarationsParser.setDefinition(DefinitionImpl.FUNCTION);
  61:             TypeSpec type = variableDeclarationsParser.parseTypeSpec(token);
  62:  
  63:             token = currentToken();
  64:  
  65:             // 返回值类型不能是数组或者记录,只能是标量类型
  66:             if (type != null) {
  67:                 TypeForm form = type.getForm();
  68:                 if ((form == TypeFormImpl.ARRAY) ||
  69:                     (form == TypeFormImpl.RECORD))
  70:                 {
  71:                     errorHandler.flag(token, INVALID_TYPE, this);
  72:                 }
  73:             } else { //函数漏掉了返回类型
  74:                 type = Predefined.undefinedType;
  75:             }
  76:  
  77:             routineId.setTypeSpec(type);
  78:             token = currentToken();
  79:         }
  80:     }

方法parseRoutineName()验证过程或函数的名字知否已在局部符号表中定义,如果有,一定得是来自一个前向申明。

方法parseHeader()调用parseFormalParameters()解析程式的形参列表。如果程式是一个函数,它还需要调用variableDeclarationsParser.parseTypeSpec()解析返回类型。Pascal不允许一个函数的返回值类型为数组或记录(为何?是不是实现会很复杂,大脑风暴一下?)

形式参数列表(Formal Parameter Lists,或形参表)

清单11-6 展示了方法parseFormalParameters()

   1: // 形参子表的同步集
   2: private static final EnumSet<PascalTokenType> PARAMETER_SET =
   3:     DeclarationsParser.DECLARATION_START_SET.clone();
   4: static {
   5:     PARAMETER_SET.add(VAR);
   6:     PARAMETER_SET.add(IDENTIFIER);
   7:     PARAMETER_SET.add(RIGHT_PAREN);
   8: }
   9:  
  10: //左括号处的同步集
  11: private static final EnumSet<PascalTokenType> LEFT_PAREN_SET =
  12:     DeclarationsParser.DECLARATION_START_SET.clone();
  13: static {
  14:     LEFT_PAREN_SET.add(LEFT_PAREN);
  15:     LEFT_PAREN_SET.add(SEMICOLON);
  16:     LEFT_PAREN_SET.add(COLON);
  17: }
  18:  
  19: //右括号的同步集
  20: private static final EnumSet<PascalTokenType> RIGHT_PAREN_SET =
  21:     LEFT_PAREN_SET.clone();
  22: static {
  23:     RIGHT_PAREN_SET.remove(LEFT_PAREN);
  24:     RIGHT_PAREN_SET.add(RIGHT_PAREN);
  25: }
  26:  
  27: /**
  28:  * 解析程式的形参列表
  29:  * @param token 当前token
  30:  * @param routineId 程式名称对应的符号表项
  31:  * @throws Exception
  32:  */
  33: protected void parseFormalParameters(Token token, SymTabEntry routineId)
  34:     throws Exception
  35: {
  36:     // 同步左括号集
  37:     token = synchronize(LEFT_PAREN_SET);
  38:     if (token.getType() == LEFT_PAREN) {//必须得是一个左括号
  39:         token = nextToken(); 
  40:         ArrayList<SymTabEntry> parms = new ArrayList<SymTabEntry>();
  41:         token = synchronize(PARAMETER_SET);
  42:         TokenType tokenType = token.getType();
  43:  
  44:         // 遍历解析形参子表,注意参数前面加VAR表示引用传递
  45:         while ((tokenType == IDENTIFIER) || (tokenType == VAR)) {
  46:             parms.addAll(parseParmSublist(token, routineId));
  47:             token = currentToken();
  48:             tokenType = token.getType();
  49:         }
  50:  
  51:         // 参数结束的右括号
  52:         if (token.getType() == RIGHT_PAREN) {
  53:             token = nextToken();  // consume )
  54:         }
  55:         else {
  56:             errorHandler.flag(token, MISSING_RIGHT_PAREN, this);
  57:         }
  58:  
  59:         routineId.setAttribute(ROUTINE_PARMS, parms);
  60:     }
  61: }

如果有形参,方法parseFormalParameters()循环的调用parseParmSublist()处理参数申明中的每一个子表。最终这回建立一个参数标识符的符号表项列表,并以此为程式名称对应表项的ROUTINE_PARMS属性值。

清单11-7 展示了方法parseParmSublist()

   1: // 一个形参后的同步集
   2:     private static final EnumSet<PascalTokenType> PARAMETER_FOLLOW_SET =
   3:         EnumSet.of(COLON, RIGHT_PAREN, SEMICOLON);
   4:     static {
   5:         PARAMETER_FOLLOW_SET.addAll(DeclarationsParser.DECLARATION_START_SET);
   6:     }
   7:  
   8:     // 逗号处的同步集
   9:     private static final EnumSet<PascalTokenType> COMMA_SET =
  10:         EnumSet.of(COMMA, COLON, IDENTIFIER, RIGHT_PAREN, SEMICOLON);
  11:     static {
  12:         COMMA_SET.addAll(DeclarationsParser.DECLARATION_START_SET);
  13:     }
  14:  
  15:     /**
  16:      * 解析形参子表,每个以分号隔开的为一个子表
  17:      * @param token 当前Token
  18:      * @param routineId 程式名称对应的符号表项
  19:      * @return 子表对应的符号表项
  20:      * @throws Exception
  21:      */
  22:     private ArrayList<SymTabEntry> parseParmSublist(Token token,
  23:                                                     SymTabEntry routineId)
  24:         throws Exception
  25:     {
  26:         boolean isProgram = routineId.getDefinition() == DefinitionImpl.PROGRAM;
  27:         Definition parmDefn = isProgram ? PROGRAM_PARM : null;
  28:         TokenType tokenType = token.getType();
  29:  
  30:         // 引用或值传递?
  31:         if (tokenType == VAR) {
  32:             if (!isProgram) {
  33:                 parmDefn = VAR_PARM;
  34:             }
  35:             else {
  36:                 errorHandler.flag(token, INVALID_VAR_PARM, this);
  37:             }
  38:  
  39:             token = nextToken();  // consume VAR
  40:         }
  41:         else if (!isProgram) {
  42:             parmDefn = VALUE_PARM;
  43:         }
  44:  
  45:         // 解析参数子表及它的类型
  46:         VariableDeclarationsParser variableDeclarationsParser =
  47:             new VariableDeclarationsParser(this);
  48:         variableDeclarationsParser.setDefinition(parmDefn);
  49:         ArrayList<SymTabEntry> sublist =
  50:             variableDeclarationsParser.parseIdentifierSublist(
  51:                                            token, PARAMETER_FOLLOW_SET,
  52:                                            COMMA_SET);
  53:         token = currentToken();
  54:         tokenType = token.getType();
  55:  
  56:         if (!isProgram) {
  57:  
  58:             // 搜寻并吞噬一个子表后的分号
  59:             if (tokenType == SEMICOLON) {
  60:                 while (token.getType() == SEMICOLON) {
  61:                     token = nextToken();  // consume the ;
  62:                 }
  63:             } else if (VariableDeclarationsParser.NEXT_START_SET.contains(tokenType)) {
  64:                 errorHandler.flag(token, MISSING_SEMICOLON, this);
  65:             }
  66:  
  67:             token = synchronize(PARAMETER_SET);
  68:         }
  69:  
  70:         return sublist;
  71:     }

在方法parseParmSublist()中,一个VAR token意味着子表中的每个形参被定义成为VAR_PARAM(通过引用传递)。否则每个参数被定义成为VALUE_PARAM(通过值传递)。不过,程序参数只能被定义为PROGRAM_PARM。此方法调用variableDeclarationsParser.setDefinition()以便让解析器知道这些参数是怎么定义的,并接着调用variableDeclarationsParser.parseIdentifierSublist()完成参数解析工作。

最后,清单11-8 展示了申明解析子类VariableDeclarationsParser中的新版本parseIdentifierSublist()方法。(改动很小,只是替换了followSet和commaSet)

   1: protected ArrayList<SymTabEntry> parseIdentifierSublist(Token token,
   2:             EnumSet<PascalTokenType> followSet,
   3:             EnumSet<PascalTokenType> commaSet)
   4:         throws Exception
   5:     {
   6:         ArrayList<SymTabEntry> sublist = new ArrayList<SymTabEntry>();
   7:         do {
   8:             token = synchronize(IDENTIFIER_START_SET);
   9:             SymTabEntry id = parseIdentifier(token);
  10:  
  11:             if (id != null) {
  12:                 sublist.add(id);
  13:             }
  14:  
  15:             token = synchronize(commaSet);
  16:             TokenType tokenType = token.getType();
  17:  
  18:             // 找逗号
  19:             if (tokenType == COMMA) {
  20:                 token = nextToken(); //有逗号吞掉以便下一个
  21:  
  22:                 if (followSet.contains(token.getType())) {
  23:                     errorHandler.flag(token, MISSING_IDENTIFIER, this);
  24:                 }
  25:             }else if (IDENTIFIER_START_SET.contains(tokenType)) {
  26:                 errorHandler.flag(token, MISSING_COMMA, this);
  27:             }
  28:         } while (!followSet.contains(token.getType()));
  29:         if (definition != DefinitionImpl.PROGRAM)
  30:         {
  31:             // 冒号后面的类型
  32:             TypeSpec type = parseTypeSpec(token);
  33:             // 将子表中的每个变量的类型设置上,比如a,b,c:int,先解析了a,b,c,然后解析了int类型
  34:             // 那么这儿就会给a,b,c设置上int类型
  35:             for (SymTabEntry variableId : sublist) {
  36:                 variableId.setTypeSpec(type);
  37:             }
  38:         }
  39:         return sublist;
  40:     }

为适应解析形参子表,而不仅仅是变量申明子表,扩展parseIdentifierSublist(),改变它的签名使之更灵活。对于程序参数,此方法一定不能解析并设置类型说明。

于是类VariableDeclarationsParser的parse()方法调用parseIdentifierSublist()时必须多加两个参数

parseIdentifierSublist(token, IDENTIFIER_FOLLOW_SET, COMMA_SET); (源代码第68行)

你必须还得修改BlockParser的parse()方法以适应程式解析。参见清单11-9

 

>> 继续第11章