正则表达式设置字符串文字的最大长度

Regular expression set max length for string literal

我想知道如何在正则表达式中设置最大长度。我的目标是将字符串文字的正则表达式设置为最大长度 80。

如果你需要的话,这是我的表达:

["]([^"\]|\(.|\n))*["]|[']([^'\]|\(.|\n))*['] 

我已经尝试在表达式的开头和末尾添加 {0,80},但是要么所有的字符串分解成更小的标识符,要么 none 到目前为止。

在此先感谢您的帮助!

编辑:

这是对我要完成的工作的更好解释。 给定“此字符串超过 80 个字符长”,当 运行 通过 flex 而不是像这样列出时:

行:1,词素:|这个字符串超过 80 个字符|,长度:81,标记 4003

我需要像这样分解它:

行:1,词位:|THIS|,长度:1,令牌 6000

行:1,词位:|STRING|,长度:1,令牌 6000

行:1,词位:|IS|,长度:1,令牌 6000

行:1,词位:|OVER|,长度:1,令牌 6000

行:1,词素:|80|,长度:1,令牌 6000

行:1,词位:|CHARACTERS|,长度:1,标记 6000

行:1,词位:|LONG|,长度:1,令牌 6000

虽然字符串“THIS STRING IS NOT OVER 80 CHARACTERS LONG”将显示为:

行:1,词位:|这个字符串不超过 80 个字符|,长度:50,标记:4003

如果您在 flex 中使用正则表达式,并且需要监视其长度,最简单的方法是查看 yylex(或类似)中保存的匹配字符串:

["]([^"\]|\(.|\n))*["]|[']([^'\]|\(.|\n))*[']    { if (strlen(yylex) > 82) { ... } }

我用82来解释两个双引号字符'"'。 如果这 不是 您的设置,请在评论中告诉我,我会删除我的答案(不需要 down-vote :))

I've tried adding {0,80} at both the front and the end of the expression

大括号运算符不是长度限制;这是一系列重复计数。它必须到达重复运算符(*+?)将到达的位置:在重复子模式之后立即。

因此,在您的情况下,您可以使用:(为了清楚起见,我省略了 ' 替代方案。)

    ["]([^"\\n]|\(.|\n)){0,80}["]

通常情况下,我会建议您不要这样做,或者至少要谨慎行事。 (F)lex 正则表达式被编译成状态转换表,编译最大重复计数的唯一方法是为每次重复复制一次子模式状态。所以上面的模式需要为 ([^"\]|\(.|\n)) 复制 80 份状态转换。 (对于这样一个简单的子模式,状态 blow-up 可能不会太严重。但是对于更复杂的子模式,您最终可能会得到巨大的转换表。)

编辑:将长字符串拆分为标记,就好像它们没有被引用一样。

对问题的编辑表明,预期的是将长度大于 80 个字符的字符串视为从未输入过引号;也就是说,将它们报告为单独的单词和数字标记,没有任何中间的 whitespace。这对我来说似乎很奇怪,我无法说服自己我正在正确阅读需求,但如果我是的话,这里是一个可能的方法的概述。

假设我们的意图是将短字符串报告为单个标记,而长字符串应重新解释为一系列标记(可能但不一定与未加引号的输入生成的标记相同)。如果是这样的话,确实有两个词法分析需要指定,并且它们不会使用相同的模式规则。 (一方面,重新扫描需要将引号识别为文字的 end,导致扫描器恢复正常处理,而原始扫描将引号视为 开始一个字符串文字。)

一种可能性是只收集整个长字符串,然后使用不同的词法扫描器将其分解成任何看起来有用的部分,但这需要一些复杂的管道来记录生成的标记流和 return 它一次向 yylex 调用者发送一个令牌。 (如果 yylex 标记推送到解析器,这将相当容易,但这是另一个场景。)所以除了提及之外,我将放弃这个选项这是可能的。

所以显然更简单的选择是确保原始扫描在第 81 个字符处停止,以便它可以更改词法规则并备份扫描以应用新规则。

(F)lex 提供 start conditions 作为提供不同词汇上下文的方式。通过在 (f)lex 操作中使用 BEGIN 宏,可以在启动条件之间动态切换,将扫描器切换到不同的上下文中。 (它们被称为“开始条件”,因为它们会在令牌开始时更改扫描器的状态。)

每个启动条件(默认启动条件除外,称为INITIAL)都需要在flex序言中声明。在这种情况下,我们只需要一个额外的开始条件,我们称之为 SC_LONG_STRING。 (按照惯例,开始条件名称全部大写,因为它们被翻译成 C 宏或枚举值。)

Flex 有两种可能的扫描备份机制,其中任何一种都可以在这里使用。我将显示显式 back-up 因为它更安全、更灵活;另一种方法是使用尾随上下文运算符 (/),它在此解决方案中效果很好,但在其他非常相似的上下文中效果不佳。

所以我们首先声明我们的开始条件,然后是在默认 (INITIAL) 词法上下文中处理带引号的字符串的规则:

%x SC_LONG_STRING
%%

我只展示了 double-quote 规则,因为 single-quote 规则实际上是相同的。 (Single-quote 将需要另一个开始条件,因为终止模式不同。)

第一个规则匹配文字中最多有 80 个字符或转义序列的字符串,使用如上所述的重复运算符。

["]([^"\\n]|\(.|\n)){0,80}["]    { yylval.str = strndup(yytext + 1, yyleng - 2);
                                     return TOKEN_STRING; 
                                   }

第二条规则正好匹配一个额外的 non-quote 字符。它不会试图找到字符串的结尾;将在 SC_LONG_STRING 规则内处理。该规则做了两件事:

  • 切换到不同的开始条件。
  • 告诉扫描器备份扫描,使用 yyless(n) 特殊操作,在 n 个字符处截断当前令牌,并导致下一个令牌扫描在该点重新启动。所以 `yyless(1) 只留下当前标记中的 "。由于我们此时不 return,因此当前标记立即被丢弃。
["]([^"\\n]|\(.|\n)){81}         { BEGIN(SC_LONG_STRING); yyless(1); }

最终规则是未终止字符串的回退;它会如果启动了看起来像字符串的东西,但上述规则都不匹配,则 gger。只有在收盘价之前遇到换行符或 end-of-file 指标才会发生这种情况:

["]([^"\\n]|\(.|\n)){0,80}       { yyerror("Unterminated string"); }

现在,我们需要为 SC_LONG_STRING 指定规则。为简单起见,我假设只需要将字符串拆分为白色space 分隔的单元;如果您想进行不同的分析,可以在此处更改模式。

通过在尖括号内写入开始条件的名称来标识开始条件。起始条件名称被认为是模式的一部分,因此不应在其后跟 space(space 字符在 lex 模式中是不允许的,除非它们是引号字符)。 Flex 更灵活;阅读引用的手册部分以获取更多详细信息。

当双引号终止字符串时,第一个规则只是 return 到 INITIAL 开始条件。第二条规则丢弃长字符串中的白色 space,第三条规则将 whitespace-separated 组件传递给调用者。最后,我们需要考虑未终止的长字符串可能出现的错误,这将导致遇到换行符或end-of-file 指示符。

<SC_LONG_STRING>["]                      { BEGIN(INITIAL); }
<SC_LONG_STRING>[ \t]+                   ;
<SC_LONG_STRING>([^"\ \n\t]|\(.|\n))+  { yylval.str = strdup(yytext);
                                           return TOKEN_IDENTIFIER;
                                         }
<SC_LONG_STRING>\n                       |
<SC_LONG_STRING><<EOF>>                  { yyerror("Unterminated string"); }

原答案:对长字符串产生有意义的错误

如果用户输入的字符串太长,您需要指定您打算执行的操作 如果您的扫描仪无法将其识别为字符串,那么它将 return 某种 fall-back 可能会导致解析器出现语法错误的标记;这不会向用户提供有用的反馈,因此他们可能不知道语法错误的来源。而且你当然不能在恰好太长的字符串中间重新开始词法分析:这将最终解释应该被引用的文本,就好像它是标记一样。

更好的策略是识别任意长度的字符串,然后检查与模式关联的动作中的长度。作为第一个近似值,你可以试试这个:

["]([^"\]|\(.|\n)){0,80}["]   { if (yyleng <= 82) return STRING;
                                  else {
                                    yyerror("String literal exceeds 80 characters");
                                    return BAD_STRING;
                                  }
                                }

(注:(F)lex将变量yyleng设置为yytext的长度,所以永远不需要调用strlen(yytext)strlen需要扫描它的参数来计算长度,所以它的效率相当低。此外,即使在你需要调用 strlen 的情况下,你也不应该用它来检查字符串是否超过最大长度。相反,使用 strnlen,这将限制扫描的长度。)

但这只是第一个近似值,因为它计算的是源字符,而不是字符串文字的长度。因此,例如,假设您计划允许十六进制转义,字符串文字 "\x61" 将被视为有四个字符,这很容易导致包含转义的字符串文字因太长而被错误地拒绝。

该问题已得到改善,但重复次数有限的模式并未解决该问题,因为该模式本身并未完全解析转义序列。在模式["]([^"\]|\(.|\n)){0,80}["]中,\x61转义序列将被算作3次重复(\x61),这仍然比单个的多它所代表的性格。作为另一个示例,拼接(\ 后跟一个换行符)将被计为一次重复,而它们根本不会增加文字的长度,因为它们只是被删除了。

所以如果你想正确地限制字符串文字的长度,你将不得不更精确地解析源表示。 (或者你需要在识别它之后重新解析它,这看起来很浪费。)这通常用 .

来完成。