Python парсер для грамматики CTF из нетерминалов - PullRequest
0 голосов
/ 04 февраля 2020

В Python 3, наиболее распространенные генераторы синтаксического анализатора, такие как ANTLR или Lark, определяют грамматики, выводя нетерминал из терминалов из строк, и создают лексер и синтаксический анализатор для оценки строк.

Вместо этого я Я ищу генератор синтаксического анализатора, который работает на «промежуточном входе», состоящем только из нетерминалов и терминалов, что означает, что я бы заранее сделал лексирование и части синтаксического анализа.

Например, если входная грамматика

S -> AB
A -> a | aA
B -> b | aB

, где заглавные буквы являются нетерминалами, а строчные буквы - терминалами, возможный ввод может быть aaaB, из которого затем можно построить дерево разбора с root S.

На самом деле ввод не может быть просто строкой символов ASCII, таких как aaaB, так как нетерминалы должны будут хранить информацию о своих собственных поддеревьях. Так что, по крайней мере, это должны быть произвольные объекты, а входные данные, скорее всего, будут списком объектов.

Есть ли библиотека или пакет, который предлагает эту функцию?

Ответы [ 2 ]

1 голос
/ 05 февраля 2020

Примечание : Это не индоссамент. Скорее всего, существует множество других пакетов для разбора Python, которые предлагают аналогичную функциональность. Он представлен просто как возможный механизм для достижения (предполагаемой) цели.

Пакет синтаксического анализа Ply содержит генератор лексеров, но вы не обязаны его использовать; Вы можете использовать любую функцию, которую хотите, для предоставления лексических токенов. Токены - это просто типы объектов, которые включают атрибут value. Однако Ply не делает никаких предположений об объекте value, кроме того, что требует его существования.

Ссылаясь на руководство здесь и также здесь :

Когда токены возвращаются lex, они имеют значение, которое хранится в атрибуте value. Обычно значением является текст, который был сопоставлен. Однако значение может быть присвоено любому объекту Python.

Если вы собираетесь создать рукописный лексер и планируете использовать его с ya cc .py, он должен соответствовать только следующие требования:

  • Он должен предусматривать метод token(), который возвращает следующий токен, или None, если токенов больше нет.
  • token() Метод должен возвращать объект tok, имеющий атрибуты type и value. Если используется отслеживание номера строки, токен также должен определить атрибут lineno.

Для передачи нетерминалов в анализатор вам потребуется чтобы они выглядели как терминалы. Самый простой способ сделать это - создать псевдотерминал для каждого нетерминала и преобразовать псевдотерминал в единицу продукции. Например, если предположить, что псевдо-терминалам даны имена, оканчивающиеся на _ (что облегчает их генерацию программным способом, вы можете изменить правило

B : b | a B

на

B : b
  | a B
  | B_

Если мы предполагаем, что вы вводите правильное значение AST в объект токена, возвращенный с помощью терминала B_, все, что вам нужно, это добавить одну функцию в вашу грамматику:

 def p_pseudo_terminals(p):
     '''A : A_
        B : B_
     '''
     p[0] = p[1]

Вот полный исполняемый пример с использованием грамматики в вопросе:

file: inject.py

from ply import yacc
from collections import namedtuple
Token = namedtuple('Token', ['type', 'value'])
Node = namedtuple('Node', ['type', 'children'])

tokens = ['a', 'b', 'A_', 'B_']
def p_S(p):
    '''S : A B'''
    p[0] = Node('S', [ p[1], p[2] ])

def p_A(p):
    '''A : a'''
    p[0] = Node('A', [ p[1] ])

def p_Al(p):
    '''A : a A'''
    p[0] = Node('A', [ p[1], p[2] ])

def p_B(p):
    '''B : b'''
    p[0] = Node('B', [ p[1] ])

def p_Bl(p):
    '''B : a B'''
    p[0] = Node('B', [ p[1], p[2] ])

def p_pseudo(p):
    '''A : A_
       B : B_
    '''
    p[0] = p[1]

class Lexer(object):
    def __init__(self, iter):
        self.iter = iter.__iter__()

    def token(self):
        try:
            retval = next(self.iter)
            if type(retval) == Token:
                # Input is a token, just pass it through
                return retval
            else:
                # Input is an AST node; fabricate a pseudo-token
                return Token(retval.type + '_', retval)
        except StopIteration:
            return None

parser = yacc.yacc()

И примерный прогон:

$ python3
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from inject import *
>>> token_stream = [Token('a', 'a')] * 3 + [Node('B', ['a', Node('B', children=['a', Node('B', children=['b'])])])]
>>> lexer = Lexer(token_stream)
>>> print(parser.parse(None, lexer=lexer))
Node(type='S', children=[Node(type='A', children=['a', Node(type='A', children=['a', Node(type='A', children=['a'])])]), Node(type='B', children=['a', Node(type='B', children=['a', Node(type='B', children=['b'])])])])

В настоящая грамматика, может быть утомительно вводить все эти псевдо-токены и названия юнитов. Вы можете воспользоваться тем фактом, что вы можете генерировать строку документации самостоятельно, пока она существует, прежде чем создавать синтаксический анализатор, вызвав * 1063 Вам также нужно добавить псевдотокен в список имен токенов, чтобы Ply знал, что B_ - токен.

0 голосов
/ 05 февраля 2020

Lark поддерживает использование «пользовательского лексера», который вы можете использовать для предоставления любого потока терминалов по вашему выбору. Источник не должен быть строкой.

Простой пример этой функции вы можете найти здесь: https://github.com/lark-parser/lark/blob/master/examples/custom_lexer.py

...