【问题标题】:Bison style: is using my own stack bad? Are globals bad?野牛风格:使用我自己的堆栈不好吗?全局变量不好吗?
【发布时间】:2012-02-17 01:46:13
【问题描述】:

我的问题基本上是“什么是 YACC / Bison 的好风格?”以及相关的,我是否让 Bison 做它擅长的事情。

例如,我发现我的 Bison 程序比我的原始代码更依赖全局变量。考虑以下几点:

 prog : 
      vents{ /*handle semantics...*/ } 
      unity{ /*handle semantics...*/ } 
      defs;

如果我想在“vents”和“unity”之后的两个大括号分隔的块之间传递信息,我认为使用全局变量(从技术上讲,具有文件级范围和内部链接的变量)是最好的从信息隐藏的角度来看,我可以做到。我在这些块中声明的任何变量都是其块的本地变量(我认为......),我可以放置 C++ 声明的其他指定位置导致文件级范围。

如果我可以将变量声明注入“yyparse()”函数,这将更适合我的需要。是否有这种代码的钩子,或者注入这种变量的其他方式?还是全局变量只是使用 Bison 的公认部分?

我还想到,也许我什至不应该以这种方式在这些部分之间传递信息。但是对我来说,仅使用 $$、$1、$2 等来传递所有内容似乎很困难。我只是没有“明白”吗?

我发现我的一个全局变量特别值得怀疑,即使我接受了其余的变量。它是 std::stack 类型,与输入语言对条件的支持有关。

当我在编译器输入中遇到条件(“if/else”)时,这会导致最终发出三个汇编语言标签,由一个文本字符串和一个从序列中提取的数字组成。

所以,当我第一次遇到“if”时,我正在获取一个序列号,将它推入堆栈(因为“if”结构可以嵌套),然后再使用它(通过“peeks”或“pops” ) 来构造必要的标签和跳转,例如在我的条件、我的“if”块和我的“else”块之后。

我尝试改用 $-2 之类的东西来完成这项工作,但发现这个标识符与我的条件的开头无关,而是与刚刚编译的任何块的结尾有关。 $ 抽象的系统似乎与从左到右读取的代码有关,没有任何关于其中的结构如何嵌套的概念。

我不希望你们所有人都能为我完成这项工作......但我至少在尝试使用 $$、$1、$-1 等方面是正确的吗?很可能我只是太早放弃了,和/或我会受益于采用干净的工作表方法,即完全丢弃我的旧 ad hoc 代码。

是这样吗?或者我的 std:stack 和全局变量的组合方法好吗?

【问题讨论】:

    标签: c++ unix yacc flex-lexer


    【解决方案1】:

    我没有看到避免使用全局变量的困难,我几乎不使用它们来表示错误或类似的事情。

    想想解析器,它应该产生什么?抽象语法树..

    它是如何制作的?它是一个 n 叉树,其中每个节点都包含一些信息,只有它的子节点,因此不需要全局变量。

    我会让你看一下我正在写的语言,只是为了给你一个想法:

    bexp:
      bexp T_PLUS bexp { $$ = new ASTBExp($2,$1,$3); }
      | bexp T_MINUS bexp { $$ = new ASTBExp($2,$1,$3); }
      | bexp T_TIMES bexp { $$ = new ASTBExp($2,$1,$3); }
      | bexp T_DIV bexp { $$ = new ASTBExp($2,$1,$3); }
    
    uexp:
      raw_value { $$ = $1; }
      | UOP_NOT uexp { $$ = new ASTUExp($1,$2); }
      | T_LPAREN bexp T_LPAREN { $$ = $2; }
      | var_ref { $$ = new ASTVarRef((ASTIdentifier*)$1); }
      | call { $$ = $1; }
    

    正如您所见,每个解析的节点都使用语法的子节点进行实例化,它们在语义上也是抽象语法树的子节点,并在 $$ 中返回

    根元素类似于

    start: root { Compiler::instance()->setAST((ASTRoot*)$1); }
    ;
    
    root:
      function_list { $$ = new ASTRoot($1); }
    ;
    

    我只是获取整个树并将其传递给我的 Compiler 类的实例。

    现在如果你看一下调用yyparse()的函数

    bool parseSource()
    {
      //yydebug = 1;
      freopen(fileName, "r", stdin);
      yyparse();
    
      return !failed;
    }
    

    我只是打开一个文件并调用解析例程。这个函数在这里被Compiler 类调用:

      bool compile()
      {
        if (!parseSource())
          return false;
    
        if (!populateFunctionsTable())
          return false;
    
        ast->recursivePrint(0);
        Utils::switchStdout(binaryFile);
        ast->generateASM();
        Utils::revertStdout();
    
        assemble();
    
        return true;
      }
    

    正如您在此处看到的,解析例程被调用,该例程创建整个树,然后将其设置在 Compiler 类中。树的递归访问(函数generateASM)完成了这项肮脏的工作。

    我希望这能说明您应该如何使用解析器,如果您需要任何进一步的信息,请告诉我......您不需要在解析器中完成所有工作。只需在那里进行解析,其他一切都可以通过对抽象语法树的一些递归调用来解决。

    另一个实际的例子是你所说的 if/else 语句,在语法中它被定义为

    if_stat:
      KW_IF T_LPAREN exp T_RPAREN block %prec LOWER_THAN_ELSE { $$ = new ASTIfStat($3, $5); }
      | KW_IF T_LPAREN exp T_RPAREN block KW_ELSE block { $$ = new ASTIfStat($3, $5, $7); }
    ;
    

    创建了一个特殊的节点来管理 if/else 结构,然后通过这个generateASM 函数来工作:

     void generateASM()
      { 
        if (m_fbody == NULL)
        {
          m_condition->generateASM();
          printf("NOT\n");
          printf("JUMPC iflabel%u\n", labelCounter);
          m_tbody->generateASM();
          printf("iflabel%u:\r\n", labelCounter);
    
          ++labelCounter;
        }
        else
        {
          u32 c = labelCounter++;
          u32 d = labelCounter++;
    
          m_condition->generateASM();
          printf("JUMPC iflabel%u\n", c);
          m_fbody->generateASM();
          printf("JUMP iflabel%u\n", d);
          printf("iflabel%u:\n", c);
          m_tbody->generateASM();
          printf("iflabel%u:\n", d);
        }
      }
    

    【讨论】:

    • 拥有代表语法中所有杂项元素的类库非常好,因为您可以随心所欲地处理它们。只需进行一些递归访问,您就可以对它们进行类型检查、修剪树、优化结构和生成 ASM 代码。
    【解决方案2】:

    中级规则可以有一个压入堆栈的值。

    如果你有

    rule
        : A B { ... } C
    

    Bison 自动将其转换为

    some_identifier
        : /* empty */ { ... }
    
    rule
        : A B some_identifier C
    

    它的值可以完全这样访问。在这种情况下,中间规则语义动作的值存储在 Bison 堆栈中,然后在同一规则中再次访问。

    通常,这些函数是递归的。考虑以下简单的 sn-p

    // C++
    class Statement { public: virtual ~Statement() {} };
    class Expression : public Statement {};
    class IfStatement : public Statement { Statement* if_true; Expression* condition; }
    
    // Bison
    %type if_statement if_stmt
    %type statement stmt
    %union {
        IfStatement* if_stmt;
        Statement* stmt;
    }
    
    if_statement
        : if { $$ = new IfStatement(); } 
          '(' expression { $2->condition = $4; } 
          ')' statement { $2->if_true = $7; $$ = $2; }
    
    statement
        : if_statement { $$ = $1; }
        | ...
    

    不需要外部堆栈来执行这样的递归功能。

    【讨论】:

      【解决方案3】:

      您可以使用%parse-param directive 声明要传递的其他数据。这可以让您更好地隐藏额外的数据,尽管您还必须将其传递到您的解析函数中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-01
        • 1970-01-01
        相关资源
        最近更新 更多