我尝试在表达式的前面和结尾都添加{0,80}
大括号运算符没有长度限制;这是一个重复计数的范围。它必须去重复操作符(*、+ 或 ?)要去的地方:在子模式被重复之后。
因此,在您的情况下,您可以使用:(为清楚起见,我省略了 ' 替代项。)
["]([^"\\\n]|\\(.|\n)){0,80}["]
通常情况下,我建议您不要这样做,或者至少要小心谨慎。 (F)lex 正则表达式被编译成状态转换表,编译最大重复计数的唯一方法是每次重复复制一次子模式状态。因此,上述模式需要为([^"\\]|\\(.|\n)) 制作 80 个状态转换副本。 (对于这样一个简单的子模式,状态爆炸可能不会太严重。但是对于更复杂的子模式,您最终可能会得到巨大的转换表。)
编辑:将长字符串拆分为标记,就好像它们没有被引用一样。
对该问题的编辑表明,预期将长度大于 80 个字符的字符串视为从未输入过引号;也就是说,将它们报告为单个单词和数字标记,没有任何中间空格。这对我来说似乎很特殊,以至于我无法说服自己我正确地阅读了要求,但如果我是的话,这里是一种可能方法的概要。
假设我们的意图是短字符串应该被报告为单个标记,而长字符串应该被重新解释为一系列标记(可能但不一定与不带引号的输入产生的标记相同)。如果是这样的话,确实需要指定两个词法分析,并且它们不会使用相同的模式规则。 (一方面,重新扫描需要将引号识别为文字的end,导致扫描仪恢复正常处理,而原始扫描将引号视为start 字符串文字。)
一种可能性是只收集整个长字符串,然后使用不同的词法扫描器将其分解成任何看起来有用的部分,但这需要一些复杂的管道来记录生成的令牌流并返回一个令牌一次给yylex 来电者。 (如果yylex 将令牌推送 到解析器,这将相当容易,但这是完全不同的情况。)所以我将放弃这个选项,而不是提到它是可能的。
因此,显然更简单的选择是确保原始扫描在第 81 个字符处停止,以便它可以更改词法规则并备份扫描以应用新规则。
(F)lex 提供start conditions 作为提供不同词汇上下文的一种方式。通过在 (f)lex 操作中使用 BEGIN 宏,可以在启动条件之间动态切换,将扫描仪切换到不同的上下文。 (它们被称为“开始条件”,因为它们会在令牌开始时更改扫描仪的状态。)
每个开始条件(除了默认的开始条件,称为INITIAL)都需要在flex prologue中声明。在这种情况下,我们只需要一个额外的开始条件,我们称之为SC_LONG_STRING。 (按照惯例,开始条件名称全部大写,因为它们被翻译成 C 宏或枚举值。)
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 的情况下,也不应使用它来检查字符串是否超过最大长度。而是使用strnlen,这将限制字符串的长度扫描。)
但这只是第一个近似值,因为它计算源字符,而不是字符串文字的长度。因此,例如,假设您计划允许十六进制转义,字符串文字 "\x61" 将被视为有四个字符,这很容易导致包含转义的字符串文字被错误地拒绝太长。
限制重复次数的模式改善了这个问题,但并没有解决这个问题,因为模式本身并没有完全解析转义序列。在["]([^"\\]|\\(.|\n)){0,80}["] 模式中,\x61 转义序列将被计为三个重复(\x、6、1),这仍然比它所代表的单个字符要多。再举一个例子,拼接(\ 后跟换行符)将被计为一次重复,而它们根本不影响文字的长度,因为它们只是被删除了。
因此,如果您想正确限制字符串文字的长度,则必须更精确地解析源表示。 (或者你需要在识别它之后重新解析它,这似乎很浪费。)这通常使用start condition来完成。