【问题标题】:Symbol Table Population after parsing; Compiler building解析后的符号表填充;编译器构建
【发布时间】:2012-03-30 23:41:06
【问题描述】:

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

我必须存储类似的信息

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

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

我是怎么知道整个事情的。谢谢。

【问题讨论】:

    标签: c compiler-construction symbol-table


    【解决方案1】:

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

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

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

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

    查看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;
    }
    

    等等……

    【讨论】:

    • 所以我正在使用 C,我的 parsetree 节点具有信息词位值和令牌名称,即 if/IF 或 name/ID 或 12/INTEGER。现在说,我正在遍历树,每次遇到 ID 时,我都必须将值连同类型、范围等一起输入到符号表中,比如对于范围,我检查是否遇到“{”或函数名,但是我怎么知道这是什么类型的 ID。
    • @Kraken:听起来你没有正确构建树。查看更新。
    • 让我带你了解我到目前为止所做的一切。 1. 词法分析:正确标记词位,维护源代码传递过程中收集的所有信息(即词位值,tokeName,lineno。)。 2.语法分析:使用预测解析器(使用解析表),我维护的语法规则为数组规则,数组的每个条目是一个链表并维护每个规则。类似地,首先和后续集被维护。现在我维护一个带有起始符号的堆栈,从词法分析器生成的标记列表中检查下一个标记和(续)..
    • 如果下一个标记在第一个非终结符中,则将该规则添加为解析树,其中每个节点,如果它是非终结符,则将信息作为非终结符名称,如果它是终端即令牌,然后存储令牌中的信息,即词位值、令牌名称和行号。此外,每个父节点都有其子节点作为链表,因为每个规则的子节点数量不同。我的解析错误吗?此外,我在语法中所做的所有更改都是消除歧义、左递归和左因式分解。我正在使用 C 语言。请告诉我。谢谢。
    • @Kraken:关于你的第二条评论,是的,这就是我们之前提到的树的处理。这样做来填充符号表。您的 declareStmt 节点应包含符号名称、类型和初始化表达式(至少)。您应该首先构建符号表,然后再检查任何未定义的变量并进行其他语义检查。为什么?以 Java 为例,编译器允许使用尚未声明的变量/方法。这取决于您的语言,但无论如何,这很容易做到,并且可以省去很多麻烦。
    【解决方案2】:

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

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

    【讨论】:

    • 通过处理你的意思是,创建属性语法(继承属性)[我刚刚遇到这些术语,因此我可能完全错了],对于范围,我可以检查新的括号是否是遇到过,或者有新功能。
    • @Kraken 是的,这就是我的意思。您永远不会仅使用词位和行号来处理符号。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-14
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2014-05-22
    • 2023-03-19
    相关资源
    最近更新 更多