Welcome

首页 / 软件开发 / C# / C#词法分析器(六)构造词法分析器

C#词法分析器(六)构造词法分析器2014-04-14现在最核心的 DFA 已经成功构造出来了,最后一步就是根据 DFA 得到完整的词法分析器。

由于目前还不能像 Flex 那样支持词法定义文件,所以仍然需要在程序中定义规则,而且也不能非常灵活的自定义词法分析器,不过基本的东西完全够用了。

一、词法规则的定义

词法分析器用到的所有规则都在 Grammar<T> 类中定义,这里的泛型参数 T 表示词法分析器的标识符的类型(必须是一个枚举类型)。定义规则方法包括:定义上下文的 DefineContext 方法、定义正则表达式的 DefineRegex 方法和定义终结符的 DefineSymbol 方法。

调用 DefineContext 方法定义的词法分析器上下文,会使用 LexerContext 类的实例表示,它的基本定义如下所示:

// 当前上下文的索引。 int Index; // 当前上下文的标签。 string Label; // 当前上下文的类型。 LexerContextType ContextType;
在词法分析器中,仅可以通过标签来切换上下文,因此 LexerContext 类本身被设置为 internal。

上下文的类型就是包含型或者排除型,等价于 Flex 中的 %s 和 %x 定义(可参见 Flex 的 Start Conditions)。这里简单的解释下,在进行词法分析时,如果当前上下文是排除型的,那么仅在当前上下文中定义的规则会被激活,其它的(非当前上下文中定义的)规则都会失效。如果当前上下文是包含型的,那么没有指定任何上下文的规则也会被激活。

默认上下文标签为 "Initial"。

Grammar<T> 中定义正则表达式的 DefineRegex 方法,就等价于 Flex 中的定义段(Definitions Section),可以定义一些常见的正则表达式以简化规则的定义,例如可以定义

grammar.DefineRegex("digit", "[0-9]");
在正则表达式的定义中,就可以直接使用 "{digit}" 来引用预先定义的正则表达式。

最后是定义终结符的 DefineSymbol 方法,就对应于 Flex 中的规则段(Rules Section),可以定义终结符的正则表达式和相应的动作。

终结符的动作使用 Action<ReaderController<T>> 来表示,由 ReaderController<T> 类来提供 Accept,Reject,More 等方法。

其中,Accept 方法会接受当前词法单元,并返回 Token 对象。Reject 方法会拒绝当前匹配,转而寻找次优的规则,这个操作会使词法分析器的所有匹配变慢,需要谨慎使用。More 方法通知词法分析器,下次匹配成功时,不替换当前的文本,而是把新的匹配追加在后面。

Accept 方法和 Reject 方法是相互冲突的,每次匹配成功只能调用其中的一个。如果两个都未调用,那么词法分析器会认为当前匹配是成功的,但不会返回 Token,而是继续匹配下一个词法单元。

二、词法分析器的实现

2.1 基本的词法分析器

由于多个规则间是可能产生冲突的,例如字符串可以与多个正则表达式匹配,因此在说明词法分析器之前,首先需要定义一个解决冲突的规则。这里采用与 Flex 相同的规则:

总是选择最长的匹配。

如果最长的匹配与多个正则表达式匹配,总是选择先被定义的正则表达式。

基本的词法分析器非常简单,它只能实现最基础的词法分析器功能,不能支持向前看符号和 Reject 动作,但是大部分情况下,这就足够了。

这样的词法分析器几乎相当于一个 DFA 执行器,只要不断从输入流中读取字符送入 DFA 引擎,并记录下来最后一次出现的接受状态就可以了。当 DFA 引擎到达了死状态,找到的词素就是最后一次出现的接受状态对应的符号(这样就能保证找到的词素是最长的),对应多个符号的时候只取第一个(之前已经将符号索引从小到大进行了排序,因此第一个符号就是最先定义的符号)。