作为玩具项目的一部分,我一直试图根据flex / bison对其他人的解析器做一个小修改。我真的没有经验。您可以找到原始解析器here。
我一直在尝试组合一个接受字符串并返回解析树的简单函数,因此我可以通过FFI公开它以便在另一种编程语言中使用。我所拥有的主要是基于原始程序中的<html>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<?php
/* Connect to an ODBC database using driver invocation */
$dsn = 'mysql:dbname=testdb;host=127.0.0.1;charset=UTF8;'
$user = 'dbuser'; // don't hardcode this...store it elsewhere
$password = 'dbpass'; // this too...
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sql = "SELECT :column FROM :table";
$opts = array(
':column' => 'Name',
':table' => 'Mothakirat'
);
$dbh->beginTransaction();
$statement = $dbh->prepare($sql);
if ($statement->execute($opts)) {
$resultArray = array(); // If so, then create a results array and a temporary one
$tempArray = array(); // to hold the data
while ($row = $result->fetch_assoc()) // Loop through each row in the result set
{
$tempArray = $row; // Add each row into our results array
array_push($resultArray, $tempArray);
}
echo json_encode($resultArray); // Finally, encode the array to JSON and output the results
}
$dbh->commit();
</html>
函数,我的屠宰版本如下:
main()
这实际上工作正常,至少在我第一次调用它时。第二次抱怨错误的令牌,并且在调用TreeNode* parse_string(char *s)
{
FILE *in = fmemopen(s, strlen(s), "r");
lex2_initialise();
parse_file(in);
fclose(in);
preprocess_tokens();
yyparse();
return top;
}
期间,生成的解析器中的goto语句迷宫内的某个地方似乎调用了错误报告函数,此时我不明白是什么继续下去。
原始程序本身似乎只是为了提前获取所有输入然后退出,所以它不会让我对我所遗漏的内容有太多线索。撇开一些不完全古怪的想法,一些旧的国家在其他方案中保留在其他地方,我的主要问题是:
答案 0 :(得分:3)
Flex或Bison是否在调用yyparse()
之间保持全局状态
Flex维护有关当前输入流的信息。如果解析不消耗整个输入流(这对于在错误时异常终止的解析器很常见),那么下一次调用yyparse
将继续读取前一个停止的位置。提供新的输入缓冲区将(大部分)重置词法分析器的状态,但可能存在一些尚未重置的方面,特别是当前的启动条件,以及条件堆栈(如果已启用该选项)。
野牛生成的解析器不依赖于全局状态。它旨在从yyparse
返回之前清除其内部状态。但是,如果解析器操作直接执行return
语句(建议不),则会绕过清理,这可能会造成内存泄漏。过早终止解析的操作应使用宏YYACCEPT
或YYABORT
而不是return
语句。
是否有一些简单的函数调用我可以在上面的函数结尾处擦除它并将所有内容重置回初始状态?
默认的flex生成的解析器,设计为每次需要令牌时调用,严重依赖于全局变量。大多数(但不是全部)flex状态保持在当前YY_BUFFER_STATE
(保存在全局变量中),并且该对象可以由yyreset
函数或任何函数重置它提供字符缓冲区作为词法输入。但是,这些函数不会重置启动条件,也不会刷新条件堆栈(如果启用)或缓冲区堆栈。如果要完全重置状态,则需要手动刷新堆栈,并使用BEGIN(INITIAL)
重置启动条件。
制作更易于重新启动的扫描程序的一种方法是构建reentrant scanner。可重入扫描器将其所有状态(包括启动条件和缓冲区堆栈)保持在扫描仪结构中,这意味着您只需创建一个新的扫描仪结构即可完全重置扫描仪状态(当然,还要破坏旧的扫描仪结构以避免泄露记忆。)
使用重入扫描仪有很多充分的理由[注1]。首先,它允许您同时激活多个解析器,并且它消除了对全局状态的依赖。但不幸的是,它并不像设置弹性选项那么简单。
可重入扫描程序具有不同的API(包括指向扫描程序状态结构的指针)。需要将此状态结构传递到yyparse
,yyparse
需要将其传递给yylex
;所有这些都需要对野牛选项进行一些修改。此外,可重入扫描程序无法使用全局yylval
将标记的语义值传递给解析器[注2]。
如果您使用%bison-bridge
选项并告诉bison生成可重入的解析器,那么yylex
将使用另一个附加参数(或两个,如果您使用位置)以及可重入参数调用野牛解析器将提供其他参数。这一切都运行正常,但它可以将yylval
(和yylloc
(如果使用的话)更改为指针,这意味着您需要完成更改yylval.something
的所有扫描程序操作到yylval->something
。
您还可以使用一些额外的野牛选项创建可重入的解析器。通常,bison生成的解析器使用的唯一可变全局变量是yylval
和yylloc
(如果使用位置报告)。 (和yynerrs
,但很少在解析器操作之外引用该变量。)指定可重入解析器会将这些全局变量转换为词法分析器参数,但它不会创建外部可见的解析器状态结构。但它也为您提供了使用&#34;推送解析器&#34;的选项,它具有持久的解析器状态结构。在某些情况下,推送解析器的灵活性可以显着简化扫描程序。
严格来说,没有什么可以阻止你创建一个仍然使用全局变量与解析器通信的可重入扫描器,除了它不再是真正的重入。出于显而易见的原因,我不建议使用此选项,但您可能希望将其作为过渡策略来执行,因为它需要对解析器和扫描程序操作进行较少的修改。
答案 1 :(得分:1)
即使您使用的是非重入解析器,也可以在lexing之后使用yylex_destroy
(不带参数)强制初始化,下次调用词法分析器时:
extern int yylex_destroy(void);
...
// do parsing here
...
yylex_destroy()
对于重入解析器,请参阅here。