Создание DSL на Python с библиотекой textx

от автора

Для описания объектов и процессов в терминах бизнес-логики, конфигурирования и определения структуры и логики в сложных системах популярным подходом является использование предметно-специфических языков (Domain Specific Language — DSL), которые реализуются либо через синтаксические особенности языка программирования (например, с использованием средств метапрограммирования, аннотаций/декораторов, переопределения операторов и создания инфиксных операторов, как например в Kotlin DSL) или с помощью применения специализированных инструментов разработки и компиляторов (например, Jetbrains MPS или парсеров общего назначения, таких как ANTLR или Bison). Но существует также подход реализации DSL, основанный на синтаксическом разборе и одновременной кодогенерации для создания исполняемого кода по описанию и в этой статье мы рассмотрим некоторые примеры использования библиотеки textx для создания DSL на Python.

textX — это инструмент для создания языковых моделей (DSL) на Python. Он позволяет быстро и легко определить грамматику языка и сгенерировать парсер для этого языка. textX распространяется с открытым исходным кодом, легко интегрируется с другими инструментами Python и может быть использован в различных проектах, где необходимо определять и обрабатывать языки на основе текста.

С помощью textX можно определять различные типы языковых конструкций, такие как ключевые слова, идентификаторы, числа, строки и т. д., а также определять их свойства, например, типы данных или ограничения на значения. Определение грамматики языка происходит в текстовом файле в формате, который называется meta-DSL (и зарегистрован как язык tx, связанный с маской файлов *.tx). Метамодель используется для проверки синтаксической корректности DSL-модели и позволяет сформировать дерево объектов для использования в кодогенераторе Python (который может сформировать как другой Python-код, так и исходный текст на любом другом языке программирования и даже сделать PDF-документ или создать dot-файл для graphviz с целью визуализации мета-модели).

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

Для использования textX прежде всего установим необходимые модули:

pip install textX click 

После установки появится консольная утилита textx, которая будет использоваться для проверки корректности метамоделей и DSL-модулей (в соответствии с грамматикой языка). Textx использует setuptools для поиска зарегистрированных компонентов и позволяет расширять свои возможности через добавление языков (список может быть просмотрен через textx list-languages) и подключение генераторов (textx list-generators). Расширения могут быть установлены как модули через pip install, например:

  • textx-jinja — использование шаблонизатора jinja для преобразования DSL-модели в текстовый документ (например, в HTML)

  • textx-lang-questionnaire — DSL для определения опросников

  • PDF-Generator-with-TextX — генератор PDF на основе DSL-описания

Мы будем создавать свой язык без использования дополнительных расширений, чтобы увидеть весь процесс. Начнем с простой задачи интерпретатора текстового файла, состоящего из строк «hello «, который будет генерировать выполняемый код на Python для отображения адресных приветствий. Генератор и описание языка будем создавать в пакете hello и также определим файл setup.py для конфигурирования точек входа в извлечение метамодели для определяемого языка и генератора кода.

Описание (hello.tx) может выглядеть следующим образом:

DSL:   hello*=Hello ;  Hello:   'Hello' Name ;  Name:   name=/[A-Za-z\ 0-9]+/ ; 

В определении используются условные обозначения:

  • hello*=Hello — перечисление из нескольких элементов (Hello), могут отсутствовать (будут собираться в список hello)

  • hello+=Hello — один или несколько элементов

  • hello?=Hello — элемент может присутствовать, но необязательно (в модели будет None)

  • hello=Hello — точно один элемент

Также определение может включать в себя строковые константы, регулярные выражения, их группировки (например, через вертикальную черту обозначается выбор одного из значений) с модификаторами (+, ?, * имеют обычный смысл как для регулярных выражений, # подразумевает возможный произвольный порядок определений).

Создадим определение языка в hello/__init__.py:

import os.path from os.path import dirname  from textx import language, metamodel_from_file   @language('hello', '*.hello') def hello():     """Sample hello language"""     return metamodel_from_file(os.path.join(dirname(__file__), 'hello.tx')) 

Здесь мы определяем новый язык с идентификатором hello, который будет применяться к любым файлам с маской имени *.hello. Добавим определение setup.py:

from setuptools import setup, find_packages  setup(     name='hello',     packages=find_packages(),     package_data={"": ["*.tx"]},     version='0.0.1',     install_requires=["textx_ls_core"],     description='Hello language',     entry_points={         'textx_languages': [             'hello = hello:hello'         ]     } ) 

И установим наш модуль:

python setup.py install 

И проверим наличие установленного языка:

textx list-languages    textX (*.tx)                  textX[3.1.1]                            A meta-language for language definition hello (*.hello)               hello[0.0.1]                            Sample hello language 

Теперь создадим тестовую модель, основанную на грамматике (test.hello):

Hello World Hello Universe 

Проверим корректность метамодели и нашей DSL-модели (на соответствие метамодели):

textx check hello/hello.tx hello/hello.tx: OK. textx check test.hello     test.hello: OK. 

Мы можем получить визуальную диаграмму для описания композиции DSL (может быть создано описание для Graphviz или для PlantUML):

textx generate hello/hello.tx --target dot dot -Tpng -O hello/hello.dot 

Теперь добавим возможность генерации кода, но перед этим сделаем программный разбор DSL-определения на метамодели:

from textx import metamodel_from_file  metamodel = metamodel_from_file('hello/hello.tx') dsl = metamodel.model_from_file('test.hello') for entry in dsl.hello:     print(entry.name) 

Здесь мы увидим список имен (World, Universe), которые относятся к термам Hello (сами термы будут доступны через список hello в корневом объекте разобранной модели). Добавим теперь возможность кодогенерации, для этого добавим функцию с аннотацией @generator:

@generator('hello', 'python') def python(metamodel, model, output_path, overwrite, debug, **custom):     """Generate python code"""     if output_path is None:         output_path = dirname(__file__)     with open(output_path + "/hello.py", "wt") as p:         for entry in model.hello:             p.write(f"print('Generated Hello {entry.name}')\n")     print(f"Generated file: {output_path}/hello.py") 

Генератор создается для языка hello и будет доступен как target python. Добавим в entry_points регистрацию в setup.py:

'textx_generators': [             'python = hello:python'         ] 

И теперь выполним генерацию кода:

textx generate test.hello --target python 

Результат генерации будет выглядеть так:

print('Generated Hello World') print('Generated Hello Universe') 

Теперь немного усложним задачу и реализуем конечный автомат. Определение DSL для конечного автомата может выглядеть так:

states {   RED,   YELLOW,   RED_WITH_YELLOW,   GREEN,   BLINKING_GREEN }  transitions {   RED -> RED_WITH_YELLOW (on1)   RED_WITH_YELLOW -> GREEN (on2)   GREEN -> BLINKING_GREEN (off1)   BLINKING_GREEN -> YELLOW (off2)   YELLOW -> RED (off3) } 

Здесь мы перечисляем возможные состояния и переходы между ними (с идентификаторами переходов). Для определения грамматики можно использовать такое описание:

StateMachine:   'states' '{'     states+=State   '}'   'transitions' '{'     transitions+=Transition   '}' ;  State:   id=ID (',')? ;  TransitionName:   /[A-Za-z0-9]+/ ;  Transition:   from=ID '->' to=ID '(' name=TransitionName ')' ; 

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

from textx import metamodel_from_file  metamodel = metamodel_from_file('statemachine/statemachine.tx') dsl = metamodel.model_from_file('test.sm')   # описание перехода class Transition:     state_from: str     state_to: str     action: str      def __init__(self, state_from, state_to, action):         self.state_from = state_from         self.state_to = state_to         self.action = action      def __repr__(self):         return f"{self.state_from} -> {self.state_to} on {self.action}"   states = map(lambda state: state.id, dsl.states) transitions = map(lambda transition: Transition(transition.__getattribute__('from'), transition.to, transition.name),                   dsl.transitions) # извлекаем название действий (для меню) actions = list(map(lambda t: t.action, transitions)) 

И теперь реализуем конечный автомат:

current_state = states[0]  while True:     print(f'Current state is {current_state}')     print('0. Exit')     for p, a in enumerate(actions):         print(f'{p + 1}. {a}')     i = int(input('Select option: '))     if i == 0:         break     if 0 < i <= len(actions):         a = actions[i-1]         for t in transitions:             if t.state_from == current_state and t.action == a:                 current_state = t.state_to                 break 

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/732562/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *