Парсер-комбинаторы: лучше, чем регулярные выражения

Parser Combinators

Парсер-комбинаторы: лучше, чем регулярные выражения

Парсинг превращает текст в структурированные данные. Regex плохо справляется со сложными задачами, а tree-sitter — избыточен. Автор показывает золотую середину: парсер-комбинаторы. В основе — частичный парсер, функция вида (input: string) => [T, string], которая отрезает кусок входа и возвращает результат вместе с остатком строки. Такие парсеры соединяются в цепочки через pSeq, pMap и другие комбинаторы, а на примере разбора вывода ping видно, как из сырого текста получается аккуратный объект.

Regex плохо справляется со сложными задачами, а tree-sitter часто избыточен — для этой золотой середины парсер-комбинаторы подходят идеально.
  1. ArneCode

    Мне очень нравятся анимации, они делают понимание происходящего интуитивным!

    Один недостаток, который, как я думаю, есть у парсер-комбинаторов, например, по сравнению с библиотеками-генераторами парсеров (где парсер генерируется из отдельного файла грамматики), заключается в том, что приходится добавлять много кода для маппинга/распаковки. По моему мнению, это делает парсеры менее читаемыми.

    В попытке улучшить это я работал над библиотекой парсер-комбинаторов на Rust.

    Она добавляет два дополнительных комбинатора (capture и bind), которые могут сохранять результаты парсеров в «корзины» (bind) и позже использовать значения, сохранённые в этих корзинах, для построения результатов (capture).

    Это позволяет избавиться от кода распаковки/маппинга и отделить грамматику от построения результата.

    Если у вас есть время, я был бы очень признателен за отзыв об этом!

    Вы можете найти это здесь: https://github.com/ArneCode/marser

  2. marginalia_nu

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

    Похоже, мы без необходимости возвращаем ограничения бумажного носителя, которых не существует в цифровых медиа, из-за странных эстетических соображений?

  3. akst

    Я думаю, это действительно классный сайт, и мне очень нравится эта тема.

    Но, я думаю, вам стоит протестировать эту страницу на маленьких экранах: для интересующего контента очень мало места, верхняя правая половина — много отступов, прокручиваемый контент занимает по сути 1/3 доступного экранного пространства, левая сторона — ещё примерно 1/3.

    Смотрите, это ваш сайт, ваше видение, я понимаю и уважаю то, что вы пробуете что-то другое в UI, но если ваша цель — донести свои идеи, то как читатель я был бы признателен за больше экранного пространства для контента. Когда я кликнул на ссылку, чтобы увидеть этот конкретный пост, мне просто не интересны ваши другие посты (или, по крайней мере, до тех пор, пока я не прочитал тот, ради которого специально пришёл).

    Кстати, сейчас я использую MacBook Air.

  4. BoppreH

    Если вы пишете на Python и ваша задача слишком велика для одного регулярного выражения, но слишком мала для полноценного парсера, как в статье, я могу порекомендовать встроенный re.Scanner[0]. Это основанный на регулярных выражениях токенизатор[1] (так что без рекурсивного разбора) в модуле `re`, который по каким-то причинам полностью отсутствует в любой официальной документации.

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

    Важно, что он обрабатывает список за один проход и гарантирует, что совпадения идут подряд, тогда как наивный `re.findall` с группами захвата проигнорирует несовпавшие символы. Вы также получаете ссылку на работающий сканер, так что можете записать местоположение совпадения для сообщения об ошибках.

    import re

    # Initialize the scanner.

    scanner = re.Scanner([

    (r"\d+", lambda scanner, token: ('COUNT', int(token))),

    (r"\w+", lambda scanner, token: ('ANIMAL', token.removesuffix('s'))),

    (r"[,. ]", None), # None -> skip token.

    ])

    # Scan a sample string.

    results, remainder = scanner.scan("45 pigeons, 23 cows, 11 spiders.")

    # Ensure we parsed the whole thing.

    assert not remainder

    print(results)

    # [('COUNT', 45),

    # ('ANIMAL', 'pigeon'),

    # ('COUNT', 23),

    # ('ANIMAL', 'cow'),

    # ('COUNT', 11),

    # ('ANIMAL', 'spider')]

    [0]: https://stackoverflow.com/a/693818/252218

    [1]: https://en.wikipedia.org/wiki/Lexical_analysis#Tokenization

  5. boomlinde

    Первый адрес, вероятно, не следует парсить как IP-адрес, так как он также может быть именем хоста. Может показаться придиркой, но это создаёт возможность продемонстрировать, что делать, когда ожидаешь одну из нескольких конструкций.

    Статью было несколько трудно читать из-за ограниченной области, отведённой под тело текста. Мне пришлось прокручивать на нескольких страницах, несмотря на короткие главы.

Ещё за этот день

2026-10-18