续 第一部分
(因为BlockParser的parse()方法里面只改了一行,即在第41行增加了一个routineId参数,所以代码我就不贴了,自己看吧)
新版本的parse方法多传了一个参数,即父程式名字对应的符号比表项,此参数接着被传给了declarationsParser.parse()。
解析过程和函数调用
图11-6 展示了作为语句的过程调用以及作为因子的函数调用对应的语法图,这些图扩展了图5-2 中关于因子的部分,以及图7-1中语句的部分。
(可以看到,多了一个procedure call语句,在右边的factor(因子)部分,多了一个function all)
左边的图11-7展示了语句解析子类的UML类图,它们被用来解析针申明或标准(内置)过程和函数的调用。
语句解析子类CallParser有两个子类, CallDeclaredParser 和 CallStandardParser。两个都用到了parseActualParameters()方法。
针对申明过的程式调用和Pascal标准程式的调用,你需要不同的类处理。标准程式某些情况下意味着调用不标准(因为标准是约定的,而申明是解析的,解析的你很定知道程式的每一个细节,而约定的不是,规定了什么就什么,这个规定就是不标准)。比如,过程read有一个可变实参表(是否想起了C的 ... ?)。过程writer和writeln的每个参数后面可以有一个可选并用冒号隔开的域宽数和精度值。函数诸如abs的参数可为整数或实数,其返回值与参数类型一致。基于这些原因,类CallStandardParser针对这些调用有特别的方法来处理。
清单11-10 展示了包intermediate.symtabimpl中的类Predefined附加的东西,即把Pascal标准过程和函数的名称放入全局符号表中。
1: //预定义过程和函数
2: public static SymTabEntry readId;
3: public static SymTabEntry readlnId;
4: public static SymTabEntry writeId;
5: public static SymTabEntry writelnId;
6: public static SymTabEntry absId;
7: public static SymTabEntry arctanId;
8: public static SymTabEntry chrId;
9: public static SymTabEntry cosId;
10: public static SymTabEntry eofId;
11: public static SymTabEntry eolnId;
12: public static SymTabEntry expId;
13: public static SymTabEntry lnId;
14: public static SymTabEntry oddId;
15: public static SymTabEntry ordId;
16: public static SymTabEntry predId;
17: public static SymTabEntry roundId;
18: public static SymTabEntry sinId;
19: public static SymTabEntry sqrId;
20: public static SymTabEntry sqrtId;
21: public static SymTabEntry succId;
22: public static SymTabEntry truncId;
23:
24: /**
25: * 初始化预定义到符号表堆栈
26: * @param symTab 堆栈
27: */
28: public static void initialize(SymTabStack symTabStack)
29: {
30: initializeTypes(symTabStack);
31: initializeConstants(symTabStack);
32: initializeStandardRoutines(symTabStack);
33: }
34: //初始化标准程式到全局符号表中
35: private static void initializeStandardRoutines(SymTabStack symTabStack) {
36: readId = enterStandard(symTabStack, PROCEDURE, "read", READ);
37: readlnId = enterStandard(symTabStack, PROCEDURE, "readln", READLN);
38: writeId = enterStandard(symTabStack, PROCEDURE, "write", WRITE);
39: writelnId = enterStandard(symTabStack, PROCEDURE, "writeln", WRITELN);
40:
41: absId = enterStandard(symTabStack, FUNCTION, "abs", ABS);
42: arctanId = enterStandard(symTabStack, FUNCTION, "arctan", ARCTAN);
43: chrId = enterStandard(symTabStack, FUNCTION, "chr", CHR);
44: cosId = enterStandard(symTabStack, FUNCTION, "cos", COS);
45: eofId = enterStandard(symTabStack, FUNCTION, "eof", EOF);
46: eolnId = enterStandard(symTabStack, FUNCTION, "eoln", EOLN);
47: expId = enterStandard(symTabStack, FUNCTION, "exp", EXP);
48: lnId = enterStandard(symTabStack, FUNCTION, "ln", LN);
49: oddId = enterStandard(symTabStack, FUNCTION, "odd", ODD);
50: ordId = enterStandard(symTabStack, FUNCTION, "ord", ORD);
51: predId = enterStandard(symTabStack, FUNCTION, "pred", PRED);
52: roundId = enterStandard(symTabStack, FUNCTION, "round", ROUND);
53: sinId = enterStandard(symTabStack, FUNCTION, "sin", SIN);
54: sqrId = enterStandard(symTabStack, FUNCTION, "sqr", SQR);
55: sqrtId = enterStandard(symTabStack, FUNCTION, "sqrt", SQRT);
56: succId = enterStandard(symTabStack, FUNCTION, "succ", SUCC);
57: truncId = enterStandard(symTabStack, FUNCTION, "trunc", TRUNC);
58: }
59: //录入标准程式
60: private static SymTabEntry enterStandard(SymTabStack symTabStack,
61: Definition defn, String name,
62: RoutineCode routineCode)
63: {
64: SymTabEntry procId = symTabStack.enterLocal(name);
65: procId.setDefinition(defn);
66: procId.setAttribute(SymTabKeyImpl.ROUTINE_CODE, routineCode);
67: return procId;
68: }
清单11-11 展示了语句解析子类CallParser的parse方法
1: public ICodeNode parse(Token token)
2: throws Exception
3: {
4: //视情况调用申明解析或标准解析
5: SymTabEntry pfId = symTabStack.lookup(token.getText().toLowerCase());
6: RoutineCode routineCode = (RoutineCode) pfId.getAttribute(ROUTINE_CODE);
7: StatementParser callParser = (routineCode == DECLARED) ||
8: (routineCode == FORWARD)
9: ? new CallDeclaredParser(this)
10: : new CallStandardParser(this);
11:
12: return callParser.parse(token);
13: }
parse()方法在符号表中搜寻被调用的程式名称,看是否是申明的、前向的还是预定义的,对于申明与前向调用CallDeclaredParser解析;对于预定义的,调用CallStandardParser解析。
调用申明(过)过程和函数
清单11-12 展示了类CallDeclaredParser的parse方法
1: public ICodeNode parse(Token token)
2: throws Exception
3: {
4: // 创建CALL节点
5: ICodeNode callNode = ICodeFactory.createICodeNode(CALL);
6: SymTabEntry pfId = symTabStack.lookup(token.getText().toLowerCase());
7: callNode.setAttribute(ID, pfId);
8: callNode.setTypeSpec(pfId.getTypeSpec());
9:
10: token = nextToken(); //跳过程式名称
11:
12: //解析参数
13: ICodeNode parmsNode = parseActualParameters(token, pfId,
14: true, false, false);
15: //参数作为子节点
16: callNode.addChild(parmsNode);
17: return callNode;
18: }
parse()调用parseActualParameters()来解析实参并返回一个PARAMETERS节点,如果没有参数值为null。CALL节点收纳PARAMETERS为子节点。
举个例子,假设你有如下的Pascal申明
TYPE
arr = ARRAY [1..5] OF real;
VAR
i, m : integer;
a : arr;
v, y : real;
t : boolean;
PROCEDURE proc(j, k : integer; VAR x, y, z : real; VAR v : arr;
VAR p : boolean; ch : char);
BEGIN
...
END;
那么过程调用proc(i, -7 + m, a[m], v, y, a, t, 'r'); 将会生成如清单11-13所示的分析树
<CALL id="proc" level="1" line="81">
<PARAMETERS>
<VARIABLE id="i" level="1" type_id="integer" />
<ADD type_id="integer">
<NEGATE>
<INTEGER_CONSTANT value="7" type_id="integer" />
</NEGATE>
<VARIABLE id="m" level="1" type_id="integer" />
</ADD>
<VARIABLE id="a" level="1" type_id="real">
<SUBSCRIPTS type_id="real">
<VARIABLE id="m" level="1" type_id="integer" />
</SUBSCRIPTS>
</VARIABLE>
<VARIABLE id="v" level="1" type_id="real" />
<VARIABLE id="y" level="1" type_id="real" />
<VARIABLE id="a" level="1" type_id="arr" />
<VARIABLE id="t" level="1" type_id="boolean" />
<STRING_CONSTANT value="r" type_id="char" />
</PARAMETERS>
</CALL>
调用标准过程和函数
清单11-14 展示了类CallStandardParser的parse()方法。它调用适当的私有方法解析每一个标准过程或函数的调用。
1: public ICodeNode parse(Token token)
2: throws Exception
3: {
4: ICodeNode callNode = ICodeFactory.createICodeNode(CALL);
5: SymTabEntry pfId = symTabStack.lookup(token.getText().toLowerCase());
6: RoutineCode routineCode = (RoutineCode) pfId.getAttribute(ROUTINE_CODE);
7: callNode.setAttribute(ID, pfId);
8:
9: token = nextToken(); // 跳过程式名称
10: //不同的程式,不同的解析
11: switch ((RoutineCodeImpl) routineCode) {
12: case READ:
13: case READLN: return parseReadReadln(token, callNode, pfId);
14:
15: case WRITE:
16: case WRITELN: return parseWriteWriteln(token, callNode, pfId);
17:
18: case EOF:
19: case EOLN: return parseEofEoln(token, callNode, pfId);
20:
21: case ABS:
22: case SQR: return parseAbsSqr(token, callNode, pfId);
23:
24: case ARCTAN:
25: case COS:
26: case EXP:
27: case LN:
28: case SIN:
29: case SQRT: return parseArctanCosExpLnSinSqrt(token, callNode,
30: pfId);
31:
32: case PRED:
33: case SUCC: return parsePredSucc(token, callNode, pfId);
34:
35: case CHR: return parseChr(token, callNode, pfId);
36: case ODD: return parseOdd(token, callNode, pfId);
37: case ORD: return parseOrd(token, callNode, pfId);
38:
39: case ROUND:
40: case TRUNC: return parseRoundTrunc(token, callNode, pfId);
41:
42: default: new UnsupportedOperationException("No such routine "+token.getText());
43: }
44: return null;
45: }
清单11-15 展示了CallStandardParser的各种私有方法(细节请参看源代码,这里不贴了)
调用标准过程如read、readln、write、writeln可有任意个实参;每个对read和write的调用必须至少有一个参数。标准函数eof和eoln调用没有实参,而调用其它标准函数必须有且仅有一个实参。
在每一个解析方法里(即parseXXX),都会调用parseActualParameters()解析实参并返回一个PARAMETERS节点,没有参数返回null。CALL节点将PARAMETERS加为子节点。方法checkParmCount()用来验证调用中的实参数目是否正确。
函数eof和eoln返回布尔值。函数abs和sqr接受整数或实数参数并返回同类型的值。函数arctan、cos、exp、ln、sin和sqrt每个都接受一个整数或实数参数且它的返回值类型一定是实数。函数pred和succ可接受整数或枚举常量参数,其返回值与参数同类型。函数chr接受整数参数返回字符。函数odd接受整数参数返回布尔类型。函数ord有一个字符或枚举常量参数,返回整数值。函数round和trunc接受实数参数并返回整数值。
实参列表
如果被调用的申明/标准程式有实参,则用类CallParser的parseActualParameters()解析。布尔参数isDeclared, isReadReadln, isWriteWriteln用来分别说明调用的是否是一个申明程式,或者是标准过程read/readln,异或是标准过程write/writeln。这三个布尔值异或,也就是一个调用只可能有一个为真。参见清单11-16
1: protected ICodeNode parseActualParameters(Token token, SymTabEntry pfId,
2: boolean isDeclared,
3: boolean isReadReadln,
4: boolean isWriteWriteln)
5: throws Exception
6: {
7: ExpressionParser expressionParser = new ExpressionParser(this);
8: ICodeNode parmsNode = ICodeFactory.createICodeNode(PARAMETERS);
9: ArrayList<SymTabEntry> formalParms = null;
10: int parmCount = 0;
11: int parmIndex = -1;
12: //如果是申明程式,很容易取得参数个数
13: if (isDeclared) {
14: formalParms =
15: (ArrayList<SymTabEntry>) pfId.getAttribute(ROUTINE_PARMS);
16: parmCount = formalParms != null ? formalParms.size() : 0;
17: }
18:
19: if (token.getType() != LEFT_PAREN) {
20: if (parmCount != 0) {
21: errorHandler.flag(token, WRONG_NUMBER_OF_PARMS, this);
22: }
23:
24: return null;
25: }
26:
27: token = nextToken(); // 跳过左口号XX(y,z)
28:
29: // 遍历每一个实参
30: while (token.getType() != RIGHT_PAREN) {
31: ICodeNode actualNode = expressionParser.parse(token);
32:
33: //如果是申明程式,需要检查形参和实参的匹配
34: if (isDeclared) {
35: if (++parmIndex < parmCount) {
36: SymTabEntry formalId = formalParms.get(parmIndex);
37: checkActualParameter(token, formalId, actualNode);
38: }
39: else if (parmIndex == parmCount) {
40: errorHandler.flag(token, WRONG_NUMBER_OF_PARMS, this);
41: }
42: }
43:
44: // 对于read/readln来说,实参必须为标量/布尔还有整数子界
45: else if (isReadReadln) {
46: TypeSpec type = actualNode.getTypeSpec();
47: TypeForm form = type.getForm();
48:
49: if (! ( (actualNode.getType() == ICodeNodeTypeImpl.VARIABLE)
50: && ( (form == SCALAR) ||
51: (type == Predefined.booleanType) ||
52: ( (form == SUBRANGE) &&
53: (type.baseType() == Predefined.integerType) ) )
54: )
55: )
56: {
57: errorHandler.flag(token, INVALID_VAR_PARM, this);
58: }
59: }
60:
61: // 对于write/writeln,除了参数要求外,还有可选的宽度和精度
62: else if (isWriteWriteln) {
63:
64: // Create a WRITE_PARM node which adopts the expression node.
65: ICodeNode exprNode = actualNode;
66: actualNode = ICodeFactory.createICodeNode(WRITE_PARM);
67: actualNode.addChild(exprNode);
68:
69: TypeSpec type = exprNode.getTypeSpec().baseType();
70: TypeForm form = type.getForm();
71:
72: if (! ( (form == SCALAR) || (type == Predefined.booleanType) ||
73: (type.isPascalString())
74: )
75: )
76: {
77: errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
78: }
79:
80: //可选的宽度
81: token = currentToken();
82: actualNode.addChild(parseWriteSpec(token));
83:
84: //可选的精度
85: token = currentToken();
86: actualNode.addChild(parseWriteSpec(token));
87: }
88:
89: parmsNode.addChild(actualNode);
90: token = synchronize(COMMA_SET);
91: TokenType tokenType = token.getType();
92:
93: // 是否分割实参的逗号?
94: if (tokenType == COMMA) {
95: token = nextToken();
96: }
97: else if (ExpressionParser.EXPR_START_SET.contains(tokenType)) {
98: errorHandler.flag(token, MISSING_COMMA, this);
99: }
100: else if (tokenType != RIGHT_PAREN) {
101: token = synchronize(ExpressionParser.EXPR_START_SET);
102: }
103: }
104:
105: token = nextToken(); // 跳过右括号
106:
107: if ((parmsNode.getChildren().size() == 0) ||
108: (isDeclared && (parmIndex != parmCount-1)))
109: {
110: errorHandler.flag(token, WRONG_NUMBER_OF_PARMS, this);
111: }
112:
113: return parmsNode;
114: }
方法parseActualParameters()创建并返回一个PARAMETERS节点,在没有参数的情况下返回null。它遍历解析每个实参,然后PARAMETERS将每个实参的分析树收纳为子节点。
如果调用的是申明程式,方法将调用checkActualParameter()检查每个实参-形参的匹配,个数是否相等。对于标准过程read/readln的调用,此方法检查它的每个实参是否是一个类型为标量/布尔/整数子界的变量。
调用标准过程write/writeln有些特殊。每个实参必须是一个类型为标量/布尔/Pascal字符串的表达式。每个参数可跟着一个用冒号,然后是一个表示输出字符宽度的整数常量。宽度之后还可有第二个冒号,紧接着是一个表示输出实数值的精度值的整数常量(也就是小数个数)。例如:
writeln('The square root of ', number:5, ' is ', sqrt(number):10:6);
因此,对于每个实参,方法parseActualParameters()创建一个WRITE_PARM节点,它将实参表达式分析树加为第一个子节点。如果有宽度值,宽度值为WRITE_PARAM节点的第二个子节点;如果有精度,则精度将会是WRITE_PARAM的第三个子节点。清单11-17 展示了上面writeln调用解析产生的分析树。
<CALL line="11" id="writeln" level="0">
<PARAMETERS>
<WRITE_PARM>
<STRING_CONSTANT value="The square root of "
type_id="$anon_4cb088b" />
</WRITE_PARM>
<WRITE_PARM>
<VARIABLE id="number" level="1" type_id="integer" />
<INTEGER_CONSTANT value="5" type_id="integer" />
</WRITE_PARM>
<WRITE_PARM>
<STRING_CONSTANT value=" is " type_id="$anon_4cb0887" />
</WRITE_PARM>
<WRITE_PARM>
<CALL id="sqrt" level="0" type_id="real">
<PARAMETERS>
<VARIABLE id="number" level="1" type_id="integer" />
</PARAMETERS>
</CALL>
<INTEGER_CONSTANT value="10" type_id="integer" />
<INTEGER_CONSTANT value="6" type_id="integer" />
</WRITE_PARM>
</PARAMETERS>
</CALL>
清单11-18 展示了CallParser的checkActualParameter()方法。
1: //检测实参/形参的匹配
2: private void checkActualParameter(Token token, SymTabEntry formalId,
3: ICodeNode actualNode)
4: {
5: Definition formalDefn = formalId.getDefinition();
6: TypeSpec formalType = formalId.getTypeSpec();
7: TypeSpec actualType = actualNode.getTypeSpec();
8:
9: // VAR参数(引用传递): 实参类型必须与形参类型保持一致
10: if (formalDefn == VAR_PARM) {
11: if ((actualNode.getType() != ICodeNodeTypeImpl.VARIABLE) ||
12: (actualType != formalType))
13: {
14: errorHandler.flag(token, INVALID_VAR_PARM, this);
15: }
16: }
17:
18: // VALUE参数(值传递): 检查实参能否与形参赋值兼容?
19: else if (!TypeChecker.areAssignmentCompatible(formalType, actualType)) {
20: errorHandler.flag(token, INCOMPATIBLE_TYPES, this);
21: }
22: }
清单11-19 展示了parseWriteSpec()方法,用来解析write/writeln实参中额外的宽度和精度值。
1: //解析write/writeln实参额外的宽度/精度 比如 writeln(xx:50:2)
2: private ICodeNode parseWriteSpec(Token token)
3: throws Exception
4: {
5: if (token.getType() == COLON) {
6: token = nextToken(); // 跳过冒号
7:
8: ExpressionParser expressionParser = new ExpressionParser(this);
9: ICodeNode specNode = expressionParser.parse(token);
10: //必须是整数
11: if (specNode.getType() == INTEGER_CONSTANT) {
12: return specNode;
13: }
14: else {
15: errorHandler.flag(token, INVALID_NUMBER, this);
16: return null;
17: }
18: }
19: else {
20: return null;
21: }
22: }
方法parseWriteSpec()先搜寻冒号,接着解析后面的整数常量。
修改类StatementParser,使之可以处理过程调用、赋值到形参以及函数标识符。清单11-20展示了其parse()方法中switch语句部分,新版本的case IDENTIFIER。
1: case IDENTIFIER: {
2: String name = token.getText().toLowerCase();
3: SymTabEntry id = symTabStack.lookup(name);
4: Definition idDefn = id != null ? id.getDefinition()
5: : DefinitionImpl.UNDEFINED;
6:
7: // 赋值语句或是程式
8: switch ((DefinitionImpl) idDefn) {
9:
10: case VARIABLE:
11: case VALUE_PARM:
12: case VAR_PARM:
13: case UNDEFINED: {
14: AssignmentStatementParser assignmentParser =
15: new AssignmentStatementParser(this);
16: statementNode = assignmentParser.parse(token);
17: break;
18: }
19:
20: case FUNCTION: {
21: AssignmentStatementParser assignmentParser =
22: new AssignmentStatementParser(this);
23: statementNode =
24: assignmentParser.parseFunctionNameAssignment(token);
25: break;
26: }
27:
28: case PROCEDURE: {
29: CallParser callParser = new CallParser(this);
30: statementNode = callParser.parse(token);
31: break;
32: }
33:
34: default: {
35: errorHandler.flag(token, UNEXPECTED_TOKEN, this);
36: token = nextToken(); //跳过标识符
37: }
38: }
39: }
如果当前token是一个函数名称,方法parse()会调用assignmentParser.parseFunctionNameAssignment();如果是过程名称,则调用callParser.parse()。
清单11-21 展示了AssignmentStatementParser额外的处理代码。
1: //是否是一个函数为赋值目标
2: private boolean isFunctionTarget = false;
3:
4: //函数式的赋值
5: public ICodeNode parseFunctionNameAssignment(Token token)
6: throws Exception
7: {
8: isFunctionTarget = true;
9: return parse(token);
10: }
方法parseFunctionNameAssignment()在调用parse之前设置私有isFunctionTarget为真。方法parse()同样需要个小改动。(第54行)
ICodeNode targetNode =isFunctionTarget
? variableParser.parseFunctionNameTarget(token)
: variableParser.parse(token);
TypeSpec targetType = targetNode != null ? targetNode.getTypeSpec()
: Predefined.undefinedType;
这需要修改类VariableParser,参见下面清单11-22