牛骨文教育服务平台(让学习变的简单)

4.3 标记的规则

每种标记用一个正则表达式规则来表示,每个规则需要以”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’,这通常不是你想要的。