Skip to the content.

Операторы и управляющие конструкции

Создания объектов и присвоения новых значений

Для создания объектов и присвоения им новых значений в NewLang используется сразу три разных оператора:

Использование трех разных операторов для создания/изменения объектов позволяет более гибко контролировать подобные операции и выявлять логические ошибки в коде на более раннем этапе. Например, при определении класса:

:NewClass2 := :NewClass() {
    filed ::= 2; # Будет ошибка, т.к. если field уже есть в базовом классе
    method() = {}; # Аналог override, т.к. method должен существовать в базовом классе
};

Если же контролировать момент создание объектов и присвоения им значений не требуется, то можно пользоваться единственным оператором :=.

    var ::= 1.0; # Создать новую переменную var без явного указания типа
    var = 100; # Присвоить новое значение уже существующей переменной
    printf := :Pointer('printf(format:FmtChar, ...):Int32'); /* Создать новый или переопределить существующий объект printf */

Присваивание значения сразу нескольким переменным и оператор распаковки словаря

NewLang поддерживает операцию присваивания сразу нескольким переменным, которые должны быть перечислены через запятую слева от оператора присвоения. С правой стороны от оператора присвоения может находится одно или несколько значений или оператор распаковки словаря (многоточие). Оператор распаковки словаря можно использовать и при передаче аргументов в функцию.

Причем словарь может быть указан и с левой стороны от оператора присвоения и таким образом можно записать самый простой способ перебора всех его элементов: item, dict := ... dict;, т.е. когда в цикле первый элемент словаря сохраняется в переменную item, а из самого словаря удаляется.

Пример реализации цикла foreach для суммирования всех элементов словаря (или одномерного тензора) с использованием оператора раскрытия списка:

    summa := 0;
    dict := (1,2,3,4,5,);
    \while( dict ) {
        # Первый элемент словаря перемещается в item
        item, dict := ... dict; 
        summa += item;
    };

Арифметические операторы

Все операторы имеют парный аналог с присвоением значения:

Операторы сравнения:

Проверка типа (имени класса объекта):

Для оператора проверки имени типа (класса) объекта используется символ тильда ~. Он немного похож на оператор instanceof в Java. Левым операндом должен быть проверяемый объект, а правым — проверяемый тип, который можно указать строкой литералом, переменной строкового типа или именем проверяемого класса непосредственно. Результатом операции будет истина, если правый операнд содержит название класса проверяемого объекта или он присутствует в иерархии наследования.

    name := "class"; # Строковая переменная с именем класса
    var ~ name; 
    var ~ :class; # Сравнение с типом
    var ~ "class"; # Имя типа как строка литерал

Утиная типизация

Оператор утиной типизации, два символа тильды ~~ — приблизительный аналог функции isinstance() в Python, который для простых типов сравнивает непосредственную совместимость типа левого операнда по отношению к правому. А для словарей и классов в левом операнде проверяется наличие всех имен полей, присутствующих у правого операнда, т.е.:

    (field1=«value», field2=2,) ~~ (); # Истина (т. е. левый операнд словарь)
    (field1=«value», field2=2,) ~~ (field1=_); # Тоже истина (т. к. поле field1 присутствует у левого операнда)
    (field1=«value», field2=2,) ~~ (not_found=_); # Ложь, т.к. поле not_found у левого операнда отсутствует

Строгая утиная типизация ~~~ — для простых типов сравнивается идентичности типов без учета совместимости, а для составных типов происходит сравнение всех свойств с помощью оператора строгого равенства. Для данной операции, пустой тип совместим только с другим пустим типом.

Управляющие конструкции

К управляющим конструкциям языка NewLang относятся условный оператор, два вида циклов, оператор оценки выражения, оператор прерывания последовательности выполнения команд и перехват прерывания. Операторы проверки условий всегда указываются в квадратных скобках, а последовательность команд для выполнения — в фигурных.

Условный оператор

В качестве оператора проверки условия используется синтаксическая конструкция, соответствующая по смыслу термину «следует», т.е. два тире и угловая скобка –>. Такая запись условного оператора очень похожа на математическую и легко объединяется в последовательности для проверки множественных условий вида «else if».

В общем случае условный оператор имеет вид: [ условие ] –> { действие }; или с условием иначе [ условие ] –> { действие }, [_] –> { действие иначе };

Для наглядности записанный с отступами:

    [ условие1 ] --> { действие1 },
    [ условие2 ] --> действие2,
    [ условие3 ] --> действие3,
    [_] --> {действие_иначе};

Или тоже самое, но с использованием макросов из модуля dsl.:

    \if( условие1 ) { 
        действие1 
    } \elif( условие2 ) действие2
    \elif( условие3 ) действие3
    \else { 
        действие_иначе
    };

Оценка выражения

Синтаксическая конструкция с помощью которой реализуется аналог оператора switch выглядит следующим образом:

    [ $var ] ==> {
        [1] --> { code }; # Выполнится проверка условия $var == 1
        [1, 2] --> { code }; # Выполнится проверка условия ($var == 1 || $var == 2)
        [_] --> { code default }; # Ветка условия иначе
    };

Или тоже самое, но с использованием макросов из модуля dsl.:

    \match( $var ) ==> {
        \if( 1 ) { code };
        \if( 1, 2) { code };
        \else { code default };
    };

Этот оператор очень похож на Pattern Matching, но все же не является сопоставлением с образцом, а скорее более краткая запись множественного оператора сравнения, так как в качестве оператора для оценки могут быть использован любые имеющиеся операторы сравнения на равенство:

Но если в качестве оператора сравнения использовать оператор утиной типизации, то оценка выражения превращается в классический Pattern Matching:

    $value := (f1=1, f2="2",);
    \match( $value ) ~~~> {
        \if((f1=_, ), (f1=_, f2=0, )) { code }; # Поле f2 отсутствует или число 
        \if((f1=_, f2="",), (f1=_, f2='',)) { code }; # Поле f2 строка
        \else { code default }; # Код по умолчанию
    };

Операторы циклов

Для записи циклов используются оператор <->, который ставится между условием цикла (проверкой логического выражения) и телом цикла. Условие цикла записывается в квадратных скобках и в зависимости от взаимного расположения цикл может быть с предусловием (while) или постусловием (dowhile):

    [условие while] <-> {
        тело цикла while
    };

    {
        тело цикла dowhile
    } <-> [условие dowhile];

Пример реализации цикла foreach для суммирования всех элементов словаря (или одномерного тензора) с использованием оператора раскрытия списка:

    summa := 0;
    dict := (1,2,3,4,5,);
    [ dict ] <-> { # Условие цикла, пока есть данные
        item, dict := ... dict; # Результат оператора распаковка словаря - первый его элемент перемещается в item
        summa += item; # Вычисление суммы всех элементов словаря
    };

Цикл с предусловием (while) поддерживает конструкцию else, которая выполняется, если условие входа в цикл не было выполнено.

Внимание! Это поведение отличается от аналогичных конструкций в языке Python, у которого секция else выполняется всегда, кроме прерывания цикла по break.

Ветка else у оператора цикла записывается так же как и ветка иначе в условном операторе, т.е.

    [ cond ] <-> {
        ...
    },[_] --> {
        ...
    };

Или тоже самое, но с использованием макросов из модуля dsl.:

    \while(cond) {
        ...
    } \else {
        ... # Выполнится, только если cond будет false при входе в цикл
    };

Операторы прерывания выполнения (оператор возврата)

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

Изменена, а точнее полностью переделана идеология возвратов из функций и обработки ошибок. Теперь она чем-то похожа на подход, примененный в Ruby. Любая последовательность команд заключенные в фигурные скобки (в том числе тело функции), рассматривается как блок кода у которого нет специального оператора аналога return, который возвращает какое либо значение. Просто любой блок кода всегда возвращает последнее вычисленное значение (это чем то похоже на оператор «запятая» в языках C/C++).

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

По умолчанию данные операции возвращают пустое значение. Чтобы прерывание вернуло результат, возвращаемые данные нужно записывать между парой соответствующих символов, т.е. -- 100 --, что является близким аналогом оператора return 100; в других языках программирования, а ++«Строка»++ - аналогом return «Строка»;.

Хотя более точным аналогом этих операторов будет все таки не return, а throw, т.к. эти команды не только прерывают выполнение последовательности команд в блоке, но их еще можно «ловить». Для этого используется блок кода с соответствующей семантикой, {++} - блок кода, который перехватывает положительные прерывания и {--} - блок кода, который перехватывает прерывания, созданные операторами .

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

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

    Test0(arg) := { \if($arg==0) \return("DONE - 0"); «FAIL» };
    Test1(arg) := { \if($arg==1) \return("DONE - 1"); Test0($arg); };
    Test(arg) := {+ \if($arg >= 0) Test1($arg); $arg; +};

    Test(0); # Вернет «DONE — 0» возврат из вложенной функции Test0
    Test(1); # Вернет «DONE — 1» возврат из вложенной функции Test1
    Test(2); # Вернет «FAIL» возврат из вложенной функции Test0
    Test(-2); # Вернет -2 — возврат из функции Test

Есть еще блок {* … *}, который перехватывает оба типа прерываний. Такой блок кода поддерживает типизацию возвращаемого значения, что позволяет в явном виде указывать типы данных, которые нужно перехватывать. Например, {* ... *} :Type1 — будет перехвачено прерывание вида ++ :Type1 ++ или --:Type1--, что позволяет очень гибко формировать логику работы программы.

Блоки кода с перехватом исключений также поддерживают оператор иначе (\else) который, по аналогии с оператором \else в циклах, выполняется только тогда, если прерывания не произошло.

Можно указать сразу несколько типов, которые нужно перехватывать:

    {* 
        ....
    *} <:Type1, :Type2, :Type3>;