zoukankan      html  css  js  c++  java
  • (基于Java)编写编译器和解释器第10章:类型检查第二部分

    第一部分

    控制语句中的类型检查

    因为Pascal控制语句中有表达式,所以它们的解析器同样需要做类型检查。

    清单10-2 展示了语句解析子类AssignmentStatementParser新版本的parse()方法。(留意加粗部分

       1: /**
       2:  * 解析如 a = xx+yy; 之类的赋值语句
       3:  * 会有左值/右值两个子节点,并且节点类型与左值类型保持一致
       4:  * @param token
       5:  *            第一个token,肯定是identifier了。
       6:  * @return 语句子树根节点
       7:  * @throws Exception
       8:  */
       9: public ICodeNode parse(Token token) throws Exception {
      10:     ICodeNode assignNode = ICodeFactory.createICodeNode(ASSIGN);
      11:     //交由变量解析左边的变量
      12:     VariableParser variableParser = new VariableParser(this);
      13:     ICodeNode targetNode = variableParser.parse(token);
      14:     TypeSpec targetType = targetNode != null ? targetNode.getTypeSpec()
      15:                                              : Predefined.undefinedType;
      16:     assignNode.addChild(targetNode);
      17:     //等号处同步
      18:     token = synchronize(COLON_EQUALS_SET);
      19:     // 找不到赋值:=就报错,找到就吞噬
      20:     if (token.getType() == COLON_EQUALS) {
      21:         token = nextToken();
      22:     } else {
      23:         errorHandler.flag(token, MISSING_COLON_EQUALS, this);
      24:     }
      25:     // 解析赋值语句右边的表达式,将其子树作为赋值节点的第二个孩子
      26:     ExpressionParser expressionParser = new ExpressionParser(this);
      27:     ICodeNode exprNode = expressionParser.parse(token);
      28:     assignNode.addChild(exprNode);
      29:     //左值变量,右值表达式,是否能赋值兼容。
      30:      TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
      31:              : Predefined.undefinedType;
      32:      if (!TypeChecker.areAssignmentCompatible(targetType, exprType)) {
      33:         errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
      34:     }
      35:     assignNode.setTypeSpec(targetType);
      36:     return assignNode;
      37: }

    parse()方法调用variableParser.parse()解析目标类型,并调用TypeChecker.areAssignmentCompatible()检查目标变量的类型是否与表达式返回类型保持赋值兼容。它设置ASSIGN节点的类型为目标变量的类型(第35行)。

    类class RepeatStatementParser中的新parse()方法同样加了类型检查。它调用s TypeChecker.isBoolean()确保标识是布尔类型。

    清单10-13 RepeatStatementParser中parse()方法的类型检查(参见工程源代码 46行处

       1: ExpressionParser expressionParser = new ExpressionParser(this);
       2: ICodeNode exprNode = expressionParser.parse(token);
       3: testNode.addChild(exprNode);
       4: loopNode.addChild(testNode);//最后一个子节点
       5:  
       6: TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
       7:         : Predefined.undefinedType;
       8: if (!TypeChecker.isBoolean(exprType)) {
       9:     errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
      10: }

    WhileStatementParser类中的parse()方法做了类似的更新。它也调用了TypeChecker.isBoolean()确保表达式是布尔类型。参见下面的清单10-14(参见工程源代码53行处

       1: ExpressionParser expressionParser = new ExpressionParser(this);
       2: ICodeNode exprNode = expressionParser.parse(token);
       3: notNode.addChild(exprNode);
       4:  
       5:  TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
       6:          : Predefined.undefinedType;
       7: if (!TypeChecker.isBoolean(exprType)) {
       8:     errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
       9: }

    用类似的搞法,清单10-15、10-16、10-17分别展示ForStatementParser、IfStatementParse和CaseStatementParser中的新版本parse()方法。

    清单10-15 类ForStatementParser中parse()方法的类型检查(为省空间,我只贴改变的部分,详细请参考工程源代码

       1: //第77-84行
       2: TypeSpec controlType = initAssignNode != null
       3:                 ? initAssignNode.getTypeSpec()
       4:                 : Predefined.undefinedType;
       5: //for语句的初始变量一定得是一个整数或者枚举
       6: if (!TypeChecker.isInteger(controlType)
       7:        && (controlType.getForm() != TypeFormImpl.ENUMERATION)) {
       8:      errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
       9: }
      10: //第101行
      11: relOpNode.setTypeSpec(Predefined.booleanType); //关系默认为布尔类型
      12: //第109-114行
      13: TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
      14:                  : Predefined.undefinedType;
      15: //控制变量能够与表达式类型赋值兼容,不然无法复制
      16: if (!TypeChecker.areAssignmentCompatible(controlType, exprType)) {
      17:     errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
      18: }
      19: //第131行
      20: nextAssignNode.setTypeSpec(controlType);
      21: //第135行
      22: arithOpNode.setTypeSpec(Predefined.integerType);
      23: //第140行
      24: oneNode.setTypeSpec(Predefined.integerType);

    FOR语句的变量类型必须是整数,字符或枚举。parse()方法调用assignmentParser.parse(),赋值语句的parse()为控制变量和初始表达式执行类型检查。此方法调用TypeChecker.areAssignmentCompatible()验证结束表达式(Pascal表达式 FOR i = 0 to 5 DO expr; 中的红色部分为结束表达式,在TO|DOWNTO 和DO之间)。

    清单10-16 类IfStatementParser方法parse()中的类型检查(工程源代码46行处)

       1: ExpressionParser expressionParser = new ExpressionParser(this);
       2: ICodeNode exprNode = expressionParser.parse(token);
       3: ifNode.addChild(exprNode);
       4:  
       5: // Type check: The expression type must be boolean.
       6: TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
       7:                                      : Predefined.undefinedType;
       8: if (!TypeChecker.isBoolean(exprType)) {
       9:     errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
      10: }

    CaseStatementParser中改进过的parse()方法必须保证case表达式类型一定是整形,字符或者枚举类型。

       1: ExpressionParser expressionParser = new ExpressionParser(this);
       2:         ICodeNode exprNode = expressionParser.parse(token);
       3:         selectNode.addChild(exprNode);
       4:         TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
       5:                 : Predefined.undefinedType;
       6: if (!TypeChecker.isInteger(exprType)
       7:         && !TypeChecker.isChar(exprType) 
       8:         && (exprType.getForm() != TypeFormImpl.ENUMERATION))
       9: {
      10:     errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
      11: }

    方法parseConstant()现在要设置常量节点的类型。它调用TypeChecker.areComparisonCompatible()检查每个常量值是否与case表达式的类型达到比较兼容。参见清单10-18

       1: switch ((PascalTokenType) token.getType()) {
       2:  
       3:           case IDENTIFIER: {//未实现
       4:               constantNode = parseIdentifierConstant(token, sign);
       5:               if (constantNode != null) {
       6:                   constantType = constantNode.getTypeSpec();
       7:               }
       8:               break;
       9:           }
      10:  
      11:           case INTEGER: {
      12:               constantNode = parseIntegerConstant(token.getText(), sign);
      13:               constantType = Predefined.integerType;
      14:               break;
      15:           }
      16:  
      17:           case STRING: {
      18:               constantNode =
      19:                   parseCharacterConstant(token, (String) token.getValue(),
      20:                                          sign);
      21:               constantType = Predefined.charType;
      22:               break;
      23:           }
      24:  
      25:           default: {
      26:               errorHandler.flag(token, INVALID_CONSTANT, this);
      27:               break;
      28:           }
      29:       }
      30: if (!TypeChecker.areComparisonCompatible(exprType,
      31:               constantType)) {
      32:           errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
      33:       }
      34: constantNode.setTypeSpec(constantType);

    清单10-19 展示了parseIdentifierConstant()方法,现在它解析每一个是标识符的分支常量。

       1: /**
       2:  * 解析标识符常量,并判断类型是否与整数兼容
       3:  * @param token 常量token
       4:  * @param sign 符号token
       5:  * @return 常量节点
       6:  */
       7: private ICodeNode parseIdentifierConstant(Token token, TokenType sign)
       8:     throws Exception
       9: {
      10:     ICodeNode constantNode = null;
      11:     TypeSpec constantType = null;
      12:  
      13:     // 符号表堆栈中查找此常量标识符
      14:     String name = token.getText().toLowerCase();
      15:     SymTabEntry id = symTabStack.lookup(name);
      16:  
      17:     //如果未定义,直接结束
      18:     if (id == null) {
      19:         id = symTabStack.enterLocal(name);
      20:         id.setDefinition(DefinitionImpl.UNDEFINED);
      21:         id.setTypeSpec(Predefined.undefinedType);
      22:         errorHandler.flag(token, PascalErrorCode.IDENTIFIER_UNDEFINED, this);
      23:         return null;
      24:     }
      25:  
      26:     Definition defnCode = id.getDefinition();
      27:  
      28:     // 常量标识符是否符合定义
      29:     if ((defnCode == DefinitionImpl.CONSTANT) || (defnCode == DefinitionImpl.ENUMERATION_CONSTANT)) {
      30:         Object constantValue = id.getAttribute(SymTabKeyImpl.CONSTANT_VALUE);
      31:         constantType = id.getTypeSpec();
      32:  
      33:         // 与整数兼容才行
      34:         if ((sign != null) && !TypeChecker.isInteger(constantType)) {
      35:             errorHandler.flag(token, INVALID_CONSTANT, this);
      36:         }
      37:  
      38:         constantNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
      39:         constantNode.setAttribute(VALUE, constantValue);
      40:     }
      41:  
      42:     id.appendLineNumber(token.getLineNumber());
      43:  
      44:     if (constantNode != null) {
      45:         constantNode.setTypeSpec(constantType);
      46:     }
      47:  
      48:     return constantNode;
      49: }

    方法parseIdentifierConstant()验证标识符是一个常量或枚举常量,并检测在标识符钱是否有+或-,还有确保标识符必须是整数类型(或整数兼容)。

    CASE语句的另一个改变这次在解释器后端。清单10-20 展示了SelectExecutor中新版本的createJumpTable()方法。对于CASE语句中表达式值为字符的情况,此方法将每个CASE分支常量值在作为键放入跳转表之前,从一个字符串(它仅包含一个字符)转换成一个字符。(改动参见加粗部分

       1: /**
       2:  * 为某一个SELECT节点根据CASE情况创建静态查找表
       3:  * @param node SELECT节点
       4:  * @return 查找表
       5:  */
       6: private HashMap<Object, ICodeNode> createJumpTable(ICodeNode node)
       7: {
       8:     HashMap<Object, ICodeNode> jumpTable = new HashMap<Object, ICodeNode>();
       9:  
      10:     // 遍历分支,将常量和语句变成查找表的某一项
      11:     List<ICodeNode> selectChildren = node.getChildren();
      12:     for (int i = 1; i < selectChildren.size(); ++i) {
      13:         ICodeNode branchNode = selectChildren.get(i);
      14:         ICodeNode constantsNode = branchNode.getChildren().get(0);
      15:         ICodeNode statementNode = branchNode.getChildren().get(1);
      16:         //将如1,2,3: xx的三个常量变成三个查找项
      17:         List<ICodeNode> constantsList = constantsNode.getChildren();
      18:         for (ICodeNode constantNode : constantsList) {
      19:             Object value = constantNode.getAttribute(VALUE);
      20:             if (constantNode.getType() == ICodeNodeTypeImpl.STRING_CONSTANT) {
      21:                 value = ((String) value).charAt(0);
      22:             }
      23:             jumpTable.put(value, statementNode);
      24:         }
      25:     }
      26:     return jumpTable;
      27: }

    程序10:Pascal语法检查器III

    语法检查现在包含了类型检查,且解析器能为带下标和字段的变量生成分析树。清单10-21 是下面命令行的输出

    java -classpath classes Pascal compile -i block.txt

    这个命令用“编译”而不是解析执行,那是因为你还没有为变量编写执行器(executor)。你将在第12章完成这个功能。

    清单10-21 带类型检查的Pascal语法检查器的输出。(输出比较庞大,建议自己实验,这里输出一些样例

       1: 001 CONST
       2: 002     seven =  7;
       3: 003     ten   = 10;
       4: 004 
       5: //.......省略
       6: 053 BEGIN
       7: 054     var1[5] := 3.14;
       8: 055     var1[var7.i] := var9.rec.flda[e, ten]['q'].fldi;
       9: 056 
      10: 057     IF var9.a[seven-3] THEN var2[beta] := 'x';
      11: 058 
      12: //.......省略
      13: 080     var9.rec.flda[b][0, 'm'].flda[d] := 'p';
      14: 081 END.
      15:  
      16: ----------代码解析统计信息--------------
      17: 源文件共有    81行。
      18: 有    0个语法错误.
      19: 解析共耗费    0.07秒.
      20:  
      21: ===== 中间码XML展示 =====
      22:  
      23: *** PROGRAM dummyprogramname ***
      24:  
      25: <COMPOUND line="53">
      26:     <ASSIGN line="54" type_id="real">
      27:         <VARIABLE id="var1" level="1" type_id="real">
      28:             <SUBSCRIPTS type_id="real">
      29:                 <INTEGER_CONSTANT value="5" type_id="integer" />
      30:             </SUBSCRIPTS>
      31:         </VARIABLE>
      32:         <REAL_CONSTANT value="3.14" type_id="real" />
      33:     </ASSIGN>
      34:     //.......省略
      35:     <ASSIGN line="80" type_id="range2">
      36:         <VARIABLE id="var9" level="1" type_id="range2">
      37:             <FIELD id="rec" level="2" type_id="$anon_2adab05" />
      38:             <FIELD id="flda" level="3" type_id="$anon_51a0a458" />
      39:             <SUBSCRIPTS type_id="$anon_cf7e46b">
      40:                 <INTEGER_CONSTANT value="1" type_id="enum1" />
      41:             </SUBSCRIPTS>
      42:             <SUBSCRIPTS type_id="$anon_28da9e1">
      43:                 <INTEGER_CONSTANT value="0" type_id="integer" />
      44:                 <STRING_CONSTANT value="m" type_id="char" />
      45:             </SUBSCRIPTS>
      46:             <FIELD id="flda" level="2" type_id="$anon_480ec542" />
      47:             <SUBSCRIPTS type_id="range2">
      48:                 <INTEGER_CONSTANT value="3" type_id="enum1" />
      49:             </SUBSCRIPTS>
      50:         </VARIABLE>
      51:         <STRING_CONSTANT value="p" type_id="char" />
      52:     </ASSIGN>
      53: </COMPOUND>
      54:  
      55: ----------编译统计信--------------
      56: 共生成        0 条指令
      57: 代码生成共耗费    0.00秒

    上面分析树的输出现在包含了每个有类型说明的类型标识。清单10-22 展示了类ParseTreePrinter(在包util中)的新printTypeSpec()方法。

       1: /**
       2:     * 打印分析树节点的类型说明
       3:     * @param node 某一节点
       4:     */
       5:    private void printTypeSpec(ICodeNodeImpl node)
       6:    {
       7:          TypeSpec typeSpec = node.getTypeSpec();
       8:  
       9:          if (typeSpec != null) {
      10:              String saveMargin = indentation;
      11:              indentation += indent;
      12:  
      13:              String typeName;
      14:              SymTabEntry typeId = typeSpec.getIdentifier();
      15:  
      16:              //有名字
      17:              if (typeId != null) {
      18:                  typeName = typeId.getName();
      19:              }
      20:  
      21:              // 匿名
      22:              else {
      23:                  int code = typeSpec.hashCode() + typeSpec.getForm().hashCode();
      24:                  typeName = "$anon_" + Integer.toHexString(code);
      25:              }
      26:  
      27:              printAttribute("TYPE_ID", typeName);
      28:              indentation = saveMargin;
      29:          }
      30:    }

    清单10-23 展示了语法检查器中类型检查错误的输出。使用 java -classpath classes Pascal compile -i blockerrors.txt

       1: 001 CONST
       2: 002     Seven =  7;
       3: 003     Ten   = 10;
       4: 004 
       5: 005 TYPE
       6: 006     range1 = 0..ten;
       7: 007     range2 = 'a'..'q';
       8: 008     range3 = range1;
       9: 009 
      10: 010     enum1 = (a, b, c, d, e);
      11: 011     enum2 = enum1;
      12: 012 
      13: 013     range4 = b..d;
      14: 014 
      15: 015     arr1 = ARRAY [range1] OF real;
      16: 016     arr2 = ARRAY [(alpha, beta, gamma)] OF range2;
      17: 017     arr3 = ARRAY [enum2] OF arr1;
      18: 018     arr4 = ARRAY [range3] OF (foo, bar, baz);
      19: 019     arr5 = ARRAY [range1] OF ARRAY[range2] OF ARRAY[c..e] OF enum2;
      20: 020     arr6 = ARRAY [range1, range2, c..e] OF enum2;
      21: 021 
      22: 022     rec7 = RECORD
      23: 023                i : integer;
      24: 024                r : real;
      25: 025                b1, b2 : boolean;
      26: 026                c : char
      27: 027            END;
      28: 028 
      29: 029     arr8 = ARRAY [range2] OF RECORD
      30: 030                                  fldi  : integer;
      31: 031                                  fldr : rec7;
      32: 032                                  flda : ARRAY[range4] OF range2;
      33: 033                              END;
      34: 034 
      35: 035 VAR
      36: 036     var1 : arr1;  var5 : arr5;
      37: 037     var2 : arr2;  var6 : arr6;
      38: 038     var3 : arr3;  var7 : rec7;
      39: 039     var4 : arr4;  var8 : arr8;
      40: 040 
      41: 041     var9 : RECORD
      42: 042                b   : boolean;
      43: 043                rec : RECORD
      44: 044                          fld1 : arr1;
      45: 045                          fldb : boolean;
      46: 046                          fldr : real;
      47: 047                          fld6 : arr6;
      48: 048                          flda : ARRAY [enum1, range1] OF arr8;
      49: 049                      END;
      50: 050                a : ARRAY [1..5] OF boolean;
      51: 051            END;
      52: 052 
      53: 053 BEGIN
      54: 054     var2[a] := 3.14;
      55:              ^
      56: *** 不兼容的类型 [在 "a" 处]
      57:                    ^
      58: *** 不兼容的类型 [在 "3.14" 处]
      59: 055     var1[var7.i] := var9.rec.flda['e', ten]['q'].fldr;
      60:                                       ^
      61: *** 不兼容的类型 [在 "'e'" 处]
      62:                         ^
      63: *** 不兼容的类型 [在 "var9" 处]
      64: 056 
      65: 057     IF var9.rec.fldr THEN var2[beta] := seven;
      66:            ^
      67: *** 不兼容的类型 [在 "var9" 处]
      68:                                             ^
      69: *** 不兼容的类型 [在 "seven" 处]
      70: 058 
      71: 059     CASE var5[seven, 'm', d]  OF
      72: 060         foo:      var3[e] := 12;
      73:             ^
      74: *** 不兼容的类型 [在 "foo" 处]
      75:                                  ^
      76: *** 不兼容的类型 [在 "12" 处]
      77: 061         bar, baz: var3[b] := var1.rec.fldb;
      78:             ^
      79: *** 不兼容的类型 [在 "bar" 处]
      80:                  ^
      81: *** 不兼容的类型 [在 "baz" 处]
      82:                                       ^
      83: *** 非法域(field) [在 "rec" 处]
      84:                                           ^
      85: *** 非法域(field) [在 "fldb" 处]
      86: 062     END;
      87: 063 
      88: 064     REPEAT
      89: 065         var7[3] := a;
      90:                  ^
      91: *** 太多下标 [在 "3" 处]
      92:                        ^
      93: *** 不兼容的类型 [在 "a" 处]
      94: 066     UNTIL var6[3, 'a', c] + var5[4]['f', d];
      95:                                 ^
      96: *** 不兼容的类型 [在 "var5" 处]
      97:               ^
      98: *** 不兼容的类型 [在 "var6" 处]
      99: 067 
     100: 068     var9.rec.flda[b][0, 'm', foo].flda[d] := 'p';
     101:                                  ^
     102: *** 太多下标 [在 "foo" 处]
     103: 069 END.
     104:  
     105: ----------代码解析统计信息--------------
     106: 源文件共有    69行。
     107: 有    17个语法错误.
     108: 解析共耗费    0.07秒.

    因为实现了类型检查,我们不再用第6章描述的Hack#4技巧(也就是第六章是的前端是跳过了类型检查的,这里补上了)。

    接下来一张,你将会继续完善解析器,可以处理过程,函数,和复杂的Pascal程序。

  • 相关阅读:
    SVN自动更新测试服务器工作副本(C#写winform程序实现)
    两台服务器上SQL Server数据库数据互操作示例
    ASP.NET根据URL生成网页缩略图示例程序(C#语言)
    Flutter Widget API
    BPM、BPMN、BPMN2.0概念介绍
    H5多图上传调研
    lIUNX如何加载U盘,光盘
    如何解决exchange2003被中继的问题?(网上转载)
    window无法安装服务
    如何在WORD里面插入不同的页眉和页脚
  • 原文地址:https://www.cnblogs.com/lifesting/p/2858377.html
Copyright © 2011-2022 走看看