Python на основе библиотеки синтаксического анализа, наблюдения


Это отклик на этот вопрос: ссылка. Как советовали здесь, я переписал класс, которые также упрощены некоторые другие код.

Я пишу библиотеку парсера. Я хотел бы знать, если основой его является звук и можно ли его улучшить каким-либо образом. Весь код можно увидеть в этом РЕПО: ссылка, в замороженном ветку review-11-02-2018 (файл core.py). Я выложу соответствующие разделы ниже.

В библиотеке написано всего две вещи: State класс и понятие эффекта. State объекты представляют текущее состояние анализатора цепей, и следить за входной, что осталось разобрать и часть входной сигнал анализируется последний парсер. Парсер-это просто дежурный, который принимает State объект и возвращает новый (State объекты являются неизменными). Выход один парсер передается следующему парсеру в цепи. Анализатор может также терпеть неудачу, бросая ParsingFailure исключение. Любой парсер в цепочке может зарегистрировать эффект - отзывной, который принимает произвольное значение в качестве первого аргумента и State объект в качестве второго. Если цепь прошла успешно, все эффекты, зарегистрированные в ходе анализа выполнения применяются последовательно для затравки (с семенами или возвращаемое значение предыдущий эффект первый аргумент, и состояние цепи в момент регистрации второй), и возвращаемое значение последнего эффекта становится выход из всей цепочки, наряду с итоговой государственной. Понятие вменяемый? Это работает, но это разумный способ сделать это?

State класс-это просто именованный кортеж с некоторыми дополнительными методами и определяется следующим образом:

class State(namedtuple("State", "string effect left_start left_end parsed_start parsed_end")):
    """
    State objects represent current state of a parser chain (or an individual
    parser).

    State objects provide two views over the input string: 'left', which spans
    a substring between 'left_start' and 'left_end' and represents unparsed
    input left from the previous parser, and 'parsed', which spans a substring
    between 'parsed_start' and 'parsed_end' and represents a portion of input
    the previous parser has parsed. Windows may overlap, and usually
    'parsed_end' and 'left_start' are the same, but not always.

    A State object is immutable and has following fields:
    * string (str): the input the parser chain is supposed to parse.
    * effect ((value, state) -> value): if the chain is successful, this will
      be called in sequence with other effects from the chain to form the
      chain's output value.
    * left_start, left_end (int): see above about the 'left' window.
    * parsed_start, parser_end (int): see above about the 'parsed' window.

    State objects are just named tuples, so they support a very convenient
    '_replace' method. !Note!: to avoid duplicating effects accidentally,
    '_replace' treats lack of 'effect' in its arguments as 'effect=None'. So if
    you want to copy an effect from another parser, you have to do it
    explicitly.

    State objects' constructor takes the following arguments:
    1. string - the input.
    2. effect=None - the effect, transformation to be performed on success of
       the last parser.
    3. start=0 - will be translated into 'left_start'
    4. end=None - will be translated into 'left_end'. If set to None,
      'left_end' will be set to the length of the input.
    State objects created via this constructor have both 'parsed_start' and
    'parsed_end' set to 'left_start'.

    State objects have several properties:
    * left - returns a slice of input that's left to parse.
    * left_len - returns the length of the above slice without computing the
      slice itself.
    * parsed - returns a slice of input that's been parsed.
    * parsed_len - returns the length of the above slice, again without
      computing the slice.

    Finally, State objects have following public methods:
    * consume(how_many) - move 'how_many' characters from the left window into
      the parsed window. Raise ValueError if more input was consumed than left.
    * split(at) - split the State in two (and return them). The first keeps
      the input up to, but not including, 'at' as its 'left' window, the second
      gets the rest. Both have their 'parsed' windows reset to an empty string.
      The first gets 'effect' of the original, the second gets None.
    """

    __slots__ = []

    def __new__(cls, string, effect=None, start=0, end=None):
        if end is None:
            end = len(string)
        assert 0 <= start <= end <= len(string)
        return super().__new__(cls, string, effect, start, end, start, start)

    def _replace(self, **kwargs):
        if "effect" not in kwargs:
            return super()._replace(effect=None, **kwargs)
        return super()._replace(**kwargs)

    def consume(self, how_many):
        """
        Return a new State object with 'how_many' characters consumed and moved
        to the 'parsed' window.

        Raise ValueError if 'how_many' is negative or if consuming more
        characters than left in the 'left' window.
        """
        if how_many < 0:
            raise ValueError("Negative number of consumed characters")
        left_start = self.left_start + how_many
        parsed_start = self.left_start
        parsed_end = parsed_start + how_many
        if left_start > self.left_end:
            raise ValueError("Consumed more characters than fits in the 'left' window")
        return self._replace(left_start=left_start, parsed_start=parsed_start,
                             parsed_end=parsed_end)

    def split(self, at):
        """
        Split the State in two. The first one keeps a portion of input up to
        'at'th character (exclusive), the second one gets the rest. Both have
        'parsed' window reset to an empty string. First one gets the effect of
        the original, the second one gets None.
        """
        split_point = self.left_start + at
        first = self._replace(effect=self.effect,
                              left_end=split_point,
                              parsed_start=self.left_start,
                              parsed_end=self.left_start)
        second = self._replace(effect=None,
                               left_start=split_point,
                               parsed_start=split_point,
                               parsed_end=split_point)
        return first, second

    @property
    def left(self):
        """
        Return the portion of input the last parser hasn't consumed.
        """
        return self.string[self.left_start:self.left_end]

    @property
    def left_len(self):
        """
        Return the length of the portion of input the last parser hasn't
        consumed.
        """
        return self.left_end - self.left_start

    @property
    def parsed(self):
        """
        Return the string parsed by the last parser.
        """
        return self.string[self.parsed_start:self.parsed_end]

    @property
    def parsed_len(self):
        """
        Return the length of the string parsed by the last parser.
        """
        return self.parsed_end - self.parsed_start

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

Еще одна важная вещь, parse функции, которые пользователь должен позвонить с парсер Парсеры вместо того, чтобы позвонить напрямую. Вот это:

def parse(seed, state_or_string, parser, verbose=False):
    """
    Run a given parser on a given state object or a string, then apply combined
    chain or parser's effects to 'seed' and return a tuple
    (seed after effects, final state).

    On failure, return None unless 'verbose' is truthy, in which case return
    the ParsingFailure exception that has terminated the parsing process.
    """
    if isinstance(state_or_string, str):
        state = State(state_or_string)
    else:
        state = state_or_string
    try:
        after = parser(state)
        if after.effect is not None:
            return after.effect(seed, after), after
        return seed, after
    except ParsingFailure as failure:
        if verbose:
            return failure
        return None
    except ParsingEnd as end:
        if end.state.effect is not None:
            return end.state.effect(seed, end.state), end.state
        return seed, end.state

Еще одна важная вещь, chain парсер-генератор, который выполняет цепочки логики, описанной выше, но я не хочу, чтобы разместить его здесь, потому что а) вопрос обожралась уже, б) она также занимается предвидение, которое как мне кажется вопрос стоит задать отдельный вопрос.

Если вы дочитали до этого места, Спасибо! Любые предложения по улучшению библиотеки?



131
6
задан 11 февраля 2018 в 03:02 Источник Поделиться
Комментарии
1 ответ

Опираясь на предыдущие замечания, которые они сделали по поводу разбора, я остановлюсь на удобочитаемость кода

Вы можете рассмотреть вопрос о переносе некоторых пунктах классе строкой документации на соответствующие части кода, как в пункте о A State object is immutable and has following fields может костюм лучше __new__ метод документирования, а также параметр

Кроме того, не забывайте документировать параметры методов/функций, поможет много, зная, что ваш парсер делает, когда ссылка на них

Такие параметры, как at может быть лучше а index которая повсеместно используется для этого.

Такие параметры, как how_many может быть лучше а characters_count или consumed_characters_count или похожие, просто читая это дает более полное представление о том, что параметр относится к

if verbose:
return failure
return None

Это может быть переведен на тернарный оператор

return failure if verbose else None

Для следующих мелких методов, я хотел бы рассмотреть лучше, называя так же, как этот

def left(self):
"""
Return the portion of input the last parser hasn't consumed.
"""

Почему не называть это portion_not_consumed или что-то в этом роде?

Только представьте себе, что вы ничего не знаете о библиотеки, и вы найдете способ называется left_len. Если Вы читаете строкой документации, трудно сказать, что делает

Вы можете также рассмотреть возможность выкладывания интервал между строк ту же функцию, чтобы улучшить читаемость между участками его

Сохранить хорошую работу!

1
ответ дан 17 февраля 2018 в 09:02 Источник Поделиться