问题描述
我正在尝试从头开始为 C++ 代码制作一个非常简单的词法分析器(Tokenizer),而不使用 PLY 或任何其他库。
到目前为止我做过的事情:
- 定义关键字、字典中的运算符。
- 为注释、文字等定义正则表达式
我遇到的问题:
问题 1:
现在我正在尝试创建一个函数 check_line(line)
,它将使用一行代码并返回字典中的标记。例如:
check_line('int main()')
输出应该是:
Tokens = {'Keyword':'int','Keyword':'main','opening parenthesis':'(','Closing Parenthesis':')'}
但我得到的输出是:
Tokens = {'Keyword':'main','Closing Parenthesis':')'}
因为 main 在这里覆盖了 int。
有没有办法解决这样的问题?
问题 2:
当我在函数内部传递 check_line('int main()')
时,程序与 main
不匹配,因为这里我们有括号。我该如何解决这个问题。
我正在粘贴我到目前为止编写的代码,请看一下并告诉我您的想法。
import re
# Keywords
keywords = ['const','float','int','struct','break','continue','else','for','switch','void','case','enum','sizeof','typedef','char','do','if','return','union','while','new','public','class','friend','main']
# Regular Expression for Identifiers
re_id = '^[_]?[a-z]*[A-Z]([a-z]*[A-Z]*[0-9]+)'
# Regular Expression for Literals
re_int_lit = '^[+-]?[0-9]+'
re_float_lit = '^[+-]?([0-9]*)\.[0-9]+'
re_string_lit = '^"[a-zA-Z0-9_ ]+"$'
# Regular expression of Comments
re_singleline_comment = '^//[a-zA-Z0-9 ]*'
re_multiline_comment = '^/\\*(.*?)\\*/'
operators = {'=':'Assignment','-':'Subtraction','+':'Addition','*':'Multiplication','/':'Division','++':'increment','--':'Decrement','||':'OR','&&':'AND','<<':'Cout operator','>>':'Cin Operator',';':'End of statement'}
io = {'cin':'User Input','cout':'User Output'}
brackets = {'[':'Open Square',']':'Close Square','{':'Open Curly','}':'Close Curly','(':'Open Small',')':'Close Small'}
# Function
def check_line(line):
tokens = {}
words = line.split(' ')
for word in words:
if word in operators.keys():
tokens['Operator ' + word] = word
if word in keywords:
tokens['Keywords'] = word
if re.match(re_singleline_comment,word):
break
return tokens
check_line('int main()')
输出:
{'Keywords': 'main'}
输出应该是:
Tokens = {'Keyword':'int','Closing Parenthesis':')'}
PS:我还没有完成条件,只是想先解决这个问题。
解决方法
字典对于这个函数来说是一个非常糟糕的数据结构选择,因为字典的本质是每个键都与一个对应的值相关联。
分词器应该返回的是完全不同的:一个有序的标记对象流。在一个简单的实现中,这可能是一个元组列表,但对于任何重要的应用程序,您很快就会发现:
-
令牌不仅仅是一个句法类型和一个字符串。有很多重要的辅助信息,最值得注意的是令牌在输入流中的位置(用于错误消息)。
-
代币几乎都是按顺序消费的,一次生产多个代币并没有什么特别的优势。在 Python 中,生成器是一种更自然的生成标记流的方式。如果创建一个标记列表很有用(例如,为了实现一个回溯解析器),那么逐行工作就没有意义了,因为换行符在 C++ 中通常是无关紧要的。
如评论中所述,C++ 标记并不总是由空格分隔,这在您的示例输入中很明显。 (main()
是不包含单个空格字符的三个标记。)将程序文本拆分为标记流的最佳方法是重复匹配当前输入光标处的标记模式,返回最长匹配,并移动输入光标比赛结束。