如何在 LEX/FLEX 中匹配模式时仅选择前 N 个字符并丢弃剩余的字符 注意事项

问题描述

我需要编写 Lex/Flex 代码来识别 Identifiers。 这里,Identifiers 被定义为 -

标识符 - 它可以有字母(小写)数字组合,以字母或下划线开头,并且只能选择前 20 个字符,剩下的要删除

我的问题是如何只选择前 20 个字符并丢弃剩余的字符。 示例输入:

_sdfasdfjh89234792jashdf

89ajshdf

所需输出

_sdfasdfjh89234792ja is an Identifier

89ajshdf is a normal string

经过多次尝试,我想出了以下解决方案,但这不是所需的输出。 我得到的输出

 _sdfasdfjh89234792jashdf is an Identifier
 89ajshdf is a normal string

我的解决方代码

%{
%}

%%
([a-z]|_)[_a-z0-9]{1,19} {printf("%s is an identifier\n",yytext);}
.* {printf("%s is normal string\n",yytext);} /* we will use ctrl+d to exit*/
%%

int yywrap(){}
int main(){
yylex();
return 0;
}

解决方法

问题陈述要求您识别标识符,然后使用每个标识符的前 20 个字符。这与接受最多 20 个字符作为标识符令牌完全不同,这正是您的代码尝试做的事情,因为在您扫描 20 个字符后,标识符的其余部分仍在输入流中,下次扫描将选择它作为第二个令牌,这是不想要的。所以你需要去掉有界重复运算符 {1,19}

yytext 中拥有令牌后,您需要在操作中将其截断。这是简单的 C 字符串操作。此处唯一有用的相关 (f)lex 功能是它将全局 yyleng 设置为令牌的长度(在 yytext 中)。

yytext 是一个内部临时缓冲区,因此如果您希望它的内容比 (f)lex 操作更有效,您需要制作一个副本。但是,如果您只想打印最多 20 个字符的令牌,则可以在 printf 格式字符串中使用长度限制:

[a-z_][a-z0-9_]*   { printf("%.20s is an identifier.\n",yytext); }

您还需要更改第二条规则,因为 .* 将匹配当前行的末尾。除非标识符正好位于行尾,否则 .* 将产生更长的匹配并且不会使用标识符规则。 (F)lex 总是选择最长的匹配项;仅在两个或多个规则都产生相同最长匹配的情况下优先考虑规则顺序。

如果您确实想返回字符串值,则需要制作最多 20 个字符的副本。最简单的方法是使用 strndup 函数:

yylval = strndup(yytext,20); /* This is a Posix function,so it's not in all C libraries. */

如果您没有 strndup,则必须自己制作副本,在这种情况下 yyleng 会派上用场:

if (yyleng > 20) yyleng = 20;
yylval = malloc(yyleng + 1);
memcpy(yylval,yytext,yyleng);
yylval[yyleng] = `\0`;

注意事项

  1. 您需要检查 strndupmalloc 返回的值以确保它不为 NULL。 NULL 表示内存不足错误。您还需要在某处声明 yylval;如果您使用 yacc/bison 进行解析,这将是自动的,但是您需要告诉 yacc/bison yylvalchar* 而不是默认的 int。并且不要忘记在您不再需要分配的字符串时free

  2. yyleng 稍微提高了效率,但如果您在其他编码环境中,您可以只使用 strnlen 来计算有界字符串长度:

     leng = strnlen(yytext,20);
     yylval = malloc(leng + 1);
     memcpy(yylval,leng);
     yylval[leng] = `\0`;
    

    不要使用 strlen 然后测试。无论字符串有多长,strlen 都必须计数到字符串的末尾,并且您不在乎精确的长度计数是多少。 strnlen 在达到限制时停止计数,从而避免了额外的工作。这不太可能对扫描仪产生很大的影响,但对于大获全胜的情况,这是一个好习惯。