假设您的语言标识符可能以关键字开头。例如,假设“case”是关键字,但“caser”是有效的标识符。还假设词法分析器规则只能处理正则表达式。然后似乎我不能在词法分析器中的标识符规则之前放置关键字规则,因为这会将“caser”解析为“case”后跟“r”。我也不能在标识符规则之后放置关键字lexing规则,因为标识符规则将匹配关键字,关键字规则永远不会触发。
所以,相反,我可以在词法分析器中创建一个keyword_or_identifier规则,并让解析器确定keyword_or_identifier是关键字还是标识符。这是通常做的吗?
我意识到“使用具有前瞻性的不同词法分析器”是一种答案(种类),但我也对如何在传统的基于DFA的词法分析器中完成这一操作感兴趣,因为我当前的词法分析器似乎有效方式。
答案 0 :(得分:7)
从原始lex
开始,大多数词法分子符合以下选项:
使用最长的匹配。
如果有两个或更多选项与最长匹配相关,请使用词法分析器定义中的第一个。
这允许以下样式:
"case" { return CASE; }
[[:alpha:]][[:alnum:]]* { return ID; }
如果输入模式为caser
,则将使用第二种替代方案,因为它是最长匹配。如果输入模式为case r
,则将使用第一个替代方案,因为它们都匹配case
,并且通过上面的规则(2),第一个获胜。
虽然这似乎有点武断,但它主要与DFA方法一致。首先,DFA在第一次达到接受状态时不会停止。如果确实如此,则像[[:alpha:]][[:alnum:]]*
这样的模式将毫无用处,因为它们会在第一个字符上进入接受状态(假设其为字母)。相反,基于DFA的词法分析器会继续,直到当前状态没有可能的转换,然后它们会备份直到最后一个接受状态。 (见下文。)
由于两个不同的规则,给定的DFA状态可能正在接受,但这也不是问题;只记录第一个接受规则。
公平地说,这与DFA的数学模型略有不同,DFA的每个状态中的每个符号都有一个转换(尽管它们中的许多可能转换为“接收”状态),并且匹配完整输入取决于当读取输入的最后一个符号时自动机是否处于接受状态。词法分析器模型略有不同,但也可以很容易地形式化。
理论模型中唯一的困难是“回到最后的接受状态”。在实践中,这通常通过在每次达到接受状态时记住状态和输入位置来完成。这意味着可能需要回滚输入流,可能需要任意数量。
大多数语言不需要经常备份,很少需要无限期备份。如果没有备份状态,某些词法生成器可以生成更快的代码。 (flex
如果您使用-Cf
或-CF
,则会执行此操作。)
导致无限期备份的一个常见情况是无法为字符串文字提供适当的错误返回:
["][^"\n]*["] { return STRING; }
/* ... */
. { return INVALID; }
此处,如果同一行上有匹配的"
,则第一个模式将匹配以"
开头的字符串文字。 (为了简单起见,我省略了\
- escapes。)如果字符串文字是未终止的,则最后一个模式将匹配,但输入需要重绕到"
。在大多数情况下,通过忽略无法匹配的"
来尝试继续进行词汇分析是毫无意义的。忽略整行的其余部分会更有意义。因此,不仅备份效率低下,而且还可能导致虚假错误消息的爆炸。更好的解决方案可能是:
["][^"\n]*["] { return STRING; }
["][^"\n]* { return INVALID_STRING; }
这里,第二个替代方案只有在字符串未终止时才能成功,因为如果字符串终止,则第一个替代字符将匹配另一个字符。因此,这些替代品出现的顺序并不重要,尽管我认识的每个人都会按照我的顺序排列。