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

时间:2020-09-08 03:48:11

标签: c++ regex lex lexical-analysis

我试图弄清楚如何在正则表达式中设置最大长度。我的目标是将字符串文字的正则表达式设置为最大长度为80。

如果需要,这是我的表情:

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

我尝试在表达式的开头和结尾处都添加{0,80},但是到目前为止所有字符串都分解为较小的标识符,或者到目前为止都没有。

提前感谢您的帮助!

编辑:

这是我要完成的工作的更好解释。 给定“此字符串长度超过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,词位:|字符|,长度:1,令牌6000

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

字符串“此字符串长度不超过80个字符”时显示为:

行:1,词位:|此字符串长度不超过80个字符|,长度:50,令牌:4003

2 个答案:

答案 0 :(得分:1)

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

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

我用82来解释两个双引号字符'"'。 如果这不是您的设置,请在评论中让我知道,我将删除我的答案(无需投票:))

答案 1 :(得分:1)

我尝试在表达式的开头和结尾处添加{0,80}

大括号运算符不是长度限制;这是重复次数的范围。必须去到重复运算符(*+?)将要去的地方:子图案被重复之后。

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

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

通常,我建议您不要这样做,或者至少要格外小心。 (F)lex正则表达式被编译为状态转换表,并且编译最大重复计数的唯一方法是为每个重复复制一次子模式状态。因此,上述模式需要为([^"\\]|\\(.|\n))制作80个状态转换的副本。 (使用这样的简单子模式,状态爆炸可能不会太严重。但是,如果使用更复杂的子模式,则最终可能会有大量的过渡表。)

编辑:将长字符串拆分为标记,就像没有引号一样。

对该问题进行的编辑表明,所期望的是将长度大于80个字符的字符串视为从未输入引号引起来。也就是说,将它们作为单个单词和数字令牌报告,而无需插入空格。在我看来,这是如此特质,以致于无法说服自己正确地阅读了要求,但是如果我愿意,这里是一种可行方法的概述。

让我们的目的是,短字符串应报告为单个标记,而长字符串应重新解释为一系列标记(可能但不一定与未引用输入产生的标记相同)。在这种情况下,实际上需要指定两个词法分析,并且它们将不使用相同的模式规则。 (一方面,重新扫描需要将引号识别为文字的 end ,从而使扫描器恢复正常处理,而原始扫描将引号视为 start < / em>字符串文字。)

一种可能性是只收集整个长字符串,然后使用不同的词法扫描器将其分成看起来有用的任何部分,但这将需要一些复杂的操作才能记录生成的令牌流并返回一个令牌一次接到yylex呼叫者。 (如果yylex推入标记到解析器中,这将相当容易,但这是另一种情况。)因此,我将丢弃此选项,除了提及它是可能。

因此,看似更简单的选择是确保原始扫描停止在第81个字符上,以便它可以更改词汇规则并备份扫描以应用新规则。

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

每个启动条件(默认启动条件除外,称为INITIAL)都需要在flex序言中声明。在这种情况下,我们只需要一个额外的开始条件,我们将其称为SC_LONG_STRING。 (按照惯例,由于起始条件名称会转换为C宏或枚举值,因此会以ALL CAPS格式编写。)

Flex有两种可能的机制来备份扫描,这两种机制都可以在这里使用。我将显示显式备份,因为它更安全,更灵活。另一种选择是使用尾随上下文运算符(/),该尾部上下文运算符在此解决方案中会很好地工作,但在其他非常相似的上下文中则不能。

因此,我们首先声明启动条件,然后声明在默认(INITIAL)词法上下文中处理带引号的字符串的规则:

%x SC_LONG_STRING
%%

我只显示双引号规则,因为单引号规则实际上是相同的。 (单引号将需要另一个开始条件,因为终止方式不同。)

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

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

第二个规则恰好匹配一个附加的非引号字符。它不会尝试找到字符串的结尾;将在SC_LONG_STRING规则内处理。该规则有两件事:

  • 切换到其他启动条件。​​
  • 使用yyless(n)特殊操作告诉扫描程序以备份扫描,该操作将以n个字符截断当前令牌,并在该点重新开始下一次令牌扫描。因此,`yyless(1)仅在当前令牌中保留“。”由于我们此时不返回,因此将立即丢弃当前令牌。
["]([^"\\\n]|\\(.|\n)){81}         { BEGIN(SC_LONG_STRING); yyless(1); }

最终规则是未终止字符串的后备;如果启动了一个看起来像字符串的东西,但以上两个规则都不匹配,它将触发。仅当在结束引号之前遇到换行符或文件结尾指示符时,才会发生这种情况:

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

现在,我们需要为SC_LONG_STRING指定规则。为了简单起见,我将假设只需要将字符串拆分为空格分隔的单元;如果要进行其他分析,可以在此处更改模式。

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

当双引号终止字符串时,第一个规则只是返回到INITIAL起始条件。第二条规则丢弃长字符串内的空格,而第三条规则将由空格分隔的组件传递给调用方。最后,我们需要考虑未终止的长字符串的可能错误,这将导致遇到换行符或文件结尾指示符。

<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"); }

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

如果用户输入的字符串太长,则需要指定要执行的操作如果扫描仪无法将其识别为字符串,则它将返回某种回退令牌,这可能会导致来自解析器的语法错误;不会向用户提供有用的反馈,因此他们可能不会知道语法错误的来源。而且,您当然不能在恰好太长的字符串中间重新开始词法分析:这最终将解释原本应该被当作引号引用的文本。

更好的策略是识别任意长度的字符串,然后在与模式相关联的操作中检查长度。作为第一近似,您可以尝试以下方法:

["]([^"\\]|\\(.|\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的情况下,也不应使用它来检查字符串是否超过最大长度,而应使用{ {3}},这将限制扫描的时间。)

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

通过重复次数有限的模式,该问题得以缓解,但并未解决,因为模式本身无法完全解析转义序列。在模式["]([^"\\]|\\(.|\n)){0,80}["]中,\x61转义序列将被计为三个重复(\x61),这仍然比单个重复数多它代表的字符。再举一个例子,拼接(\后跟换行符)将被视为一次重复,而拼接却被删除,因此根本不影响文字的长度。

因此,如果您想正确地限制字符串文字的长度,则必须更精确地解析源表示形式。 (或者您需要在识别它之后重新解析它,这似乎很浪费。)通常使用strnlen完成。