我目前正在努力完成这本书: http://www1.idc.ac.il/tecs/
我目前正处于一个部分,其中excersize是为非常简单的java语言创建编译器。 这本书总是说需要什么而不是如何(这是一件好事)。我还应该提一下,它是关于yacc和lex的,并且特别说要为了自己学习而在书中的项目中避免使用它们。
我正在使用chaper 10并开始编写tokenizer。
1)任何人都可以给我一些一般的建议 - 正则表达式是标记源文件的最佳方法吗?
2)我想在解析之前从源文件中删除注释 - 这并不难,但是大多数编译器会告诉你行发生错误,如果我只是删除注释这会弄乱行数,有没有简单在仍然删除垃圾的同时保留行数的策略?
提前致谢!
答案 0 :(得分:5)
标记器本身通常使用描述所有可能的有效标记的大型DFA表来编写(类似的东西,标记可以以字母开头,后跟其他字母/数字后跟非字母,或者后跟数字其他数字和非数字/点或点后跟至少1个数字然后是非数字等等)。我构建我的方法是识别我的tokenizer将接受的所有正则表达式,将它们转换为DFA并将它们组合起来。
现在要“删除注释”,当你解析一个令牌时,你可以有一个注释令牌(用于解析注释的正则表达式,用语言描述的时间太长),当你完成解析这个注释时,你只需解析一个新的令牌,因此忽略它。或者,您可以将它传递给编译器并让它处理它(或者忽略它)。无论是aproach都会保留元数据,如行号和字符在线。
编辑 DFA理论:
出于性能原因,每个正则表达式都可以转换(并转换)为DFA。这将删除解析它们时的任何回溯。 This link让您了解如何完成此操作。首先将正则表达式转换为NFA(带回溯的DFA),然后通过膨胀有限自动机来删除所有回溯节点。
您可以使用一些常识手工制作DFA的另一种方法。例如,可以解析标识符或数字的有限自动机。这当然是不够的,因为你很可能也想添加评论,但它会让你了解底层结构。
A-Z space
->(Start)----->(I1)------->((Identifier))
| | ^
| +-+
| A-Z0-9
|
| space
+---->(N1)---+--->((Number)) <----------+
0-9 | ^ | |
| | | . 0-9 space |
+-+ +--->(N2)----->(N3)--------+
0-9 | ^
+-+
0-9
关于所使用的符号的一些注释,DFA从(开始)节点开始,并在从文件中读取输入时移动箭头。在任何一点它只能匹配一条路径。缺少的任何路径都默认为“错误”节点。 ((Number))
和((Identifier))
是您的结束,成功节点。进入这些节点后,您将返回令牌。
所以从一开始,如果你的令牌以字母开头,那么它必须继续一堆字母或数字,并以“空格”(空格,新行,标签等)结束。没有回溯,如果失败,则标记化过程失败并且您可以报告错误。你应该阅读一本关于错误恢复的理论书来继续解析,这是一个非常大的话题。
但是,如果您的令牌以数字开头,则必须跟随一串数字或一个小数点。如果没有小数点,则“空格”必须跟随数字,否则必须跟随一个数字,然后是一堆数字,后跟一个空格。我没有包含科学记数法,但不难添加。
现在,为了解析速度,它会转换为DFA表,垂直和水平线上都有所有节点。像这样:
I1 Identifier N1 N2 N3 Number
start letter nothing number nothing nothing nothing
I1 letter+number space nothing nothing nothing nothing
Identifier nothing SUCCESS nothing nothing nothing nothing
N1 nothing nothing number dot nothing space
N2 nothing nothing nothing nothing number nothing
N3 nothing nothing nothing nothing number space
Number nothing nothing nothing nothing nothing SUCCESS
您运行此方法的方法是存储起始状态,并在逐个读取输入字符时在表格中移动。例如,输入“1.2”将解析为开始 - > N1-> N2-> N3->数字 - >成功。如果您在任何时候点击“无”节点,则表示您有错误。
编辑2:该表实际上应该是node-&gt; character-&gt;节点,而不是node-&gt; node-&gt;字符,但无论如何它在这种情况下都能正常工作。自从我上次手工编写编译器以来已经有一段时间了。
答案 1 :(得分:1)
1-是正则表达式可以很好地实现tokenizer。如果使用生成的标记器(如lex),则将每个标记描述为正则表达式。见马克的答案。
2-词法分析器通常跟踪行/列信息,因为令牌器使用令牌,使用令牌跟踪行/列信息,或将其作为当前状态。因此,当发现问题时,标记生成器会知道您的位置。因此,在处理注释时,当处理新行时,标记生成器只会增加line_count。
在Lex中你也可以解析状态。多行注释通常使用这些状态实现,因此允许更简单的正则表达式。一旦找到评论开头的匹配项,例如'/ *',您就会转换为评论状态,您可以将其设置为从正常状态独占。因此,当您使用文本查找结束注释标记'* /'时,您将不匹配正常标记。
这个基于状态的过程对于允许嵌套的终端制作者的进程字符串文字也很有用,例如“test”更多文本“。