续 第一部分
| 设计笔记 |
| 在提取token和解析结构之间有一种对称美。扫描器用token的第一个字符来判断接下来用何种类型去提取token。在token被提取后,当前字符是token的尾字符后的第一个字符(比如xy = bc,在提取到特殊符号token "="之后,当前字符指向'b')。同样,解析器用Pascal结构的第一个token(比如复合语句的BEGIN)来判断解析何种结构类型(复合语句还是复制语句?,还是其它类型?)。在这个结构被解析后,当前的Token是结构最后一个token的下一个token。(这种对称性解释了为什么Antlr对Lexer和Parser采用一样的LL(*)解析方式) |
解析语句
清单5-14 展示了frontend.pascal.parsers包中Pascal解析器子类StatementParser中的parse()和setLineNumber()方法。其中构造器如上所述获取解析上下文(获得父解析器的scanner,也就是解析的上下文)。
/**
* 解析一个语句,以传入的token判断该进行何种解析,子类会覆盖此方法
* @param token 语句的第一个token
* @return 分析子树的根节点
* @throws Exception
*/
public ICodeNode parse(Token token)
throws Exception
9: {
10: ICodeNode statementNode = null;
11:
switch ((PascalTokenType) token.getType()) {
13:
case BEGIN: {
16: CompoundStatementParser compoundParser =
this);
18: statementNode = compoundParser.parse(token);
break;
20: }
21:
//类似于 a = b 之类的赋值语句,这里a就是标识符identifier
case IDENTIFIER: {
24: AssignmentStatementParser assignmentParser =
this);
26: statementNode = assignmentParser.parse(token);
break;
28: }
default: {
30: statementNode = ICodeFactory.createICodeNode(NO_OP);
break;
32: }
33: }
34:
// 设置根节点“行位置”属性,即第一个token的行位置
36: setLineNumber(statementNode, token);
return statementNode;
38: }
39:
/**
* 设置节点的行属性。PS: 我认为这种写法很怪,为何不setLineNumber(ICodeNode, int)?
* @param node 分析树节点
* @param token token
*/
void setLineNumber(ICodeNode node, Token token)
46: {
if (node != null) {
48: node.setAttribute(LINE, token.getLineNumber());
49: }
50: }