Введение
Примеры
Начало работы с PLY
Чтобы установить PLY на вашем компьютере для python2 / 3, выполните действия, описанные ниже:
- Скачать исходный код здесь .
- Распакуйте загруженный zip-файл
- Перейдите в распакованный
ply-3.10
папку - Выполните следующую команду в терминале:
python setup.py install
Если вы выполнили все вышеперечисленное, теперь вы сможете использовать модуль PLY. Вы можете проверить это, открыв интерпретатор питона и набрав import ply.lex
.
Примечание: Не используйте pip
установить PLY, он установит битый дистрибутив на вашей машине.
"Привет, мир!" PLY - простой калькулятор
Давайте продемонстрируем силу PLY на простом примере: эта программа примет арифметическое выражение в качестве строкового ввода и попытается его решить.
Откройте ваш любимый редактор и скопируйте следующий код:
from ply import lex
import ply.yacc as yacc
tokens = (
'PLUS',
'MINUS',
'TIMES',
'DIV',
'LPAREN',
'RPAREN',
'NUMBER',
)
t_ignore = '\t'
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIV = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
def t_NUMBER( t ) :
r'[0-9]+'
t.value = int( t.value )
return t
def t_newline( t ):
r'\n+'
t.lexer.lineno += len( t.value )
def t_error( t ):
print("Invalid Token:",t.value[0])
t.lexer.skip( 1 )
lexer = lex.lex()
precedence = (
( 'left', 'PLUS', 'MINUS' ),
( 'left', 'TIMES', 'DIV' ),
( 'nonassoc', 'UMINUS' )
)
def p_add( p ) :
'expr : expr PLUS expr'
p[0] = p[1] + p[3]
def p_sub( p ) :
'expr : expr MINUS expr'
p[0] = p[1] - p[3]
def p_expr2uminus( p ) :
'expr : MINUS expr %prec UMINUS'
p[0] = - p[2]
def p_mult_div( p ) :
'''expr : expr TIMES expr
| expr DIV expr'''
if p[2] == '*' :
p[0] = p[1] * p[3]
else :
if p[3] == 0 :
print("Can't divide by 0")
raise ZeroDivisionError('integer division by 0')
p[0] = p[1] / p[3]
def p_expr2NUM( p ) :
'expr : NUMBER'
p[0] = p[1]
def p_parens( p ) :
'expr : LPAREN expr RPAREN'
p[0] = p[2]
def p_error( p ):
print("Syntax error in input!")
parser = yacc.yacc()
res = parser.parse("-4*-(3-5)") # the input
print(res)
Сохраните этот файл как calc.py
и запустить его.
Выход:
-8
Какой правильный ответ на -4 * - (3 - 5)
.
Часть 1: токенизация ввода с помощью Lex
Есть два шага , что код , полученный в примере 1 , осуществляется: один был tokenizing входа, который означает , что он искал символы , которые представляют собой арифметическое выражение, а второй шаг синтаксического анализа, который включает в себя анализ извлеченных маркеров и оценку результата.
В этом разделе приводится простой пример того , как разметить пользовательского ввода, а затем разбивает его построчно.
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'\+'
Окончательные приготовления:
Построить лексер используя 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)
, где данные является строкой
Для того, чтобы получить маркеры, используйте lexer.token()
, который возвращает лексемы совпавшие. Вы можете перебрать лексер в цикле, как в:
##
ибо я в лексере:
print(i)
Часть 2: парсинг токенизированного ввода с помощью Yacc
В этом разделе объясняется, как обрабатывается входной токен из части 1 - это делается с помощью контекстно-свободных грамматик (CFG). Грамматика должна быть указана, а токены обрабатываются в соответствии с грамматикой. Под капотом парсер использует LALR-парсер.
# Yacc example
import ply.yacc as yacc
# Get the token map from the lexer. This is required.
from calclex import tokens
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_div(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
# Error rule for syntax errors
def p_error(p):
print("Syntax error in input!")
# Build the parser
parser = yacc.yacc()
while True:
try:
s = raw_input('calc >')
except EOFError:
break
if not s: continue
result = parser.parse(s)
print(result)
Сломать
Каждое правило грамматики определяется функцией, в которой строка документации для этой функции содержит соответствующую контекстно-независимую грамматическую спецификацию. Операторы, составляющие тело функции, реализуют семантические действия правила. Каждая функция принимает один аргумент p, который представляет собой последовательность, содержащую значения каждого грамматического символа в соответствующем правиле. Значения p[i]
сопоставляются грамматические символы , как показано здесь:
def p_expression_plus(p):
'expression : expression PLUS term'
# ^ ^ ^ ^
# p[0] p[1] p[2] p[3]
p[0] = p[1] + p[3]
- Для лексем, «ценность» соответствующий
p[i]
является таким же , какp.value
атрибута , назначенным в модуле лексического анализатора. Таким образом,PLUS
будет иметь значение+
.
Обратите внимание , что функция может иметь любое имя, до тех пор , как оно предваряется p_
.
p_error(p)
правило, определяется , чтобы поймать синтаксические ошибки ( такие же , какyyerror
в YACC / зубров).
Несколько правил грамматики могут быть объединены в одну функцию, что является хорошей идеей, если производства имеют схожую структуру.
def p_binary_operators(p):
'''expression : expression PLUS term
| expression MINUS term
term : term TIMES factor
| term DIVIDE factor'''
if p[2] == '+':
p[0] = p[1] + p[3]
elif p[2] == '-':
p[0] = p[1] - p[3]
elif p[2] == '*':
p[0] = p[1] * p[3]
elif p[2] == '/':
p[0] = p[1] / p[3]
Символьные литералы могут использоваться вместо токенов.
def p_binary_operators(p):
'''expression : expression '+' term
| expression '-' term
term : term '*' factor
| term '/' factor'''
if p[2] == '+':
p[0] = p[1] + p[3]
elif p[2] == '-':
p[0] = p[1] - p[3]
elif p[2] == '*':
p[0] = p[1] * p[3]
elif p[2] == '/':
p[0] = p[1] / p[3]
Конечно, литералы должны быть указаны в модуле lexer.
- Чтобы явно задать начальный символ, используйте
start = 'foo'
, гдеfoo
некоторая нетерминал.
Установка приоритета и ассоциативности может быть выполнена с помощью переменной приоритета.
##
precedence = (
('nonassoc', 'LESSTHAN', 'GREATERTHAN'), # Nonassociative operators
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('right', 'UMINUS'), # Unary minus operator
)
Жетоны упорядочены по возрастанию. nonassoc
означает , что эти маркеры не ассоциируют. Это значит , что - то вроде a < b < c
незаконна тогда a < b
по - прежнему законно.