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

Парсинг превращает текст в структурированные данные. Regex плохо справляется со сложными задачами, а tree-sitter — избыточен. Автор показывает золотую середину: парсер-комбинаторы. В основе — частичный парсер, функция вида (input: string) => [T, string], которая отрезает кусок входа и возвращает результат вместе с остатком строки. Такие парсеры соединяются в цепочки через pSeq, pMap и другие комбинаторы, а на примере разбора вывода ping видно, как из сырого текста получается аккуратный объект.
Regex плохо справляется со сложными задачами, а tree-sitter часто избыточен — для этой золотой середины парсер-комбинаторы подходят идеально.
- ArneCode
Мне очень нравятся анимации, они делают понимание происходящего интуитивным!
Один недостаток, который, как я думаю, есть у парсер-комбинаторов, например, по сравнению с библиотеками-генераторами парсеров (где парсер генерируется из отдельного файла грамматики), заключается в том, что приходится добавлять много кода для маппинга/распаковки. По моему мнению, это делает парсеры менее читаемыми.
В попытке улучшить это я работал над библиотекой парсер-комбинаторов на Rust.
Она добавляет два дополнительных комбинатора (capture и bind), которые могут сохранять результаты парсеров в «корзины» (bind) и позже использовать значения, сохранённые в этих корзинах, для построения результатов (capture).
Это позволяет избавиться от кода распаковки/маппинга и отделить грамматику от построения результата.
Если у вас есть время, я был бы очень признателен за отзыв об этом!
Вы можете найти это здесь: https://github.com/ArneCode/marser
- marginalia_nu
Я не понимаю, почему это не может быть на одной странице. Это действительно затрудняет возврат к более ранним «главам», когда появляется новая информация, а это слайд-шоу. Искать тоже нельзя.
Похоже, мы без необходимости возвращаем ограничения бумажного носителя, которых не существует в цифровых медиа, из-за странных эстетических соображений?
- akst
Я думаю, это действительно классный сайт, и мне очень нравится эта тема.
Но, я думаю, вам стоит протестировать эту страницу на маленьких экранах: для интересующего контента очень мало места, верхняя правая половина — много отступов, прокручиваемый контент занимает по сути 1/3 доступного экранного пространства, левая сторона — ещё примерно 1/3.
Смотрите, это ваш сайт, ваше видение, я понимаю и уважаю то, что вы пробуете что-то другое в UI, но если ваша цель — донести свои идеи, то как читатель я был бы признателен за больше экранного пространства для контента. Когда я кликнул на ссылку, чтобы увидеть этот конкретный пост, мне просто не интересны ваши другие посты (или, по крайней мере, до тех пор, пока я не прочитал тот, ради которого специально пришёл).
Кстати, сейчас я использую MacBook Air.
- 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
- boomlinde
Первый адрес, вероятно, не следует парсить как IP-адрес, так как он также может быть именем хоста. Может показаться придиркой, но это создаёт возможность продемонстрировать, что делать, когда ожидаешь одну из нескольких конструкций.
Статью было несколько трудно читать из-за ограниченной области, отведённой под тело текста. Мне пришлось прокручивать на нескольких страницах, несмотря на короткие главы.