Ввозврат, прерывания и обработка ошибок

Последовательность команд (блок кода) можно прервать в любом месте с помощью специальных операторов.

Для этого используются две разные команды, которые условно можно назвать положительным и отрицательным прерыванием потока выполнения, что примерно соответствует семантике их записи. “Отрицательное” прерывание записывается в виде двух минусов, а “положительное” прерывание в виде двух плюсов, т.е. -- или ++.

Оператор прерывания не возвращает никакого значения (точнее, возвращает пустое значение). Чтобы прерывание вернуло другое значение, возвращаемые данные нужно записать между парой соответствующих символов, т.е. -- 100 --, что является примерным аналогом оператора return 100; в других языках программирования, а ++‘Строка’++ - аналогом return 'Строка';.

Локальный переход

Именованные блоки кода могут выступать в качестве локальных меток для передачи потока выполнения. Если перед оператором прерывания указать пространство имен, то оператор прерывания станет именованным и будет действовать только для указанного блока кода.

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

Локальная передача потока управления работает с именованными блоками кода, поэтому переход в начало блока кода или выход из него, возможны в том числе и изнутри нескольких вложенных именнованных блоков кода.

    b1 := 1;

    # Бесконечный цикл с именованным блоком кода
    [1] <-> outer_loop:: { 
        # Именованный блок кода outer_loop

        b2 := 1;
        [1] <-> inner_loop {
            # Именованный блок кода inner_loop

            printf("Values: b1=%d, b2=%d", b1, b2);
            
            # Проверка условий
            [b1 == 2 && b2 == 2] --> {
                outer_loop:: ++;    # Прерываем outer_loop
            }, [b2 == 5] --> {
                inner_loop:: ++;    # Прерываем inner_loop
            }
            b2 += 1;
        }
        b1 += 1;
    }

Тоже самое с использованием макросов DSL.:

    b1 := 1;
    @loop outer_loop:: { # @while(@true)

        b2 := 1;
        @loop inner_loop {

            printf("Values: b1=%d, b2=%d", b1, b2);

            @if( b1 == 2 @and b2 == 2) {
                @break outer_loop;
            } @elif( b2 == 5 ) {
                @break inner_loop;
            }
            b2 += 1;
        }
        b1 += 1;
    }

Возврат результата

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


ns:: { # Пространство имен ns
    
    func_name(val):Int32 ::= {  
        # Блок кода тела функции ns::func_name::
        ...

        # Возврат из функции
        func_name ++ 0 ++;
        # Или @return(0); с ипользованием DSL

        :: ++ 0 ++; # Завершение программы? exit(0)
        :: -- ; # Прерывание выполенения программы? abort()
    };
};

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

Аналогом такого поведения являются функции exit() для положительного прерывания и abort() для отрицательного, что отражено в DSL.

Перехват прерывааний

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

Для не именованных прерываний такое ограничение отсуствует, поэтому более точным аналогом не именованного прерывания будет не return или break, а throw, т.к. эти операторы не только прерывают выполнение последовательности команд в любом блоке кода и возвращают результат, но их еще и можно “ловить”.

Для перехвата не именованных прерываний используются блоки кода с соответствующей семантикой, {++} - блок кода, который перехватывает положительные прерывания и {--} - блок кода, который перехватывает отрицательные прерывания, созданные операторами --, и блок кода {**}, который перехватывает сразу оба типа прерываний.

Например, возврат из нескольких вложенных функций без необходимости обрабатывать результат возврата в каждой из них. В этом примере функция Test перехватывает “положительные” прерывания из вложенных функций:

    Level2(arg) := { 
        if($arg==2) {
            ++ "Level - 2" ++; 
        };
        "Level2 - DONE";
    };

    Level1(arg) := { 
        if($arg==1) {
            ++ "Level - 1" ++; 
        };
        Level2($arg); 
    };

    Test(arg) := {+ 
        if($arg >= 0) {
            Level1($arg); 
        }
        $arg; 
    +};

    Test(2); # Вернет "Level — 2" возврат из вложенной функции Level2
    Test(1); # Вернет "Level — 1" возврат из вложенной функции Level1
    Test(0); # Вернет "Level2 - DONE" возврат из вложенной функции Level2
    Test(-2); # Вернет -2 — возврат из функции Test

Обработка ошибок

Оба варианта прерываний равнозначны и “отрицательное” прерывание для возврата ошибки используется только условно. Блоки кода с перехватом прерываний, совместно с оператором оценки выражения, можно использовать для обработки исключений в классическом стиле.

    [ {*  # Перехват всех исключений на базовом синтаксисе
        op1;
        op2;
        ...

    *} ] ~> { # Сопоставление по типу возвращенного значения
    
        [:ErrorType1] --> { code };
        [:ErrorType2, :ErrorType3] --> { code };
        [...] --> { code default };
    };


    # Семантика с применением DSL
    @try {

        op1;
        op2;
        ...
        
    } @catch (...) { # Перехват всех исключений
        @case(:Error){ 
            ...
        };
        @default @forward;
    };

    # Тоже самое, только для заданных типов
    @try {

        op1;
        op2;
        ...
        
    } @catch (:Type, :Type2, :Type3) {
        @case(:Type){ 
            ...
        };
        @case(:Type2, :Type3){ 
            ...
        };
        @default @forward;
    };

Расширенные варинаты прерывания выполнения

Добавить реализациию для короутин после их полной реализации в clang (скорее всего на уровне аннотаций)


@@ coroutine @@ ::= @@ __ANNOTATION_SET__(coroutine) @@;

@@ co_yield(...)    @@ ::= @@ __ANNOTATION_CHECK__(coroutine) @__FUNC_BLOCK__ :: -- @$... --
@@ co_await         @@ ::= @@ __ANNOTATION_CHECK__(coroutine) @__FUNC_BLOCK__ :: +-
@@ co_return(...)   @@ ::= @@ __ANNOTATION_CHECK__(coroutine) @__FUNC_BLOCK__ :: ++ @$... ++

ns {

    @coroutine
    co_func(val):Int32 ::= {  
        # Блок кода тела функции
        ...

        # Возврат и приостановка выполнения функции
        @__FUNC_BLOCK__ :: -- 0 --; # ns::co_func::  -- 0 --;  
        # Или @co_yield(0); с ипользованием DSL

        # Приостановить и вернуть управление
        @__FUNC_BLOCK__ :: +-; # ns::co_func::  +-;  
        # Или @co_await; с ипользованием DSL

        # Возврат и завершение функции
        @__FUNC_BLOCK__ :: ++ 0 ++; # ns::co_func::  ++ 0 ++;  
        # Или @co_return(0); с ипользованием DSL
    };

};

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

co_await +-

Унарный оператор co_await приостанавливает сопрограмму и возвращает управление вызывающей стороне. Его операнд представляет собой выражение, которое либо (1) относится к типу класса, определяющему оператор-член co_await, либо может быть передано оператору, не являющемуся членом co_await, либо (2) может быть преобразовано в такой тип класса с помощью текущей сопрограммы.

    auto request = await socket.read(...);
    auto result = await db_client.do_query(prepare_request(request));
    await socket.write(prepare_result(result));
    socket.close();
    request := (){ socket.read(...) };
    auto result = (){ request, db_client.do_query(prepare_request(request)) };
    (){ socket.write(prepare_result(result)) };
    socket.close();