/ 是 (f)lex (“尾随上下文”)中的正则表达式运算符。要将其用作文字字符,它必须被引用、转义或包含在字符类中。所以其中任何一个都可以用来表示固定字符串</xs:
<"/"xs:
"</xs:"
<\/xs:
<[/]xs:
如果您是 (f)lex 的新手,您可能不熟悉尾随上下文运算符或带引号的字符串等功能,这些功能在大多数正则表达式库中都没有实现。您可能需要花几分钟时间阅读flex manual section on pattern syntax。时间不长。
识别开始和结束分隔符之间的文本的最简单模板如下:
%x COMMENT
// ...
%%
"<xs:annotation>" { BEGIN(COMMENT) ; }
<COMMENT>"</xs:annotation>" { BEGIN(INITIAL); }
<COMMENT>.|\n ;
<COMMENT><<EOF>> { fputs("Unterminated comment\n", stderr): }
(除了倒数第二行之外的所有内容都与您已有的相同。)
值得注意的是它是如何工作的。模式.|\n 绝对匹配任何单个字符。但它不会干扰结束分隔符的检测,因为所谓的“最大咀嚼”规则规定词法扫描器总是在每个点接受最长的匹配。这是 (f)lex 生成的扫描器用来决定两个匹配模式中的哪一个适用的规则。 (如果多个规则产生相同的最长匹配,扫描器会选择文件中第一个出现的那个。)
因此,当输入以 </xs:annotation> 开头时,.|\n 模式将不匹配,因为存在(很多)更长的匹配项。
你可以停在那里。但是,正如 John Levine 在 Flex 和 Bison 中指出的那样,这并不是最有效的解决方案,因为每个单独的字符都被视为单个标记。即使没有与令牌关联的操作,它仍然会产生与令牌匹配相关的所有开销。所以添加一个匹配更长序列的附加规则是有意义的;但是,这种较长的模式不得干扰对结束分隔符的识别。
例如,我们可以添加规则:
<COMMENT>[^<]+ ;
这将匹配不包括< 的任何字符序列,从而在文本上移动更少的标记。 (在 Flex 和 Bison 中,等效规则写为 ([^*]|\n)+,但不需要显式换行匹配,因为在 (f)lex 中,否定字符类确实匹配换行,除非在设置。)
但请注意,我们仍然需要一个匹配单个字符的规则,因为上面的规则不会匹配 <。由于这两个规则具有相同的作用(即什么都不做),我们可以将它们结合起来:
<COMMENT>[^<]+|. ;
这几乎肯定是您应该停止阅读的地方 :-) 但是您似乎一直在寻求扩展此优化以匹配以 < 开头的其他较长序列,这些序列与结束分隔符不匹配,所以我会注意到这种优化可以扩展,但这需要小心谨慎。例如,如果我们要写:
<COMMENT>([^<]+|<[^/])|. ;
我们会发现(很快,如果我们编写了足够的单元测试:-))扫描仪无法识别
<xs:annotation>This is not valid XML: <</xs:annotation>
这对您的项目来说可能不是一个大问题,但在尝试手写否定正则表达式时需要考虑到这一点。正确的扩展名是:
<COMMENT>([^<]+|<[^/<])|. ;
实际上,额外的令牌匹配所产生的开销确实很小,不值得英勇努力避免它。简单的版本几乎肯定足以满足所有实际用途。但是,它确实会带来不同的词汇开销,因为它会强制扫描器在每次遇到关闭分隔符以外的关闭标记时回退到先前的位置。回退的问题与其说是回退的成本——在大多数情况下,这种情况并不常见——不如说是词法规则中任何地方都存在回退阻止 flex 对词法分析器应用优化. (在没有回退的扫描仪中,没有必要在每个输入位置检查规则是否可以在该点匹配以保存回退信息。)
为了消除回退,我们可以添加一个匹配结束分隔符的任何前缀的规则。 (由于<在开始后没有出现在结束分隔符中,我们不必担心可能的重叠,如上所述。并非所有可能的结束分隔符都是这种情况。)
<COMMENT><("/"(x(s(:(a(n(n(o(t(a(t(i(on?)?)?)?)?)?)?)?)?)?)?)?)?)? ;
但请注意,只有在词汇语法的其他地方没有后备时才值得这样做。因此,如果您不想仔细计算括号,请不要担心。