(基于Java)编写编译器和解释器-第9章:解析声明-第三部分(连载)

时间:2022-12-27 17:08:32

第二部分

图9-6 展示了这两个同等类型定义所生成的符号表项和类型说明。此图展示了数组元素类型是怎样通过与数组类型说明的ARRAY_ELEMENT_TYPE属性值联系在一起的。

(基于Java)编写编译器和解释器-第9章:解析声明-第三部分(连载)

两种定义格式不一样,但是生成同样的符号表项与类型说明配置。遵照图9-3的惯例,SymTabEntry对象以亮灰色而TypeSepc对象以暗灰显示。属性值ARRAY_ELEMENT_TYPE是带加粗箭头的连线。在Pascal中,字符'a'和'z'的序数值分别是97和122。


清单9-26 展示了类型说明解析子类ArrayTypeParser中的parse方法。

   1: //数组 左方括号之后的同步集
   2:    private static final EnumSet<PascalTokenType> LEFT_BRACKET_SET =
   3:        SimpleTypeParser.SIMPLE_TYPE_START_SET.clone();
   4:    static {
   5:        LEFT_BRACKET_SET.add(LEFT_BRACKET);
   6:        LEFT_BRACKET_SET.add(RIGHT_BRACKET);
   7:    }
   8:  
   9:    // 右方括号之后的同步集
  10:    private static final EnumSet<PascalTokenType> RIGHT_BRACKET_SET =
  11:        EnumSet.of(RIGHT_BRACKET, OF, SEMICOLON);
  12:  
  13:    // 相当于元素类型的同步集
  14:    private static final EnumSet<PascalTokenType> OF_SET =
  15:        TypeSpecificationParser.TYPE_START_SET.clone();
  16:    static {
  17:        OF_SET.add(OF);
  18:        OF_SET.add(SEMICOLON);
  19:    }
  20:  
  21:    public TypeSpec parse(Token token)
  22:        throws Exception
  23:    {
  24:        TypeSpec arrayType = TypeFactory.createType(ARRAY);
  25:        token = nextToken();  //剔掉ARRAY关键字
  26:  
  27:        //在左括号出同步一下
  28:        token = synchronize(LEFT_BRACKET_SET);
  29:        if (token.getType() != LEFT_BRACKET) {
  30:            errorHandler.flag(token, MISSING_LEFT_BRACKET, this);
  31:        }
  32:  
  33:        // 解析索引类型
  34:        TypeSpec elementType = parseIndexTypeList(token, arrayType);
  35:  
  36:        // 在右括号出同步一下
  37:        token = synchronize(RIGHT_BRACKET_SET);
  38:        if (token.getType() == RIGHT_BRACKET) {
  39:            token = nextToken();  
  40:        }
  41:        else {
  42:            errorHandler.flag(token, MISSING_RIGHT_BRACKET, this);
  43:        }
  44:  
  45:        // 在OF处同步一下
  46:        token = synchronize(OF_SET);
  47:        if (token.getType() == OF) {
  48:            token = nextToken(); 
  49:        }
  50:        else {
  51:            errorHandler.flag(token, MISSING_OF, this);
  52:        }
  53:  
  54:        // 然后是元素类型
  55:        elementType.setAttribute(ARRAY_ELEMENT_TYPE, parseElementType(token));
  56:  
  57:        return arrayType;
  58:    }

方法parse()首先调用 TypeFactory.createType()创建一个数组TypeSpec对象,接着调用parseIndexTypeList()解析逗号分割的一个或多个索引类型说明。在吞噬掉OF关键字后,parse()调用parseElementType()解析元素类型说明。

清单9-27 展示了方法parseIndexTypeList()和parseIndexType()

   1: //索引同步集
   2:    private static final EnumSet<PascalTokenType> INDEX_START_SET =
   3:        SimpleTypeParser.SIMPLE_TYPE_START_SET.clone();
   4:    static {
   5:        INDEX_START_SET.add(COMMA);
   6:    }
   7:  
   8:    // 索引结束后的同步集
   9:    private static final EnumSet<PascalTokenType> INDEX_END_SET =
  10:        EnumSet.of(RIGHT_BRACKET, OF, SEMICOLON);
  11:  
  12:    // 索引之后的同步集
  13:    private static final EnumSet<PascalTokenType> INDEX_FOLLOW_SET =
  14:        INDEX_START_SET.clone();
  15:    static {
  16:        INDEX_FOLLOW_SET.addAll(INDEX_END_SET);
  17:    }
  18:  
  19:    /**
  20:     * 解析索引类型说明列表,比如 ARRAY [1..3] 里面的 1..3
  21:     * @param token 当前Token,即左括号
  22:     * @param arrayType 父的数组类型说明
  23:     * @return 元素类型说明
  24:     * @throws Exception
  25:     */
  26:    private TypeSpec parseIndexTypeList(Token token, TypeSpec arrayType)
  27:        throws Exception
  28:    {
  29:        TypeSpec elementType = arrayType;
  30:        boolean anotherIndex = false;
  31:  
  32:        token = nextToken();  // 剔掉左括号
  33:  
  34:        // 解析索引类型说明列表,多维如 ARRAY [1..3, 'a'..'z'] 等
  35:        do {
  36:            anotherIndex = false;
  37:  
  38:            //索引类型
  39:            token = synchronize(INDEX_START_SET);
  40:            parseIndexType(token, elementType);
  41:  
  42:            //索引结束同步一下
  43:            token = synchronize(INDEX_FOLLOW_SET);
  44:            TokenType tokenType = token.getType();
  45:            if ((tokenType != COMMA) && (tokenType != RIGHT_BRACKET)) {
  46:                if (INDEX_START_SET.contains(tokenType)) {
  47:                    errorHandler.flag(token, MISSING_COMMA, this);
  48:                    anotherIndex = true;
  49:                }
  50:            }else if (tokenType == COMMA) { //一个逗号以为这多维索引
  51:                TypeSpec newElementType = TypeFactory.createType(ARRAY);
  52:                elementType.setAttribute(ARRAY_ELEMENT_TYPE, newElementType);
  53:                elementType = newElementType;
  54:  
  55:                token = nextToken(); //吞掉逗号
  56:                anotherIndex = true;
  57:            }
  58:        } while (anotherIndex);
  59:  
  60:        return elementType;
  61:    }
  62:  
  63:    /**
  64:     * 解析索引类型说明,这个是单个的 []包含的,一定是个简单类型,不包含嵌套。
  65:     * @param token 
  66:     * @param 传入的数组类型说明,解析过程会加上一些属性
  67:     * @throws Exception
  68:     */
  69:    private void parseIndexType(Token token, TypeSpec arrayType)
  70:        throws Exception
  71:    {
  72:        SimpleTypeParser simpleTypeParser = new SimpleTypeParser(this);
  73:        TypeSpec indexType = simpleTypeParser.parse(token);
  74:        arrayType.setAttribute(ARRAY_INDEX_TYPE, indexType);
  75:  
  76:        if (indexType == null) {
  77:            return;
  78:        }
  79:  
  80:        TypeForm form = indexType.getForm();
  81:        int count = 0;
  82:  
  83:        // 如果是子界类型
  84:        if (form == SUBRANGE) {
  85:            Integer minValue =
  86:                (Integer) indexType.getAttribute(SUBRANGE_MIN_VALUE);
  87:            Integer maxValue =
  88:                (Integer) indexType.getAttribute(SUBRANGE_MAX_VALUE);
  89:  
  90:            if ((minValue != null) && (maxValue != null)) {
  91:                count = maxValue - minValue + 1;
  92:            }
  93:        }//枚举类型
  94:        else if (form == ENUMERATION) {
  95:            ArrayList<SymTabEntry> constants = (ArrayList<SymTabEntry>)
  96:                indexType.getAttribute(ENUMERATION_CONSTANTS);
  97:            count = constants.size();
  98:        }
  99:        else {
 100:            errorHandler.flag(token, INVALID_INDEX_TYPE, this);
 101:        }
 102:        arrayType.setAttribute(ARRAY_ELEMENT_COUNT, count);
 103:    }

方法parseIndexTypeList()用来解析索引类型说明列表。它初始化变量elementType为parse()创建的数组TypeSpec对象(即最外围的那个)。在循环中,它调用parseIndexType()解析每一个索引类型说明。在第一个索引类型之后(意味着有多维出现),parseIndexTypeList()必须调用TypeFactory.createType()创建一个新的数组TypeSpec对象作为新的元素TypeSpec。方法设置嗲一个elementType的ARRAY_ELEMENT_TYPE属性为新的元素TypeSpec对象,并马上将elementType指向新的元素TypeSpec对象(见51,52,53行)。在循环中,这将会创建一个数组TypeSpec对象的ARRAY_ELEMENT_TYPE链,就如图9-6展示的那样。方法返在链条最末端的数组TypeSpec对象(也就是最里层的数组对象)。

最终,parseElementType()方法返回typeSpecificationParser.parse()所解析到的结果,我们知道,typeSpecificationParser.parse()是用来解析一般类型说明的。

 

   1: /**
   2:   * 数组元素可以是另外一个数组,也可是个简单类型,交给TypeSpec来决定吧。
   3:   * @param token 当前token, OF之后的第一个
   4:   * @return 元素类型说明
   5:   * @throws Exception
   6:   */
   7:  private TypeSpec parseElementType(Token token)
   8:      throws Exception
   9:  {
  10:      TypeSpecificationParser typeSpecificationParser =
  11:          new TypeSpecificationParser(this);
  12:      return typeSpecificationParser.parse(token);
  13:  }

记录类型(Record Types)

余下的Pascal类型就是记录类型了。清单9-29 展示了类型说明解析子类RecordTypeParser的parse()方法。

   1: class RecordTypeParser extends TypeSpecificationParser
   2: {
   3:     protected RecordTypeParser(PascalParserTD parent)
   4:     {
   5:         super(parent);
   6:     }
   7:  
   8:     // END处的同步集
   9:     private static final EnumSet<PascalTokenType> END_SET =
  10:         DeclarationsParser.VAR_START_SET.clone();
  11:     static {
  12:         END_SET.add(END);
  13:         END_SET.add(SEMICOLON);
  14:     }
  15:  
  16:     public TypeSpec parse(Token token)
  17:         throws Exception
  18:     {
  19:         TypeSpec recordType = TypeFactory.createType(RECORD);
  20:         token = nextToken(); //吞掉RECORD关键字
  21:         //每个RECORD是一个作用域,需要加入新的符号表
  22:         recordType.setAttribute(RECORD_SYMTAB, symTabStack.push());
  23:  
  24:         // 记录中的字段申明,一个或多个
  25:         VariableDeclarationsParser variableDeclarationsParser =
  26:             new VariableDeclarationsParser(this);
  27:         variableDeclarationsParser.setDefinition(FIELD);
  28:         variableDeclarationsParser.parse(token);
  29:  
  30:         // 退出record作用域
  31:         symTabStack.pop();
  32:  
  33:         // 同步在end处
  34:         token = synchronize(END_SET);
  35:  
  36:         if (token.getType() == END) {
  37:             token = nextToken();
  38:         }
  39:         else {
  40:             errorHandler.flag(token, MISSING_END, this);
  41:         }
  42:  
  43:         return recordType;
  44:     }
  45: }

就如图9-1 所示,记录类型定义中的字段申明和变量申明有同样的语法,因此这个parse()方法相对简单些。它调用TypeFactory.createType()创建一个记录TypeSpec对象来开始。因为一个记录类型说明得创建一个新的作用域用来包含它的字段申明,所以语句

recordType.setAttribute(RECORD_SYMTAB, symTabStack.push());

往符号表堆栈推入一个新表并设置它为记录TypeSpec对象的RECORD_SYMTAB的值。接着parse()设置variableDeclarationsParser的定义为FIELD并调用variableDeclarationsParser.parseFields()解析字段申明。完事后弹出记录的符号表。马上你就会看到类VariableDeclarationsParser

解析变量申明

现在是解析变量申明的时候了。前面讲过,这个变量申明解析器同样会解析记录字段申明。清单9-16和9-29 展示了它的调用者首先设置definition域。清单9-30 展示了 申明解析器子类VariableDeclarationsParser的parse()方法。

   1: // 标识符怎么定义的?同是变量申明,上下文不一样,定义不一样
   2:     private Definition definition;  
   3:     public VariableDeclarationsParser(PascalParserTD parent)
   4:     {
   5:         super(parent);
   6:     }
   7:     // 变量申明的同步集
   8:     static final EnumSet<PascalTokenType> IDENTIFIER_SET =
   9:         DeclarationsParser.VAR_START_SET.clone();
  10:     static {
  11:         IDENTIFIER_SET.add(IDENTIFIER);
  12:         IDENTIFIER_SET.add(END);
  13:         IDENTIFIER_SET.add(SEMICOLON);
  14:     }
  15:  
  16:     // 下一个定义或申明的同步集
  17:     static final EnumSet<PascalTokenType> NEXT_START_SET =
  18:         DeclarationsParser.ROUTINE_START_SET.clone();
  19:     static {
  20:         NEXT_START_SET.add(IDENTIFIER);
  21:         NEXT_START_SET.add(SEMICOLON);
  22:     }
  23:  
  24:     public void parse(Token token)
  25:         throws Exception
  26:     {
  27:         token = synchronize(IDENTIFIER_SET);
  28:  
  29:         // 遍历由分号分割的一系列变量申明
  30:         while (token.getType() == IDENTIFIER) {
  31:  
  32:             //解析每一个子表标识申明,比如 a,b,c : int
  33:             parseIdentifierSublist(token);
  34:  
  35:             token = currentToken();
  36:             TokenType tokenType = token.getType();
  37:  
  38:             // 一个定义由分号结束
  39:             if (tokenType == SEMICOLON) {
  40:                 while (token.getType() == SEMICOLON) {
  41:                     token = nextToken();  // consume the ;
  42:                 }
  43:             }else if (NEXT_START_SET.contains(tokenType)) {
  44:                 //没有分号便开始下一个了。
  45:                 errorHandler.flag(token, MISSING_SEMICOLON, this);
  46:             }
  47:  
  48:             token = synchronize(IDENTIFIER_SET);
  49:         }
  50:     }

方法parse()循环的解析由分号; 连接起来的符号申明。它调用parseIdentifierSublist()解析任意一个或用逗号隔开的多个标识符形成的子表以及它们的类型说明。参见清单9-31:

   1: // 子表标识同步集,这里a,b,c: int 中的a,b,c算作子表
   2:   static final EnumSet<PascalTokenType> IDENTIFIER_START_SET =
   3:       EnumSet.of(IDENTIFIER, COMMA);
   4:  
   5:   // 标识符后的同步集
   6:   private static final EnumSet<PascalTokenType> IDENTIFIER_FOLLOW_SET =
   7:       EnumSet.of(COLON, SEMICOLON);
   8:   static {
   9:       IDENTIFIER_FOLLOW_SET.addAll(DeclarationsParser.VAR_START_SET);
  10:   }
  11:  
  12:   // 逗号的同步集
  13:   private static final EnumSet<PascalTokenType> COMMA_SET =
  14:       EnumSet.of(COMMA, COLON, IDENTIFIER, SEMICOLON);
  15:  
  16:   /**
  17:    * 解析标识符子表及类型说明
  18:    * @param token the current token.
  19:    * @return 一个申明中的子表标识符列表
  20:    * @throws Exception
  21:    */
  22:   protected ArrayList<SymTabEntry> parseIdentifierSublist(Token token)
  23:       throws Exception
  24:   {
  25:       ArrayList<SymTabEntry> sublist = new ArrayList<SymTabEntry>();
  26:  
  27:       do {
  28:           token = synchronize(IDENTIFIER_START_SET);
  29:           SymTabEntry id = parseIdentifier(token);
  30:  
  31:           if (id != null) {
  32:               sublist.add(id);
  33:           }
  34:  
  35:           token = synchronize(COMMA_SET);
  36:           TokenType tokenType = token.getType();
  37:  
  38:           // 找逗号
  39:           if (tokenType == COMMA) {
  40:               token = nextToken(); //有逗号吞掉以便下一个
  41:  
  42:               if (IDENTIFIER_FOLLOW_SET.contains(token.getType())) {
  43:                   errorHandler.flag(token, MISSING_IDENTIFIER, this);
  44:               }
  45:           }else if (IDENTIFIER_START_SET.contains(tokenType)) {
  46:               errorHandler.flag(token, MISSING_COMMA, this);
  47:           }
  48:       } while (!IDENTIFIER_FOLLOW_SET.contains(token.getType()));
  49:  
  50:       //冒号后面的类型
  51:       TypeSpec type = parseTypeSpec(token);
  52:  
  53:       //将子表中的每个变量的类型设置上,比如a,b,c:int,先解析了a,b,c,然后解析了int类型
  54:       //那么这儿就会给a,b,c设置上int类型
  55:       for (SymTabEntry variableId : sublist) {
  56:           variableId.setTypeSpec(type);
  57:       }
  58:  
  59:       return sublist;
  60:   }

方法parseIndentifierSublist()遍历冒号:之前以逗号隔开的变量标识子表(比如 a,b,c : int),接着调用parseIndentifier()解析每个标识符。然后它调用parseTypeSpec解析类型说明。代码55行处的for循环遍历子表中的符号表项并设置类型说明。清单9-32 展示了parseIndentifier()和parseTypeSpec()。

   1: /**
   2:  * 解析标识符
   3:  * @param token
   4:  * @return 对应符号表项
   5:  * @throws Exception
   6:  */
   7: private SymTabEntry parseIdentifier(Token token)
   8:     throws Exception
   9: {
  10:     SymTabEntry id = null;
  11:  
  12:     if (token.getType() == IDENTIFIER) {
  13:         String name = token.getText().toLowerCase();
  14:         id = symTabStack.lookupLocal(name);
  15:  
  16:         //申明必须得没有,否则重定义了
  17:         if (id == null) {
  18:             id = symTabStack.enterLocal(name);
  19:             id.setDefinition(definition);
  20:             id.appendLineNumber(token.getLineNumber());
  21:         }
  22:         else {
  23:             errorHandler.flag(token, IDENTIFIER_REDEFINED, this);
  24:         }
  25:  
  26:         token = nextToken(); 
  27:     }
  28:     else {
  29:         errorHandler.flag(token, MISSING_IDENTIFIER, this);
  30:     }
  31:  
  32:     return id;
  33: }
  34:  
  35: // 类型冒号处的同步集
  36: private static final EnumSet<PascalTokenType> COLON_SET =
  37:     EnumSet.of(COLON, SEMICOLON);
  38:  
  39: /**
  40:  * 解析变量申明里的类型
  41:  * @param token 当前token
  42:  * @return 类型说明
  43:  * @throws Exception
  44:  */
  45: protected TypeSpec parseTypeSpec(Token token)
  46:     throws Exception
  47: {
  48:     // 同步在:处
  49:     token = synchronize(COLON_SET);
  50:     if (token.getType() == COLON) {
  51:         token = nextToken(); // 吞掉 :
  52:     }
  53:     else {
  54:         errorHandler.flag(token, MISSING_COLON, this);
  55:     }
  56:  
  57:     // 解析类型说明
  58:     TypeSpecificationParser typeSpecificationParser =
  59:         new TypeSpecificationParser(this);
  60:     TypeSpec type = typeSpecificationParser.parse(token);
  61:  
  62:     return type;
  63: }

方法parseIndentifier()解析每个变量标识并将其放入符号表中。它检查此变量在当前作用域中是否被重定义。最后设置表项的定义并附加当前代码行数。

方法parseTypeSpec()调用typeSpecificationParser.parse()解析并返回类型说明(当然是变量的)。

设计笔记

你已经去掉了第6章所使用的前三个hack技巧。源程序中的每个变量现在都有一个数据类型,此为#1;每个被申明的变量都被放到了符号表,此为#2;第三条hack技巧再也不需要了,因为现在栈上有了不止一个符号表。

程序 9:Pascal交叉引用II

现在让我们来分别扩展一下在第4,5章开发的交叉引用和分析树打印程序。不过首先得改一下主类Pascal的构造函数,如清单9-33所显示。中间码变量iCode来自于主程序函数标识符的符号表项的ROUTINE_ICODE属性值。

   1: if (parser.getErrorCount() == 0) {
   2:         stack = parser.getSymTabStack();
   3:  
   4:         SymTabEntry programId = stack.getProgramId();
   5:         iCode = (ICode) programId.getAttribute(SymTabKeyImpl.ROUTINE_ICODE);
   6:  
   7:         if (xref) {
   8:             CrossReferencer crossReferencer = new CrossReferencer();
   9:             crossReferencer.print(stack);
  10:         }
  11:  
  12:         if (intermediate) {
  13:             ParseTreePrinter treePrinter =
  14:                                  new ParseTreePrinter(System.out);
  15:             treePrinter.print(stack);
  16:         }
  17:         //后端处理
  18:         backend.process(iCode, stack);
  19:     }

这儿的代码参考SVN源代码第68行-86行

设计笔记

这儿的交叉引用工具不是仅本身有用,更有用的是它的完整版本,它为主程序、记录类型定义、每个过程和函数,对符号表内容执行关键验证。
本章开发所需要的代码和数据结构跟符号表一样复杂,这意味着交叉引用工具得确保所有数据和引用正确,那样你才有信心认为后端依赖符号表的执行器和生成器能够正常工作。

清单9-34 展示了工具类CrossReferencer中的print(),printColumnHeadings()以及printSymTab()方法,还有一个新增的printRoutine()。(请参考工程源代码,这里不再展示

方法print()调用symTabStack.getProgramId()得到Pascal程序标识符对应的符号表项,接着调用printRoutine()。

方法printRoutine()先获得 程式标识表项的ROUTINE_SYMTAB属性值,这个值是符号表,存有此程式中定义的所有标识符。方法接着调用printSymTab答应符号表内容并创建一个程式中定义的所有记录类型数组列表。方法printRecords()答应出这些记录类型的详细信息。最后,方法printRoutine()调用自身递归的处理所有嵌套过程和函数定义。

除了简单打印每个符号表项的标识符名称和所在行数之外,方法printSymTab同时还得调用printEntry()打印每个项的类容。

清单9-35展示了方法printEntry()和valueToString()。(请参考工程源代码,这里不再展示

方法printEntry()调用printType()答应标识符的类型说明。接着打印符号表中与此标识符定义有关的信息。对于常量标识符和枚举常量,它打印出常量值;对于类型,在其被首次定义的时候,调用printTypeDetail打印此类型标识符的详细信息;对于标量标识符,它仅在变量类型是匿名的情况下才调用printTypeDetails。(这个很容易理解,因为如果不是匿名的,肯定在前面的类型定义中首次定义的时候被打印过了,再打印多余,只有匿名才每次都是一个新类型)。

清单9-36 展示了方法printType和printTypeDetail。(请参考工程源代码,这里不再展示

printTypeDetail()方法答应的类型信息取决于类型格式。对于枚举类型,它打印出枚举常量和它们的值。对于子界(Subrange)类型,它打印基类型以及上限和下限值。对于数组类型,它打印的信息有索引类型和元素类型。最后,对于记录类型来说,方法简单将此记录类型说明放到参数记录类型列表中。

对于列表中的每个记录类型说明,方法printRecords()取得RECORD_SYMTAB属性值,这个值是包含记录类型的所有字段的符号表。方法printSymTab打印字段交叉引用清单。此方法还递归调用自己打印所有嵌套的记录类型定义。

   1: /**
   2:     * 打印程式中定义的记录类型
   3:     */
   4:    private void printRecords(ArrayList<TypeSpec> recordTypes)
   5:    {
   6:        for (TypeSpec recordType : recordTypes) {
   7:            SymTabEntry recordId = recordType.getIdentifier();
   8:            String name = recordId != null ? recordId.getName() : "<匿名>";
   9:  
  10:            System.out.println("\n--- 记录类型  " + name + " ---");
  11:            printColumnHeadings();
  12:  
  13:            // 打印记录所对应符号表中的项
  14:            SymTab symTab = (SymTab) recordType.getAttribute(TypeKeyImpl.RECORD_SYMTAB);
  15:            ArrayList<TypeSpec> newRecordTypes = new ArrayList<TypeSpec>();
  16:            printSymTab(symTab, newRecordTypes);
  17:  
  18:            // 记录定义里面嵌套的记录
  19:            if (newRecordTypes.size() > 0) {
  20:                printRecords(newRecordTypes);
  21:            }
  22:        }
  23:    }

方法printEntry,printType()和printTypeDetails一起为每个标识符打印出交叉引用以及类型信息。下面是每种各种类型表示的样例输出。(这里纯属作者凑字数,我这里省略,请读者自己运行Pascal程序查看各种输出)。

清单9-38 展示了源程序文件declarations.txt的交叉应用输出。命令行为

java -classpath classes Pascal execute -x declarations.txt

因为你还有修改任何语句解析器,此源文件仅仅包含一个“空”的复合语句。(这个输出有500多行,省略,请自己生成看看结果,这儿输出一些片段)

   1: 001 CONST
   2: 002     ten = 10;
   3: 003     epsilon = 1.0E-6;
   4: 004     x = 'x';
   5: 005     limit = -epsilon;
   6: 006     hello = 'Hello, world!';
   7: 007 
   8: 008 TYPE
   9: 009     range1 = 0..ten;
  10: 010     range2 = 'a'..'q';
  11: 011     range3 = range1;
  12: 012 
  13: ...............
  14: 省略
  15: ...............
  16: 048 
  17: 049     var10 : rec1;
  18: 050     var11 : RECORD
  19: 051                 b : boolean;
  20: 052                 r : RECORD
  21: 053                         aa : ar1;
  22: 054                         bb : boolean;
  23: 055                         r  : real;
  24: 056                         v1 : ar6;
  25: 057                         v2 : ARRAY [enum1, range1] OF ar7;
  26: 058                     END;
  27: 059                 a : ARRAY [1..5] OF boolean;
  28: 060             END;
  29: 061 
  30: ...............
  31: 省略
  32: ...............
  33: 068 
  34: 069 BEGIN
  35: 070 END.
  36:  
  37: ----------代码解析统计信息--------------
  38: 源文件共有    70行。
  39: 有    0个语法错误.
  40: 解析共耗费    0.05秒.
  41:  
  42: ============ 交叉引用列表  ========
  43:  
  44: *** PROGRAM dummyprogramname ***
  45:  
  46: 标识符           所在行位置      类型说明
  47: ----------   ------------    ------------------
  48: a            013
  49:                              定义: 枚举常量
  50:                              嵌套层级: 1
  51:                              类型格式 = 枚举, 类型ID = enum1
  52:                              值为 0
  53: alpha        022
  54:                              定义: 枚举常量
  55:                              嵌套层级: 1
  56:                              类型格式 = 枚举, 类型ID = <匿名>
  57:                              值为 0
  58: ar1          021 023 053 062
  59:                              定义: 类型
  60:                              嵌套层级: 1
  61:                              类型格式 = 数组, 类型ID = ar1
  62:                              --- 索引类型  ---
  63:                              类型格式 = 子界, 类型ID = range1
  64:                              --- 元素类型 ---
  65:                              类型格式 = 标量, 类型ID = integer
  66:                              共有11 个元素
  67: ar2          022
  68:                              定义: 类型
  69:                              嵌套层级: 1
  70:                              类型格式 = 数组, 类型ID = ar2
  71:                              --- 索引类型  ---
  72:                              类型格式 = 枚举, 类型ID = <匿名>
  73:                              --- 枚举常量 ---
  74:                                     alpha = 0
  75:                                      beta = 1
  76:                                     gamma = 2
  77:                              --- 元素类型 ---
  78:                              类型格式 = 子界, 类型ID = range2
  79:                              共有3 个元素
  80: ...............
  81: 省略
  82: ...............
  83: x            004
  84:                              定义: 常量
  85:                              嵌套层级: 1
  86:                              类型格式 = 标量, 类型ID = char
  87:                              值为 'x'
  88:  
  89: --- 记录类型  <匿名> ---
  90:  
  91: 标识符           所在行位置      类型说明
  92: ----------   ------------    ------------------
  93: a            038
  94:                              定义: 记录域
  95:                              嵌套层级: 2
  96:                              类型格式 = 数组, 类型ID = <匿名>
  97: r            037
  98:                              定义: 记录域
  99:                              嵌套层级: 2
 100:                              类型格式 = 记录, 类型ID = rec1
 101: ten          036
 102:                              定义: 记录域
 103:                              嵌套层级: 2
 104:                              类型格式 = 标量, 类型ID = integer
 105:  
 106: --- 记录类型  rec1 ---
 107:  
 108: 标识符           所在行位置      类型说明
 109: ----------   ------------    ------------------
 110: b1           031
 111:                              定义: 记录域
 112:                              嵌套层级: 2
 113:                              类型格式 = 枚举, 类型ID = boolean
 114: b2           031
 115:                              定义: 记录域
 116:                              嵌套层级: 2
 117:                              类型格式 = 枚举, 类型ID = boolean
 118: c            032
 119:                              定义: 记录域
 120:                              嵌套层级: 2
 121:                              类型格式 = 标量, 类型ID = char
 122: i            029
 123:                              定义: 记录域
 124:                              嵌套层级: 2
 125:                              类型格式 = 标量, 类型ID = integer
 126: r            030
 127:                              定义: 记录域
 128:                              嵌套层级: 2
 129:                              类型格式 = 标量, 类型ID = real
 130: ...............
 131: 省略
 132: ...............
 133:  
 134: ----------解释统计信息------------
 135: 共执行    0 条语句。
 136: 运行中发生了    0 个错误。
 137: 执行共耗费    0.00 秒。

清单9-39 展示了申明解析过程中的语法检查输出,请执行 java -classpath classes Pascal execute -x declerrors.txt (请自己运行查看结果,这里不再展示输出)

清单9-40 列出了ParseTreePrinter的print()和printRountine()方法。

   1: /**
   2:     * 打印中间码
   3:     * @param symTabStack 符号表堆栈
   4:     */
   5:    public void print(SymTabStack symTabStack)
   6:    {
   7:        ps.println("\n===== INTERMEDIATE CODE =====");
   8:  
   9:        SymTabEntry programId = symTabStack.getProgramId();
  10:        printRoutine(programId);
  11:    }
  12:    /**
  13:     * 打印程式
  14:     * @param routineId 程式所对应的符号表项
  15:     */
  16:    private void printRoutine(SymTabEntry routineId)
  17:    {
  18:        Definition definition = routineId.getDefinition();
  19:        System.out.println("\n*** " + definition.toString() +
  20:                           " " + routineId.getName() + " ***\n");
  21:  
  22:        // 表项上的中间码
  23:        ICode iCode = (ICode) routineId.getAttribute(SymTabKeyImpl.ROUTINE_ICODE);
  24:        if (iCode.getRoot() != null) {
  25:            printNode((ICodeNodeImpl) iCode.getRoot());
  26:        }
  27:  
  28:        // 打印在程式中定义的过程和函数
  29:        ArrayList<SymTabEntry> routineIds =
  30:            (ArrayList<SymTabEntry>) routineId.getAttribute(SymTabKeyImpl.ROUTINE_ROUTINES);
  31:        if (routineIds != null) {
  32:            for (SymTabEntry rtnId : routineIds) {
  33:                printRoutine(rtnId);
  34:            }
  35:        }
  36:    }

方法print()现在用一个符号表栈作为参数,因为你把每个程式的中间码存放在程式名称对用的符号表项上(即23行处的ROUTINE_ICODE属性)。

在第十章中,你将修改Pascal语句解析器,用来处理数组变量,记录变量,还有完善类型检查。