Compiler construction 在创建DFA后实现识别令牌的lexer

Compiler construction 在创建DFA后实现识别令牌的lexer,compiler-construction,lexer,Compiler Construction,Lexer,我想了解一些关于实现lexer的知识,我不想使用scanner生成器。从我所读到的内容来看,我用正则表达式来标识语言的规范,每个正则表达式用于不同的标记。然后我应该做一个大的正则表达式,对所有令牌的表达式进行排序,对吗?!然后创建这个大正则表达式的NFA然后DFA,对吗?!如果是这样,那么当一个单词被最终的DFA匹配时,我怎么知道这个单词代表哪个标记 您在这里描述的是手动实现生成的扫描仪。你不是这样做的。只需编写一个包含大型switch语句的循环,其大小写是每个令牌类型的初始字母,每个大小写都是

我想了解一些关于实现lexer的知识,我不想使用scanner生成器。从我所读到的内容来看,我用正则表达式来标识语言的规范,每个正则表达式用于不同的标记。然后我应该做一个大的正则表达式,对所有令牌的表达式进行排序,对吗?!然后创建这个大正则表达式的NFA然后DFA,对吗?!如果是这样,那么当一个单词被最终的DFA匹配时,我怎么知道这个单词代表哪个标记

您在这里描述的是手动实现生成的扫描仪。你不是这样做的。只需编写一个包含大型switch语句的循环,其大小写是每个令牌类型的初始字母,每个大小写都是一个循环,用于使用令牌的其余部分并返回其类型。空格大小写是相同的,只是它不返回。标识符的情况还需要查找关键字表。

FSM-based Lexer 手动编写有限状态机(FSM)lexer,循环输入中的字符,并使用两个级别的switch语句处理这些字符:

  • 外部开关语句正在打开状态
  • 内部switch语句正在打开已读取的字符
这是处理令牌的有限状态机的实现。这样做的缺点是维护起来会很复杂,特别是当有许多状态要处理每个令牌时。好处是利用有限状态机(例如,使用转换表而不是switch语句)更容易重构

转换表类似于switch语句:行定义状态,列定义数据值,单元格定义要转换到的下一个状态(使用类似于
-1
的命令发出停止处理的信号)。通过这种方法,可以使用结束状态来确定令牌类型。这里,您将有一个
token_类型的tokens[N_STATES]数组,然后您可以执行
token=tokens[当前状态]
来获取令牌

非有限状态机Lexer 另一种方法是打开第一个字符,然后作为case语句的一部分读取该标记中的其余字符。这可能更容易阅读,也更容易编写

您还可以将字符拆分为不同的类(例如数字、字母、减号和小于),这些类可以定义为256项查找表。这可以简化case语句

正则表达式 正如您所指出的,使用大型正则表达式是有问题的,因为您无法获取令牌类型。这里的一种方法是拥有一个正则表达式列表,这些正则表达式与令牌匹配,并将其与令牌类型相关联。例如,在python中:

_tokens = [
    (re.compile('\\s+'), WhiteSpace),
    (re.compile('[a-zA-Z_][a-zA-Z0-9_]*'), Identifier),
    (re.compile('[0-9]+'), Integer),
]

您需要正确地对这些词进行排序(例如,将关键字匹配放在标识符之前)。

一旦匹配了一个词,您就知道从哪里开始,从哪里结束,对吗?我想我将使用转换表,尽管我对关键字有一个问题,在我的DFA中,我没有关键字的状态,所以我想知道我是否在标识符状态中完成了检查它是否是一个关键字,方法是将所有关键字都放在一个表中,然后循环查找表中的变量,或者创建一个将所有关键字都进行or运算的正则表达式,这会更快吗?@Doggynub如果你想使用DFA,您需要使用令牌类型标记每个接受状态。这和教科书上的DFA不太一样,但实际上并没有那么难。在原始模式中,每个接受状态都是不同的,因此没有问题;在闭包构造中,当您将两组状态(包括两个不同的接受状态)组合在一起时,只需保留数量较小的一组状态(假设您按顺序对模式进行编号)。在扫描期间,您还需要记住上次接受状态的位置和令牌类型,因为您可能需要回退。