第 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)