您的语言几乎太简单了,不需要解析器生成器。同时,它不是上下文无关的,这使得解析器生成器的使用变得困难。因此,Nearly 解析器很可能不是最适合您的工具,尽管可能需要一些技巧才能使其工作。
首先要事。您实际上并没有提供您的语言的明确定义,这就是您的解析器报告歧义的原因。要查看歧义性,请考虑输入
[aa]My text[/ab][ab]Another Text[/aa]
这与您的测试输入非常相似;我所做的只是交换一对字母。现在,问题是:这是一个由单个组成的有效输入吗?aa
标签?或者是语法错误? (这是一个严肃的问题。像这样的标签系统的一些定义认为标签只能由匹配的关闭标签关闭,因此看起来像不同标签的东西被认为是纯文本。这样的系统将接受输入作为单个标记值。)
问题是你定义了string
as sstrchar:*
,如果我们看一下定义sstrchar
in string.ne https://github.com/kach/nearley/blob/master/builtin/string.ne,我们看到(忽略不相关的后处理操作):
sstrchar -> [^\\'\n]
| "\\" strescape
| "\\'"
现在,第一种可能性是“除反斜杠、单引号或换行符之外的任何字符”,并且很容易看出中的所有字符[/ab]
are in sstrchar
。 (我不清楚你为什么选择sstrchar
;单引号在您的语言中似乎并不特殊。或者也许你只是没有提到它们的重要性。)所以string
可以延伸到输入的末尾。当然,该语法需要结束标记,并且如果有的话,Nearley 解析器会确定查找匹配项。但事实上,有两个。因此解析器声明了一个歧义,因为它没有任何标准在两个关闭标记之间进行选择。
这就是我们遇到的问题,即您的语言不是上下文无关的。 (实际上,从某种技术意义上来说,它是上下文无关的,因为“只有”676 个不区分大小写的两个字母的标签,理论上可以列出所有 676 种可能性。但我猜你不希望要做到这一点。)
上下文无关语法无法表达坚持两个非终结符扩展为同一字符串的语言。这就是上下文无关的定义:如果一个非终端只能匹配与前一个非终端相同的输入,那么
第二个非终结符匹配取决于上下文,特别是第一个非终结符产生的匹配。在上下文无关语法中,无论文本的其余部分如何,非终结符都会扩展为相同的内容。非终结符出现的上下文不允许影响扩展。
现在,您很可能期望您的宏定义:
openAndCloseTag[X] -> "[" $X "]" string "[/" $X "]"
通过重复来表达上下文相关的匹配$X
宏参数。但是,Nearley 文档将这种构造描述为宏并非偶然。X
这里指的是宏调用中使用的字符串。所以当你说:
openAndCloseTag[[a-zA-Z] [a-zA-Z]]
近乎宏将其扩展为
"[" [a-zA-Z] [a-zA-Z] "]" string "[/" [a-zA-Z] [a-zA-Z] "]"
这就是它将用作语法产生式的内容。观察两者$X
宏参数已扩展为相同的参数,但这并不意味着将匹配相同的输入文本。每个子模式都将独立匹配任意两个字母字符。与上下文无关。
正如我之前提到的,您可以使用此宏写出 676 种可能的标记模式:
tag -> openAndCloseTag["aa"i]
| openAndCloseTag["ab"i]
| openAndCloseTag["ac"i]
| ...
| openAndCloseTag["zz"i]
如果你这样做了(并且你成功地正确列出了所有可能性),那么解析器就不会抱怨歧义只要您永远不会在同一输入中使用同一标签两次。因此,您的原始输入和我更改后的输入都可以(只要您接受我的输入是单个标记对象的解释)。但它仍然会报告以下内容不明确:
[aa]My text[/aa][aa]Another Text[/aa]
这是不明确的,因为语法允许它是一个单一的aa
带标签的字符串(其文本包含看起来像关闭标签和打开标签的字符)或作为两个连续的aa
标记的字符串。
为了消除歧义,您必须编写string
以不允许内部标签的方式进行模式,就像sstrchar
不允许内部单引号。当然,匹配不包含模式的字符串并不比匹配不包含单个字符的字符串那么简单。可以使用 Nearley 来完成,但我真的不认为这是你想要的。
也许你最好的选择是使用原生 Javascript 正则表达式来匹配标记的字符串。这将被证明更简单,因为 JavaScript 正则表达式比数学正则表达式更强大,甚至允许匹配(某些)上下文相关结构的可能性。例如,您可以将 Javascript 正则表达式与 Moo 词法分析器一起使用,它可以很好地集成到 Nearley 中。或者您可以直接使用正则表达式,因为一旦匹配了标记的文本,您就不需要做太多其他事情了。
为了帮助您入门,这里有一个简单的 Javascript 正则表达式,它将标记的字符串与匹配的不区分大小写的标签(i
标志在最后):
/\[([a-zA-Z]{2})\].*?\[\/\1\]/gmi
您可以使用它在线玩正则表达式101 https://regex101.com/r/Z9U0s1/1