野牛风格:使用我自己的堆栈不好?全局变坏了吗?

时间:2012-02-17 01:46:14

标签: c++ unix yacc flex-lexer

我的问题基本上是“YACC / Bison的优秀风格是什么?”而且,相关地,我是否让Bison做了擅长的事情。

例如,我发现我的Bison程序更多地依赖于全局变量而不是原始代码。请考虑以下事项:

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

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

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

我也想到,也许我甚至不想以这种方式在这些部分之间传递信息。但是使用$$,$ 1,$ 2等传递所有东西对我来说似乎很难。我不是“得到它”吗?

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

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

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

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

我不希望你们都为我做这个工作......但是我至少在正确的道路上尝试使用$$,$ 1,$ -1等等?我很可能太早放弃了,和/或我会从采用干净的工作表方法中受益,即完全丢弃旧的 ad hoc 代码。

是这样的吗?或者是我的std:stack和它的全局变量的抛出方法吗?

3 个答案:

答案 0 :(得分:2)

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

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

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

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

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

正如您所看到的,解析的每个节点都使用语法的childred进行实例化,这些语法也是语法上的抽象语法树的子项,并在$$中返回

根元素类似于

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);
    }
  }

答案 1 :(得分:0)

中级规则可以在堆栈上推送一个值。

如果你有

rule
    : A B { ... } C

Bison会自动将其转换为

some_identifier
    : /* empty */ { ... }

rule
    : A B some_identifier C

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

通常,这些函数是递归的。考虑以下简单的片段

// 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; }
    | ...

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

答案 2 :(得分:0)

您可以使用%parse-param directive声明要传递的其他数据。这可以让你更好地隐藏额外的数据,不过你也必须将它传递给你的解析函数。