(基于Java)编写编译器和解释器-第10章:类型检查-第二部分
2013-01-13 14:04
633 查看
续 第一部分
清单10-2 展示了语句解析子类AssignmentStatementParser新版本的parse()方法。(留意加粗部分)
parse()方法调用variableParser.parse()解析目标类型,并调用TypeChecker.areAssignmentCompatible()检查目标变量的类型是否与表达式返回类型保持赋值兼容。它设置ASSIGN节点的类型为目标变量的类型(第35行)。
类class RepeatStatementParser中的新parse()方法同样加了类型检查。它调用s TypeChecker.isBoolean()确保标识是布尔类型。
清单10-13 RepeatStatementParser中parse()方法的类型检查(参见工程源代码 46行处)
WhileStatementParser类中的parse()方法做了类似的更新。它也调用了TypeChecker.isBoolean()确保表达式是布尔类型。参见下面的清单10-14(参见工程源代码53行处)
用类似的搞法,清单10-15、10-16、10-17分别展示ForStatementParser、IfStatementParse和CaseStatementParser中的新版本parse()方法。
清单10-15 类ForStatementParser中parse()方法的类型检查(为省空间,我只贴改变的部分,详细请参考工程源代码)
FOR语句的变量类型必须是整数,字符或枚举。parse()方法调用assignmentParser.parse(),赋值语句的parse()为控制变量和初始表达式执行类型检查。此方法调用TypeChecker.areAssignmentCompatible()验证结束表达式(Pascal表达式 FOR i = 0 to 5 DO expr; 中的红色部分为结束表达式,在TO|DOWNTO 和DO之间)。
清单10-16 类IfStatementParser方法parse()中的类型检查(工程源代码46行处)
CaseStatementParser中改进过的parse()方法必须保证case表达式类型一定是整形,字符或者枚举类型。
方法parseConstant()现在要设置常量节点的类型。它调用TypeChecker.areComparisonCompatible()检查每个常量值是否与case表达式的类型达到比较兼容。参见清单10-18
清单10-19 展示了parseIdentifierConstant()方法,现在它解析每一个是标识符的分支常量。
方法parseIdentifierConstant()验证标识符是一个常量或枚举常量,并检测在标识符钱是否有+或-,还有确保标识符必须是整数类型(或整数兼容)。
CASE语句的另一个改变这次在解释器后端。清单10-20 展示了SelectExecutor中新版本的createJumpTable()方法。对于CASE语句中表达式值为字符的情况,此方法将每个CASE分支常量值在作为键放入跳转表之前,从一个字符串(它仅包含一个字符)转换成一个字符。(改动参见加粗部分)
java -classpath classes Pascal compile -i block.txt
这个命令用“编译”而不是解析执行,那是因为你还没有为变量编写执行器(executor)。你将在第12章完成这个功能。
清单10-21 带类型检查的Pascal语法检查器的输出。(输出比较庞大,建议自己实验,这里输出一些样例)
上面分析树的输出现在包含了每个有类型说明的类型标识。清单10-22 展示了类ParseTreePrinter(在包util中)的新printTypeSpec()方法。
清单10-23 展示了语法检查器中类型检查错误的输出。使用 java -classpath classes Pascal compile -i blockerrors.txt
因为实现了类型检查,我们不再用第6章描述的Hack#4技巧(也就是第六章是的前端是跳过了类型检查的,这里补上了)。
接下来一张,你将会继续完善解析器,可以处理过程,函数,和复杂的Pascal程序。
控制语句中的类型检查
因为Pascal控制语句中有表达式,所以它们的解析器同样需要做类型检查。清单10-2 展示了语句解析子类AssignmentStatementParser新版本的parse()方法。(留意加粗部分)
/**
* 解析如 a = xx+yy; 之类的赋值语句
* 会有左值/右值两个子节点,并且节点类型与左值类型保持一致
* @param token
*第一个token,肯定是identifier了。
* @return 语句子树根节点
* @throws Exception
*/
public ICodeNode parse(Token token) throws Exception{
ICodeNode assignNode = ICodeFactory.createICodeNode(ASSIGN);
//交由变量解析左边的变量
VariableParser variableParser = new VariableParser(this);
ICodeNode targetNode = variableParser.parse(token);
TypeSpec targetType = targetNode != null ? targetNode.getTypeSpec()
: Predefined.undefinedType;
assignNode.addChild(targetNode);
//等号处同步
token = synchronize(COLON_EQUALS_SET);
// 找不到赋值:=就报错,找到就吞噬
if (token.getType() == COLON_EQUALS){
token = nextToken();
} else{
errorHandler.flag(token, MISSING_COLON_EQUALS, this);
}
// 解析赋值语句右边的表达式,将其子树作为赋值节点的第二个孩子
ExpressionParser expressionParser = new ExpressionParser(this);
ICodeNode exprNode = expressionParser.parse(token);
assignNode.addChild(exprNode);
//左值变量,右值表达式,是否能赋值兼容。
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
if (!TypeChecker.areAssignmentCompatible(targetType, exprType)){
errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
}
assignNode.setTypeSpec(targetType);
return assignNode;
}
parse()方法调用variableParser.parse()解析目标类型,并调用TypeChecker.areAssignmentCompatible()检查目标变量的类型是否与表达式返回类型保持赋值兼容。它设置ASSIGN节点的类型为目标变量的类型(第35行)。
类class RepeatStatementParser中的新parse()方法同样加了类型检查。它调用s TypeChecker.isBoolean()确保标识是布尔类型。
清单10-13 RepeatStatementParser中parse()方法的类型检查(参见工程源代码 46行处)
ExpressionParser expressionParser = new ExpressionParser(this);
ICodeNode exprNode = expressionParser.parse(token);
testNode.addChild(exprNode);
loopNode.addChild(testNode);//最后一个子节点
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
if (!TypeChecker.isBoolean(exprType)){
errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
}
WhileStatementParser类中的parse()方法做了类似的更新。它也调用了TypeChecker.isBoolean()确保表达式是布尔类型。参见下面的清单10-14(参见工程源代码53行处)
ExpressionParser expressionParser = new ExpressionParser(this);
ICodeNode exprNode = expressionParser.parse(token);
notNode.addChild(exprNode);
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
if (!TypeChecker.isBoolean(exprType)){
errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
}
用类似的搞法,清单10-15、10-16、10-17分别展示ForStatementParser、IfStatementParse和CaseStatementParser中的新版本parse()方法。
清单10-15 类ForStatementParser中parse()方法的类型检查(为省空间,我只贴改变的部分,详细请参考工程源代码)
//第77-84行
TypeSpec controlType = initAssignNode != null
? initAssignNode.getTypeSpec()
: Predefined.undefinedType;
//for语句的初始变量一定得是一个整数或者枚举
if (!TypeChecker.isInteger(controlType)
&& (controlType.getForm() != TypeFormImpl.ENUMERATION)){
errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
}
//第101行
relOpNode.setTypeSpec(Predefined.booleanType); //关系默认为布尔类型
//第109-114行
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
//控制变量能够与表达式类型赋值兼容,不然无法复制
if (!TypeChecker.areAssignmentCompatible(controlType, exprType)){
errorHandler.flag(token, PascalErrorCode.INCOMPATIBLE_TYPES, this);
}
//第131行
nextAssignNode.setTypeSpec(controlType);
//第135行
arithOpNode.setTypeSpec(Predefined.integerType);
//第140行
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行处)
ExpressionParser expressionParser = new ExpressionParser(this);
ICodeNode exprNode = expressionParser.parse(token);
ifNode.addChild(exprNode);
// Type check: The expression type must be boolean.
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
if (!TypeChecker.isBoolean(exprType)){
errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
}
CaseStatementParser中改进过的parse()方法必须保证case表达式类型一定是整形,字符或者枚举类型。
ExpressionParser expressionParser = new ExpressionParser(this);
ICodeNode exprNode = expressionParser.parse(token);
selectNode.addChild(exprNode);
TypeSpec exprType = exprNode != null ? exprNode.getTypeSpec()
: Predefined.undefinedType;
if (!TypeChecker.isInteger(exprType)
&& !TypeChecker.isChar(exprType)
&& (exprType.getForm() != TypeFormImpl.ENUMERATION))
{
errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
}
方法parseConstant()现在要设置常量节点的类型。它调用TypeChecker.areComparisonCompatible()检查每个常量值是否与case表达式的类型达到比较兼容。参见清单10-18
switch ((PascalTokenType) token.getType()){
case IDENTIFIER:{//未实现
constantNode = parseIdentifierConstant(token, sign);
if (constantNode != null){
constantType = constantNode.getTypeSpec();
}
break;
}
case INTEGER:{
constantNode = parseIntegerConstant(token.getText(), sign);
constantType = Predefined.integerType;
break;
}
case STRING:{
constantNode =
parseCharacterConstant(token, (String) token.getValue(),
sign);
constantType = Predefined.charType;
break;
}
default:{
errorHandler.flag(token, INVALID_CONSTANT, this);
break;
}
}
if (!TypeChecker.areComparisonCompatible(exprType,
constantType)){
errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
}
constantNode.setTypeSpec(constantType);
清单10-19 展示了parseIdentifierConstant()方法,现在它解析每一个是标识符的分支常量。
/**
* 解析标识符常量,并判断类型是否与整数兼容
* @param token 常量token
* @param sign 符号token
* @return 常量节点
*/
private ICodeNode parseIdentifierConstant(Token token, TokenType sign)
throws Exception
{
ICodeNode constantNode = null;
TypeSpec constantType = null;
// 符号表堆栈中查找此常量标识符
String name = token.getText().toLowerCase();
SymTabEntry id = symTabStack.lookup(name);
//如果未定义,直接结束
if (id == null){
id = symTabStack.enterLocal(name);
id.setDefinition(DefinitionImpl.UNDEFINED);
id.setTypeSpec(Predefined.undefinedType);
errorHandler.flag(token, PascalErrorCode.IDENTIFIER_UNDEFINED, this);
return null;
}
Definition defnCode = id.getDefinition();
// 常量标识符是否符合定义
if ((defnCode == DefinitionImpl.CONSTANT) || (defnCode == DefinitionImpl.ENUMERATION_CONSTANT)){
Object constantValue = id.getAttribute(SymTabKeyImpl.CONSTANT_VALUE);
constantType = id.getTypeSpec();
// 与整数兼容才行
if ((sign != null) && !TypeChecker.isInteger(constantType)){
errorHandler.flag(token, INVALID_CONSTANT, this);
}
constantNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
constantNode.setAttribute(VALUE, constantValue);
}
id.appendLineNumber(token.getLineNumber());
if (constantNode != null){
constantNode.setTypeSpec(constantType);
}
return constantNode;
}
方法parseIdentifierConstant()验证标识符是一个常量或枚举常量,并检测在标识符钱是否有+或-,还有确保标识符必须是整数类型(或整数兼容)。
CASE语句的另一个改变这次在解释器后端。清单10-20 展示了SelectExecutor中新版本的createJumpTable()方法。对于CASE语句中表达式值为字符的情况,此方法将每个CASE分支常量值在作为键放入跳转表之前,从一个字符串(它仅包含一个字符)转换成一个字符。(改动参见加粗部分)
/**
* 为某一个SELECT节点根据CASE情况创建静态查找表
* @param node SELECT节点
* @return 查找表
*/
private HashMap<Object, ICodeNode> createJumpTable(ICodeNode node)
{
HashMap<Object, ICodeNode> jumpTable = new HashMap<Object, ICodeNode>();
// 遍历分支,将常量和语句变成查找表的某一项
List<ICodeNode> selectChildren = node.getChildren();
for (int i = 1; i < selectChildren.size(); ++i){
ICodeNode branchNode = selectChildren.get(i);
ICodeNode constantsNode = branchNode.getChildren().get(0);
ICodeNode statementNode = branchNode.getChildren().get(1);
//将如1,2,3: xx的三个常量变成三个查找项
List<ICodeNode> constantsList = constantsNode.getChildren();
for (ICodeNode constantNode : constantsList){
Object value = constantNode.getAttribute(VALUE);
if (constantNode.getType() == ICodeNodeTypeImpl.STRING_CONSTANT){
value = ((String) value).charAt(0);
}
jumpTable.put(value, statementNode);
}
}
return jumpTable;
}
程序10:Pascal语法检查器III
语法检查现在包含了类型检查,且解析器能为带下标和字段的变量生成分析树。清单10-21 是下面命令行的输出java -classpath classes Pascal compile -i block.txt
这个命令用“编译”而不是解析执行,那是因为你还没有为变量编写执行器(executor)。你将在第12章完成这个功能。
清单10-21 带类型检查的Pascal语法检查器的输出。(输出比较庞大,建议自己实验,这里输出一些样例)
001 CONST
002 seven =7;
003 ten = 10;
004
//.......省略
053 BEGIN
054 var1[5] := 3.14;
055 var1[var7.i] := var9.rec.flda[e, ten]['q'].fldi;
056
057 IF var9.a[seven-3] THEN var2[beta] := 'x';
058
//.......省略
080 var9.rec.flda[b][0, 'm'].flda[d] := 'p';
081 END.
----------代码解析统计信息--------------
源文件共有81行。
有0个语法错误.
解析共耗费0.07秒.
===== 中间码XML展示 =====
*** PROGRAM dummyprogramname ***
<COMPOUND line="53">
<ASSIGN line="54" type_id="real">
<VARIABLE id="var1" level="1" type_id="real">
<SUBSCRIPTS type_id="real">
<INTEGER_CONSTANT value="5" type_id="integer" />
</SUBSCRIPTS>
</VARIABLE>
<REAL_CONSTANT value="3.14" type_id="real" />
</ASSIGN>
//.......省略
<ASSIGN line="80" type_id="range2">
<VARIABLE id="var9" level="1" type_id="range2">
<FIELD id="rec" level="2" type_id="$anon_2adab05" />
<FIELD id="flda" level="3" type_id="$anon_51a0a458" />
<SUBSCRIPTS type_id="$anon_cf7e46b">
<INTEGER_CONSTANT value="1" type_id="enum1" />
</SUBSCRIPTS>
<SUBSCRIPTS type_id="$anon_28da9e1">
<INTEGER_CONSTANT value="0" type_id="integer" />
<STRING_CONSTANT value="m" type_id="char" />
</SUBSCRIPTS>
<FIELD id="flda" level="2" type_id="$anon_480ec542" />
<SUBSCRIPTS type_id="range2">
<INTEGER_CONSTANT value="3" type_id="enum1" />
</SUBSCRIPTS>
</VARIABLE>
<STRING_CONSTANT value="p" type_id="char" />
</ASSIGN>
</COMPOUND>
----------编译统计信--------------
共生成0 条指令
代码生成共耗费0.00秒
上面分析树的输出现在包含了每个有类型说明的类型标识。清单10-22 展示了类ParseTreePrinter(在包util中)的新printTypeSpec()方法。
/**
* 打印分析树节点的类型说明
* @param node 某一节点
*/
private void printTypeSpec(ICodeNodeImpl node)
{
TypeSpec typeSpec = node.getTypeSpec();
if (typeSpec != null){
String saveMargin = indentation;
indentation += indent;
String typeName;
SymTabEntry typeId = typeSpec.getIdentifier();
//有名字
if (typeId != null){
typeName = typeId.getName();
}
// 匿名
else{
int code = typeSpec.hashCode() + typeSpec.getForm().hashCode();
typeName = "$anon_" + Integer.toHexString(code);
}
printAttribute("TYPE_ID", typeName);
indentation = saveMargin;
}
}
清单10-23 展示了语法检查器中类型检查错误的输出。使用 java -classpath classes Pascal compile -i blockerrors.txt
001 CONST
002 Seven =7;
003 Ten = 10;
004
005 TYPE
006 range1 = 0..ten;
007 range2 = 'a'..'q';
008 range3 = range1;
009
010 enum1 = (a, b, c, d, e);
011 enum2 = enum1;
012
013 range4 = b..d;
014
015 arr1 = ARRAY [range1] OF real;
016 arr2 = ARRAY [(alpha, beta, gamma)] OF range2;
017 arr3 = ARRAY [enum2] OF arr1;
018 arr4 = ARRAY [range3] OF (foo, bar, baz);
019 arr5 = ARRAY [range1] OF ARRAY[range2] OF ARRAY[c..e] OF enum2;
020 arr6 = ARRAY [range1, range2, c..e] OF enum2;
021
022 rec7 = RECORD
023i : integer;
024r : real;
025b1, b2 : boolean;
026c : char
027END;
028
029 arr8 = ARRAY [range2] OF RECORD
030fldi: integer;
031fldr : rec7;
032flda : ARRAY[range4] OF range2;
033END;
034
035 VAR
036 var1 : arr1;var5 : arr5;
037 var2 : arr2;var6 : arr6;
038 var3 : arr3;var7 : rec7;
039 var4 : arr4;var8 : arr8;
040
041 var9 : RECORD
042b : boolean;
043rec : RECORD
044fld1 : arr1;
045fldb : boolean;
046fldr : real;
047fld6 : arr6;
048flda : ARRAY [enum1, range1] OF arr8;
049END;
050a : ARRAY [1..5] OF boolean;
051END;
052
053 BEGIN
054 var2[a] := 3.14;
^
*** 不兼容的类型 [在 "a" 处]
^
*** 不兼容的类型 [在 "3.14" 处]
055 var1[var7.i] := var9.rec.flda['e', ten]['q'].fldr;
^
*** 不兼容的类型 [在 "'e'" 处]
^
*** 不兼容的类型 [在 "var9" 处]
056
057 IF var9.rec.fldr THEN var2[beta] := seven;
^
*** 不兼容的类型 [在 "var9" 处]
^
*** 不兼容的类型 [在 "seven" 处]
058
059 CASE var5[seven, 'm', d]OF
060 foo:var3[e] := 12;
^
*** 不兼容的类型 [在 "foo" 处]
^
*** 不兼容的类型 [在 "12" 处]
061 bar, baz: var3[b] := var1.rec.fldb;
^
*** 不兼容的类型 [在 "bar" 处]
^
*** 不兼容的类型 [在 "baz" 处]
^
*** 非法域(field) [在 "rec" 处]
^
*** 非法域(field) [在 "fldb" 处]
062 END;
063
064 REPEAT
065 var7[3] := a;
^
*** 太多下标 [在 "3" 处]
^
*** 不兼容的类型 [在 "a" 处]
066 UNTIL var6[3, 'a', c] + var5[4]['f', d];
^
*** 不兼容的类型 [在 "var5" 处]
^
*** 不兼容的类型 [在 "var6" 处]
067
068 var9.rec.flda[b][0, 'm', foo].flda[d] := 'p';
^
*** 太多下标 [在 "foo" 处]
069 END.
----------代码解析统计信息--------------
源文件共有69行。
有17个语法错误.
解析共耗费0.07秒.
因为实现了类型检查,我们不再用第6章描述的Hack#4技巧(也就是第六章是的前端是跳过了类型检查的,这里补上了)。
接下来一张,你将会继续完善解析器,可以处理过程,函数,和复杂的Pascal程序。
相关文章推荐
- (基于Java)编写编译器和解释器-第10章:类型检查-第一部分
- (基于Java)编写编译器和解释器-第7章:解析(Parsing)控制语句-第二部分(连载)
- (基于Java)编写编译器和解释器-第9章:解析声明-第二部分(连载)
- (基于Java)编写编译器和解释器-第3章:扫描-第二部分(连载)
- (基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第二部分
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第二部分(连载)
- (基于Java)编写编译器和解释器-第5章:解析表达式和赋值语句-第二部分(连载)
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第二部分(连载)
- (基于Java)编写编译器和解释器-第11章:解析程序、过程和函数-第一部分
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第三部分(连载)
- (基于Java)编写编译器和解释器-第9章:解析声明-第三部分(连载)
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第三部分(连载)
- (基于Java)编写编译器和解释器-第4章:符号表(连载)
- (基于Java)编写编译器和解释器-第1章:介绍(连载)
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第一部分(连载)
- (基于Java)编写编译器和解释器-第5章:解析表达式和赋值语句-第一部分(连载)
- (基于Java)编写编译器和解释器-第1章:介绍(连载)
- (基于Java)编写编译器和解释器-第2章:框架I:编译器和解释器-第一部分(连载)
- (基于Java)编写编译器和解释器-简介(连载)
- (基于Java)编写编译器和解释器-第6章:解释执行表达式和赋值语句(连载)