textX

Проф. др Игор Дејановић (igord на uns ac rs)

Креирано 2025-12-06 Sat 21:51, притисни ESC за мапу, Ctrl+Shift+F за претрагу, "?" за помоћ

1. Основни подаци

  • 100% Пајтон код
  • МИТ лиценца - користи се и у комерцијалним решењима и на истраживачким пројектима
  • Најпопуларнија библиотека за израду ЈСД-ова у Пајтону
    • GitHub stars - 800+
    • GitHub forks - 80
    • GitHub contributors - 24
    • GitHub used by - 584
  • Екосистем под GitHub организацијом https://github.com/textX/
  • Документација, примери и туторијали доступни на https://textx.github.io/textX/
  • Интеграција са VS Code за све језике базиране на textX-у кроз textX-LS пројекат
  • Онлајн игралиште за испробавање https://textx.github.io/textx-playground/

2. Архитектура

textX.png

3. Инсталација

3.1. Инсталација

  • Са PyPI
~> mkdir jsd
~> cd jsd
~/jsd> uv init
Initialized project `jsd`
?main ~/jsd> ls
main.py  pyproject.toml  README.md
?main ~/jsd> uv add textx[cli]
Using CPython 3.14.0
Creating virtual environment at: .venv
Resolved 5 packages in 8ms
Installed 3 packages in 15ms
 + arpeggio==2.0.3
 + click==8.3.1
 + textx==4.2.3
?main ~/jsd> uv run textx
Usage: textx [OPTIONS] COMMAND [ARGS]...

Options:
  --debug  Debug/trace output.
  --help   Show this message and exit.

Commands:
  check            Check/validate model given its file path.
  generate         Run code generator on a provided model(s).
  list-generators  List all registered generators
  list-languages   List all registered languages
  version          Print version info.

3.2. Инсталација развојне верзије са GitHub-а

~> mkdir jsd
~> cd jsd
~/jsd> uv init
Initialized project `jsd`
?main ~/jsd> uv add git+https://github.com/textx/textx@master
Using CPython 3.14.0
Creating virtual environment at: .venv
Resolved 3 packages in 210ms
    Updated https://github.com/textx/textx (6ef5164d77988a8b905a98748e90586c44efba9e)
      Built textx @ git+https://github.com/textx/textx@6ef5164d77988a8b905a98748e90586c44efba9e
Prepared 1 package in 2.20s
Installed 2 packages in 2ms
 + arpeggio==2.0.3
 + textx==4.3.0.dev0 (from git+https://github.com/textx/textx@6ef5164d77988a8b905a98748e90586c44efba9e)

3.3. Инсталација за развој

~> mkdir jsd
~> cd jsd
~/jsd> git clone git@github.com:textX/textX
Cloning into 'textX'...
remote: Enumerating objects: 14302, done.
remote: Counting objects: 100% (2641/2641), done.
remote: Compressing objects: 100% (975/975), done.
remote: Total 14302 (delta 1463), reused 2486 (delta 1365), pack-reused 11661 (from 1)
Receiving objects: 100% (14302/14302), 17.28 MiB | 12.45 MiB/s, done.
Resolving deltas: 100% (8808/8808), done.
~/jsd> cd textX
master ~/jsd/textX> make dev
rm -fr build/
rm -fr dist/
rm -fr .eggs/
...
 + urwid-readline==0.15.1
 + wcwidth==0.2.14
 + webencodings==0.5.1

4. Основна употреба

4.1. Граматика = мета-модел + конкретна синтакса

HelloWorldModel:
  'hello' to_greet+=Who[',']
;

Who:
  name = /[^,]*/
;
from textx import metamodel_from_file
hello_meta = metamodel_from_file('hello.tx')

hello.dot.png

4.2. Модел = програм

hello World, Solar System, Universe

hello_parts.png

4.3. Парсирање - инстанцирање модела

example_hello_model = hello_meta.model_from_file('example.hello')

example.dot.png

  • Модел је граф Пајтон објеката чија структура је у складу са граматиком (нпр. HelloWorldModel објекат садржи Пајтон листу to_greet).
  • Модел можемо даље интерпретирати, анализирати, генерисати код…

4.4. Провера и визуализација мета-модела

  • textX ће при парсирају граматике пријавити синтаксне грешке.
  • Ако желимо можемо проверити граматику у току развоја:

      textx check hello.tx
    
    /home/igor/repos/igordejanovic.github.io/courses/tech/textX/hello.tx: OK.
    
  • У случају грешке биће пријављена тачна локација.

      Error in meta-model file.
      Expected 'abstract_rule_ref' at position (6, 9) => ':   name |*= /[^,]*/ '.
    
  • или визуализовати

      textx list-generators
    
    any -> dot           textX    Generating dot visualizations from arbitrary models
    textX -> dot         textX    Generating dot visualizations from textX grammars
    textX -> PlantUML    textX    Generating PlantUML visualizations from textX grammars
    
      textx generate hello.tx --target dot
    
    Generating dot target from models:
    /home/igor/repos/igordejanovic.github.io/courses/tech/textX/hello.tx
    -> /home/igor/repos/igordejanovic.github.io/courses/tech/textX/hello.dot
       To convert to png run "dot -Tpng -O hello.dot"
    

4.5. Робот пример

fajl robot.tx

Program:
  'begin'
    commands*=Command
  'end'
;

Command:
  InitialCommand | MoveCommand
;

InitialCommand:
  'initial' x=INT ',' y=INT
;

MoveCommand:
  direction=Direction (steps=INT)?
;

Direction:
  "up"|"down"|"left"|"right"
;

Comment:
  /\/\/.*$/
;

fajl program.rbt

begin
    initial 3, 1
    up 4
    left 9
    down
    right 1
end

4.6. Инстанцирање мета-модела

from textx import metamodel_from_file
robot_mm = metamodel_from_file('robot.tx')

robot.dot.png

textx generate robot.tx --target dot
dot -Tpng -O robot.dot

4.7. Парсирање и инстанцирање модела

robot_model = robot_mm.model_from_file('program.rbt')
begin
    initial 3, 1
    up 4
    left 9
    down
    right 1
end

program.dot.png

textx generate program.rbt --grammar robot.tx --target dot
dot -Tpng -O program.dot

4.8. Шта радити са моделом?

  • Интерпретирање
  • Генерисање кода
  • Разне врсте анализе и трансформације

4.9. Интерпретирање Робот модела

class Robot(object):

    def __init__(self):
        # Initial position is (0,0)
        self.x = 0
        self.y = 0

    def __str__(self):
        return "Robot position is {}, {}.".format(self.x, self.y)

4.10. Интерпретирање Робот модела

    def interpret(self, model):

        # model is an instance of Program
        for c in model.commands:

            if c.__class__.__name__ == "InitialCommand":
                print("Setting position to: {}, {}".format(c.x, c.y))
                self.x = c.x
                self.y = c.y
            else:
                dir = c.direction
                print("Going {} for {} step(s).".format(dir, c.steps))

                move = {
                    "up": (0, 1),
                    "down": (0, -1),
                    "left": (-1, 0),
                    "right": (1, 0)
                }[dir]

                # Calculate new robot position
                self.x += c.steps * move[0]
                self.y += c.steps * move[1]

4.11. Интерпретација Робот модела

    robot = Robot()
    robot.interpret(robot_model)
begin
    initial 3, 1
    up 4
    left 9
    down
    right 1
end
Setting position to: 3, 1
Robot position is 3, 1.
Going up for 4 step(s).
Robot position is 3, 5.
Going left for 9 step(s).
Robot position is -6, 5.
Going down for 0 step(s).
Robot position is -6, 5.
Going right for 1 step(s).
Robot position is -5, 5.

Проблем: Ако не задамо корак подразумевано је 0 (textX дефинише default вредности за базичне типове).

4.12. Object processor

def move_command_processor(move_cmd):
    """
    This is object processor for MoveCommand instances.
    It implements a default step of 1 in case not given
    in the program.
    """

    if move_cmd.steps == 0:
        move_cmd.steps = 1
MoveCommand:
  direction=Direction (steps=INT)?
;

Регистрација процесора на мета-моделу:

        # Register object processor for MoveCommand
        robot_mm.register_obj_processors({'MoveCommand': move_command_processor})

Сада се робот понаша исправно.

Setting position to: 3, 1
Robot position is 3, 1.
Going up for 4 step(s).
Robot position is 3, 5.
Going left for 9 step(s).
Robot position is -6, 5.
Going down for 1 step(s).
Robot position is -6, 4.
Going right for 1 step(s).
Robot position is -5, 4.

5. textX језик

5.1. textX граматичка правила

textX метајезик, односно граматика, се састоји од скупа правила.

На пример, ако развијамо језик за опис цртежа, концепти овог језика би могли бити Shape, Line, Circle итд. Следеће правило се користи да опише концепт Circle:

Circle:
  'Circle' '(' color=ID ',' size=INT ')'
;

5.2. Врсте правила

Постоје три врсте правила у textX-у:

  • Обична правила (Common rules),
  • Апстрактна правила (Abstract rules), i
  • Правила препознавања (Match rules).

5.3. Обична правила

На пример:

InitialCommand:
  'initial' x=INT ',' y=INT
;

Правило InitialCommand ће довести до креирања Пајтон класе истог имена чије instance će imati dva atributa: x и y.

5.4. Апстрактна правила

Апстрактна правила су правила која немају изразе доделе и референцирају бар једно апстрактно или обично правило. Најчешће су дефинисана као уређени избор других правила јер се користе да генерализују друга правила. На пример:

Program:
  'begin'
    commands*=Command
  'end'
;

Command:
  MoveCommand | InitialCommand
;

У овом примеру, Пајтон објекат у листи commands ће бити или MoveCommand или InitialCommand. Command правило је апстрактно. Ово правило никада неће резултовати Пајтон објектом.

ListOfCommands:
  commands*=[Command][',']
;

Такође, апстрактна правила могу референцирати правила препознавања и базичне типове. На пример:

Value:
    STRING | FLOAT | BOOL | Object
    | Array | "null"
;

У овом примеру, базични типови као и препознавање стринг "null" су правила препознавања, али Object и Array су обична правила и стога је Value правило апстрактно.

Апстрактна правила могу бити сложена секвенца или уређени избор референци и правила препознавања док год имамо бар једну референцу на апстрактно или обично правило. На пример:

Value:
  'id' /\d+-\d+/ | FLOAT | Object
;

Правило чије тело се састоји само од једне референце препознавања на друго апстрактно или обично правило је такође апстрактно правило:

Value:
    OtherRule
;

Уколико је OtherRule апстрактно или обично правило тада је и Value апстрактно правило.

5.5. Правила препознавања

Widget:
  "edit"|"combo"|"checkbox"|"togglebutton"
;

Name:
  STRING|/(\w|\+|-)+/
;

Сва базична, имплицитна, textX правила (нпр. INT, STRING, BASETYPE) су правила препознавања.

5.6. Базична правила/типови

base_types.png

Препознати уграђени типови се аутоматски конвертују у одговарајуће Пајтон типове и постављају на подразумевану вредност у оквиру опционих препознавања.

5.7. Препознавања (Matches)

Поред уграђених базичних правила, правила препознавања су правила најнижег нивоа. Представљају основне градивне јединице сложенијих правила. Ова правила ће конзумирати део улаза уколико је препознавање успешно.

Постоје две врсте препознавања: препознавање стринга и препознавање регуларног израза.

5.8. Препознавање стринга

Препознавање стринга се пише као стринг у једноструким или двоструким знацима навода. Овако написано правило препознаће задати стринг са улаза у облику у ком је задат.

Примери:

'blue'
'zero'
'person'

5.9. Препознавање регуларног израза

Препознавање регуларног израза користи Пyтхон регуларне изразе1 који се наводе унутар / /. Дакле, дефинишу класу стрингова који се могу наћи на улазу.

Примери:

  • /\d+/ — препознаје стринг од једне или више цифри.
  • /\d{3,4}-\d{3}/ — 3 или 4 цифре, затим '-' па затим још 3 цифре.
  • /[^}]*/ — нула или више карактера различитих од '}'.

5.10. Секвенца (Sequence)

Секвенца је најједноставнији сложени израз који се добија навођењем подизраза један иза другог. На пример, следеће правило:

Colors:
  "red" "green" "blue"
;

је дефинисано као секвенца која се састоји од три стринг препознавања (red, greeen i blue). Секвенца ће успешно бити препозната ако су препознати сви њени подизрази у редоследу у ком су наведени. Претходно Colors правило ће препознати следећи стринг:

red green   blue

Уколико је укључено аутоматско прескакање празних карактера (whitespace skip), што је подразумевано, тада се може између два подизраза у секвенци наћи произвољан број празних карактера као што је приказано у претходном примеру.

5.11. Уређени избор (Ordered choice)

Уређени избор се наводи као скуп израза раздвојених знаком |. Овај израз ће покушати да препозна подизразе с лева на десно. Први израз који се успешно препозна биће коришћен као резултат препознавања.

На пример, следеће правило

Color:
  "red" | "green" | "blue"
;

ће препознати или реч red или green или blue при чему ће парсер да покуша сваки од подизраза с лева на десно.

5.12. Опционо препознавање (Optional)

Опционо препознавање је израз који ће да покуша да препозна свој подизраз ако може, али ће успети у препознавању и уколико подизраз није препознат.

На пример, уколико имамо правило

MoveUp:
  'up' INT?
;

INT ће бити опционо (јер је наведен знак ?) па ће бити могуће навести број иза речи up, али неће бити обавезно.

Следеће линије ће бити успешно препознате претходним правилом:

up 45
up 1
up

Опциони изрази могу бити и сложенији. На пример, код правила

MoveUp:
  'up' ( INT | FLOAT )?
;

имамо да је уређени избор опциони тако да ћемо у овом случају моћи навести цео или реалан број (INT или FLOAT), али нисмо обавезни да то учинимо.

5.13. Понављања (Repetitions)

Понављање нула или више пута (zero or more) се наводи употребом оператора * иза подизраза. Подизраз ће у том случају бити препознат нула или више пута.

На пример, уколико имамо правило

Colors:
  ("red" | "green" | "blue")*
;

Следећи улаз ће бити успешно парсиран:

red blue green

али такође и

red blue blue red red blue green

или празан стринг (препознавање нула пута).

5.14. Понављања (Repetitions)

Понављање једном или више пута се наводи употребом оператора + иза подизраза. Подизраз ће у том случају бити препознат један или више пута.

На пример, уколико имамо правило које је слично претходном, али користи овај облик понављања

Colors:
  ("red" | "green" | "blue")+
;

обавезно је навођење бар једне боје, али може се навести и више у произвољном редоследу и са понављањем као и код претходног правила. Дакле, ово правило не препознаје празан стринг.

5.15. Доделе (Assignments)

Доделе се користе као део поступка за дедукцију метамодела. Свака додела резултује креирањем атрибута мета-класе креиране textX правилом.

Person:
  name=Name ',' surname=Surname ','
    age=INT ',' height=INT ';'
;

Напомена: Правило Name и Surname су дефинисани у граматици, али нису дати у овом примеру.

5.16. Доделе - креирање атрибута

Претходни пример описује правило и мета-класу Person које ће парсирати и инстанцирати објекат са четири атрибута.

Пример:

  Petar, Petrović, 25, 185;

Зарези, дати у претходном примеру, који ће бити препознати између препознавања правила доделе, као и тачка-зарез на крају, морају се наћи у улазном стрингу, али ће бити одбачени приликом креирања модела јер немају никакво семантичко значење. Кажемо да представљају синтаксни шум.

5.17. Доделе - конверзије и вредности

Увек када је на RHS неки од базичних типова (нпр. INT, BOOL, FLOAT, ID) доћи ће до конверзије препознатог стринга у одговарајући Пајтон тип (нпр. int, bool, float, str).

Ако је на RHS препознавање стринга или регуларног израза као у следећем примеру:

Color:
  color=/\w+/
;

тада ће атрибут на LHS (color) бити постављен на вредност коју препозна RHS правило.

Уколико се на RHS налази референца на друго правило тада ће бити препознат и инстанциран објекат класе датог правила и атрибут на LHS ће бити референца на дату инстанцу. На пример:

Project:
  'project' name=ID 'lead' lead=Person
;

lead атрибут биће референца на објекат класе Person а правило Person мора успешно препознати овај објекат иза кључне речи lead.

Постоје четири врсте доделе: obična, bulova, nula ili više и jedan ili više.

5.18. Обична додела

Обична додела ( = ) ће обавити препознавање RHS једном и објекат који се креира доделити атрибуту на LHS.

a=INT
b=FLOAT
c=/[a-Z0-9]+/
dir=Direction

5.19. Булова додела

Булова додела ( ?= ) ће поставити LHS атрибут на True уколико је RHS препознат на улазу или на False уколико није.

cold ?= 'cold'
number_given ?= INT

5.20. Додела нула/један или више

Додела нула или више ( *= ) ће препознавати RHS све док успева и све објекте редом сместити у Пајтон листу која је на LHS. Ако препознавање не успе ни једном LHS ће бити празна листа.

commands*=Command  // опциони низ команди
numbers*=INT       // опциони низ целих бројева

Додела један или више ( += ) ради исто као претходна с тим што RHS мора да препозна бар један објекат тј. LHS никада неће бити празна листа.

numbers+=INT       // низ целих бројева, мора постојати бар један

5.21. Референце (References)

Правила могу да се међусобно референцирају. Референце се наводе на RHS. Постоје два начина референцирања правила: референцирање преко препознавања и референцирање преко везе.

5.22. Референцирање преко препознавања

Structure:
  'structure' '{'
    elements*=StructureElement
  '}'
;

У претходном примеру правило Structure референцира преко препознавања правило StructureElement. Унутар тела Structure концепта, биће препознато нула или више инстанци StructureElement класе. Инстанце ће бити додељене elements атрибуту који ће, у овом случају, бити типа Пајтон листе.

5.23. Референцирање преко везе

ScreenType:
  'screen' name=ID "{"
  '}'
;

ScreenInstance:
  'screen' type=[ScreenType]
;

type атрибут који припада правилу ScreenInstance референцира преко везе правило ScreenType.

Ово би био пример правилне употребе:

// Ово је дефиниција ScreenType објекта
// који се зове Introduction
screen Introduction {

}

// А ово је инстанца ScreenInstance која
// референцира претходни Introduction ScreenType.
screen Introduction

Подразумевано се користи ID правило за препознавање назива циљног објекта. Уколико желимо то да променимо можемо урадити следеће:

ScreenInstance:
  'screen' type=[ScreenType|WORD]
;

У претходном примеру ће за препознавање имена циљног објекта бити коришћено правило WORD.

5.24. Синтаксни предикати (Syntactic predicates)

5.25. Негативни поглед унапред

Not — негативни поглед унапред (!) — Успева уколико правило које следи иза ! предиката не препознаје наставак улазног стринга и обрнуто.

Пример проблема:

Expression: Let | ID | NUMBER;
Let:
    'let'
        expr+=Expression
    'end'
;

У претходном примеру имамо рекурзивно правило Let које се индиректно референцира преко правила Expression. Проблем је у томе што ће ID правило које се позива из Expression правила препознати кључну реч end што ће довести до тога да ниједно Let правило неће моћи успешно да се заврши.

Да бисмо решили овај проблем модификујемо граматику на следећи начин:

Expression: Let | MyID | NUMBER;
Let:
    'let'
        expr+=Expression
    'end'
;
Keyword: 'let' | 'end';
MyID: !Keyword ID;

Уместо директне употребе уграђеног ID правила уводимо правило MyID које користи Not синтаксни предикат да спречи препознавање кључних речи let и end као изразе Expression правила. На овај начин ће end бити конзумирано као завршетак Let правила и граматика ће исправно функционисати.

5.26. Позитивни поглед унапред

And — позитивни поглед унапред (&) — Успева уколико правило које следи иза & предиката препознаје наставак улазног стринга и обрнуто.

Пример:

Model:
    elements+=Element
;
Element:
    AbeforeB | A | B
;
AbeforeB:
    a='a' &'b'      // правило успева само
                    // ако 'b' следи после 'a'
;
A: a='a';
B: a='b';

Уколико имамо улазни стринг "a a a b", прва два a токена ће бити препознати правилом A док ће трећи токен а бити препознат правилом AbeforeB. Иако се увек проверава прво AbeforeB, правило неће успети за прва два a токена јер иза не следи токен b. Последњи токен ће бити препознат правилом B јер га претходно успешно правило AbeforeB није конзумирало са улаза.

5.27. Уклањање препознатог улаза (Match Suppression)

Некада је потребно дефинисати правило препознавања које ће вратити само део препознатог улаза. У овом случају можемо користити оператор за уклањање препознатог улаза (-) који се наводи после израза препознавања.

На пример:

FullyQualifiedID[noskipws]:
    /\s*/-
    QuotedID+['.']
    /\s*/-
;
QuotedID:
    '"'?- ID '"'?-
;

5.28. Модификатори понављања (Repetition modifiers)

Користе се за модификацију понашања свих оператора понављања (*, +, *=, +=). Наводе се унутар угластих заграда иза оператора понављања. Може се навести више модификатора и у том случају се раздвајају зарезима.

У текућој имплементацији је дефинисано два модификатора понављања: модификатор сепарације и модификатор краја линије.

5.29. Модификатор сепарације

Модификатор сепарације (Separator modifier) се користи да дефинише сепаратор код вишеструког препознавања. Наводи се као једноставно препознавање (препознавање стринга или регуларног израза).

numbers*=INT[',']
45, 47, 3, 78

Такође, можемо дефинисати као модификатор препознавање регуларног израза. На пример:

fields += ID[/;|,|:/]
first, second; third, fourth: fifth

5.30. Модификатор краја линије

Модификатор краја линије (End-of-line terminate modifier) се наводи као кључна реч eolterm. Уколико је укључен овај модификатор оператори понављања ће завршити понављање на крају текућег реда тј. радиће само за текући ред.

STRING*[',', eolterm]

Код овог примера вршимо препознавање нула или више стрингова раздвојених зарезима, али само унутар текућег реда. Ако задамо следећи улаз:

"first", "second", "third"
"fourth"

правило ће препознати и конзумирати само прави ред. Стринг "fourth" неће бити обухваћен.

6. Метамодели

Пример креирања метамодела из граматике дефинисане у фајлу:

from textx.metamodel import metamodel_from_file
my_metamodel = metamodel_from_file('my_grammar.tx')

Парсирање текстуалне репрезентације модела и креирање меморијске објектне репрезентације се обавља позивом метода model_from_file i model_from_str метамодел објекта.

my_model = my_metamodel.model_from_file('some_input.md')

6.1. Корисничке класе

from textx.metamodel import metamodel_from_str

grammar = '''
EntityModel:
  entities+=Entity
;

Entity:
  'entity' name=ID '{'
    attributes+=Attribute
  '}'
;

Attribute:
  name=ID ':' type=[Entity]
;
'''

class Entity:
  def __init__(self, parent, name, attributes):
    self.parent = parent
    self.name = name
    self.attributes = attributes


# Користимо "нашу" Entity класу.
# "Attribute" класа ће бити креирана динамички.
entity_mm = metamodel_from_str(grammar, classes=[Entity])

6.2. Процесори

Процесори објеката (object processors) — су процесори који се позивају после сваког успешног препознавања објекта. Као једини параметар добијају објекат који требају да провере/модификују.

Процесори модела (Model processors) — су процесори који се позивају када се цео модел успешно парсира. Као параметар добијају метамодел и модел. Могу да обаве произвољну проверу и/или модификацију модела. Региструју се позивом методе register_model_processor над метамодел објектом.

from textx.metamodel import metamodel_from_file

# Процесор модела је функција који ће прихватити метамодел
# и модел као своје параметре.
def check_some_semantics(metamodel, model):
  ...
  ... Vrši proveru modela i baca TextXSemanticError
  ... ako su semantička pravila narušena.

my_metamodel = metamodel_from_file('mygrammar.tx')

# Региструјемо модел процесор на инстанци метамодела
my_metamodel.register_model_processor(check_some_semantics)

# Парсирамо модел. Функција check_some_semantics ће бити
# аутоматски позвана након успешног парсирања да обави
# додатну семантичку проверу модела.
my_metamodel.model_from_file('some_model.ext')

6.3. Уграђени објекти

class Entity:
  def __init__(self, parent, name, attributes):
      self.parent = parent
      self.name = name
      self.attributes = attributes

entity_builtins = {
        'integer': Entity(None, 'integer', []),
        'string': Entity(None, 'string', [])
}
entity_mm = metamodel_from_file(
  'entity.tx',
  classes=[Entity],        # Регистровање Entity
                           # корисничке класе,
  builtins=entity_builtins # Регистровање integer и string
)                          # уграђених Entity објеката

У претходном примеру региструјемо корисничку класу Entity и затим две њене инстанце (integer и string) које ће представљати уграђене типове за атрибуте.

6.4. Конфигурација парсера

Arpeggio парсер креиран од стране textX-а се може конфигурисати са становишта осетљивости на величину слова (case-sensitivity), третирању празних карактера (white-space handling) и аутоматској детекцији кључних речи.

6.4.1. Конфигурација парсера - case-sensitivity

from textx.metamodel import metamodel_from_file

my_metamodel = metamodel_from_file('mygrammar.tx',
                                   ignore_case=True)

6.4.2. Конфигурација парсера - whitespaces

from textx.metamodel import metamodel_from_file
my_metamodel = metamodel_from_file('mygrammar.tx',
                                   skipws=False, ws='\s\n')

7. Визуализације

7.1. Визуализација метамодела

Метамодел се може визуализовати директно из програмског кода на следећи начин:

from textx.metamodel import metamodel_from_file
from textx.export import metamodel_export

entity_mm = metamodel_from_file('entity.tx')

metamodel_export(entity_mm, 'entity.dot')

Позив функције metamodel_export над метамоделом ће произвести dot фајл датог имена (у овом случају entity.dot).

Текстуални dot фајл можемо превести у неки од графичких формата на следећи начин:

$ dot -Tpng -O entity.dot

Ова команда ће произвести фајл entity.dot.png графичког битмапираног формата PNG.

entity.dot.png

7.2. Визуализација модела

Такође, и модели се могу визуализовати из програмског кода. То се изводи на следећи начин:

from textx.export import model_export

person_model = entity_mm.model_from_file('person.ent')

model_export(person_model, 'person.dot')

Претходни кôд ће произвести фајл person.dot који се може превести у графички формат следећом командом:

$ dot -Tpng -O person.dot

person-entity.png

8. Обрада грешака

from textx.metamodel import metamodel_from_file

robot_metamodel = metamodel_from_file('robot.tx', debug=True)

или

robot_program = robot_metamodel.model_from_file('program.rbt',
                                                debug=True)
$ textx -d visualize robot.tx program.rbt

 *** PARSING LANGUAGE DEFINITION ***
New rule: grammar_to_import -> RegExMatch
New rule: import_stm -> Sequence
New rule: rule_name -> RegExMatch
New rule: param_name -> RegExMatch
New rule: string_value -> OrderedChoice
New rule: rule_param -> Sequence
Rule rule_param founded in cache.
New rule: rule_params -> Sequence
...

>> Matching rule textx_model=Sequence at position 0 =
  >> Matching rule ZeroOrMore in textx_model at posit
      >> Matching rule import_stm=Sequence in textx_m
        ?? Try match rule StrMatch(import) in import_
        >> Matching rule comment=OrderedChoice in imp
            ?? Try match rule comment_line=RegExMatch
            -- NoMatch at 0
            ?? Try match rule comment_block=RegExMatc

...


Generating 'robot.tx.dot' file for meta-model.
To convert to png run 'dot -Tpng -O robot.tx.dot'
Generating 'program.rbt.dot' file for model.
To convert to png run 'dot -Tpng -O program.rbt.dot'

9. RREL

Attribute: 'attr' ref=[Class|FQN|^packages*.classes]
                  name=ID ';';

10. textx команда

10.1. textx команда

textx CLI команда није подразумевано инсталирана када инсталирате textX библиотеку. Да бисте имали ову команду доступну потребно је да инсталирате cli зависности на следећи начин:

pip install textx[cli]

10.2. Основне поткоманде

Основно упутство за употребу може се добити позивом команде без параметара или са параметром --help односно -h.

$ textx --help
Usage: textx [OPTIONS] COMMAND [ARGS]...

Options:
  --debug  Debug/trace output.
  --help   Show this message and exit.

Commands:
  check            Check/validate model given its file path.
  generate         Run code generator on a provided model(s).
  list-generators  List all registered generators
  list-languages   List all registered languages
  version          Print version info.

Излистане команде доступне су од стране основне textX библиотеке. Додатне команде ће бити доступне по инсталацији Пајтон пакета који региструју нове textX команде.

10.3. check поткоманда

$ textx check --grammar robot.tx program.rbt
Error:
/home/igor/repos/textX/textX/examples/robot/program.rbt:3:3:
Expected 'initial' or 'up' or 'down' or 'left' or 'right'
        or 'end' => 'al 3, 1   *gore 4    '

Vidimo da u redu 3, koloni 3 imamo grešku. Parser nam prijavljuje šta je očekivano na toj lokaciji i iza znaka => vidimo deo fajla, odnosno kontekst gde se greška nalazi. Karakter * obeležava lokaciju unutar konteksta.

10.4. Визуализација generate поткомандом

$ textx list-generators
any -> dot         textX[2.3.0]  Generating dot visual...
textX -> dot       textX[2.3.0]  Generating dot visual...
textX -> PlantUML  textX[2.3.0]  Generating PlantUML v...

Видимо да имамо три регистрована генератора од стране textX пакета. Први генератор је у стању да трансформише било који модел на dot језик. Други генератор трансформише textX моделе (тј. метамоделе) у dot. Трећи генератор трансформише метамоделе у PlantUML дијаграме1.

10.5. Генерисање dot фајла

На пример, за визуализацију метамодела потребно је урадити следеће:

$ textx generate robot.tx --target dot --overwrite
Generating dot target from models:
/home/igor/repos/textX/textX/examples/robot/robot.tx
-> /home/igor/repos/textX/textX/examples/robot/robot.dot
    To convert to png run "dot -Tpng -O robot.dot"

Овом командом позивамо генератор који је регистрован за .tx екстензију и бирамо циљни формат/платформу, у овом случају dot. Генератор који ће бити позван је textX -> dot са списка добијеног употребом list-generators. dot фајл који смо добили можемо конвертовати у слику према упутству:

$ dot -Tpng -O robot.dot

Добићемо слику у облику PNG фајла.

robot.dot.png

Уместо креирања слике, dot фајл можемо прегледати неким од dot прегледача. На пример:

$ xdot robot.dot

10.6. Конверзија у PlantUML

Алтернативно, метамодел можемо конвертовати у UML дијаграм употребом PlantUML алата. Да бисмо креирали PlantUML фајл из метамодела позивамо команду generate на следећи начин:

$ textx generate robot.tx --target plantuml --overwrite
Generating plantuml target from models:
/home/igor/repos/textX/textX/examples/robot/robot.tx
-> /home/igor/repos/textX/textX/examples/robot/robot.pu
    To convert to png run "plantuml robot.pu"

Затим можемо креирати PNG слику са plantuml командом према упутству.

robot.png

10.7. Генерисање dot фајла за модел

Уколико желимо да визуализујемо модел потребно је навести и граматику на следећи начин:

$ textx generate --grammar robot.tx program.rbt --target dot --overwrite
Generating dot target from models:
/home/igor/repos/textX/textX/examples/robot/program.rbt
-> /home/igor/repos/textX/textX/examples/robot/program.dot
    To convert to png run "dot -Tpng -O program.dot"

11. Регистрација језика и генератора

11.1. Регистрација језика

Инстанцирати LanguageDesc класу где као параметре наводимо: јединствени назив језика, фајл образац/екстензију за моделе на датом језику, опис и функцију (тачније Пајтон callable) који врши креирање и конфигурацију метамодела.

from textx import LanguageDesc

def entity_metamodel():
    # Funkcija konstruiše i vraća metamodel
    # Npr. poziva metamodel_from_file
    ...

entity_lang = LanguageDesc(
    'entity',
    pattern='*.ent',
    description='Entity-relationship language',
    metamodel=entity_metamodel)

Инстанцу LanguageDesc затим можемо регистровати употребом register_language позива:

from textx import register_language
register_language(entity_lang)

По обављеној регистрацији метамодел се може добити на следећи начин:

from textx import metamodel_for_language
lang_mm = metamodel_for_language('entity')

Декларативан начин регистрације путем setup.py, односно setup.cfg. Користимо улазне тачке (енг. entry points), стандардан механизам Пајтон setuptools пакета:

setup(
    ...
    entry_points={
        'textx_languages': [
            'entity = entity.metamodel:entity_lang',
        ],
    },

Улазна тачка се зове textx_languages - листа стрингова облика "<име језика> = <путања_до_LanguageDesc_инстанце>". У овом примеру инстанца LanguageDesc се налази у пакету entity.metamodel. Варијабле тј. референца се зове entity_lang.

Алтернативно, можемо користити и новији начин употребом setup.cfg фајла:

[options.entry_points]
textx_languages =
    entity = entity.metamodel:entity_lang

Декоратор language - параметри су назив језика и екстензија фајлова модел. Docstring се користи као опис.

from textx import language

@language('entity', '*.ent')
def entity_lang():
    """
    Entity-relationship language
    """
    # Funkcija konstruiše i vraća metamodel
    # Npr. poziva metamodel_from_file
    ...

Уколико смо успешно регистровали језик команда textx list-languages ће га приказати у листи.

11.2. Регистрација генератора

Користимо инстанцу GeneratorDesc. При инстанцирању наводимо: назив језика, назив циљне технологије, опис и на крају функцију која врши генерисање. Функција мора бити следећег облика:

def generator(metamodel, model, output_path, overwrite, debug,
              **custom_args)

Параметри генератор функције су следећи:

  • metamodel — инстанца метамодела изворног језика,
  • model — инстанца модела за који вршимо генерисање,
  • output_path — циљна путања у фајл систему где треба сместити генерисани кôд,
  • overwrite — да ли се врши преписивање циљних фајлова,
  • debug — да ли се генератор позива у моду за отклањање грешака,
  • **custom_args — додатни параметри специфични за генератор.

Декоратор generator - параметри су назив језика и назив циљне платформе. Опис генератора се наводи у docstring-у генератор функције.

from textx import generator

@generator('entity', 'java')
def entity_java_generator(metamodel, model, output_path,
                          overwrite, debug, **custom_args)
    "Entity-relationship to Java language generator"
    # Код који врши генерисање на основу модела.

Регистрација се врши у setup.cfg (или setup.py) на исти начин као и језик с тим што се улазна тачка назива textx_generators:

[options.entry_points]
textx_generators =
    entity_java = entity.generators:entity_java_generator

По успешној регистрацији команда textx list-generators ће листати информације о генератору.

Регистровани генератор се може позвати командом text generate. На пример:

$ textx generate mymodel.ent --target java --overwrite
        --meaning_of_life 42

У претходној команди позивамо генератор регистрован за фајлове са екстензијом .ent над моделом mymodel.ent. У питању је Entity језик. Циљна платформа је java. Додатни параметар meaning_of_life је специфичан за генератор и биће прослеђен кроз custom_args речник.

12. Креирање иницијалног пројекта - scaffolding

Иницијални пројекат креирамо са следећом командом:

$ textx startproject <folder>

Ова команда покреће генератор који поставља пар питања1 и затим креира пројекат у задатом фолдеру. Добра пракса је да се затим пројекат инсталира у текуће радно окружење у развојном моду са:

$ pip install -e <folder>

По успешној инсталацији ваш језик, односно генератор је исправно регистрован и видљив за команде textx list-languages, односно textx list-generators.

Команда startproject није дефинисана основном textX библиотеком већ пројектом textX-dev. Због тога је потребно инсталирати овај Пајтон пакет или директно са:

$ pip install textX-dev

или путем инсталације свих развојних зависности за textX на следећи начин:

$ pip install textX[dev]

да бисте имали startproject доступну као поткоманду textx команде.

13. Примери

13.1. Генерисање кода - Entity пример

13.2. State Machine

13.3. Израда мини компајлера - ppci

13.4. Quick Domain-Specific Languages in Python with textX

13.5. PyFlies - језик за психолошке тестове

14. Протокол језичких сервера (Language Server Protocol - LSP)

Да би се повећала поновна искористивост језичких подршки, фирма Microsoft је у склопу свог VS Code едитора издвојила језичку "памет" у посебну серверску компоненту са којом едитор комуницира у улози клијента. Протокол комуникације је данас отворени стандард под називом протокол језичких сервера (енг. Language Server Protocol - LSP).

lsp-editors-languages.svg

Проблем димензије \(m × n\) мењамо у проблем димензије \(m + n\).

lsp-example.svg

LSP протокол је базиран на JSON-RPC, односно позивању удаљених процедура (енг. Remote Procedure Call - RPC) разменом JSON порука.

15. textX-LS

Алат за креирање ЈСД textX има подршку за LSP кроз пројекат textX-LS. Идеја овог пројекта је аутоматско генерисање језичких сервера за све језике који су креирани употребом textX алата. Библиотека која пружа генеричке сервисе и омогућава брз развој језичких сервера на програмском језику Пајтон је pygls.

textX-LS.png

16. textX игралиште (textX playground)

  • Веб апликација доступна на адреси https://textx.github.io/textx-playground/
  • Омогућава једноставно испробавање и играње са textX језицима без потребе за локалном инсталацијом.

textX-playground.png

17. Литература

  • Игор Дејановић, Језици специфични за домен, Факултет техничких наука, Нови Сад, 2021. (доступно у скриптарници ФТН-а)
  • textX dokumentacija