0

创建解析树后,我现在必须填充符号表。

我必须存储信息,例如

标识符的类型、范围、偏移量等。

现在我怎么知道标识符的类型和范围,因为我只知道该特定 ID 的词位值和行号(在词法分析之后)。

我怎么知道整件事。谢谢。

4

2 回答 2

2

现在我怎么知道标识符的类型和范围,因为我只知道该特定 ID 的词位值和行号(在词法分析之后)。

正如 EJP 提到的,您需要单步执行解析树。您的树应该已经创建,以便您可以进行中序遍历,以与评估源代码语句和表达式的顺序相同的顺序访问每个节点。您的树节点还应与特定的语言结构相对应,例如WhileStmtNode,MethodDeclNode等。

假设我正在构建符号表,递归地遍历树,并且我刚刚输入了一个方法主体节点。我可能有以下内容:

public void doAction(MethodBodyNode methodBody) {
    currScope = 2;
    methodBody.getExpr().applyAction(this);
    currScope = 2;
}

我保留一个全局变量来管理范围。每次我进入一个范围发生变化的块时,我都会增加currScope. 同样,我会维护currClasscurrMethod变量以存储符号名称、类型、偏移量等,以供以后阶段使用。

更新:

现在说,我正在遍历树,每次遇到 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。您不能只存储一种类型的节点并将非终端和终端放入其中。一个非终端表示为一个树节点,除了组成它的部分之外没有其他信息可以存储,这些部分通常是非终端本身。您不会存储任何令牌信息。只有少数情况下您实际上会存储词位的字符串值:用于标识符和字符串/布尔值/整数文字。

看看这个例子。在第一次归约期间,当归S约到时(S + F),您将 a 附加ParenExprNode到树根。您还附加 aAddExprNode作为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位于堆栈顶部,并按F规则 1 减少。由于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;
}

等等...

于 2012-03-17T16:46:35.817 回答
0

我只知道该特定 ID 的词位值和行号

这不是真的。您知道它在解析树中的声明位置,它会告诉您所需的一切。您通过处理解析树来完成此步骤。

于 2012-03-15T00:44:01.653 回答