H2 и DSL. Плиз, подскажите...
От: PSV100  
Дата: 26.03.12 16:16
Оценка:
Всем добрый. У меня небольшой философский вопросик про Н2. Навеяно соседней темой про draft Н2. В самом документе сейчас мало чего вразумительного сказано, а в его обсуждении сплошные и традиционные для Н эмоции и сплошная абстракция. Другие слухи про Н2 ещё менее информативны.

Я не немерлист, и с нет-ом плотно не работаю. Немерля смотрел по-диагонали, глубоко копать, к сожалению, нет возможностей, пока. Но разобраться в нём всё-таки непросто, например, в MetaLua можно быстро въехать фактически без документации. У меня сейчас назревает потребность в разработке своего языка под JVM. Пока всё на уровне прощупывания/оценки, но хотелось бы понять:

— сможет ли чем-нибудь помочь Немерле, хотя бы в будущем. К тому же, есть отдаленная перспектива и разработки под Net;
— определиться в принципах/подходах для своей задачи, ибо опыта языкостроения под такие ВМ как Java/Net нет. А в Немерле, вроде, с DSL собак уже наелись, и решения для него с потолка не принимают (я надеюсь).

Я недавно создавал тему по поводу своего DSL (по другим вопросам), я приведу его псевдо-пример оттуда, чтобы не объяснять "на пальцах":
/*
*  Все комментарии как в SQL
*/

-- однострочный комментарий

-- Заголовок для отчёта. С ";" завершаются законченные выражения.
-- Все подробности опускаем. Интуитивно должно быть понятно.

report spool "sample.txt" pagesize 35 noalign title "Sample Report";


-- установка встроенных констант

printer_copies        = 1;
printer_paper_size    = 'A4';
printer_orientation   = 'landscape';
printer_top_margin    = 200;
printer_bottom_margin = 100;
printer_left_margin   = 250;
printer_autolines     = false;

column_count          = 1;


-- Параметры для отчёта. Параметры, константы и пр. для отчёта можно указывать как
-- внутри отчёта, так и внешне. С отчётом можно работать отдельно от системы,
-- для тестирования, разработки и пр.

param Dept display "Enter Dept";
param Employee display "Enter Employee";
param order_param display "Sorted by";


-- Выражение, создающее объект для работы с SQL-запросом.
-- MainQuery - его имя. Для выполнения запроса используется 
-- параметр отчета Dept, который был выше указан.
-- В "{...}" указывается как бы тело выражения, где в данном случае
-- текст запроса.

query MainQuery fetch by 100
{
    select e1.*,
    --, emp e2   <-- этот комментарий уже и для SQL
    --, emp e3
    where (e1.deptno = :dept) or (:dept is null)
    order by e1.deptno
};


-- Это примеры агрегатных переменных, вычисляемых по правилам на каждом шаге
-- формирования отчёта. Например, sum(mainquery->deptno) - сумма по полю deptno
-- из выше указанного запроса MainQuery. 
-- when, before, after  - признак срабатывания события, напр.:
--    when check(mainquery->deptno) - когда изменилось поле deptno
-- reset to ... - когда сработало событие, то сбрасываем значение, напр., в 0

sumdept = sum(mainquery->deptno) when check(mainquery->deptno) reset to 0;
page_sumdept = sum(mainquery->deptno) before PageStart() reset to 0;
page_sumdept_after = sum(mainquery->deptno) after PageStart() reset to 0;
emp_count = sum (1) when check(mainquery->deptno) reset to 0;


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

EmpNo   = iif((SomeFunc(20) + 234.5) >= var2, MainQuery->EmpNo, MainQuery->ename);
EmpName = MainQuery->ename;
Job     = MainQuery->job;
ckk     = Check(MainQuery->deptno);


-- Это пример выражения "control" - некий управляющий элемент для вывода данных.
-- В данном случае в его теле указывается текст для форматирования, т.е. это
-- случай для матричных принтеров (они ещё лазерников переживут).
-- turn ...  - когда нужно выводить данные

control PageHdr turn when (PageStart() or ckk) and not ReportFinish()
{
{Bold}page {pagenum+1}{NoBold} Current Date: {current_date() picture dd.mm.yyyy}
+-------------------------------------------------------+
| DPT|  EMPNO | EmpName    |   Job     |    Salary      |
+-------------------------------------------------------+
};


-- так можно строки данных выводить:
-- (можно и в таком стиле: {var1} | {var2} | {var3}  )

control RegularBand
{
{MainQuery->deptno picture ##} | {
EmpNo picture ######} | {
EmpName picture w10} | {
job picture w9} | {
MainQuery->sal picture ###,###,##0.00;; } |
};


-- Графические отчёты можно делать по таким принципам:

page_width    = 2200;    -- в 1/10 мм

table_header  = DrawTable(
    0,                                    -- top
    0,                                    -- left
   -1,                                    -- height
    page_width,                           -- width
    'top | left | right | bottom',        -- какие линии рисовать
    
    ... 
    -- и прочие параметры таблицы

    -- первая ячейка
    'Department',                         -- текст для вывода
    10,                                   -- ширина (% от ширины таблицы)

    -- вторая ячейка
    'Emp No',
    10,
    
    ...
);

-- Это управляющий элемент для рисования графических данных, т.к. в объявлении
-- есть слово draw. Тело состоит из вызова функций для рисования.

control DrawPageHeader draw turn when PageStart() or (ckk and not ReportFinish())
{
  table_header;
  ...
};


Кроме декларативного описания могут быть свои примитивные функции, как здесь:
-- сделаем модель данных

model SampleModel extend BaseModel
   not (null, zero)
{
    DeptNo    integer         as "Типа номер",
    EmpNo     numeric(8, 2)   as "А ещё один номер",
    EmpName   char(120)       as "Это типа имя" allow null,
    Job       integer         as "Что умеем делать" allow zero,
    Salary    currency        as "И ещё заголовок для поля"
}
options
{
    (EmpName, Job)   alignment right    styling by SomeStyle,
    Salary           HideZero true,
    Job map {
        1   as "Временно безработный",
        2   as "Доктор-землекоп",
        10  as "Умею всё"
    }
};

-- таблица для работы (это внутри скрипта, не для SQL)

table SampleTable on SampleModel;

-- объект для манипулирования данных

view SampleView on SampleTable active false
    sort by DeptNo
    filter on Job > 1000.00
    aggregate {
        SumSal on sum(Salary)
    }
;

-- можно делать простейшие функции

function some_func(NeedView, Param2, Param3)
    if NeedView then
        use(SampleView);
        ...
        -- можно манипулировать данными, работать с SQL-базой и пр.
        -- доступ к данным также:

        var1 = SampleView->SumSal;

        -- есть ещё форма через точку, но это для свойств, т.е Obj.Prop
    end;
end;

-- или так

other_func = do
   ...
end;


Сейчас потребуется и опциональная статическая типизация, и многие всякие расширения для вычислений. Причём язык работает не в режиме последовательного выполнения скрипта, а в определенные моменты читаются декларативные указания и дергаются всякие вычисления из разных мест. Фактически, это замена мутным XML-конфигурациям, но с человеческим лицом и возможностью указывать свои вычисления.

Как можно реализовать такой язык в Н2:

— вариант первый: решение "в лоб".

Сейчас реализовано так: свой парсер, который динамически расширяется/настраивается, т.е. указывается, какие литералы или термы разбирать (это важно для задачи). Разбор ведется поточно, когда разруливается какое-то законченное выражение, то создаются специализированные объекты и помещаются в спец-коллекции, по ходу дела выполняется всякий анализ, проверки и пр. Полноценного AST-дерева, в классическом понимании, сейчас фактически нет. Система работает по принципу SQL-машины: получили DSL, отпарсели/зарядили, постреляли, когда нужно — DSL-снаряды выгрузили. И нужно сейчас реализовать нечто своего лиспа, как та же кложура, но со своим синтаксисом, и с меньшим функционалом.

Н2 сможет предоставить мне для такого случая набор "помогалок": удобный парсер, настройка которого выполняется декларативно, набор какого-то API, где я смогу анализировать разобранное AST, выполнять генерацию байт-кода для JVM/Net, если будут бакенды для этого, и пр. Сейчас есть много непоняток, типа, будет ли парсер расширяться и внутри кода, кроме DSL для этого (имхо, скорее да), возможен ли "поточный" разбор и нужен ли он (в моём случае специально избавились от промежуточного AST для облегчения/ускорения), возможна ли правка AST "вручную" и уместна ли она, и, в целом, пока нет общего представления о всех механизмах. Кстати, какова скорость парсера, ведь решение сложное и очень универсальное? И т.д.
Одним словом, Н2 позволит мне, и в какой-то степени вынудит меня (об этом ниже), решить задачу "вручную", и предоставит для этого немало "облегчалок".

— вариант второй: всё декларативно, как "лисп-счастье".

Как я понимаю, декларативное объявление макросов в Н является таким же полноправным явлением (если не более), как и объявление типов, классов и пр. Сейчас макросы в Н направлены, прежде всего, на расширение самого Немерля как языка, т.е., в основном, через макросы пытаются генерировать код для классов (создавать конструкторы, поля и т.д.), ну и делать алгоритмические помогалки внутри кода. Сейчас в Н1 есть ряд ограничений, и я даже не могу представить, возможно ли реализовать мой такой DSL через макросы. В Н2 вроде заявляется гораздо больший потенциал.

В идеале хотелось бы так, раз уж макросы могут писать любые кухарки. Наваять макросы, которые фактически создадут новый язык, рассказать пользователю об этом языке, не посвящая в сам Немерле (чтобы ноги были целыми), он ваяет на этом языке, когда нужно, мы компилируем в байт-код нужной платформы. Хотелось бы, чтобы это легко можно было делать динамически, из своего приложения. При этом, для упрощения жизни (если не будет другого варианта) можно анализировать, что наваял пользователь-программист, через стандартные средства платформы для метаданных, т.е. через всякие рефлексии и пр. Т.е. компилируем, грузим созданные классы/сборки, анализируем, работаем, выгружаем и т.д.


Вот меня и интересует, возможен ли второй вариант для реализации моего языка, где всё декларативно и гармонично из коробки?
И вообще, я правильно оцениваю картину будущих событий?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.