Автор Тема: [pure С] Макросы как инструмент построения eDSL  (Прочитано 67629 раз)

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Баловство с препроцессором -- немного оффтопик на Оберон-форуме, но позволяет продемонстрировать, как за счёт простейших сишных макросов можно не только симитировать цикл Дейкстры, но и повысить надёжность софта за счёт автоматизации построения некоторых специализированных структур управления.

Вот, предположим, нужно, что бы в некоей процедуры куски кода выполнялись поочерёдно при каждом вызове этой процедуры, причём эти куски кода нежелательно выделять в отдельные процедуры, поскольку сами по себе они большого смысла не имеют.
В сях это можно изобразить примерно так:
void test(void)
{
    static int step = 0;

    switch (step++)
    {
    case 0:
        printf("step 0\\n");
        break;

    case 1:
        printf("step 1\\n");
        break;

    case 2:
        printf("step 2\\n");
        break;

    case 3:
        printf("step 3\\n");

    default:
        step = 0;
        break;
    }
}

void main(void)
{
    int i;
    for (i = 0; i < 10; i++)
    {
        test();
    }
}

step 0
step 1
step 2
step 3
step 0
step 1
step 2
step 3
step 0
step 1

Проблема: нужно вручную помечать номер каждого шага в "case x:" (а их может быть немало, легко обсчитаться).

Решение проблемы: сделать макросы, иммтирующие новую языковую конструкцию, которые возьмут на себя эту рутину.

valeksey предложил такой вариант:
#define FIRST(code)  { int flag = 0; static int i = 0; if (i==0) { { code }; flag = 1; }
#define NEXT(code)   if (!flag && i == __LINE__) { { code }; flag = 1; } else if (flag == 1) { i = __LINE__; flag=2;}
#define LAST(code)   if (!flag && i == __LINE__) { { code }; i    = 0; } else if (flag == 1) { i = __LINE__; } }

void test(void)
{
    FIRST
    (
        printf("step 0\\n");
    )
    NEXT
    (
        printf("step 1\\n");
    )
    NEXT
    (
        printf("step 2\\n");
    )
    LAST
    (
        printf("step 3\\n");
    )
}

Это решение меня смутило количеством if-ов и не вполне очевидной логикой работы, поэтому я сделал такой вариант:
#define FIRST(var) { static int STEPPER_##var##step = 0; switch (STEPPER_##var##step) { case 0:
#define NEXT(var)  STEPPER_##var##step = __LINE__; break; case __LINE__:
#define END(var)   default: STEPPER_##var##step = 0; break; } }

void test(void)
{
    FIRST(some_id)

        printf("step 0\\n");

    NEXT(some_id)

        printf("step 1\\n");

    NEXT(some_id)

        printf("step 2\\n");

    NEXT(some_id)

        printf("step 3\\n");

    END(some_id)
}

Мне кажется, что тут логика выполнения более понятно, более нагляден поток выполнения в раскрытых макросах.
Ну и такой варант может быть вложенным, так как эта конструкция параметризуется неким идентификатором...
« Последнее редактирование: Апрель 05, 2011, 06:23:46 am от Geniepro »
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #1 : Апрель 05, 2011, 08:02:04 am »
Кроме того твой вариант работает быстрее.

Но есть нюанс. У тебя в твоих блоках кода не получится объявлять перменные, а это иногда нужно. Для того, чтобы их объявить, придется писать как-то так:
Код: (cpp) [Выделить]
void test(void)
{
    FIRST(some_id)
    {
        int j = 0;
        printf("step 0 %d\\n", j);
    }
    NEXT(some_id)
    {
        printf("step 1\\n");
    }
    NEXT(some_id)
    {
        printf("step 2\\n");
    }
    NEXT(some_id)
    {
        printf("step 3\\n");
    }
    END(some_id)
}
Ну и return из середины блока тут точно также как и у меня приводит к тому, что в следующий раз будет исполняться тот же самый блок, а не следующий.

Ну а параметризацию макросов я же предлагал. Только я посчитал что матрёшка тут не нужна, а в случае чего сделать вариант с параметризацией будет тривиально.
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #2 : Апрель 05, 2011, 08:26:46 am »
Но есть нюанс. У тебя в твоих блоках кода не получится объявлять перменные, а это иногда нужно. Для того, чтобы их объявить, придется писать как-то так:
Код: (cpp) [Выделить]
   FIRST(some_id)
    {
        int j = 0;
        printf("step 0 %d\\n", j);
    }

Да, я в результате тоже пришёл к этим фигурным скобкам. Ну, они тут более ожидаемы, чем круглые.
А, да, я так и задеплоил эти макросы в свой уютненький продакшн -- пусть следующие поколения прогеров, что меня заменят, подивятся... :о)

Ну и return из середины блока тут точно также как и у меня приводит к тому, что в следующий раз будет исполняться тот же самый блок, а не следующий.
Эти макросы и специально сделал, что бы избавиться от кучи ретурнов в первоначальном коде, наиндокоденном сотрудником. Рефакторинг пришлось делать, млин... :о)
Если нужно досрочно выйти из блока -- будет if-then-else вместо этого goto замаскированного под return...
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #3 : Апрель 05, 2011, 08:30:47 am »

Да, я в результате тоже пришёл к этим фигурным скобкам. Ну, они тут более ожидаемы, чем круглые.
А, да, я так и задеплоил эти макросы в свой уютненький продакшн -- пусть следующие поколения прогеров, что меня заменят, подивятся... :о)
...
Если нужно досрочно выйти из блока -- будет if-then-else вместо этого goto замаскированного под return...

Дык я к тому, что следующие поколения удивятся когда им нужно будет поправить что-то в этом коде и они, по привычке, где-нибудь воткнут что-то вроде:
Код: (cpp) [Выделить]
if (!cond) return; // предусловие типа такое
И внезапно у них там это дело может зациклиться.

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

Peter Almazov

  • Sr. Member
  • ****
  • Сообщений: 482
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #4 : Апрель 05, 2011, 08:34:54 am »
Вот, предположим, нужно, что бы в некоей процедуры куски кода выполнялись поочерёдно при каждом вызове этой процедуры, причём эти куски кода нежелательно выделять в отдельные процедуры, поскольку сами по себе они большого смысла не имеют.
Предлагаю всем участникам соревнований, написать, как нужно сделать это по-хорошему.
Без всяких костылей, так сказать, Идеальный Конечный Результат. На идеальном языке, никаких ограничений для полета фантазии.
У меня есть вариант, выложу его чуть позже.

igor

  • Sr. Member
  • ****
  • Сообщений: 438
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #5 : Апрель 05, 2011, 09:14:49 am »
Вот, предположим, нужно, что бы в некоей процедуры куски кода выполнялись поочерёдно при каждом вызове этой процедуры, причём эти куски кода нежелательно выделять в отдельные процедуры, поскольку сами по себе они большого смысла не имеют.
Предлагаю всем участникам соревнований, написать, как нужно сделать это по-хорошему.

Вот мой вариант на КП:
MODULE TempTest3;

IMPORT Log := StdLog;

VAR s: INTEGER;

PROCEDURE Test*;
BEGIN
 CASE s OF
 | 0: Log.String("Test 0");
  | 1: Log.String("Test 1");
  | 2: Log.String("Test 2");
  | 3: Log.String("Test 3");
  END;
  INC(s);
   IF s > 3 THEN s := 0 END;
  Log.Ln;
END Test;

BEGIN s := 0;
END TempTest3.

А в чём прикол-то? Тривиальная задачка.  :)

valexey

  • Administrator
  • Hero Member
  • *****
  • Сообщений: 1990
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #6 : Апрель 05, 2011, 09:27:48 am »
Вот мой вариант на КП:
MODULE TempTest3;

IMPORT Log := StdLog;

VAR s: INTEGER;

PROCEDURE Test*;
BEGIN
    CASE s OF
 | 0: Log.String("Test 0");
  | 1: Log.String("Test 1");
  | 2: Log.String("Test 2");
  | 3: Log.String("Test 3");
  END;
  INC(s);
   IF s > 3 THEN s := 0 END;
  Log.Ln;
END Test;

BEGIN s := 0;
END TempTest3.

А в чём прикол-то? Тривиальная задачка.  :)

Во-первых в этом решении кто угодно (в этом модуле) может как угодно менять переменную s. При этом чисто из объявлений не следует какое значение s как эта процедура будет интерпретировать. То есть вызыв процедуры в текущем решении будет иметь семантику undefined behaviour.

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

Решение не безопасно.
"но сейчас, чтобы компенсировать растущую мощность компьютеров, программисты используют фреймворки"

igor

  • Sr. Member
  • ****
  • Сообщений: 438
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #7 : Апрель 05, 2011, 09:47:29 am »
Во-первых в этом решении кто угодно (в этом модуле) может как угодно менять переменную s. При этом чисто из объявлений не следует какое значение s как эта процедура будет интерпретировать. То есть вызыв процедуры в текущем решении будет иметь семантику undefined behaviour.

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

Решение не безопасно.

Даже не интересно отвечать.  ;D

Тот, кто "как угодно может менять переменную s", тот точно так же может как угодно менять процедуру Test. Замечу, что переменная s не экспортируется.

Процедуры "живут" в модулях, а не сами по себе. Единицей компиляции является модуль, а не процедура. Единицей загрузки/выгрузки является модуль, а не процедура.

Глобальная переменная s имеет вполне определённый смысл: она показывает какой из блоков будет выполнен при следующем вызове процедуры Test.

"Магические константы" локализованы в процедуре Test. В случае добавления новых блоков, понадобится только расширение диапазона переменной s, а не "переписывание".

Может ты и не заметил, но секция ELSE у оператора CASE отсутствует. Значит, если программист где-то в этом модуле присвоит переменной s недопустимое значение, то ошибка обнаружится при первых N тестовых вызовах процедуры Test.

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #8 : Апрель 05, 2011, 09:48:58 am »
На хаскелле реализация этой конструкции выглядит крайне ужасно, пришлось счётчик шагов протаскивать, но вроде работает:

module Main where

import Control.Monad
import Data.IORef


execute_stepper :: [IO ()] -> IORef Int -> IO ()
execute_stepper actions stepper_var = do
    let max_step = length actions
    step <- readIORef stepper_var
    if step < max_step
    then do
        actions !! step
        stepper_var `modifyIORef` (\\x -> if x == max_step - 1 then 0 else x + 1)
    else do
        head actions
        stepper_var `writeIORef` 1


test :: IORef Int -> IO ()
test = execute_stepper
    [ do putStrLn "step 0"
    , do putStrLn "step 1"
    , do putStrLn "step 2"
    , do putStrLn "step 3"
    ]


main = do
    stepper_var <- newIORef 0
    forM_ [1..10] $ \\i -> do
        test stepper_var

output:
step 0
step 1
step 2
step 3
step 0
step 1
step 2
step 3
step 0
step 1
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #9 : Апрель 05, 2011, 09:57:14 am »
А в чём прикол-то? Тривиальная задачка.  :)
Решённая Вами задачка и впрямь тривиальна, вот только это не та задачка, что обсуждается в данной теме.
Речь об автоматизации рутины с целью повышения надёжности программы, а Вы предлагаете делать всё вручную...
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #10 : Апрель 05, 2011, 10:05:01 am »
Вот, предположим, нужно, что бы в некоей процедуры куски кода выполнялись поочерёдно при каждом вызове этой процедуры, причём эти куски кода нежелательно выделять в отдельные процедуры, поскольку сами по себе они большого смысла не имеют.
Предлагаю всем участникам соревнований, написать, как нужно сделать это по-хорошему.
Без всяких костылей, так сказать, Идеальный Конечный Результат. На идеальном языке, никаких ограничений для полета фантазии.

Вы упростили формулировку задачи, сведя её к тривиальной... :о(
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

Peter Almazov

  • Sr. Member
  • ****
  • Сообщений: 482
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #11 : Апрель 05, 2011, 10:18:08 am »
Пока низачот всем...

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #12 : Апрель 05, 2011, 10:30:22 am »
Пока низачот всем...
Ну нинаю, нинаю...
Мне зачет дважды, стопудово! :о))
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

igor

  • Sr. Member
  • ****
  • Сообщений: 438
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #13 : Апрель 05, 2011, 10:43:36 am »
Речь об автоматизации рутины с целью повышения надёжности программы, а Вы предлагаете делать всё вручную...
Автоматизация не всегда ведёт к повышению надёжности. Всё зависит от того, какой ценой даётся эта автоматизация. В конкретном данном примере автоматизация не нужна, IMHO.

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re:[pure С] Макросы как инструмент построения eDSL
« Ответ #14 : Апрель 05, 2011, 11:10:27 am »
В конкретном данном примере автоматизация не нужна, IMHO.
Почему?
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…