(基于Java)编写编译器和解释器-第10章:类型检查-第一部分

时间:2022-12-25 17:08:10

在本章,你将会包含类型检查,完成第9张工作。如今你可以把Pascal变量申明为各种类型,你必须确保当这些变量出现在语句中的时候,它们的类型必须与它们的操作符兼容。如第1章所说的那样,语法检查是语义分析的一部分。

==>> 本章中文版源代码下载:svn co http://wci.googlecode.com/svn/branches/ch10/ 源代码使用了UTF-8编码,下载到本地请修改!

方法和目标

本章的目标是在前端集成类型检查。方法是将类型检查加到语句解析器中,以便在解析语句过程中可以用到。针对第5章开发的语法检查器搞了个新的版本,用来验证你的成果。

类型检查

让我们从intermediate.typeimpl包中的TypeChecker类开始。这个类附带的静态方法实现了类型兼容规则,可以被语句解析器用来执行语法检查。清单10-1 展示了检查具体类型的各种方法。

   1: /**
   2:  * 检查类型是否一个整数
   3:  * @param type 被检查类型说明
   4:  * @return true/false
   5:  */
   6: public static boolean isInteger(TypeSpec type)
   7: {
   8:     return (type != null) && (type.baseType() == Predefined.integerType);
   9: }
  10:  
  11: /**
  12:  *  检查二元操作符的两个类型都是否是整数
  13:  * @param type1 第一个被检查的类型
  14:  * @param type2 第二个被检查的类型
  15:  * @return true/false
  16:  */
  17: public static boolean areBothInteger(TypeSpec type1, TypeSpec type2)
  18: {
  19:     return isInteger(type1) && isInteger(type2);
  20: }
  21:  
  22: /**
  23:  * 检查类型是否一个实数,即计算机里面的浮点数
  24:  * @param type 被检查类型
  25:  * @return true/false
  26:  */
  27: public static boolean isReal(TypeSpec type)
  28: {
  29:     return (type != null) && (type.baseType() == Predefined.realType);
  30: }
  31:  
  32: /**
  33:  * 检查类型是否是整数或者实数,也就是一个实数系内的数
  34:  * @param type 被检查类型
  35:  * @return true/false
  36:  */
  37: public static boolean isIntegerOrReal(TypeSpec type)
  38: {
  39:     return isInteger(type) || isReal(type);
  40: }
  41:  
  42: /**
  43:  * 检查两个类型中至少有一个是实数,这个即计算机里面的数系扩大。比如INT+DOUBLE=DOUBLE
  44:  * @param type1 第一个被检查类型
  45:  * @param type2 第二个被检查类型
  46:  * @return true/false
  47:  */
  48: public static boolean isAtLeastOneReal(TypeSpec type1, TypeSpec type2)
  49: {
  50:     return (isReal(type1) && isReal(type2)) ||
  51:            (isReal(type1) && isInteger(type2)) ||
  52:            (isInteger(type1) && isReal(type2));
  53: }
  54:  
  55: /**
  56:  * 检查类型是否布尔类型
  57:  * @param type 被检查类型
  58:  * @return true/false
  59:  */
  60: public static boolean isBoolean(TypeSpec type)
  61: {
  62:     return (type != null) && (type.baseType() == Predefined.booleanType);
  63: }
  64:  
  65: /**
  66:  * 检查参加布尔二元运算如&&,|| 的两个类型是否都是布尔数。
  67:  * @param type1 第一个被检查类型
  68:  * @param type2 第二个被检查类型
  69:  * @return true/false
  70:  */
  71: public static boolean areBothBoolean(TypeSpec type1, TypeSpec type2)
  72: {
  73:     return isBoolean(type1) && isBoolean(type2);
  74: }
  75:  
  76: /**
  77:  * 检查类型是否一个字符类型
  78:  * @param type 被检查类型
  79:  * @return true/false
  80:  */
  81: public static boolean isChar(TypeSpec type)
  82: {
  83:     return (type != null) && (type.baseType() == Predefined.charType);
  84: }

清单10-2 展示了TypeChecker类中的两个兼容方法

   1: /**
   2:  * 检查赋值两端的类型是否兼容,即左值是否能被右值兼容
   3:  * @param targetType 左值类型
   4:  * @param valueType 右值类型
   5:  * @return true可以赋值/false不能赋值
   6:  */
   7: public static boolean areAssignmentCompatible(TypeSpec targetType,
   8:                                               TypeSpec valueType)
   9: {
  10:     if ((targetType == null) || (valueType == null)) {
  11:         return false;
  12:     }
  13:  
  14:     targetType = targetType.baseType();
  15:     valueType  = valueType.baseType();
  16:  
  17:     boolean compatible = false;
  18:  
  19:     // 类型一样,ok
  20:     if (targetType == valueType) {
  21:         compatible = true;
  22:     }
  23:  
  24:     // 左实数,右整数,ok
  25:     else if (isReal(targetType) && isInteger(valueType)) {
  26:         compatible = true;
  27:     }
  28:  
  29:     // 字符串对字符串,赋值没问题
  30:     else {
  31:         compatible =
  32:             targetType.isPascalString() && valueType.isPascalString();
  33:     }
  34:  
  35:     return compatible;
  36: }
  37:  
  38: /**
  39:  * 检查两个类型是否能做到比较兼容,即a < b中的a/b可以兼容,如果a是INT,而b是String,这个就是不兼容的。
  40:  * @param type1 比较操作符左端的类型
  41:  * @param type2 比较操作符右端的类型
  42:  * @return true/false
  43:  */
  44: public static boolean areComparisonCompatible(TypeSpec type1,
  45:                                               TypeSpec type2)
  46: {
  47:     if ((type1 == null) || (type2 == null)) {
  48:         return false;
  49:     }
  50:  
  51:     type1 = type1.baseType();
  52:     type2 = type2.baseType();
  53:     TypeForm form = type1.getForm();
  54:  
  55:     boolean compatible = false;
  56:  
  57:     // 两个标量或者枚举类型
  58:     if ((type1 == type2) && ((form == SCALAR) || (form == ENUMERATION))) {
  59:         compatible = true;
  60:     }
  61:  
  62:     // 至少有一个是实数,另一个整数,没问题
  63:     else if (isAtLeastOneReal(type1, type2)) {
  64:         compatible = true;
  65:     }
  66:  
  67:     // 两个字符串也可以比较
  68:     else {
  69:         compatible = type1.isPascalString() && type2.isPascalString();
  70:     }
  71:  
  72:     return compatible;
  73: }

areAssignmentCompatible()方法用来检查赋值语句中的目标(左边)和值(右边)类型(通用的说法是a = b;,则a是左值,b是右值)。赋值语句只能在两个类型相等、都是字符串类型、还有一个是实数另一个是整数的情况下才被容许。

areComparisonCompatible()方法检查比较操作符两端的类型。比较只有在以下情况下被容许:

  • 它们的基类型(base type)相等且其格式为标量或枚举
  • 它们的基类型都是字符串类型
  • 它们的基类型中至少有一个为实数(另一个为整数)

表达式类型检查

在有了变量类型之后,包含变量的表达式同样有了类型。在解析表达式或子表达式时,把类型信息记在生成的分析树上。就如你在前面章节把类型信息放在SymTabEntry对象类似,我们把类型信息放在ICodeNode对象上。(为啥分两个地方存储?因为SymTabEntry上的类型信息是语法的,单元的,而ICodeNode上的类型信息是语义的,复合的。举个例子,如果定义了a=1;b=1; c=a+b ;在解析过程中a,b的类型信息会被放在对应的符号表项上,而a+b的类型肯定符号表中没有,这个只有放在a+b的二叉树上才可以

清单10-3 展示了 intermediate包中的接口ICodeNode新增的get和set方法。

   1: /**
   2:  * 设置此节点的类型说明
   3:  * @param typeSpec 类型
   4:  */
   5: public void setTypeSpec(TypeSpec typeSpec);
   6:  
   7: /**
   8:  * 返回此节点的类型说明
   9:  * @return 绑定的类型
  10:  */
  11: public TypeSpec getTypeSpec();

设计笔记

新方法getTypeSpec和setTypeSpec的实现使得解析器可在分析树上“雕刻”类型信息。正如你所见,这可以让语义分析过程带上语法检查。

这两个新方法在包intermediate.icodeimpl的类ICodeNodeImpl中实现的比较直接,必须得加一个新的域:

private TypeSpec typeSpec; //节点类型

此时语句解析子类ExpressionParser得有新版本的parseExpression(), parseSimpleExpression(), parseTerm(), andparseFactor()等方法,还有一个新增的parseIdentifier()。每个方法执行类型检查,并将类型信息放在生成的分析树上的根节点上。

清单10-4 展示了新版本的parseExpression()方法(请留意加粗部分为新增的类型检查)。

   1: /**
   2:     * @param token 初始token
   3:     * @return 表达式分析子树根节点
   4:     * @throws Exception if an error occurred.
   5:     */
   6:    private ICodeNode parseExpression(Token token)
   7:        throws Exception
   8:    {
   9:        //参见语法图5-2的语法规则,首先是一个simpleExpression,如果有关系
  10:        //操作符,还有一个用来比较的simpleExpression
  11:        ICodeNode rootNode = parseSimpleExpression(token);
  12:        //检测类型
  13:        TypeSpec resultType = rootNode != null ? rootNode.getTypeSpec()
  14:                : Predefined.undefinedType;
  15:        token = currentToken();
  16:        TokenType tokenType = token.getType();
  17:  
  18:        // 有比较
  19:        if (REL_OPS.contains(tokenType)) {
  20:  
  21:            // 如果有比较操作符,则比较符是根节点,两个simpleExpression分别作为左右子节点
  22:            //比如 a > b,那么分析树为  >
  23:            //                 / \
  24:            //                a   b
  25:            ICodeNodeType nodeType = REL_OPS_MAP.get(tokenType);
  26:            ICodeNode opNode = ICodeFactory.createICodeNode(nodeType);
  27:            opNode.addChild(rootNode);
  28:            //跳过比较符
  29:            token = nextToken();  
  30:            ICodeNode simExprNode = parseSimpleExpression(token);
  31:            opNode.addChild(simExprNode);
  32:            rootNode = opNode;
  33:            
  34:            TypeSpec simExprType = simExprNode != null
  35:                    ? simExprNode.getTypeSpec()
  36:                    : Predefined.undefinedType;
  37:            //加入类型检查,比较操作符的两端是否类型兼容         
  38:            if (TypeChecker.areComparisonCompatible(resultType, simExprType)) {
  39:                    resultType = Predefined.booleanType;
  40:            }else {
  41:                errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
  42:                resultType = Predefined.undefinedType;
  43:            }
  44:        }
  45:        if (rootNode != null) {
  46:            rootNode.setTypeSpec(resultType);
  47:        }
  48:        return rootNode;
  49:    }

方法parseExpression()调用parseSimpleExpression()并设置变量resultType为第一个简单表达式的TypeSpec对象。如果还有一个关系操作符和另一个简单表达式,方法会调用TypeChecker.areComparisonCompatible()验证两个操作数(即两个简单表达式的返回值)是否可以比较。如果一切OK,方法设置rootNode的类型说明为预定义的布尔类型(第39行),否则为未定义(第42行)类型。

清单10-5 展示了带类型检查的新版本parseSimpleExpression()方法(请留意加粗部分为新增的类型检查)。

   1: private ICodeNode parseSimpleExpression(Token token)
   2:        throws Exception
   3:    {
   4:        //参见语法图5-2 的语法规则
   5:        TokenType signType = null; 
   6:        Token signToken = null;
   7:        //探测前面可选的正/负 符号
   8:        TokenType tokenType = token.getType();
   9:        if ((tokenType == PLUS) || (tokenType == MINUS)) {
  10:            signType = tokenType;
  11:            signToken = token;
  12:            token = nextToken(); 
  13:        }
  14:        // 紧接着的一个term
  15:        ICodeNode rootNode = parseTerm(token);
  16:        TypeSpec resultType = rootNode != null ? rootNode.getTypeSpec() : Predefined.undefinedType;
  17:        //既然都有符号位+-了,那么后面的必须是数字才行
  18:        if (null != signType && !TypeChecker.isIntegerOrReal(resultType)){
  19:            errorHandler.flag(signToken, PascalErrorCode.INCOMPATIBLE_TYPES,this);
  20:        }
  21:        // 正号忽略掉,负号须创建一个表示反值运算的"NEGATE”节点
  22:        if (signType == MINUS) {
  23:            ICodeNode negateNode = ICodeFactory.createICodeNode(NEGATE);
  24:            negateNode.addChild(rootNode);
  25:            negateNode.setTypeSpec(rootNode.getTypeSpec()); //负号不改变类型
  26:            rootNode = negateNode;
  27:        }
  28:        
  29:        token = currentToken();
  30:        tokenType = token.getType();
  31:  
  32:        // 探测有没有加法类运算,如果有,加法类运算符后面还有另一个term,并以运算符作为根节点。
  33:        // 如果类似于EBNF语法 (OP TERM)+的多个,请留意它根节点的变化,是从右到左的。
  34:        while (ADD_OPS.contains(tokenType)) {
  35:            TokenType operator = tokenType;
  36:            ICodeNodeType nodeType = ADD_OPS_OPS_MAP.get(tokenType);
  37:            ICodeNode opNode = ICodeFactory.createICodeNode(nodeType);
  38:            opNode.addChild(rootNode);
  39:            //跳过运算符
  40:            token = nextToken(); 
  41:            // 另一个term
  42:            ICodeNode termNode = parseTerm(token);
  43:            opNode.addChild(termNode);
  44:            TypeSpec termType = null != termNode ? termNode.getTypeSpec() : Predefined.undefinedType;
  45:            rootNode = opNode;
  46:            switch ((PascalTokenType)operator){
  47:            //加减号的语法检查一样
  48:            case PLUS:
  49:            case MINUS:
  50:                //两个都是整数,结果还是整数
  51:                if (TypeChecker.areBothInteger(resultType, termType)){
  52:                    resultType = Predefined.integerType;
  53:                }else if (TypeChecker.isAtLeastOneReal(resultType, termType)){ //有一个实数,结果肯定是实数,数系扩充
  54:                    resultType = Predefined.realType;
  55:                }else{
  56:                    errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
  57:                }
  58:                break;
  59:            case OR: //OR是加法类操作符,我吐血啊
  60:                if(TypeChecker.areBothBoolean(resultType, termType)){
  61:                    resultType = Predefined.booleanType;
  62:                }else{
  63:                    errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
  64:                }
  65:                break;
  66:            }
  67:            rootNode.setTypeSpec(resultType);
  68:            
  69:            token = currentToken();
  70:            tokenType = token.getType();
  71:        }
  72:  
  73:        return rootNode;
  74:    }

如果表达式前面有一个+或-符号,parseSimpleExpression()调用TypeChecker.isIntegerOrReal()检查表达式的类型。对于switch语句中解析的二元的加或者减操作符,分别调用TypeChecker.areBothInteger()和TypeChecker.isAtLeastOneReal决定结果类型。一个TypeChecker.areBothBoolean()的调用检查OR操作符的两个操作符(是否都是布尔数)。此方法将结果类型说明存入表达式分析树的根节点上。

清单10-6 展示了新版本方法parseTerm()对应的switch语句。

   1: //判断结果类型
   2: switch ((PascalTokenType) operator) {
   3:  
   4:     case STAR: {
   5:         // 两个操作数都是整数
   6:         if (TypeChecker.areBothInteger(resultType, factorType)) {
   7:             resultType = Predefined.integerType;
   8:         }else if (TypeChecker.isAtLeastOneReal(resultType,
   9:                                               factorType)) {
  10:             resultType = Predefined.realType;
  11:         }else {
  12:             errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
  13:         }
  14:         break;
  15:     }
  16:  
  17:     case SLASH: {
  18:         // 两个整数,或者有个实数,结果为实数
  19:         if (TypeChecker.areBothInteger(resultType, factorType) ||
  20:             TypeChecker.isAtLeastOneReal(resultType, factorType))
  21:         {
  22:             resultType = Predefined.realType;
  23:         }else {
  24:             errorHandler.flag(token,  PascalErrorCode.INCOMPATIBLE_TYPES, this);
  25:         }
  26:         break;
  27:     }
  28:     case DIV:
  29:     case MOD: {
  30:         //两个为整数取模相除,结果为整数
  31:         if (TypeChecker.areBothInteger(resultType, factorType)) {
  32:             resultType = Predefined.integerType;
  33:         }else {
  34:             errorHandler.flag(token,  PascalErrorCode.INCOMPATIBLE_TYPES, this);
  35:         }
  36:         break;
  37:     }
  38:  
  39:     case AND: {
  40:         // 并操作符要求两边都是boolean,结果一定是boolean
  41:         if (TypeChecker.areBothBoolean(resultType, factorType)) {
  42:             resultType = Predefined.booleanType;
  43:         }else {
  44:             errorHandler.flag(token,  PascalErrorCode.INCOMPATIBLE_TYPES, this);
  45:         }
  46:         break;
  47:     }
  48: }

对于*(即乘法)操作符,分别调用TypeChecker.areBothInteger()或者TypeChecker.isAtLeastOneReal()断定结果类型是否是整数或者实数。类似针对 /(除法) 操作符的结果永远是实数。对于DIV和MOD操作符,调用TypeChecker.areBothInteger()验证两个操作数都是整数,则其结果一定是整数。最后,如果操作符是AND,TypeChecker.areBothBoolean()被调用,它的结果一定是布尔类型。

新版本的parseFactor方法在switch语句处也有一些改变,参见清单10-7。(请留意加粗部分为新增的类型检查)

   1: case IDENTIFIER: { //变量
   2:     return parseIdentifier(token);
   3: }
   4: case INTEGER: { //整数
   5:     rootNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
   6:     rootNode.setAttribute(VALUE, token.getValue());
   7:     token = nextToken(); 
   8:     rootNode.setTypeSpec(Predefined.integerType);
   9:     break;
  10: }
  11:  
  12: case STRING: {//字符串
  13:     String value = (String) token.getValue();
  14:     rootNode = ICodeFactory.createICodeNode(STRING_CONSTANT);
  15:     rootNode.setAttribute(VALUE, value);
  16:     //长度为1则是char,大于1是字符串
  17:     TypeSpec resultType = value.length() == 1
  18:             ? Predefined.charType
  19:             : TypeFactory.createStringType(value);
  20:     token = nextToken(); 
  21:     rootNode.setTypeSpec(resultType);
  22:     break;
  23: }
  24: case NOT: {//反值 !FACTOR
  25:     //跳过NOT
  26:     token = nextToken(); 
  27:     rootNode = ICodeFactory.createICodeNode(ICodeNodeTypeImpl.NOT);
  28:     //解析NOT后面的Factor
  29:     ICodeNode factorNode = parseFactor(token);
  30:     rootNode.addChild(factorNode);
  31:     TypeSpec factorType = factorNode != null
  32:             ? factorNode.getTypeSpec()
  33:             : Predefined.undefinedType;
  34:     //NOT的右边一定得是个布尔值
  35:     if (!TypeChecker.isBoolean(factorType)) {
  36:       errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
  37:     }
  38:     rootNode.setTypeSpec(Predefined.booleanType);
  39:     break;
  40: }

处理IDENTIFIER时调用了一个新的方法parseIdentifier()。对于INTEGER,设置INTEGER_CONSTANT节点的类型说明为预定义的整数类型。(REAL情形类似,没有展示)如果是STRING,必须得决定节点到底是一个字符类型还是一个字符串类型。处理NOT时调用TypeChecker.isBoolean()验证表达式的返回类型是一个布尔类型并设置NOT节点的类型说明为预定义的布尔类型。LEFT_PAREN(表达式左括号,没有显示)简单的用到了嵌套表达式的类型。

清单10-8 展示了新增的parseIdentifier方法

   1: private ICodeNode parseIdentifier(Token token)
   2:     throws Exception
   3: {
   4:     ICodeNode rootNode = null;
   5:  
   6:     //在栈中找这个标识符
   7:     String name = token.getText().toLowerCase();
   8:     SymTabEntry id = symTabStack.lookup(name);
   9:  
  10:     //未定义
  11:     if (id == null) {
  12:         errorHandler.flag(token, IDENTIFIER_UNDEFINED, this);
  13:         id = symTabStack.enterLocal(name);
  14:         id.setDefinition(DefinitionImpl.UNDEFINED);
  15:         id.setTypeSpec(Predefined.undefinedType);
  16:     }
  17:  
  18:     Definition defnCode = id.getDefinition();
  19:  
  20:     switch ((DefinitionImpl) defnCode) {
  21:  
  22:         case CONSTANT: {
  23:             Object value = id.getAttribute(SymTabKeyImpl.CONSTANT_VALUE);
  24:             TypeSpec type = id.getTypeSpec();
  25:  
  26:             if (value instanceof Integer) {
  27:                 rootNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
  28:                 rootNode.setAttribute(VALUE, value);
  29:             }
  30:             else if (value instanceof Float) {
  31:                 rootNode = ICodeFactory.createICodeNode(REAL_CONSTANT);
  32:                 rootNode.setAttribute(VALUE, value);
  33:             }
  34:             else if (value instanceof String) {
  35:                 rootNode = ICodeFactory.createICodeNode(STRING_CONSTANT);
  36:                 rootNode.setAttribute(VALUE, value);
  37:             }
  38:  
  39:             id.appendLineNumber(token.getLineNumber());
  40:             token = nextToken();  
  41:  
  42:             if (rootNode != null) {
  43:                 rootNode.setTypeSpec(type);
  44:             }
  45:  
  46:             break;
  47:         }
  48:         //枚举常量
  49:         case ENUMERATION_CONSTANT: {
  50:             Object value = id.getAttribute(SymTabKeyImpl.CONSTANT_VALUE);
  51:             TypeSpec type = id.getTypeSpec();
  52:  
  53:             rootNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
  54:             rootNode.setAttribute(VALUE, value);
  55:  
  56:             id.appendLineNumber(token.getLineNumber());
  57:             token = nextToken();  
  58:  
  59:             rootNode.setTypeSpec(type);
  60:             break;
  61:         }
  62:         //默认变量
  63:         default: {
  64:             VariableParser variableParser = new VariableParser(this);
  65:             rootNode = variableParser.parse(token, id);
  66:             break;
  67:         }
  68:     }
  69:  
  70:     return rootNode;
  71: }

parseIdentifier方法首先检验标识符是否在局部或者外层(即嵌套层级数字越小知道全局0)被定义。如果没有的话,将此标识符作为一个类型是未定义的未定义标识符存入局部符号表(这里是为了纠错,不要误会了,不影响后续的编译解析)。

如果标识符是一个常量,那么parseIdentifier()将会根据常量值的类型创建相应的INTEGER_CONSTANT、REAL_CONSTANT或STRING_CONSTANT节点,并设置节点的VALUE属性为常量值。此方法对待枚举常量标识符类似,唯一不同的是总是创建一个INTEGER_CONSTANT节点。

如果标识符既不是一个常量,也不是一个枚举常量,那么parseIdentifier将调用variableParser.parse()去按照变量模式解析。

变量(Variables)

(基于Java)编写编译器和解释器-第10章:类型检查-第一部分

图10-1 展示了一个Pascal变量的完整语法图,变量它可以是一个赋值语句的目标,也可以出现在表达式中。此图扩展了图5-1 的变量定义。一个变量可包含多个下标和字段(下标是a[10],而字段是a.b)。
清单10-9 展示了包frontend.pascal.parsers中的新语句解析子类VariableParser的parse()方法

   1: // 在下标[]或字段a.b处的同步集
   2: private static final EnumSet<PascalTokenType> SUBSCRIPT_FIELD_START_SET =
   3:     EnumSet.of(LEFT_BRACKET, DOT);
   4:  
   5:  
   6: public ICodeNode parse(Token token)
   7:     throws Exception
   8: {
   9:     // 在栈中查找此变量
  10:     String name = token.getText().toLowerCase();
  11:     SymTabEntry variableId = symTabStack.lookup(name);
  12:  
  13:     // 如果没有找到,报错,且补偿一个未定义
  14:     if (variableId == null) {
  15:         errorHandler.flag(token, IDENTIFIER_UNDEFINED, this);
  16:         variableId = symTabStack.enterLocal(name);
  17:         variableId.setDefinition(UNDEFINED);
  18:         variableId.setTypeSpec(Predefined.undefinedType);
  19:     }
  20:  
  21:     return parse(token, variableId);
  22: }
  23:  
  24: /**
  25:  * 解析一个变量
  26:  * @param token 当前token
  27:  * @param variableId 变量符号表项
  28:  * @return 分析生成树的根节点
  29:  * @throws Exception
  30:  */
  31: public ICodeNode parse(Token token, SymTabEntry variableId)
  32:     throws Exception
  33: {
  34:     // 检查变量是怎样被定义的,有三种可能
  35:     Definition defnCode = variableId.getDefinition();
  36:     if ((defnCode != VARIABLE) && (defnCode != VALUE_PARM) &&
  37:         (defnCode != VAR_PARM))
  38:     {
  39:         errorHandler.flag(token, INVALID_IDENTIFIER_USAGE, this);
  40:     }
  41:     variableId.appendLineNumber(token.getLineNumber());
  42:     //变量根节点
  43:     ICodeNode variableNode =
  44:         ICodeFactory.createICodeNode(ICodeNodeTypeImpl.VARIABLE);
  45:     variableNode.setAttribute(ID, variableId);
  46:  
  47:     token = nextToken();
  48:     //看是否是有下标或字段[或者.
  49:     TypeSpec variableType = variableId.getTypeSpec();
  50:     while (SUBSCRIPT_FIELD_START_SET.contains(token.getType())) {
  51:         ICodeNode subFldNode = token.getType() == LEFT_BRACKET
  52:                                       ? parseSubscripts(variableType)
  53:                                       : parseField(variableType);
  54:         token = currentToken();
  55:  
  56:         //更新变量节点的类型,比如a是个对象,那么a.b有可能就是一个整数
  57:         variableType = subFldNode.getTypeSpec();
  58:         variableNode.addChild(subFldNode);
  59:     }
  60:  
  61:     variableNode.setTypeSpec(variableType);
  62:     return variableNode;
  63: }

第一个parse()方法是父类StatementParser方法parse()的一个重写。它在符号表堆栈中搜索此标识符,如果找不到,则将这个标识符以未定义类型的未定义标识符存入局部符号表。接着调用第二个parse()方法,将token和标识符对应的符号表项作为参数传入。

第二个parse()方法在创建一个VARIABLE分析树节点之前检查此标识符是怎样定义的。while 循环的每趟迭代会根据当前token是一个左方括号[ 还是一个句号 . ,来调用parseSubscripts()或parseField()方法分别用来解析任意下标或记录字段。每趟迭代更新变量variableType为当前TypeSpec对象。最后一个variableType的值将会使最后一维数组元素或最后一个记录字段的TypeSpec对象(即类型)。其中的VARIABLE节点(43行)吸收这些方法产生的SUBSCRIPTS或FIELD作为子节点。(58行)

清单10-10 展示了parseSubscripts()方法

   1: // 下标结束符的同步集
   2:    private static final EnumSet<PascalTokenType> RIGHT_BRACKET_SET =
   3:        EnumSet.of(RIGHT_BRACKET, EQUALS, SEMICOLON);
   4:  
   5:    /**
   6:     * 解析一个逗号隔开的下标集,注意Pascal支持a[1,2]这种下标格式,类似C语言的a[1][2]
   7:     * @param variableType 数组变量类型
   8:     * @return 生成分析树的根节点
   9:     * @throws Exception
  10:     */
  11:    private ICodeNode parseSubscripts(TypeSpec variableType)
  12:        throws Exception
  13:    {
  14:        Token token;
  15:        ExpressionParser expressionParser = new ExpressionParser(this);
  16:        TypeSpec currentVarType = variableType;
  17:        //创建一个下标节点
  18:        ICodeNode subscriptsNode = ICodeFactory.createICodeNode(SUBSCRIPTS);
  19:  
  20:        do {
  21:            token = nextToken(); //吞掉第一个的[或后续的,
  22:  
  23:            //传入的变量格式一定是数组才行
  24:            if (currentVarType.getForm() == ARRAY) {
  25:  
  26:                // 解析下标表达式,这儿只有1个
  27:                ICodeNode exprNode = expressionParser.parse(token);
  28:                TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
  29:                                                     : Predefined.undefinedType;
  30:  
  31:                // 下标表达式必须与数组变量的索引类型可以赋值兼容
  32:                TypeSpec indexType =
  33:                    (TypeSpec) currentVarType.getAttribute(ARRAY_INDEX_TYPE);
  34:                if (!TypeChecker.areAssignmentCompatible(indexType, exprType)) {
  35:                    errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
  36:                }
  37:  
  38:                // 下标包含表达下标的表达式节点作为子几点,即arr[1+2],那么1+2将作为arr的子节点
  39:                subscriptsNode.addChild(exprNode);
  40:  
  41:                // 更新当前变量的类型,以便下标的下标,如[1,2]。
  42:                currentVarType =
  43:                    (TypeSpec) currentVarType.getAttribute(ARRAY_ELEMENT_TYPE);
  44:            }
  45:  
  46:            //不是一个数组,用下标就不对了
  47:            else {
  48:                errorHandler.flag(token, TOO_MANY_SUBSCRIPTS, this);
  49:                expressionParser.parse(token);
  50:            }
  51:  
  52:            token = currentToken();
  53:        } while (token.getType() == COMMA);
  54:  
  55:        // 同步在下标结束符]
  56:        token = synchronize(RIGHT_BRACKET_SET);
  57:        if (token.getType() == RIGHT_BRACKET) {
  58:            token = nextToken();  
  59:        }
  60:        else {
  61:            errorHandler.flag(token, MISSING_RIGHT_BRACKET, this);
  62:        }
  63:  
  64:        subscriptsNode.setTypeSpec(currentVarType);
  65:        return subscriptsNode;
  66:    }

parseSubscripts()解析方括号中由逗号隔开的一个或多个下标。初始的currentVarType是目前被解析的变量的TypeSpec对象(第16行)。此方法创建一个SUBSCRIPTS节点并验证当前类型是否是一个数组。它循环的调用expressionParser.parse()解析每个下标表达式并将其分析子树作加为子节点。方法继续调用TypeChecker.areAssignmentCompatible()判断每个下标表达式是否能与对应的索引类型达到赋值兼容,并且检查对数组类型使用了太多下标(就是本来数组只有1维,你却用了a[1][1],超出了维数)。它解析每个下标后更新currentVarType(第42行),并设置SUBSCRIPTS节点的类型说明为currentVarType的值。(第64行)。

清单10-11 展示了parseField()方法

   1: /**
   2:    * 解析一个记录字段,比如a.b,那么b就是record a里面的一个字段,这个解析每次只能解析一个字段,比如嵌套的<br>
   3:    * a.b.c,a是一个记录,a.b也是一个记录,那么会有两次调用。
   4:    * @param variableType 记录类型的变量
   5:    * @return 分析树的根节点
   6:    * @throws Exception
   7:    */
   8:   private ICodeNode parseField(TypeSpec variableType)
   9:       throws Exception
  10:   {
  11:       //创建一个Field节点
  12:       ICodeNode fieldNode = ICodeFactory.createICodeNode(FIELD);
  13:       TypeSpec currentVarType = variableType;
  14:       Token token = nextToken();  //a.b处的.被吞掉,到b处
  15:       TokenType tokenType = token.getType();
  16:       TypeForm variableForm = currentVarType.getForm();
  17:       //必须是作用在记录上的字段标识符才有意义
  18:       if ((tokenType == IDENTIFIER) && (variableForm == RECORD)) {
  19:           SymTab symTab = (SymTab) currentVarType.getAttribute(RECORD_SYMTAB);
  20:           String fieldName = token.getText().toLowerCase();
  21:           //在记录中的符号表中去查找
  22:           SymTabEntry fieldId = symTab.lookup(fieldName);
  23:  
  24:           if (fieldId != null) {
  25:               currentVarType = fieldId.getTypeSpec();
  26:               fieldId.appendLineNumber(token.getLineNumber());
  27:  
  28:               // 设置节点的ID属性为字段表项
  29:               fieldNode.setAttribute(ID, fieldId);
  30:           }
  31:           else {
  32:               errorHandler.flag(token, INVALID_FIELD, this);
  33:           }
  34:       }
  35:       else {
  36:           errorHandler.flag(token, INVALID_FIELD, this);
  37:       }
  38:  
  39:       token = nextToken();  //吞掉字段
  40:  
  41:       fieldNode.setTypeSpec(currentVarType);
  42:       return fieldNode;
  43:   }

方法parseField()解析单个记录字段(参见注释)。它同样有一个currentVarType参数,其初始值为当前被解析变量的TypeSpec对象值(即类型)。此方法创建一个FIELD节点,确保当前类型是一个记录并在记录类型的符号表中搜寻此字段标识符。如果找到此标识符,将字段标识符的名字存入FIELD节点上(第29行)。最后设置FIELD节点的类型说明为记录标识符的类型。

图10-2 展示了一个赋值语句且其目标标量带下标和字段的分析树。

(基于Java)编写编译器和解释器-第10章:类型检查-第一部分

标识符b是一个枚举常量,其值是1

标识符d是一个枚举常量,其值为3


>> 继续第10章