Що таке SPARK?
TODO. Дивіться авторську документацію тут.
Двома словами: це інструмент (модуль Python), який дає змогу генерувати «код» з «джерельних текстів» у чотири кроки та трьома рядками:
f = open(filename) generate(semantic(parse(scan(f)))) f.close() |
Постановка задачі
Скажімо, я хочу якимось чином автоматизувати конфігурування мого шейпера (Linux, HTB). Отже, мені треба продумати синтаксис, потім «розібрати» конфігураційний файл та зґенерувати «код» (shell-скрипт для ініціалізації шейпера).
Наскільки це реальна задача? Я би сказав, реальна: адміністратор може конфігурувати шейпер як заманеться (ґрафічний інтерфейс, конфігураційні дані у SQL базі тощо), але із використаням SPARK можна створити інструмент для перевірки (валідації) конфігурації. Безумовно, можна використовувати правила та тріґґери бази даних, інші засоби інших інструментів, але все це може завершувати SPARK. Я маю на увазі, можна зчитати конфігурацію з бази, записати у текстовий файл та обробити: перевірити конфігурацію та створити код (скрипт у даному випадку).
І, звичайно, я трохи спростив синтаксис. Однак ми спробуємо, задля демонстрації ;-)
Синтаксис
Ми почнемо з такого конфігураційного файлу:
# коментарі будемо іґнорувати. class 1:5 root { rate = 10240 descr = Root_as_root ceil = 20480 } class 1:50 parent 1:5 { ceil = 2048 rate = 1024 descr = My_favorite_client } class 1:53 parent 1:50 { descr = My_other_client ceil = 2048 rate = 1024 } |
Це гарний синтаксис? Чи поганий?
Зараз ми не можемо відповісти :-)
Він гарний, якщо дає змогу визначити конфігурацію для нашого завдання; тобто, зараз нам важливо визначити синтаксис для нашої «малої мови».
Після деяких роздумів та експериментів я зупинився на такому синтаксисі:
classes ::= rootdef classdefs (1) rootdef ::= rootclass { params } (2) classdefs ::= classdef (3) classdefs ::= classdefs classdef classdef ::= ordclass { params } (4) params ::= ratedef params ::= ceildef params ::= descrdef params ::= params params ratedef ::= rate = number ceildef ::= ceil = number descrdef ::= descr = string rootclass ::= class classid root ordclass ::= class classid parent classid |
Це означає:
- Наші класи — кореневий клас плюс решта класів («звичайних»),
- Визначення кореневого класу повинно містити визначення «
rootclass
» та набір параметрів, - «Всі інші класи» — це одне чи більше визначень «
classdef
«… - яке повинно містити «
classdef
» та параметри… … і так далі.
Ми будемо намагатися вибудувати AST таким чином:
classes /\ / \ / \ / \ / \ root classdefs /\ / \ / \ / \ / \ classdefs classdef . . . classdefs /\ / \ / \ / \ / \ classdefs classdef | | | classdef
Видно, що наше дерево не відображає наш синтаксис, якщо чесно :-) Воно не містить вузлів «params
», «ratedef
» тощо. Але ми пропустили їх навмисно: SPARK дає змогу будувати дерева «як треба», пропускаючи одні вузли та «прививаючи» інші. Це питання стилю, напевно, — які вузли повинні формувати дерево для певної задачі.
(Можливо, я вибрав вузли для дерева не оптимально. Але будуємо дерево саме так, заради демонстрації SPARK, знову ж таки.)
Але це ще попереду; зараз переходимо до Сканування.