gpt4 book ai didi

compilation - 如何一次一次灵活地返回多个终端

转载 作者:行者123 更新时间:2023-12-04 02:58:24 25 4
gpt4 key购买 nike

为了使我的问题易于理解,我想使用以下示例:

以下代码在fortran语言中称为非阻塞do-loop

DO 20 I=1, N      ! line 1
DO 20 J=1, N ! line 2
! more codes
20 CONTINUE ! line 4


请注意,第4行的标签 20表示内部do-loop和外部do-loop的结尾。

我希望我的flex程序正确解析该功能:当flex读取标签 20时,它将两次返回 ENDDO终端。

首先,因为我也使用bison,所以每次bison调用 yylex()来获得一个终端。如果我可以要求野牛在某些情况下从 yylex()获取端子,而在其他情况下可以从另一函数获取端子,也许我可以解决此问题,但是,那时我不知道。

当然,有一些解决方法,例如,我可以使用flex的启动条件,但我认为这不是一个好的解决方案。所以我问如果没有解决方法是否可以解决我的问题?

最佳答案

修改(f)lex产生的词法扫描器以实现令牌队列很容易,但这不一定是最佳解决方案。 (请参阅下文,以获得更好的解决方案。)(此外,对于我的问题,我真的不清楚,在词法分析器中构造额外的标记是否确实合适。)

通用方法是将代码插入yylex函数的顶部,您可以通过将代码紧接在%%行之后和第一个规则之前来执行此操作。 (必须对代码进行缩进,以便不能将其解释为规则。)对于非可重入扫描程序,通常将涉及使用本地static变量来保存队列。对于一个简单但愚蠢的示例,使用C API但使用C ++进行编译,以便可以访问C ++标准库:

%%
/* This code will be executed each time `yylex` is called, before
* any generated code. It may include declarations, even if compiled
* with C89.
*/
static std::deque<int> tokenq;
if (!tokenq.empty()) {
int token = tokenq.front();
tokenq.pop_front();
return token;
}
[[:digit:]]+ { /* match a number and return that many HELLO tokens */
int n = atoi(yytext);
for (int i = 0; i < n; ++i)
tokenq.push_back(HELLO);
}


上面的代码没有尝试为排队的令牌提供语义值。您可以使用类似 std::queue<std::pair<int, YYSTYPE>>的令牌队列来实现此目的,但是 YYSTYPE通常是 union的事实会带来一些麻烦。同样,如果那是使用令牌队列的唯一原因,那么很明显,可以用一个简单的计数器代替它,这样会更有效。例如,参见 this answer,其含义与您的问题相似(并注意该答案的注释1中的建议)。

更好的选择:使用推送解析器

尽管令牌队列解决方案既有吸引力又简单,但它很少是最佳解决方案。在大多数情况下,如果您要求野牛产生 "push parser",则代码将更清晰,更容易编写。对于推式解析器,每当令牌可用时,词法分析器就会调用解析器。这使得从词法分析器操作返回多个令牌变得很简单;您只需为每个令牌调用解析器。同样,如果规则不产生任何令牌,则根本无法调用解析器。在此模型中,实际上是 return的唯一词法分析器操作是 <<EOF>>规则,并且只有在使用 END令牌调用解析器以指示解析完成后才这样做。

不幸的是,推送解析器的界面不仅会更改,正如该手动链接所指示的那样。它也非常糟糕地记录在案。因此,这里有一个简单但完整的示例,说明了如何完成此操作。

推式解析器将其状态保持在 yypstate结构中,该结构需要在每次调用时传递给解析器。由于每个输入文件仅对词法分析器调用一次,因此,词法分析器拥有该结构是合理的,可以使用局部静态变量按上述方法完成此操作[注1]:调用 yylex时初始化解析器状态,并且 EOF规则删除解析器状态以回收其正在使用的任何内存。

通常,构建可重入的推式解析器最为方便,这意味着解析器不依赖于全局 yylval变量[注2]。相反,必须提供指向语义值的指针作为 yypush_parse的附加参数。如果您的解析器没有引用特定令牌类型的语义值,则可以为此参数提供NULL。或者,如下面的代码所示,您可以在词法分析器中使用局部语义值变量。不必每次对推送解析器的调用都提供相同的指针。总之,对扫描仪定义的更改很小:

%%
/* Initialize a parser state object */
yypstate* pstate = yypstate_new();
/* A semantic value which can be sent to the parser on each call */
YYSTYPE yylval;
/* Some example scanner actions */
"keyword" { /* Simple keyword which just sends a value-less token */
yypush_parse(pstate, TK_KEYWORD, NULL); /* See Note 3 */
}
[[:digit:]]+ { /* Token with a semantic value */
yylval.num = atoi(yytext);
yypush_parse(pstate, TK_NUMBER, &yylval);
}
"dice-roll" { /* sends three random numbers */
for (int i = 0; i < 2; ++i) {
yylval.num = rand() % 6;
yypush_parse(pstate, TK_NUMBER, &yylval);
}
<<EOF>> { /* Obligatory EOF rule */
/* Send the parser the end token (0) */
int status = yypush_parse(pstate, 0, NULL);
/* Free the pstate */
yypstate_delete(pstate);
/* return the parser status; 0 is success */
return status;
}


在解析器中,除了添加必要的声明外,不需要做太多更改:[注4]

%define api.pure full
%define api.push-pull push




笔记


如果还正在构建可重入词法分析器,则将使用词法分析器状态对象的多余数据部分代替静态变量。
如果您在解析器中使用位置对象来跟踪源代码位置,则这也适用于 yylloc
该示例代码无法很好地检测错误,因为它不检查从 yypush_parse的调用返回的代码。我通常使用的一种解决方案是宏 SEND的某些变体:

#define SEND(token) do {                              \
int status = yypush_parse(pstate, token, &yylval); \
if (status != YYPUSH_MORE) { \
yypstate_delete(pstate); \
return status; \
} \
} while (0)


也可以使用 goto来避免 yypstate_deletereturn的多个实例。 YMMV。
您可能需要修改 yyerror的原型。如果您正在使用位置和/或为push_parser提供额外的参数,则 yyerror调用中还将显示位置对象和/或额外的参数。 (错误字符串始终是最后一个参数。)无论出于何种原因,解析器状态对象都不会提供给 yyerror,这意味着 yyerror函数不再可以访问诸如 yych这样的变量, yypstate结构的成员而不是全局变量,因此,如果在错误报告中使用这些变量(这不是真正推荐的做法),那么您将不得不寻找替代解决方案。

关于compilation - 如何一次一次灵活地返回多个终端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42434603/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com