Документация по Python

Python Lex-Yacc

В: Документация по Python

Введение

Примеры

Начало работы с PLY

Чтобы установить PLY на вашем компьютере для python2 / 3, выполните действия, описанные ниже:

  1. Скачать исходный код здесь .
  2. Распакуйте загруженный zip-файл
  3. Перейдите в распакованный ply-3.10 папку
  4. Выполните следующую команду в терминале: 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.

Сломать

  1. Импорт модуля с помощью 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 по - прежнему законно.

Синтаксис

Параметры

Примечания

Еще от кодкамп
Замечательно! Вы успешно подписались.
Добро пожаловать обратно! Вы успешно вошли
Вы успешно подписались на кодкамп.
Срок действия вашей ссылки истек.
Ура! Проверьте свою электронную почту на наличие волшебной ссылки для входа.
Успех! Ваша платежная информация обновлена.
Ваша платежная информация не была обновлена.