续 第一部分
控制语句中的类型检查
因为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程序。