【发布时间】:2017-03-15 23:29:48
【问题描述】:
我们正在尝试纠正我们的词法分析解析器中的一个错误。我们正在使用 flex 并且我们必须支持多行字符串。问题是当字符串的结尾与开头的 " 不在同一行时,我们不计算新行。我们有两种情况,换行符和 \n 从程序员插入到字符串中是否有办法理解行尾并以某种方式在规则中计算它?
【问题讨论】:
标签: compiler-construction flex-lexer
我们正在尝试纠正我们的词法分析解析器中的一个错误。我们正在使用 flex 并且我们必须支持多行字符串。问题是当字符串的结尾与开头的 " 不在同一行时,我们不计算新行。我们有两种情况,换行符和 \n 从程序员插入到字符串中是否有办法理解行尾并以某种方式在规则中计算它?
【问题讨论】:
标签: compiler-construction flex-lexer
如果您使用%option yylineno,那么 flex 将为您维护行数(在变量 yylineno 中)。只要您不致电input,它就会正确。
有两个小问题:
yylineno 的值对于读取的最后一个字符是正确的,它是令牌中的最后一个字符。但是,如果您有多行标记,您通常还想知道标记开头的行号;您需要将 yylineno 的值存储在前一个令牌的末尾。
幸运的是,您可以将宏YY_USER_ACTION 定义为一些C 代码;这将被插入到每个动作的开头(包括没有任何显式动作的规则。所以这样的事情将确保以前的值始终作为yylineno_start 可用:
#define YY_USER_ACTION \
yylineno_start = yylineno_saved; \
yylineno_saved = yylineno;
当然,您还需要声明这些变量。
Flex 不跟踪列位置。但也许你没关系。否则,您可以在上面提到的YY_USER_ACTION 中添加更多代码。简单的方法是也保存一个总字符数,并在当前行的末尾记录总字符数。维护总字符数很容易;您只需每次添加 yyleng 的值。为了保持行首的计数,您需要检查yylineno 的值是否发生了变化,如果是,则在标记中向后搜索以找到最后一个换行符。这只听起来效率低下。大多数时候,扫描时间很短。
这是一个仅使用行号跟踪的最小解决方案:
%option yylineno
%option noinput nounput noyywrap nodefault
%{
int yylineno_saved = 1;
#define YY_USER_ACTION \
yylineno_start = yylineno_saved; \
yylineno_saved = yylineno;
%}
%%
int yylineno_start;
[[:space:]] // Ignore whitespace including newlines
[[:digit:]]+ { printf("Integer %s at line %d\n", yytext, yylineno); }
\"(\\(.|\n)|[^\\"])*\" { printf("String from line %d to line %d\n",
yylineno_start, yylineno);
}
. // Ignore everything else
%%
int main(int argc, char** argv) {
return yylex();
}
正如上面第 2 点所建议的,这是一个更复杂的,它也跟踪字符位置。这个使用yylloc 全局变量,这是将完整令牌边界传达给野牛的常用方式。 (请注意,此代码不会与 less 或 more 合作。如果您使用这些功能,则需要为它们编写包装器。)
%option yylineno
%option noinput nounput noyywrap nodefault
%{
/* The following would usually be generated by bison if you
* enable location tracking in your bison definition.
*/
struct YYLTYPE {
int first_line;
int first_column;
int last_line;
int last_column;
};
struct YYLTYPE yylloc = {1,1,1,1};
/* We also need to keep the absolute character position, and the
* position at the beginning of the current line.
*/
int char_position = 0;
int line_start = 0;
#define YY_USER_ACTION \
char_position += yyleng; \
if (yylineno != yylloc.last_line) { \
char* p = yytext + yyleng; \
line_start = char_position; \
while (*--p != '\n') --line_start; \
} \
yylloc.first_line = yylloc.last_line; \
yylloc.first_column = yylloc.last_column; \
yylloc.last_line = yylineno; \
yylloc.last_column = char_position - line_start + 1;
/* Just for show */
void show_with_loc(const char* msg) {
printf("[%d:%d->%d:%d] %s",
yylloc.first_line, yylloc.first_column,
yylloc.last_line, yylloc.last_column,
msg);
}
%}
%%
[[:space:]] // Ignore whitespace including newlines
[[:digit:]]+ { show_with_loc("Integer\n"); }
\"(\\(.|\n)|[^\\"])*\" { show_with_loc("String\n"); }
. // Ignore everything else
%%
int main(int argc, char** argv) {
return yylex();
}
【讨论】:
%option yylineno 只识别换行符。 \n 是两个字符,一个反斜杠和一个 n。那不是换行符。我不知道为什么%option yylineno 不适合你;您实际上必须发布minimal reproducible example。
您可以在 Flex 中使用state or start condition 的概念。
"(双引号)时,都会启动一个名为<STRING> 的状态。<STRING> 状态中,您可以编写不同的规则集,例如 - 当您在字符串中得到一个反斜杠后跟一个新行时,您就知道它是一个多行字符串。您还可以在<STRING> 状态中单独检测换行符\n。 "(双引号)后,结束<STRING> 状态并返回<INITIAL> 状态。source code : string.l
%option noyywrap
%x STRING
%{
int line_count = 1;
%}
%%
\" {
printf("%d: String started\n", line_count);
BEGIN(STRING);
}
<STRING>"\\\n" { line_count++; }
<STRING>\" {
printf("%d: String ended\n", line_count);
BEGIN(INITIAL);
}
<STRING>"\\n" {
printf("new line\n");
}
<STRING>. {
printf("%s\n", yytext);
}
\n { line_count++; }
. {}
%%
int main(int argc,char *argv[]){
yyin = fopen(argv[1], "r"); // taking input from a file
yylex();
printf("\nTotal Lines: %d\n", line_count);
return 0;
}
试试这个输入法。
"single line"
"multi\
line"
【讨论】: