使用 Flex 将 XML 架构 xs:annotation 元素视为注释

Using Flex to treat XML Schema xs:annotation elements as comments

我正在尝试使用 Flex 来标记 XML 架构文件。我想将 元素视为注释。下面是 XML 架构中的 元素示例:

<xs:annotation>
    <xs:documentation>This is a comment for humans</xs:documentation>
    <xs:appinfo>This is a comment for machines</xs:appinfo>
</xs:annotation>

我正在按照 Flex&Bison 书第 38 页上的示例使用 COMMENT 状态。这是我正在采取的方法:在遇到

时开始评论
"<xs:annotation>"    { BEGIN(COMMENT) ; }

遇到结束标签切换状态

<COMMENT>"</xs:annotation>"  { BEGIN(INITIAL); }

位于 xs:annotation 开始标记和结束标记之间的注释是除 << 之外的任何字符后跟除 /</ 后跟 x 以外的任何字符,或 </x 后跟 s 以外的任何字符,或 </xs 后跟 : 以外的任何字符,或</xs: 后跟 a 以外的任何字符,或 </xs:a 后跟 n

以外的任何字符
<COMMENT>([^<]|<[^/]|</[^x]|</x[^s]|</xs[^:]|</xs:[^a]|</xs:a[^n])+

不幸的是,Flex 给出了这个错误信息:

unrecognized rule

请问我做错了什么?

/ 是 (f)lex(“尾随上下文”)中的正则表达式运算符。要将其用作文字字符,必须将其引用、转义或包含在字符 class 中。所以这些中的任何一个都可以用来表示固定字符串 </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 中否定字符 classes 确实匹配一个换行符,除非集合中提到了换行符。)

但是请注意,我们仍然需要一个匹配单个字符的规则,因为上面的规则不会匹配 <。由于这两个规则具有相同的操作(什么都不做),我们可以将它们组合起来:

<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?)?)?)?)?)?)?)?)?)?)?)?)?)?   ;

但是请注意,只有在词法语法的其他地方没有回退的情况下才值得这样做。所以如果你不想仔细数括号,不用担心。