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

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

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

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

ЧИТАТЬ   Сервис Magic ToDo разбивает задачи на этапы с помощью ИИ - Лайфхакер

Чтобы использовать 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)

Здесь мы увидим список имен (Мир, Вселенная), которые относятся к терминам 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")

Генератор создан для языка приветствия и будет доступен в качестве цели Python. Добавить в точки входа регистрация в 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, которая брала бы значение перечисления из пространства состояний и переход (из списка возможных) и возвращала бы новое состояние или исключение, если переход такого типа не был реализован.

ЧИТАТЬ   Великобритания выделит средства на усиление кибербезопасности в Украине

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

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

Source

От admin