如何使用 ply 库?
假设您已经安装了 Ply,您应该开始探索 official Ply website 上的教程。它们写得很好,易于理解。
这是一个好的开始,如果是,我该怎么做?
Ply 需要以标记定义开头。你已经这样做了。但是,当您的词法分析器必须区分诸如“forget”之类的字符串和诸如for 之类的保留关键字时,复杂性会增加。该库为解决语法歧义的变量优先级提供了良好的支持。这就像将优先级定义为元组一样简单:
precedence = (
('left', 'STRING', 'KEYWORD'),
('left', 'MULTIPLY', 'DIVIDE')
)
但是,我建议您在深入了解 Ply 中的 表达式 和 precedence 等更高级的功能之前,应该阅读更多关于 lexers and yacc 的信息。首先,您应该构建一个简单的数字词法分析器,它可以成功解析整数、运算符和括号符号。我已经减少了令牌定义以适应这个目的。下面的例子是根据官方教程修改的。
-
库导入和令牌定义:
import ply.lex as lex #library import
# List of token names. This is always required
tokens = [
# OPERATORS #
'PLUS' , # +
'MINUS' , # -
'MULTIPLY', # *
'DIVIDE', # /
'MODULO', # %
'NOT', # ~
'EQUALS', # =
# COMPARATORS #
'LT', # <
'GT', # >
'LTE', # <=
'GTE', # >=
'DOUBLEEQUAL', # ==
'NE', # !=
'AND', # &
'OR' , # |
# BRACKETS #
'LPAREN', # (
'RPAREN', # )
'LBRACE', # [
'RBRACE', # ]
'BLOCKSTART', # {
'BLOCKEND', # }
# DATA TYPES#
'INTEGER', # int
'FLOAT', # dbl
'COMMENT', # --
]
-
为简单标记定义正则表达式规则:Ply 使用re Python 库来查找用于标记化的正则表达式匹配。每个标记都需要一个正则表达式定义。我们首先为简单标记定义正则表达式定义。每个规则声明都以特殊前缀 t_ 开头,表示它定义了一个令牌。
# Regular expression rules for simple tokens
t_PLUS = r'\+'
t_MINUS = r'-'
t_MULTIPLY = r'\*'
t_DIVIDE = r'/'
t_MODULO = r'%'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'\['
t_RBRACE = r'\]'
t_BLOCKSTART = r'\{'
t_BLOCKEND = r'\}'
t_NOT = r'\~'
t_EQUALS = r'\='
t_GT = r'\>'
t_LT = r'\<'
t_LTE = r'\<\='
t_GTE = r'\>\='
t_DOUBLEEQUAL = r'\=\='
t_NE = r'\!\='
t_AND = r'\&'
t_OR = r'\|'
t_COMMENT = r'\#.*'
t_ignore = ' \t' ignore spaces and tabs
-
为更复杂的标记定义正则表达式规则,例如 int、float 和换行符等数据类型,以跟踪行号。您会注意到这些定义与上面的非常相似。
#Rules for INTEGER and FLOAT tokens
def t_INTEGER(t):
r'\d+'
t.value = int(t.value)
return t
def t_FLOAT(t):
r'(\d*\.\d+)|(\d+\.\d*)'
t.value = float(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)
-
为无效字符添加一些错误处理:
# Error handling rule
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
-
构建词法分析器:
lexer = lex.lex()
-
用一些输入数据测试词法分析器,标记和打印标记:
data = '''
[25/(3*40) + {300-20} -16.5]
{(300-250)<(400-500)}
20 & 30 | 50
# This is a comment
'''
# Give the lexer some input
lexer.input(data)
# Tokenize
for tok in lexer:
print(tok)
您可以将此示例代码添加到 Python 脚本文件中,例如 new_lexer.py,然后像 python new_lexer.py 一样运行它。您应该得到以下输出。请注意,输入数据由在输出中成功忽略的换行符 ('\n') 组成。
#Output
LexToken(LBRACE,'[',2,1)
LexToken(INTEGER,25,2,2)
LexToken(DIVIDE,'/',2,4)
LexToken(LPAREN,'(',2,5)
LexToken(INTEGER,3,2,6)
LexToken(MULTIPLY,'*',2,7)
LexToken(INTEGER,40,2,8)
LexToken(RPAREN,')',2,10)
LexToken(PLUS,'+',2,12)
LexToken(BLOCKSTART,'{',2,14)
LexToken(INTEGER,300,2,15)
LexToken(MINUS,'-',2,18)
LexToken(INTEGER,20,2,19)
LexToken(BLOCKEND,'}',2,21)
LexToken(MINUS,'-',2,23)
LexToken(INTEGER,16,2,24)
LexToken(FLOAT,0.5,2,26)
LexToken(RBRACE,']',2,28)
LexToken(BLOCKSTART,'{',3,30)
LexToken(LPAREN,'(',3,31)
LexToken(INTEGER,300,3,32)
LexToken(MINUS,'-',3,35)
LexToken(INTEGER,250,3,36)
LexToken(RPAREN,')',3,39)
LexToken(LT,'<',3,40)
LexToken(LPAREN,'(',3,41)
LexToken(INTEGER,400,3,42)
LexToken(MINUS,'-',3,45)
LexToken(INTEGER,500,3,46)
LexToken(RPAREN,')',3,49)
LexToken(BLOCKEND,'}',3,50)
LexToken(INTEGER,20,4,52)
LexToken(AND,'&',4,55)
LexToken(INTEGER,30,4,57)
LexToken(OR,'|',4,60)
LexToken(INTEGER,50,4,62)
LexToken(COMMENT,'# This is a comment',5,65)
您还可以使用许多其他功能。例如,可以使用lex.lex(debug=True) 启用调试。官方教程提供了有关这些功能的更多详细信息。
我希望这有助于您入门。您可以进一步扩展代码以包含保留关键字,如@987654341@、while 和STRING 的字符串标识,CHAR 的字符标识。这些教程通过定义一个键值字典映射来介绍保留字的实现,如下所示:
reserved = {
'if' : 'IF',
'then' : 'THEN',
'else' : 'ELSE',
'while' : 'WHILE',
...
}
通过将保留令牌type 定义为'ID' 并包括reserved dict 值来进一步扩展令牌列表:tokens.append('ID') 和tokens = tokens + list(reserved.values())。然后,如上所述添加t_ID 的定义。
有什么资源可以帮助我解决这个问题。
有许多资源可用于了解词法分析器、解析器和编译器。你应该从一本涵盖理论和实现的好书开始。有许多书籍涵盖了这些主题。我喜欢这个one。 Here's 另一个可能有帮助的资源。如果您想探索类似的 Python 库或资源,SO answer 可能会有所帮助。