第 1 部分使用 Lex 标记输入
示例 1 中的代码执行了两个步骤:一个是对输入进行标记,这意味着它查找构成算术表达式的符号,第二步是解析,其中包括分析提取的标记并评估结果。
本节提供了一个如何标记用户输入,然后逐行分解的简单示例。
import ply.lex as lex
# List of token names. This is always required
tokens = [
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
]
# Regular expression rules for simple tokens
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# A regular expression rule with some action code
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
# Define a rule so we can track line numbers
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# A string containing ignored characters (spaces and tabs)
t_ignore = ' \t'
# Error handling rule
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()
# Give the lexer some input
lexer.input(data)
# Tokenize
while True:
tok = lexer.token()
if not tok:
break # No more input
print(tok)
将此文件另存为 calclex.py
。我们将在构建 Yacc 解析器时使用它。
分解
-
使用
import ply.lex
导入模块 -
所有词法分析器必须提供名为
tokens
的列表,该列表定义词法分析器可以生成的所有可能的令牌名称。始终需要此列表。tokens = [ 'NUMBER', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN', ]
tokens
也可以是字符串元组(而不是字符串),其中每个字符串表示如前所述的标记。
-
每个字符串的正则表达式规则可以定义为字符串或函数。在任何一种情况下,变量名都应以 t_作为前缀,以表示它是匹配令牌的规则。
-
对于简单的标记,正则表达式可以指定为字符串:
t_PLUS = r'\+'
-
如果需要执行某种操作,则可以将令牌规则指定为函数。
def t_NUMBER(t): r'\d+' t.value = int(t.value) return t
请注意,规则在函数中指定为 doc 字符串。该函数接受一个参数,它是
LexToken
的一个实例,执行一些操作然后返回参数。如果要使用外部字符串作为函数的正则表达式规则而不是指定文档字符串,请考虑以下示例:
@TOKEN(identifier) # identifier is a string holding the regex def t_ID(t): ... # actions
-
LexToken
对象的一个实例(让我们称这个对象为t
)具有以下属性:t.type
是令牌类型(作为字符串)(例如:'NUMBER'
,'PLUS'
等)。默认情况下,t.type
设置为t_
前缀后面的名称。t.value
是 lexeme(实际文本匹配)t.lineno
是当前行号(由于词法分析器不知道行号,因此不会自动更新)。使用名为t_newline
的函数更新 lineno。
def t_newline(t): r'\n+' t.lexer.lineno += len(t.value)
t.lexpos
,它是令牌相对于输入文本开头的位置。
-
如果从正则表达式规则函数返回任何内容,则丢弃该令牌。如果要丢弃令牌,可以选择将 t_ignore_前缀添加到正则表达式规则变量,而不是为同一规则定义函数。
def t_COMMENT(t): r'\#.*' pass # No return value. Token discarded
…是相同的:
t_ignore_COMMENT = r'\#.*'
如果你在看到评论时执行某些操作,这当然是无效的。在这种情况下,使用函数来定义正则表达式规则。
如果你尚未为某些字符定义标记但仍想忽略它,请使用
t_ignore = "<characters to ignore>"
(这些前缀是必需的):t_ignore_COMMENT = r'\#.*' t_ignore = ' \t' # ignores spaces and tabs
-
构建主正则表达式时,lex 将添加文件中指定的正则表达式,如下所示:
- 由函数定义的标记的添加顺序与它们在文件中出现的顺序相同。
- 由字符串定义的标记按照定义该标记的正则表达式的字符串的字符串长度的降序添加。
如果你在同一文件中匹配
==
和=
,请利用这些规则。
-
文字是按原样返回的标记。
t.type
和t.value
都将设置为角色本身。定义文字列表:literals = [ '+', '-', '*', '/' ]
要么,
literals = "+-*/"
在匹配文字时,可以编写执行附加操作的令牌函数。但是,你需要适当地设置令牌类型。例如:
literals = [ '{', '}' ] def t_lbrace(t): r'\{' t.type = '{' # Set token type to the expected literal (ABSOLUTE MUST if this is a literal) return t
-
使用 t_error 函数处理错误。
# Error handling rule def t_error(t): print("Illegal character '%s'" % t.value[0]) t.lexer.skip(1) # skip the illegal token (don't process it)
通常,
t.lexer.skip(n)
会跳过输入字符串中的 n 个字符。
-
-
最后准备:
使用
lexer = lex.lex()
构建词法分析器。你还可以将所有内容放在类中,并调用该类的实例来定义词法分析器。例如:
import ply.lex as lex class MyLexer(object): ... # everything relating to token rules and error handling comes here as usual # Build the lexer def build(self, **kwargs): self.lexer = lex.lex(module=self, **kwargs) def test(self, data): self.lexer.input(data) for token in self.lexer.token(): print(token) # Build the lexer and try it out m = MyLexer() m.build() # Build the lexer m.test("3 + 4") #
使用
lexer.input(data)
提供输入,其中 data 是一个字符串要获得令牌,请使用
lexer.token()
返回匹配的令牌。你可以在循环中迭代词法分析器,如下所示:for i in lexer: print(i)