有些语言语法在规则中使用否定。例如,在Dart规范中使用以下规则:
~('\'|'"'|'$'|NEWLINE)
这意味着匹配任何不是括号内规则之一的东西。现在,我知道在flex中我可以否定字符规则(例如:[^ ab],但是我想要否定的一些规则可能比单个字符更复杂,所以我认为我不能使用字符规则。例如,我可能需要否定多行字符串的序列“”“”,但我不确定在flex中这样做的方法是什么。
答案 0 :(得分:8)
(TL; DR:跳到底部以获得实际答案。)
任何常规语言的反面都是常规语言。所以理论上可以将正则表达式的逆写为正则表达式。不幸的是,这并不容易。
至少"""
案例并不太难。
首先,让我们清楚一下我们想要匹配的内容。
严格地说“not """
”意味着“"""
以外的任何字符串”。但那将包括x"""
。
所以说我们正在寻找“任何不包含"""
的字符串”可能很诱人。 (即.*""".*
的倒数)。但这也不太正确。典型的用法是将输入标记为:
"""This string might contain " or ""."""
如果我们在初始"""
之后开始并查找不包含"""
的最长字符串,我们会发现:
This string might contain " or "".""
而我们想要的是:
This string might contain " or "".
所以事实证明我们需要“任何不以"
结尾且不包含"""
”的字符串,这实际上是两个反转的结合:(~.*" ∧ ~.*""".*)
(相对)容易为此生成状态图:
(请注意,上面和“任何不包含"""
的字符串”的状态图之间的唯一区别在于,在该状态图中,所有状态都将接受,并且在此状态中为1并且2不接受。)
现在,挑战在于将其转变为正则表达式。有这样做的自动化技术,但它们产生的正则表达式通常很长而且很笨拙。不过,这种情况很简单,因为只有一个接受状态,我们只需要描述可以在该状态结束的所有路径:
([^"]|\"([^"]|\"[^"]))*
这个模型适用于任何简单的字符串,但是当字符串不仅仅是一个相同字符的序列时,它会更复杂一些。例如,假设我们希望匹配以END
而不是"""
结尾的字符串。天真地修改上述模式将导致:
([^E]|E([^N]|N[^D]))* <--- DON'T USE THIS
但正则表达式将匹配字符串
ENENDstuff which shouldn't have been matched
我们正在寻找的真实状态图是
并且将其作为正则表达式的一种方式是:
([^E]|E(E|NE)*([^EN]|N[^ED]))
再次,我通过追踪所有结束状态0的方式来产生:
[^E] stays in state 0
E in state 1:
(E|NE)*: stay in state 1
[^EN]: back to state 0
N[^ED]:back to state 0 via state 2
这可能是很多工作,无论是制作还是阅读。结果很容易出错。 (对于这类问题,状态图很容易进行形式验证,而不是使用可能会变得非常大的正则表达式。)
实用的Flex规则集使用start conditions来解决此类问题。例如,以下是如何识别python三引号字符串:
%x TRIPLEQ
start \"\"\"
end \"\"\"
%%
{start} { BEGIN( TRIPLEQ ); /* Note: no return, flex continues */ }
<TRIPLEQ>.|\n { /* Append the next token to yytext instead of
* replacing yytext with the next token
*/
yymore();
/* No return yet, flex continues */
}
<TRIPLEQ>{end} { /* We've found the end of the string, but
* we need to get rid of the terminating """
*/
yylval.str = malloc(yyleng - 2);
memcpy(yylval.str, yytext, yyleng - 3);
yylval.str[yyleng - 3] = 0;
return STRING;
}
这是有效的,因为如果.
是由TRIPLEQ
匹配的字符串的一部分,则开始条件"
中的"
规则将与{end}
不匹配; flex始终选择最长的匹配。使用[^"]+|\"|\n
代替.|\n
可以提高效率,因为这会导致更长的匹配,从而减少对yymore()
的调用;为了清楚起见,我并没有这样说。
此模型更容易扩展。特别是,如果我们想使用<![CDATA[
作为开头,]]>
作为终结符,我们只需要更改定义
start "<![CDATA["
end "]]>"
(如果使用上面建议的优化,可能是开始条件中的优化规则。)