4.3 标记的规则
最后更新于:2022-04-01 01:09:34
每种标记用一个正则表达式规则来表示,每个规则需要以”t_“开头声明,表示该声明是对标记的规则定义。对于简单的标记,可以定义成这样(在Python中使用raw string能比较方便的书写正则表达式):
~~~
t_PLUS = r'\+'
~~~
这里,紧跟在t_后面的单词,必须跟标记列表中的某个标记名称对应。如果需要执行动作的话,规则可以写成一个方法。例如,下面的规则匹配数字字串,并且将匹配的字符串转化成Python的整型:
~~~
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
~~~
如果使用方法的话,正则表达式写成方法的文档字符串。方法总是需要接受一个LexToken实例的参数,该实例有一个t.type的属性(字符串表示)来表示标记的类型名称,t.value是标记值(匹配的实际的字符串),t.lineno表示当前在源输入串中的作业行,t.lexpos表示标记相对于输入串起始位置的偏移。默认情况下,t.type是以t_开头的变量或方法的后面部分。方法可以在方法体里面修改这些属性。但是,如果这样做,应该返回结果token,否则,标记将被丢弃。
在lex内部,lex.py用`re`模块处理模式匹配,在构造最终的完整的正则式的时候,用户提供的规则按照下面的顺序加入:
1. 所有由方法定义的标记规则,按照他们的出现顺序依次加入
2. 由字符串变量定义的标记规则按照其正则式长度倒序后,依次加入(长的先入)
3. 顺序的约定对于精确匹配是必要的。比如,如果你想区分‘=’和‘==’,你需要确保‘==’优先检查。如果用字符串来定义这样的表达式的话,通过将较长的正则式先加入,可以帮助解决这个问题。用方法定义标记,可以显示地控制哪个规则优先检查。
为了处理保留字,你应该写一个单一的规则来匹配这些标识,并在方法里面作特殊的查询:
~~~
reserved = {
'if' : 'IF',
'then' : 'THEN',
'else' : 'ELSE',
'while' : 'WHILE',
...
}
tokens = ['LPAREN','RPAREN',...,'ID'] + list(reserved.values())
def t_ID(t):
r'[a-zA-Z_][a-zA-Z_0-9]*'
t.type = reserved.get(t.value,'ID') # Check for reserved words
return t
~~~
这样做可以大大减少正则式的个数,并稍稍加快处理速度。注意:你应该避免为保留字编写单独的规则,例如,如果你像下面这样写:
~~~
t_FOR = r'for'
t_PRINT = r'print'
~~~
但是,这些规则照样也能够匹配以这些字符开头的单词,比如’forget’或者’printed’,这通常不是你想要的。