野牛风格:使用我自己的堆栈不好吗?全球都很糟糕
Bison style: is using my own stack bad? Are globals bad?
我的问题基本上是"YACC/Bison的好风格是什么?"以及相关的问题,我是否让Bison做它擅长的事情。
例如,我发现我的Bison程序在很大程度上比我原来的代码更依赖全局变量。考虑以下内容:
prog :
vents{ /*handle semantics...*/ }
unity{ /*handle semantics...*/ }
defs;
如果我想在"vents"answers"unity"之后的两个大括号分隔的块之间传递信息,我认为从信息隐藏的角度来看,使用全局变量(从技术上讲,是一个具有文件级作用域和内部链接的变量)是我能做的最好的事情。我在这些块中声明的任何变量都是其块的本地变量(我认为…),并且我可以将C++声明放在其他指定的位置,这将导致文件级作用域。
如果我可以将变量声明注入到"yyprse()"函数中,这将更好地满足我的需求。这类代码是否有钩子,或者注入这样一个变量的其他方法?或者全局变量只是使用Bison的一个可接受的部分?
我还想到,也许我甚至不应该想以这种方式在这些部分之间传递信息。但对我来说,仅仅用$$、$1、$2等来传递一切似乎很困难。我只是没有"得到它"吗?
我发现我的一个全局变量特别值得怀疑,即使我接受了其余的变量。它的类型是std::stack,与输入语言对条件语句的支持有关。
当我在编译器输入中遇到条件("if/else")时,这将导致最终产生三个汇编语言标签,由一个文本字符串和一个从序列中提取的数字组成。
因此,当我第一次遇到"if"时,我正在获取一个序列号,将其推到堆栈上(因为"if"结构可以嵌套),然后稍后使用它(通过"peeks"或"pops")来构造必要的标签和跳跃,例如在我的条件、我的"if"块和我的"else"块之后。
我试着用类似$-2的东西来实现这项工作,但发现这个标识符与我的条件无关,而是与刚刚编译的任何块的末尾有关。由$抽象的系统似乎与从左到右读取的代码有关,而不知道其中的结构是如何嵌套的。
我不希望你们都能为我做这件事……但我至少在尝试使用$$、$1、$-1等方面走上了正确的道路吗?很可能我放弃得太快了,和/或我会从采取零失球的方法中受益,也就是说,完全放弃我以前的临时代码。
是这样吗?或者,我的方法及其std:stack和全局变量是否可以?
我不认为避免使用全局变量有什么困难,我几乎不使用它们来表示错误或类似的事情。
想想解析器,它应该产生什么?抽象语法树。。
它是怎么做的?这是一个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("NOTn");
printf("JUMPC iflabel%un", labelCounter);
m_tbody->generateASM();
printf("iflabel%u:rn", labelCounter);
++labelCounter;
}
else
{
u32 c = labelCounter++;
u32 d = labelCounter++;
m_condition->generateASM();
printf("JUMPC iflabel%un", c);
m_fbody->generateASM();
printf("JUMP iflabel%un", d);
printf("iflabel%u:n", c);
m_tbody->generateASM();
printf("iflabel%u:n", d);
}
}
中级规则可以在堆栈上推送一个值。
如果你有
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; }
| ...
不需要外部堆栈来执行这样的递归功能。
您可以使用%parse-param
指令声明要传递的其他数据。这样可以更好地隐藏额外的数据,尽管您还必须将其传递到解析函数中。
- Qt VTK交互风格的信号到小部件
- 我可以使用条件运算符初始化C风格的字符串文字吗
- Visual Studio 2019:插入多个C++风格的单行注释
- 如何在本地机器上运行c++和javascript客户端代码(hackerbank风格)
- 算法问题:查找从堆栈中弹出的所有序列
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 为什么调用堆栈数组会导致内存泄漏
- gdb错误:Backtrace已停止:上一帧与此帧相同(堆栈已损坏?)
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 堆栈和队列是否像C++中的数组一样传递?
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 从堆栈分配的原始指针构造智能指针
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 堆栈中大小变量输入错误 (C++)
- 野牛风格:使用我自己的堆栈不好吗?全球都很糟糕
- 在 C/C++ 中使用堆栈进行内存管理时的编码风格
- 如何在msvc++中获得相当c#风格的堆栈跟踪?