- PVSM.RU - https://www.pvsm.ru -

Работа с DSL: создание собственного анализатора с использованием библиотек Python

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 1 [1]

В нашем блоге на Хабре мы пишем не только о темах, связанных с информационной безопасностью, но уделяем значительное внимание вопросам разработки софта — например, ведем цикл о создании и внедрении инструментов [2] DevOps. Сегодня же речь пойдет об использовании предметно-ориентированных языков (Domain-specific language, DSL) для решения конкретных задач с помощью Python.

Материал подготовлен на основе выступления разработчика Positive Technologies Ивана Цыганова на конференции PYCON Russia (слайды [3], видео [4]).

Задача

Представим, что мы решили написать свою собственную систему ротации логов. Очевидно, что ее необходимо настраивать. Структура реального конфига Log Rotate похожа на словарь в Python. Давайте попробуем пойти этим путём и запишем нашу конфигурацию в словарь.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 2

Вроде бы все хорошо — список файлов есть, период ротации тоже есть, все совпадает. Единственное отличие — в Log Rotate мы можем указывать единицы измерения размера файла, а в нашей конфигурации — пока нет. Первая мысль — разрешить задавать их в специальной строке и обрабатывать ее, разбивая по пробелу.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 3

Это хороший способ, который будет работать до момента, пока пользователи нашей системы не попросят дать им возможность указывать некую дельту к используемому размеру («1 мегабайт + 100 килобайт»). Зачем им это нужно они не сказали, но мы ведь любим своих пользователей. Чтобы реализовать такую функциональность, воспользуемся регулярными выражениями.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 4

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

Что такое DSL

Согласно определению Мартина Фаулера, предметно-ориентированный язык (DSL) — это язык с ограниченными выразительными возможностями, ориентированный на некую конкретную предметную область.

Существуют внутренние и внешние DSL. К первым относятся такие библиотеки как PonyORM, WTForm и Django models, а ко вторым SQl, REGEXP, TeX/LaTeX. Внутренние DSL представляют собой некое расширение базового языка, а внешние — это абсолютно независимые языки.

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

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 5

Но ограничения, накладываемые базовым языком сохранятся и избавиться при записи от лишних скобок и знаков умножения между числом и переменной (MB, KB) нам никак не удастся.

При использовании же внешнего DSL мы сможем сами придумывать синтаксис — это позволит нам избавиться от ненужных нам скобок и знаков умножения. Но нам придется разработать анализатор нашего языка.

Вернемся к нашей задаче

А что если нам потребуется хранить файл конфигурации отдельно от кода? С этим не будет проблем. Просто сохраним наш словарь с конфигурацией в YAML-файл и разрешим пользователям редактировать его.

Технически, этот YAML — уже и есть внешний DSL, при этом для него не нужны никакие анализаторы для разбора. Можно его загрузить, используя существующие библиотеки, и обработать только поле size:

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 6

Анализаторы в Python

Взглянем на то, что в Python есть для написания анализаторов.

Библиотека PLY (Python Lex-Yacc)

Анализатор состоит из лексического и синтаксического анализаторов. Исходный текст попадает на вход лексического анализатора, задача которого — разбить текст на поток токенов, то есть примитивов нашего языка. Этот поток токенов попадает в синтаксический анализатор, который проверяет правильность их расположения друг относительно друга. Если все в порядке, то выполняется либо генерация кода, либо его выполнение, либо построение абстрактного синтаксического дерева.

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

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 7

Но если ему передать семантически неправильную строку, то мы получим бессмысленный набор токенов:

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 8

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

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 9

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

Для каждого правила нужно объяснение того, что синтаксическому анализатору делать, при возникновении описываемых им условий. Поскольку мы имеем дело с арифметикой, то также желательно соблюдение всех ее правил — с приоритетом операций умножения, скобок и так далее.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 10

У использования инструмента PLY есть целый ряд преимуществ: он гибкий, предоставляет удобные механизмы отладки анализаторов, отличные методы обработки ошибок, код самой библиотеки хорошо читаем.

Однако совсем без минусов не обойтись — порог входа при начале использования инструмента очень высок, а анализаторы с использованием PLY получаются действительно многословными.

Библиотека funcparserlib

Еще один интересный инструмент для создания анализаторов — библиотека funcparserlib. Это комбинатор функциональных парсеров. Разработка анализатора с применением этой библиотеки также начинается с объявления токенов в виде регулярных выражений. Затем описывается сам парсер — задаются примитивы, описываются используемые операции, которые для удобства обработки еще и группируются по приоритету (умножение и деление/сложение и вычитание).

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 11

Теперь нужно описать всю оставшуюся грамматику — для этого мы объявляем описываем как будут выглядеть выражения, а затем описываем приоритеты операций.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 12

К преимуществам funcparserlib можно отнести компактность этой библиотеки и ее гибкость. Из-за этой же компактности многое в ней приходится делать руками — из коробки доступно не так много возможностей. И так как эта библиотека является комбинатором функциональных парсеров — она придется по душе любителям функционального программирования.

Библиотека pyparsing

Еще один вариант для создания анализатора — библиотека pyparsing. Сразу взглянем на код парсера:

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 13

Нигде не описываются токены, все внимание уделяется сразу конечному языку и описанию операций над выражениями с учетом приоритетов.

В pyparsing «из коробки» есть полезные базовые элементы, например методы работы с приоритетами — это упрощает код и делает его более понятным. Кроме того, существует возможность расширения функциональности и создания собственных компонент. С другой стороны — инструмент не может похвастать наличием качественной документации, а отлаживать получившийся компактный анализатор гораздо сложнее чем многословный анализатор с использованием PLY.

Что насчет быстродействия

Поговорим о скорости работы в случае использования каждой из описанных библиотек. Наш анализ показывает, что при обработке несложных кейсов, быстрее всех оказывается PLY.

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 14

В ходе тестов мы «скормили» всем анализаторам задачу сложения всех чисел от нуля до 9999. Вот какой результат в миллисекундах показали кандидаты:

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 15

Сообщения об ошибках

Не стоит забывать, что мы писали наш анализатор для того, чтобы распарсить одно из полей в конфиге системы ротации логов. Очевидно, что если в работе анализатора возникнут какие-то ошибки, пользователю нужно сообщить об этом в понятном формате — в какой строке, на какой позиции и что именно пошло не так.

Еще один плюс PLY — в библиотеке есть встроенный механизм обработки ошибок, возникающих на этапах лексического и синтаксического анализа. При этом не теряется состояние парсера — после ошибки можно попытаться продолжить работу

Работа с DSL: создание собственного анализатора с использованием библиотек Python - 16

Что в итоге выбрать

Конечный выбор инструмента создания парсера зависит от стоящих задач и условий их выполнения. Можно выделить ряд таких комбинаций:

  • Если нужно быстро все описать, а быстродействие не главное — вполне подойдет pyparsing.
  • В случае, если вы любите функциональное программирование, а быстродействие также не очень важно — очевидным выбором будет funcparserlib.
  • Но если скорость работы важнее всего и также хотелось бы описывать все правила «как полагается» по учебникам — конечно, нужно выбрать PLY.

Если существует возможность обработки пользовательских данных средствами самого языка — стоит так и сделать, или использовать регулярные выражения. В более сложных случаях есть смысл начать с применения внутренного DSL, а если этот вариант не подходит — начать использовать готовые языки для структурирования данных (Yaml, Json, XML). Писать собственные анализаторы следует в крайних случаях, когда ничего из перечисленного выше не позволяет решить задачу.

Автор: Positive Technologies

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/232259

Ссылки в тексте:

[1] Image: https://habrahabr.ru/company/pt/blog/319320/

[2] инструментов: https://habrahabr.ru/company/pt/blog/318128/

[3] слайды: http://www.slideshare.net/IvanTsyganov/dsl-63800380

[4] видео: https://www.youtube.com/watch?v=4pIx7TRmItk

[5] Источник: https://habrahabr.ru/post/319320/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best