【发布时间】:2012-03-30 23:41:06
【问题描述】:
创建解析树后,我现在必须填充符号表。
我必须存储类似的信息
标识符的类型、范围、偏移量等。
现在我如何知道标识符的类型和范围,因为我只知道该特定 ID 的词位值和行号(在词法分析之后)。
我是怎么知道整个事情的。谢谢。
【问题讨论】:
标签: c compiler-construction symbol-table
创建解析树后,我现在必须填充符号表。
我必须存储类似的信息
标识符的类型、范围、偏移量等。
现在我如何知道标识符的类型和范围,因为我只知道该特定 ID 的词位值和行号(在词法分析之后)。
我是怎么知道整个事情的。谢谢。
【问题讨论】:
标签: c compiler-construction symbol-table
现在我怎么知道标识符的类型和范围,因为所有我 知道是该特定 ID 的词位值和行号(之后 词法分析)。
正如 EJP 所提到的,您需要单步执行解析树。您的树应该已经创建,以便您可以进行按顺序遍历,以与评估源代码语句和表达式的顺序相同的顺序访问每个节点。您的树节点还应与特定的语言结构相对应,例如WhileStmtNode、MethodDeclNode等
假设我正在构建符号表,递归地遍历树,并且我刚刚输入了一个方法体节点。我可能有以下内容:
public void doAction(MethodBodyNode methodBody) {
currScope = 2;
methodBody.getExpr().applyAction(this);
currScope = 2;
}
我保留一个全局变量来管理范围。每次我进入一个范围发生变化的块时,我都会增加currScope。同样,我会维护currClass 和currMethod 变量以存储符号名称、类型、偏移量等以供以后阶段使用。
更新:
现在说,我正在遍历树,每次我遇到一个 ID 必须将值连同类型一起输入到符号表中, 范围和其他人,说范围我检查我是否遇到'{'或 函数名,但我怎么知道这是什么类型的 ID。
每个树节点都应该包含整个构造的所有必要信息。如果您使用解析器生成器,如 CUP 或 Bison,您可以指定如何在语法操作中构建树。例如
variableDeclaration::= identifier:i identifier:i2 SEMICOLON {: RESULT = new VarDeclNode(i, i2, null); :};
identifier::= ID:i {: RESULT = new IdNode(i.getLineNum(), i.getCharNum(), i.getStringValue()); :};
这些产生式将匹配Foo f; 并将变量声明节点附加到树。该节点封装了两个标识符节点,其中包含行号、字符号和词位的字符串值。第一个标识符节点是类型,第二个是变量名。 ID 是词法分析器在匹配标识符时返回的终端符号。我假设您在某种程度上对此很熟悉。
public class VarDeclNode extends StmtNode {
private IdNode id;
private IdNode type;
private ExprNode expr;
public VarDeclNode(IdNode id, IdNode type, ExprNode expr) {
super();
this.id = id;
this.type = type;
this.expr = expr;
}
}
当你得到一个包含这样节点的语法树时,你就得到了你需要的所有信息。
第二次更新:
无论您使用的是自定义解析器还是生成的解析器都没有关系,有一个不同的点,您可以在匹配产生式时将节点添加到树中。你使用什么语言并不重要。 C 结构体就可以了。
如果是非终端,则将信息作为非终端名称,如果是 是一个终端,即一个令牌,然后是令牌中的信息,即词位值, 存储令牌名称和行号
你必须在树中有专门的节点,例如ClassNode、TypeNode、MethodDeclNode、IfStmtNode、ExprNode。您不能只存储一种类型的节点并将非终端和终端放入其中。一个非终端表示为一个树节点,除了组成它的部分之外没有其他信息可以存储,这些部分通常是非终端本身。您不会存储任何令牌信息。只有少数情况下您实际上会存储词位的字符串值:用于标识符和字符串/布尔值/整数文字。
查看this 示例。在S 减少为(S + F) 的第一次减少期间,您将ParenExprNode 附加到树根。您还可以附加一个AddExprNode 作为ParenExprNode 的子项。在应用语法规则 2 的缩减时,该逻辑必须硬编码到您的解析器中。
树:
ExprNode (root)
|
ParenExprNode
|
AddExprNode
/ \
ExprNode ExprNode
代码:
struct ExprNode { void* exprNode; };
struct ParenExprNode { void* exprNode; };
struct AddExprNode { void* op1, * op2; };
struct IdNode { char* val; int line; int charNum; };
struct IntLiteralNode { int val; int line; int charNum; };
void reduce_rule_2(ExprNode* expr) {
//remove stack symbols
//create nodes
struct ParenExprNode* parenExpr = malloc(sizeof(struct ParenExprNode));
struct AddExprNode* addExpr = malloc(sizeof(struct AddExprNode));
addExpr->op1 = malloc(sizeof(struct ExprNode));
addExpr->op2 = malloc(sizeof(struct ExprNode));
//link them
parenExpr->exprNode = (void*)addExpr;
expr->exprNode = (void*)parenExpr;
}
在下一步中,从输入中删除左括号。之后,S 位于堆栈顶部,并根据规则 1 减少为 F。由于 F 是标识符的非终结符,因此它由 IdNode 表示。
树:
ExprNode
|
ParenExprNode
|
AddExprNode
/ \
ExprNode ExprNode
|
IdNode
代码:
reduce_rule_2(addExpr->op1);
void reduce_rule_1(ExprNode* expr) {
//reduce stack symbols
struct IdNode* id = malloc(sizeof(struct IdNode));
id->val = parser_matched_text();
id->lineNum = parser_line_num();
id->charNum = parser_char_num();
expr->exprNode = (void*)id;
}
等等……
【讨论】:
我只知道该特定 ID 的词位值和行号
那不是真的。您知道它在解析树中的声明位置,它会告诉您所需的一切。您通过处理解析树来完成这一步。
【讨论】: