RUSSIAN
(Русский)
ENGLISH


Эта функция использует куки.
Если браузер не принимает куки,
используется светлая тема.

Введение

Язык программирования AL/IV - это

   минимальный

   императивный

   объектно-ориентированный

   платформенно-независимый

язык высокого уровня

   со статической типизацией,

претендующий на чрезвычайно высокую степень надежности кода

управляемого уровня.

 

Важные семантические особенности:

NONE-объекты вместо NULL-ов

 
  • Поля NONE-объектов всегда равны NONE.
  • Запись в поля NONE-объектов игнорируется.
  • Чтение полей всегда дает нули и NONE-объекты соответствующих типов.
  • NONE-объекты возвращаются в случае обращения за пределами массивов,
  • и при потере объекта, на который имелась ссылка.
  • Все объектные переменные всегда инициализированы значениями NONE
  • и никогда не имеют значение NULL.

Разделение указателей на владеющие (сильные) и использующие_ (слабые)

 
  • Сильные указатели не могут быть использованы для организации замкнутых цепочек взаимного владения.
  • Не нужен механизм сборки мусора.
  • Объект автоматически уничтожается, когда обнуляется его счетчик использования сильными указателями.
  • При уничтожения объекта все слабые ссылки на него автоматически перенаправляются на NONE-объекты соответствующего класса.
  • Новые объекты либо должны адресоваться сильным указателем, либо передаваться во владение ранее созданному объекту.

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

 
  • Не требуются механизмы для исключительного доступа к разделяемым объектам.
  • Поток видит только свои объекты и не имеет доступа к объектам других потоков.
  • Невозможны взаимные блокировки потоков из-за доступа к данным.
  • Глобальные переменные в языке AL/IV отсутствуют, поэтому проблемы с одновременным доступом к ним не возникают.

Невозможность зацикливания и бесконечной рекурсии

 
  • Циклы возможны только в форме
    FOR var IN arr[] : ...
    (нет циклов типа WHILE/REPEAT-UNTIL). Есть циклы FOREVER, но их использование жёстко ограничено. Любой цикл FOR обязательно завершается, поэтому вечное зацикливание становится невозможным.
  • Рекурсивные функции обязательно помечаются маркером RECURSIVE, и глубина рекурсии контролируется: в случае опасности бесконечной рекурсии выполняется возврат к рекурсивному вызову первого уровня в цепочке вызовов, и она возвращает NONE-значение.

Оператор PUSH для переменных

 
  • В операторе PUSH для переменной сохраняется прежнее значение и может присваиваться новое.
  • По окончании блока, для переменной гарантированно восстанавливается ее значение.

Параметры простых типов всегда передаются "по значению" и не могут изменяться в теле функции

 
  • Повторное использование простых входных параметров для хранения промежуточных значений запрещено.
  • Отсутствует возможность изменять значение скалярного параметра (кроме изменения полей объектного параметра).
  • Для массива допускается изменение элементов, а для динамического массива-параметра - добавление и удаление элементов.

Поля и переменные класса "только для чтения"

 
  • Упрощают создание "свойств", которые может изменять только владелец (класс и его наследники).

 

Перечисления

 
  • Являются прямыми аналогами перечислений других языков высокого уровня.
  • Позволяют работать с именованными константами и флажками.
  • В качестве коллекции предлагается использовать булевские массивы с индексами типа этих перечислений.
  • Элементы перечислений заключаются в одинарные кавычки.
  • Оператор выбора CASE по перечислению требует указания всех возможных вариантов. В случае (например) расширения перечисления новыми значениями компилятор укажет как на ошибочные на все такие операторы CASE, в которых еще не определены действия для новых элементов.

Обработка исключений не требуется, ввиду отсутствия исключений

 
  • Никакие ошибки не могут изменить ход выполнения программы непосредственно (путём немедленной передачи управления в другую точку).
  • Передача управления в рекурсивную функцию верхнего уровня при превышении допустимого уровня рекурсии выполняется только для ускорения обнаружения сбоя и предотвращения "зависания" в случае большого числа вложенных циклов.
  • Большинство ошибок подавляется путём замены предполагаемого результата значением NONE.
  • В случае возникновения ошибок (работа с файлами, исключения в нативном коде и др.) они фиксируются в системном массиве ошибок, и в дальнейшем могут анализироваться в коде.

Встроенные в язык средства тестирования

 
  • Функции TEST позволяют протестировать исходный код.
  • Степень покрытия кода тестами оценивается компилятором.
  • Модуль, недостаточно покрытый тестами, должен быть помечен модификатором UNTESTED.

Возможность переопределения операторов арифметики

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

Важные синтаксические особенности:

Один класс - один модуль

 
  • Один исходный текстовой файл содержит ровно один класс.
  • Все публичные имена и параметры функций начинаются с заглавной буквы, все скрытые поля и локальные переменные - со строчной буквы.
  • Подчёркивание на конце имени используется только для имён объектных полей класса, для индикации того, что поле является слабой ссылкой на объект.

Двойное именование типов, переменных, функций, констант

 
  • Большинство объектов (переменных, функций, типов данных) имеют два имени.
  • При декларации, полное имя делится на 2 части символом '|', при этом часть до разделителя является кратким именем объекта.
  • Все сущности (за исключением переменных цикла FOR) обязаны иметь длинное имя не менее 8 символов.
  • Ключевые слова языка записываются в верхнем регистре и не могут употребляться для именования функций и переменных.

 

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

 
  • Имеется 4 простых типа данных: BOOL, INT|EGER, REAL, STR|ING.
  • Классы и перечисления именуются в фигурных скобках, например, {My|_class}.
  • Имена классов в фигурных скобках начинаются с Прописной буквы. Имена перечислений и записей - со строчной.
  • Нет типа char (используется STR).
  • Неявное преобразование типа возможно только из INT в REAL.

Ограничение длины строк кода

 
  • Исходный код не должен содержать строки длиннее 80 символов (не считая комментариев и завершающих пробелов).
  • Это правило не распространяется на строки, расположенные за финальной директивой END.

Правило продолжения оператора на другую строку

 
  • Отсутствует специальный символ, завершающий простой оператор.
  • Оператор обычно занимает одну строку.
  • Код продолжается в следующей строке, если строка завершается одним из символов ',', '(', '['
  • либо следующая строка не является пустой и не начинает новый оператор, т.е. не начинается с '{', '<', '[' или буквы,
  • и не является завершением для блока или функции, т.е. не начинается с символов ';', '.'

Возможность вызова функции в постфиксной форме для статических однопараметрических функций

 
  • Вызов Sin(x) эквивалентен x.Sin.

Массивы всегда упоминаются с квадратными скобками: A[ ]

 
  • Имеются только одномерные массивы.
  • Массив может быть динамическим или фиксированным.
  • Для фиксированного массива в качестве индексов может использоваться перечисление (как тип).

Строка содержит только один оператор

 
  • Символ ';' используется для завершения блочного оператора (CASE, FOR, FOREVER, PUSH).
  • Символ '.' завершает функцию, перечисление, блок констант, список импорта.
  • Символ завершения ';' или '.' должен быть последним таким же в строке (то есть два символа подряд '; ;' или '. .' не допускаются), но указание '; .' в одной строке возможно.
  • Любой простой оператор (кроме BREAK, CONTINUE или STOP) может быть завершён оператором выхода из функции '==>'.

Ограничения на качество кода:

 
  • Допускается не более трёх уровней вложения операторов CASE / FOR / FOREVER. (Ограничения не затрагивают операторы PUSH и DEBUG).
  • Допускается не более 7 простых и 7 блочных операторов подряд в одном блоке, после чего требуется наличие комментария вида
    ------------------- 'комментарий'
  • Допускается не более трёх явных параметров у функции (не считая неявного параметра-объекта класса THIS). Исключение: ограничение не действует на нативные функции.
  • В случае нарушения любого из приведённых правил, класс должен быть помечен маркером плохого кода (BAD).

Перегрузка операторов

 
  • Есть возможность переопределять основные операторы арифметики для классов и записей. Использование операторов в функции декларируется в заголовке функции.
 

В языке отсутствуют:

Препроцессор

 
  • Нет макросов.
  • ИСКЛЮЧЕНИЕ: Есть повторное использование кода - оператор LIKE. Но он макросом с параметрами не является, не допускает вложенных вызовов и обращений за пределами класса.
  • Нет условной компиляции.
  • Нет включения файлов.

Указатели функций

 
  • Взамен указателей функций, имеется механизм виртуальных методов.

Перегрузка функций

 
  • В классе нет одноименных функций.
  • При импорте одноименных статических функций из разных классов, следует использовать явное указание класса при вызове функции, в форме {класс}.функция(параметры).
  • ИСКЛЮЧЕНИЕ: переопределяемые операторы арифметики автоматически перегружаются (точнее - типы их аргументов фактически являются частью имени).

Массив как результат функции

 
  • Функция может возвращать только скалярное значение.
  • При необходимости возвратить массив, результат оформляется как параметр.

Множественное наследование

 
  • Класс может быть унаследован только от одного класса-предка.

Оператор go to

 
  • Нет оператора GOTO. (Оператор GOTO есть, но предназначен не для переходов между операторами, а для активации сценариев).
  • Есть возможность продолжения или выхода из глубоко вложенного цикла (CONTINUE x, BREAK x).

Итераторы, задаваемые методом

 
  • Нет возможности задавать произвольные итераторы.
  • Есть итераторы элементов массивов FOR m IN M[].

"Геттеры" и "сеттеры" с синтаксисом поля

 
  • Поле или переменная не может иметь сеттер, обрабатывающий присваивание нового значения, или геттер, реализующий чтение из поля. Геттеры и сеттеры существуют, но их назначение - обеспечивать возможность переворачивания присваиваний (оператор REVERT).
  • Чтение из поля - всегда является просто чтением значения из памяти.
  • Запись в поле всегда лишь выполняет запись значения в память.
  • ИСКЛЮЧЕНИЕ: сеттеры разрешены, но используются только в операторе REVERT, или при присваивании значения псевдо-методу .[] (используется для реализации классов {Vector}, {Matrix})

Символьный тип данных

 
  • Для представления символа используется строчная переменная или константа длиной 1 символ.

Указание разрядности типов данных

 
  • Разрядность простых типов данных не указывается.
  • Она всегда одинакова для всех типов INT, REAL, STR, BOOL, и зависит от целевой платформы, компилятора и настроек проекта.

Неявные преобразования разнородных типов данных

 
  • Нет неявных преобразований переменных, кроме преобразования целого в вещественное.
  • Нет приведения типов данных.
  • Все преобразования выполняются явным вызовом функций.

Низкоуровневое программирование

 
  • Роль ассемблера выполняют "кодовые" (native) функции языка, который является целевым для компилятора.
  • Целевым может быть любой язык (например, С++, Objective C, Java, C#, Delphi, Python, Rubi, LUA, Shell, Algol, Fortran, Oberon, ...).
  • Вся низкоуровневая поддержка делается на целевом языке, компилятору лишь указываются функции-переходники с атрибутом NATIVE. Это позволяет использовать любой язык программирования в качестве целевого - от Algol и BASIC до Zonnon.

Исключения

 
  • В программе практически невозможны исключения, связанные с неверной адресацией (благодаря использованию NONE-объектов, гарантированной инициализации переменных, проверкой выхода за границы массива).
  • Во всех случаях, когда в обычных системах генерируется исключение, в AL/IV не выполняются никакие действия, и возвращается пустой результат (NONE).
  • Соответственно, в AL/IV не требуется обработка исключений, и отсутствуют для этого средства.

I. Синтаксис операторов

I. 1. Форматирование операторов

I. 1. a. Один оператор - одна строка

 

Простые операторы не требуют завершения специальным знаком (таким, как ';').

Знак ';' используется для завершения блока.

Например:
d|escriminant = B * B - 4 * A * C
CASE d.Sign ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
     [-1]: NONE                              
     [0] : R[] << -B / 2 / A                 
     [+1]: R[] << (-B - d.Sqrt) / 2 / A      
           R[] << (-B + d.Sqrt) / 2 / A ; ▄▄▄█

 

 

Строка не может быть длиннее 80 символов (не учитывая комментарии до конца строки и пробельные символы в конце строки). Символ табуляции считается за один символ.

Оператор может быть записан в нескольких строках,

но строка считается продолжающейся на следующую только если:

  • строка заканчивается символами ',', '(', '[';
  • следующая строка начинается символами:
    • '"' (двойная кавычка - начинает строчную константу)
    • '+'
    • '-' (одинарный знак минус)
    • '*'
    • '/'
    • '%'
    • '|'
    • '&'
    • '>'
    • '='
    • '#'
 

Другой способ запомнить правило переноса:

строка считается продолжающейся на следующую только если

  • или предыдущая строка завершается символами '(', '[', ','
  • или следующая строка не пуста и не может начинать новый оператор, т.е.:
    • не начинается с буквы,
    • с символа '{' (не начинает декларацию именем типа в фигурных скобках),
    • с символа '[' (не является условием для ветви CASE),
    • с символа '<' (не начинает оператор вывода <<),
    • с символов '.' или ';' (не является отдельно стоящим символом для завершения блока кода или функции)
    • с символа '--' (не начинает блочный комментарий).
 

Или использовать иллюстрацию ниже:

 

I. 1. b. Блочные операторы

Блочные операторы создают вложенный уровень операторов.

Блок вложенных операторов завершается символом ';'.

Функции и другие блоки уровня класса завершаются символом '.' (точка). А именно: заголовок класса, список импорта, определение типа перечисления, определение блока констант.

Строка не может завершаться двумя и более подряд одинаковыми символами завершения ';' или '.'. Но сочетание '; .' для завершения последней блочной конструкции в функции и самой функции допускается.

Если завершения требуют более двух блоков операторов, то символ завершения для внешнего блока будет находиться на отдельной строке.  Например:

 
FOR i IN [1 TO 100] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
    CASE i % 3 == 0 ? << "Fizz" ; ▄▄▄▄▄▄ 
    CASE i % 5 == 0 ? << "Buzz" ; ▄▄▄▄▄▄ 
    CASE i % 3 != 0 && i % 5 != 0 ? ▄▄▄▄ 
         << i.Str ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 
 

Имеется только одно ключевое слово для условного оператора (CASE), одно для оператора цикла (FOR), один оператор сохранения и гарантированного восстановления (PUSH). Имеется так же оператор бесконечного цикла FOREVER, но его применение ограничено. Для отладки кода может использоваться блочный оператор DEBUG .

Все прочие операторы являются простыми: BREAK, CONTINUE, STOP, LIKE, REVERT, вызовы функций, декларация и модификация переменных, ввод и вывод (>>, <<). Оператор BREAK может использоваться только для прекращения цикла и не используется для других целей.

 

Компилятор требует помечать маркером BAD классы, в которых не соблюдаются правила структурирования кода:

  • Количество вложенных уровней операторов CASE и FOR не должно превышать трёх (блоки PUSH / DEBUG не учитываются, FOREVER - особая разновидность FOR, и учитывается так же, как FOR).
  • Количество простых операторов в блоке не должно превышать семи.
  • Количество вложенных блочных операторов в блоке так же не должно превышать семи.
  • Специальный оператор-комментарий вида
    --------- 'текст комментария'
    может разделять группы операторов (число знаков '-' произвольно, но не менее двух), в этом случае подсчёт ведётся только внутри группы.

 

 

I. 1. c. Комментарии

  • Многострочные комментарии */ ... /* начинаются в начале строки (не считая начальных пробелов) с символов
    */
    и заканчиваются строкой,
    завершающейся символами /*

  • Текстовой файл, содержащий исходный код, считается всегда начинающимся с многострочных комментариев. Признаком завершения таких комментариев является строка, завершающаяся символами /*. Т.е., код AL-IV всегда должен находиться между строками /* и */ (как если бы он был комментарием для C-подобного языка программирования).

Иллюстрация:

 

  • Комментарии до конца строки, начинающихся с двойной дроби (//). Не завершают текущий оператор.
  • Комментарии, начинающиеся двумя символами --, имеют форму
    -------------- 'текст'
    и служат для разбиения блоков на разделы. Так же, используются в декларациях классов и в оглавлении модуля.
    Такой блочный комментарий может начинать переиспользуемый блок кода (операторы LIKE, REVERT). В этом случае он должен иметь модификатор REUSED (записывается через запятую после метки). Например:
    ---------------- 'start extracting', REUSED

 

 

I. 1. d. Регистрозависимость для идентификаторов

  • КЛЮЧЕВЫЕ И ЗАРЕЗЕРВИРОВАННЫЕ СЛОВА записываются только в ВЕРХНЕМ регистре.
    CASE
    являются ключевым словом, но Case, case - нет.
  • {Имена классов} (а так же перечислений и записей) заключаются в фигурные скобки.
  • имена простых типов данных являются зарезервированными: BOOL, BYTE, INT, REAL, STR (и других простых типов, кроме этих пяти, нет).
  • {Имена классов} начинаются с прописных букв: {My_class}, {Car|_mobile}.
  • {имена_перечислений_и_записей} начинаются со строчной буквы: {color|s_fill}.

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

  • Рекомендуется использовать КАПИТАЛИЗИРОВАННЫЕ имена для констант.
  • Имена 'КОНСТАНТ', являющихся элементами перечислений и коллекций, всегда заключаются в апострофы.
  • Все имена регистро-зависимы.

Например, A и a - это разные переменные (или константы).

Имена Публичных полей, функций, параметров функций начинаются с Прописной буквы.

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

Локальные переменные функций рекомендуется именовать со строчной буквы, параметры функции - с прописной.

  • Символ подчеркивания в конце имени_ является неотъемлемой частью имени (и служит атрибутом слабой ссылки для объектов).

 

I. 1. e. Именование

 

При декларации переменной, функции, типа данных сущности может быть дано два имени - краткое и полное. Сначала записывается краткое имя, затем, через символ '|' - остальная часть имени. При этом:

  • Длина полного имени должна быть не менее 8 знаков:
    S|ample_val
  • Только одно исключение: переменные цикла могут иметь только краткое имя:
    FOR x IN M[] : ... ;
  • Если имя должно заканчиваться символом подчёркивания (признак слабой ссылки), то подчёркиванием должны заканчиваться обе части имени, например:
    List_|of_items_[]
  • Для имён классов, записей и перечислений действует то же правило, и при подсчёте длины имени окружающие фигурные скобки не учитываются:
    {My_class|_to_do_something}

 

I. 1. f. Модификаторы

 

Многие декларации языка (заголовок модуля, оператор, декларация переменной или типа данных) могут иметь модификаторы.

  • записываются в форме , ИМЯ в конце оператора (или декларации), к которому относятся.
  • Если модификаторов несколько, то они перечисляются через запятые.

 

Например, в заголовке класса:
CLASS {My|_class}, BITWISE, RECURSIVE :

Модификаторы не воздействуют на основную семантику кода.

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

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

 

 

I. 2. Оператор присваивания

I. 2. a. Простой оператор присваивания

x = y

где x - переменная или поле структуры или объекта, y - выражение.

 

Для сравнения на равенство двух операндов используется си-подобный оператор ==.

 

I. 2. b. Операторы присваивания в комбинации с арифметической, логической или поразрядной логической операцией

 

Дополнительные операции +=, -=, *=, /=, %=, |=, &=, ^=, ||=, &&=, могут использоваться как эквивалент присваивания результата соответствующей операции +, -, *, ... между переменной, которой присваивается результат, и выражением справа от знака операции.

 

I. 2. c. Операторы отправки данных

Операция << используется для добавления строки к строке, элемента к массиву и вывода текста в текущий главный поток вывода.
  • Для строковой переменной A и строкового выражения B запись A << B эквивалентна оператору присваивания вида A = A B
  • в случае массива A[] и выражения B запись A[] << B эквивалентна вызову функции A[].Add(B) для массива.
  • Если левый операнд не указан, то это оператор записи строки в выходной поток (в консоль - для консольной программы).

 

Аналогичная операция >> используется как замена вызова функции read для операнда слева (а так же для добавления нового объекта в массив объектов, см. в разделе, посвященном классам и объектам).
  • Операторы ввода-вывода << и >> могут комбинироваться в одном операторе в виде
    << строка [>> переменная] [>> переменная] ...
    Например:
    << 'Введите число:' >> Number

 

I. 2. d. Операторы повторного присваивания и отправки данных

 

Если оператор начинается с символа '..' (без кавычек), то это ссылка на левую часть предыдущего оператора присваивания (или на зафиксированную левую часть присваивания). Соответственно, три точки подряд означают такую ссылку с последующей операцией обращения к полю или методу объекта, или обращения к функции с указанным операндом в качестве первого параметра.

Для такого повторного присваивания (или отправки данных):

  • не ведется подсчет операторов (нет ограничения на количество таких повторных присваиваний);
  • выражение фиксируется в качестве левой части для последующих "повторных" присваиваний:

     

    • если употреблено в левой части обычного оператора присваивания(a = b, a += b, a *= b, и т.п.);
      Например:
      Text = New_memo(THIS"TEXT""VH")
      ...Set_width(200)
      ...Set_anchor_bottom(TRUE)
    • является левой частью обычного оператора отправки данных (a << b, a[] << b, в том числе в случае, когда оператор '<<' заменяет вызов метода Write);
      Например:
      Lines[] << "#!/bin/bash"
      ..[] << "echo run WinE"
      ..[] << "wine"
    • если в операторе в цепочке разыменования полей/методов использован псевдо-оператор '...' в позиции одинарной точки (например:
      A.Field1[indexA]...Do_something(params)
      ...Color = RED
      ...Foo(params)

      В приведенном примере в двух последних строчках символ '..' замещается компилятором выражением A.Field1[indexA]

 

 

I. 2. e. Операторы инкремента и декремента

 

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

  • как отдельные операторы,
  • либо внутри индексов массивов,
  • и в других частях выражений.
  • Изменение значения переменной гарантируется только по окончании выполнения всего оператора.
  • Изменение одной и той же переменной несколько раз операциями ++ / -- в одном операторе считается ошибкой.
  • Не разрешается использовать операции ++/-- в выражении с условно вычисляемыми частями (имеющими операции && / ||).

 

 

I. 3. Выражения

 
Операция Расшифровка Группа
- Взятие обратного знака Арифметика (чисел), на выходе число
* / % Умножение, деление, остаток от деления
+ - Сложение, вычитание
IN !IN Проверка на вхождение значения в диапазон или массив Сравнение чисел, строк, классов, результат булевское (для ?? - целое)
LIKE !LIKE Сравнение строк без учета регистра букв и с учетом шаблонных символов '?' и '%' во втором операнде
< <= > >= == != Сравнения
~ Поразрядное отрицание Поразрядные операции с целыми, результат - целое

 

& ^ И, Исключающее или (xor) - поразрядные
| ИЛИ поразрядное
! Логическое НЕ Логические операции (с булевскими, результат - булевское)
&& Логическое И (&&)
|| Логическое ИЛИ (||)

 

  • Операция конкатенации строк не имеет специального знака. Используется пробел.
  • При наличии поразрядных логических операций в модуле в список модификаторов модуля добавляется BITWISE.
  • смешение поразрядных и арифметическх операций в одном выражении недопустимо.
  • побитовые операции сдвигов реализуются с помощью встроенных функций ShiftL|eft, ShiftR|ight, RotateL|eft, RotateR|ight - отдельные знаки бинарных операций для этого отсутствуют.

 

 

I. 3. a. Операторы проверки наличия

 

Операторы IN и !IN могут использоваться в выражениях:

  • для проверки наличия скалярного значения в массиве (при этом массив может быть переменной или конструкцией вида
    [ значение1, значение2, ..., значение3 ])
  • для проверки наличия подстроки в строке.

 

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

Операция IN/!IN не применима к паре вещественное число - вещественный массив (так же как запрещено сравнение на равенство значений типа REAL).

 

 

I. 4. Прочие простые операторы

  • Для возврата из функции до окончания ее кода используется оператор возврата ==> .
    • Такой оператор может записываться отдельно либо следом за другим простым оператором в той же строке, образуя простой оператор с выходом из функции.
      Например:
      CASE R > 0 ? RESULT = R ==> ; ▄▄▄▄

 

  • Для завершения и продолжения цикла FOR используются операторы BREAK и CONTINUE, для которых обязательно указывается переменная цикла, Например:
    FOR x IN [1 TO 100] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        do_something                   
        CASE condition ? BREAK x; ▄▄▄▄ 
    ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

  • В цикле FOREVER нельзя использовать операторы CONTINUE и BREAK. Завершить цикл FOREVER можно только выходом из функции (оператор ==>) или в результате срабатывания исключительной ситуации.
  • Наличие метки в операторах BREAK / CONTINUE позволяет продолжить или остановить внешний цикл из тела вложенного напрямую.

 

I. 5. Условный оператор CASE

  • Выражение в операторе CASE должно иметь значение булевского, целочисленного, перечислимого или строкового типа.
  • Выражения других типов (вещественных или объектных) не допускаются.
  • Заголовок всегда завершается знаком '?'.
  • В случае булевского типа условия, немедленно начинается блок кода, который выполняется при выполнении условия. И за ним может следовать блок ELSE (см. ниже).
  • В остальных случаях, каждая ветвь начинается с массива иди диапазона значений в квадратных скобках
    [массив значений]: код
  • В качестве значений должны использоваться целочисленные, строчные или перечислимые константные значения (литеральные или именованные константы), тип значений которых соответствует типу выражения в заголовке.
  • Значение в списке значений может быть одиночным или представлять диапазон значений в виде X TO Y, где X и Y - константы (только для целочисленного условия).
  • Повторение значений (и пересечение диапазонов) не допускается.
  • В случае перечислимого типа условия, должны быть перечислены все значения соответствующего перечислимого типа, и не может использоваться ветвь ELSE.
  • Общая ветвь ELSE выполняется (при её наличии), если ни одна из констант не совпала, и ни одна из ветвей не была выполнена.
  • Символ ';' завершает весь оператор CASE.

 

Оператор BREAK не используется для завершения ветви.

По исполнении любой ветви всегда происходит выход из всего оператора CASE.

 

Классический пример - решение квадратного уравнения в области вещественных чисел:


STRUCTURE {roots_sq|uare_equation} :
    INT N|umber_solutions
    REAL X1|_solution
    REAL X2|_solution .

FUNCTION Square_eq|uation(
         REAL A|_coefficient,
         REAL B|_coefficient,
         REAL c|_coefficient) ==> {roots_sq} :

   CASE A.Near(0) ? ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
   REAL d|escriminant = B * B - 4 * A * C
   CASE d.Sign ? [0]: RESULT.N = 1 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
                      RESULT.X1 = -B / (2 * A)          
                      RESULT.X2 = RESULT.X1             
                 [1]: d = d.Sqrt                        
                      RESULT.X1 = (-B - d) / (2 * A)    
                      RESULT.X2 = (-B + d) / (2 * A)    
                      RESULT.N = 2 ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 

 

Особый вариант оператора CASE ? - без условия вообще, может использоваться для последовательного перебора вариантов, вычисляющихся последовательно, до первого выражения в квадратных скобках, получившего значение TRUE. После чего либо выполняется соответствующая ветвь, либо, если истинное условие не обнаружено, то срабатывает блок ELSE (при его наличии).

Синтаксически такой вариант оператора CASE отличается так же тем, что после каждого выражения в квадратных скобках записывается знак '?' (а не двоеточие, как в случаях выше).


FUNCTION Square_eq2|uation(
REAL A|_coefficient,
REAL B|_coefficient,
REAL c|_coefficient) ==> {roots_sq}, STATIC
   :
   ----------------------- 'sequentional version'
   REAL d|escriminant = B * B - 4 * A * C
   CASE ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
   [A.Near(0)] ? ==>                                      █
   [d.Near(0)] ? RESULT.N = 1                             █
                 RESULT.X1 = -B / (2 * A)                 
                 RESULT.X2 = RESULT.X1                    
   [d > 0] ? d = d.Sqrt                                   
             RESULT.X1 = (-B - d) / (2 * A)               
             RESULT.X2 = (-B + d) / (2 * A)               
             RESULT.N = 2 ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█


I. 6. Операторы цикла FOR

 

Оператор цикла FOR с перечислением элементов массива - это единственный вариант контролируемого цикла:


    FOR переменная IN массив[диапазон] : тело ;
или
    FOR переменная IN [диапазон]: тело ;
 

  • Переменная такого цикла всегда имеет тип, совпадающий с типом элементов перечисляемого массива.
  • В случае диапазона значений без массива, тип переменной - целое число;
  • Такая переменная:
     
    • Не требует декларации.
    • Не требует длинного синонима.
    • Не может использоваться вне цикла, кроме как в другом цикле FOR.
    • Используется как метка операторов BREAK имя и CONTINUE имя.
  • Переменная цикла FOR с диапазоном значений не может быть изменена в теле цикла.
  • В случае массива с пустым диапазоном в цикле перечисляются все элементы с индекса 0 до индекса * (индекс последнего элемента в массиве).
  • Для изменения границ перечисления и направления перечисления следует либо в индексной части массива, либо использовать конструируемый с помощью диапазона массив целых в форме [X TO Y] или [Y DOWNTO X], где X и Y - целочисленные выражения.
  • К переменной цикла применима псевдо-функция Index, возвращающая целочисленный индекс элемента в массиве.
  • Границы перечисления индексов вычисляются до начала цикла, и изменение размера динамического массива в процессе работы цикла не влияют на порядок просмотра индексов. Это может приводить к выходу индекса за границы массива, в результате чего переменная цикла будет получать значение NONE.

 

 

Для организации неконтролируемого бесконечного цикла используется блочный оператор
FOREVER : тело цикла ; ▄▄▄▄

  • Цикл FOREVER может быть завершён только оператором выхода из функции ==>.
  • Операторы BREAK и CONTINUE не используются.
  • Класс, в котором встречается цикл FOREVER, должен быть отмечен маркером INFINITIVE.

 

I. 7. Блок операторов PUSH

Предназначен для гарантированного сохранения некоторой переменной или поля с одновременным присваиванием ему нового значения на время выполнения блока, с последующим гарантированным восстановлением значения этой переменной или поля по окончании исполнения блока.

 

Восстанавливающие действия гарантируются в том числе:

  • При выполнении принудительного выхода из блока из-за срабатывания операторов BREAK, CONTINUE.
  • При выходе из функции оператором возврата ==>.

 

Синтаксис:
PUSH переменная = выражение : тело ; ▄▄▄▄

или
PUSH переменная : тело ; ▄▄▄▄

 

  • На входе в такой блок значение переменной сохраняется (например, в локальном стеке, или в локальном динамическом массиве).
  • После чего ей присваивается новое значение (если указано).
  • При достижении завершающей скобки блока (любым способом, даже аварийным), восстанавливается прежнее значение переменной.
  • В качестве переменной может быть скалярная переменная любого простого или объектного типа, или элемент массива.

 

 

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

Например:


PUSH db.Transaction : ▄▄▄▄
     // some database operations
     db.Commit           
; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 


 

I. 8. Блок операторов DEBUG

 

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

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


DEBUG : ▄▄▄▄▄▄▄
    тело ; ▄▄▄█

 

 

Если для правильной компиляции блоков DEBUG требуются дополнительные классы, отсутствующие в основном списке импорта, рекомендуется использовать оператор IMPORT со специальным модификатором DEBUG.

 

I. 9. Оператор LIKE

 

(Не путать с операцией LIKE в выражении!)

 

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

Имеются правила:

  • Используемый блок должен быть помечен модификатором REUSED.
  • Если вставляется блок из другой функции, после LIKE записывается имя этой функции.
  • Далее следует мульти-точие (множество точек, количество которых не ограничивается - но не менее трёх), после чего записывается метка вставляемого блока кода.
  • Возможна вставка только кода из своего класса.
  • Вставляемый код должен быть расположен выше по тексту.
  • Сам вставляемый код не должен содержать операторов LIKE.
  • Вставка происходит "как есть", контроль синтаксиса и семантики происходит только при компиляции оператора LIKE, поэтому следует не допускать дублирования деклараций, имён переменных, переменных циклов (во избежание ошибок компиляции).
  • Количество повторных использований в операторах LIKE одного используемого блока кода никак не ограничивается.
  • На время вставки кода счётчик вложенности блоков сбрасывается в ноль (т.е. проблем со слишком большой вложенностью блоков CASE/FOR не будет, даже если суммарный уровень вложенности превысит допустимые три уровня).


Пример:

 


...
--------------------------- 'find a dot'
pos = s.Find(".")
CASE pos < 0 ? pos = s.Len ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
------------------------------- 'end of find'
...
...
LIKE ............. 'find a dot'  // place in the same function
or
LIKE Method1 ..... 'find a dot'  // place in another function
 

 

Дополнительно, при использовании оператора LIKE, можно выполнить замены некоторых фрагментов кода:


LIKE [method].......'label', BUT (образец1 ==> замена1) [*N1] [, (образец2 ==> замена2) [*N2] ]

 

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

Все замены выполняются непосредственно перед компиляцией оператора LIKE, на участке кода, который подставляется вместо LIKE.

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

 

 

И, пожалуйста, помните:

  • Лучше использовать LIKE ......... 'имя', чем просто копировать код.
  • НО Лучше выносить общий код в отдельную функцию, если это возможно (нежели использовать LIKE).

Поэтому не злоупотребляйте использованием LIKE без необходимости.

 

I. 10. Оператор REVERT

 

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

Имеются правила:

  • Используемый блок должен быть помечен модификатором REUSED.
  • Если вставляется блок из другой функции, после REVERT записывается имя этой функции.
  • Далее следует мульти-точие (множество точек, количество которых не ограничивается - но не менее трёх), после чего записывается метка вставляемого блока кода.
  • Возможна вставка только кода из своего класса.
  • Вставляемый код должен быть расположен выше по тексту.
  • Сам вставляемый код может содержать только операторы присваивания, причём левая и правая сторона должны допускать переворачивание (хотя бы переходом к использованию сеттеров и геттеров).
  • Если вставляется код из другого метода, он не должен использовать обращений к локальным переменным (и параметрам).
  • Количество операторов REVERT для одного используемого блока кода никак не ограничивается.
Пример:
 

---------------- 'save form coordinates'
config.Set_i("Left", Left)
config.Set_i("Top", Top)
config.Set_i("Width", Width)
config.Set_i("Left", Height)
------------------------------- 'end of save'
...
...
REVERT ............. 'save form coordinates'

 

 

II. Переменные, константы и типы данных

II. 1. Простые типы данных

II. 1. a. Встроенные простые типы данных

 

Простые (встроенные) типы данных (которые не могут быть переопределены):

 

  • BOOL - булевский тип, значения TRUE и FALSE.
  • BYTE - байтовый тип, значения от 0 до 255 (использование байтового типа в классе требует добавления модификатора BYTES в заголовке класса).
  • INT|EGER - целочисленный тип (обычной точности).
  • REAL - вещественный тип.
  • STR|ING - строковый тип.

 

Для типов данных разрядность не задаётся.

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

 

 

При использовании байтового типа в классе, он должен быть помечен в заголовке модификатором BYTES, и не может маркироваться как SAFE.

 

II. 1. b. Запись констант базовых типов данных

  • Целые константы в десятичной системе: [-|+]<0...9_>...
    1_345 = 1345
    (подчеркивания игнорируются и могут использоваться для разделения групп разрядов).
  • В двоичной системе:
    0b_0100_1010_0011_1001
    (Регистр буквы, определяющей разрядность, не учитывается: B=b, G=g, X=x)
  • В шестнадцатеричной системе (x=X):
    0xF2EA_d44a
  • В восьмеричной системе (только малая буква 'o'):
    0o_7_342_001
  • Вещественные константы: {0...9}.{0...9}[e[+|-]{0...9}]
    (e=E)ю Например:
    -2.73e3
    11_E-04

  • Строковые константы: "{символ кроме "}"
    где символ - любой печатный знак или пробел,
    двойные кавычки в строке не записываются, для их записи необходимо использовать символьную константу #QU). Например:
    #QU "Этот текст заключен в двойные кавычки" #QU
  • Для конкатенации символьных и строчных констант и переменных какой-либо символ операции не используется.
    Например:
    "Этот текст завершается возвратом каретки" #CR
  • Если строка начинается символом двойной кавычки, то эта строка считается продолжением предыдущей. Т.е. для записи длинных строчных констант, не вмещающихся в одну строку, достаточно разбить строку в любом месте и записать продолжение в следующей строке. Пример:
    "Это очень длинная строка. Настолько длинная, что "
    "она не может быть записана в одной строке кода."

  • Если перед началом строковой константы записан символ @, то для всех последующих конкатенаций с последующими строковыми константами и переменными, при переносе выражения на следующую строку, все символы переноса строки становятся частью константы. Например:
    @"Эта константа состоит из трёх строк: первая,"
     "вторая,"
     "и третья"

  • Cимвол @@ перед строчной константой отменяет действие символа @ - до конца конкатенации или до следующего символа @.
  • В качестве продолжения символьной константы в отдельной строке кода может использоваться литерал ''текст'' (ограниченный удвоенным символом апострофа). Такой литерал может содержать любые символы, кроме удвоенного апострофа (в том числе двойные кавычки и одинарные апострофы).

 

 

II. 1. c. Перечисления

 

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

  • В декларации перечисления элементы перечисления записываются через запятую. Форма декларации:
    CONST {имя_типа} : ИМЯ_КОНСТАНТЫ = выражение .

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

 

ENUM {color|s_of_traffic_light} : 'R|ED_COLOR',
                                  'Y|ELLOW_COLOR',
                                  'G|REEN_COLOR' .
 

Операции над значениями перечислимых типов данных:

  • Переменной с типом, созданным оператором ENUMERATION, нельзя присвоить числовое значение или значение из чужого перечисления.
  • Значение переменной (или константы, или выражения) такого типа может быть преобразовано в целочисленное: встроенная функция Int.
  • Обратное преобразование числового значения в тип перечисления не предусмотрено (кроме как с помощью собственной функции, в которой выполняется такое преобразование, например, оператором CASE).
  • Встроенная функция Name возвращает имя элемента по его значению. В качестве имени возвращается первое имя константы без апострофов.

 

 

Перечисление может использоваться как диапазон индексов фиксированного массива при его декларации:
STR LName|s_of_lights[{color}]

В этом случае в качестве индексов этого массива могут использоваться переменные и константы соответствующего перечисления:
LName['R'] = "Red color"

 

 

Элементы перечисления считаются неупорядоченными, и не могут сравниваться операциями <, <=, >, >= (применимы сравнения == и !=). Из элементов перечисления может быть сформирован константный массив, и для пары элемент - массив элементов допустима операция IN !IN):
 CASE A IN ['R''Y'] ? ... ; ▄▄▄▄

В случае конфликта имён перечислимых элементов, они обязаны квалифицироваться именем перечисления в форме:
{enumeration_name}.'EUMERATION_ITEM'.

В случае совпадения имён самих перечислений, в квалификацию должен включаться класс, из которого это перечисление родом:
{Class_name}.{enumeration_name}.'EUMERATION_ITEM'.

 

 

При использовании в операторе CASE выражения типа перечисления:

  • запрещается использовать ветвь ELSE
  • должны быть использованы (перечислены) все константы
  • в одной ветви может использоваться более одной константы:
    CASE Greek ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        ['ALPHA''BETHA']: ...                               
        ['GAMMA''DELTA']: ...                               
        ['EPSILON']: ...                                      
        ['DZETHA''ETHA''TETHA''IOTHA''KAPPA']: ...    
        ... ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 

II. 2. Переменные, массивы

II. 2. a. Декларация переменных

 

Декларация переменной начинается с указания ее типа данных в {фигурных} скобках (или одного из зарезервированных имён типов BOOL, BYTE, INT, REAL, STR).

  • При объявлении глобальной или локальной переменной её полное имя должно быть длиной не менее 8 символов.
    • ИСКЛЮЧЕНИЕ: Переменные циклов FOR могут иметь только краткий вариант имени.
  • Имя может быть коротким, но тогда вслед за короткой частью после разделителя '|' размещается остальная часть имени.
  • Для массивов далее задаётся размерность в [квадратных] скобках.
    • для динамических массивов - пустые квадратные скобки,
    • для фиксированных массивов с целочисленными индексами указывается константа, задающая размер фиксированного массива (индекс последнего элемента + 1),
    • для фиксированных массивов с индексами из перечисления указывается имя перечислимого типа, в фигурных скобках:
      BOOL My_arr|ay_of_flags[{traffic}]
  • Затем, при необходимости, МОДИФИКАТОРЫ переменной/поля (READ, INIT, MAXLEN[n]).
  • Далее может следовать присваивание начального значения (в качестве начального значения допускается использовать константы).

 

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

  • Для объекта - это NONE-значение соответствующего класса (см. "Классы").
  • Для строк - пустая строка.
  • Для динамических массивов - пустой массив.
  • Для перечислений, коллекций, чисел - арифметический ноль (для перечислений это всегда первый элемент перечисления).
  • Структура получает в качестве значения структуру с полями, имеющими ноль-значение.
 

Правила именования переменных:

  • Краткое имя - это идентификатор от 1 символа,
  • Длинное имя - идентификатор от 8 до 80 символов,
  • Далее в коде употребляется краткое имя наравне с длинным.
 

Регистр букв имён переменных:

  • Имена Публичных полей классов и Параметров функций начинаются с прописной буквы (в том числе с атрибутом READ - только для чтения).
  • КОНСТАНТЫ (обычно) содержит только прописные буквы.
  • Со строчной буквы начинается имя приватных полей класса и локальных переменных.

 

II. 2. b. Модификаторы полей

 

Если переменная требует модификаторов, они записываются в декларации вслед за именами переменной и её размерностями.

  • READ|ONLY - используется для переменных уровня модуля, полей классов (а так же параметров функций).
    • Для поля класса разрешает доступ только для чтения, кроме самого модуля и из методов класса-наследника.
    • Поле должно быть публичным (именоваться с Прописной буквы).
  • INIT|IALIZE - для поля класса, требует инициализации поля при конструировании объекта (см. "Классы").
  • MAXLEN|GTH[n] - для полей записи (RECORD) строкового типа, позволяет задать фиксированную строку с заданным максимальным количеством символов. Запись может содержать только такие фиксированные строки. Класс, содержащий такие строки и оперирующий с такими строками, должен иметь модификатор SHORT_STRINGS в заголовке класса.
  • DEPRECATED('текст') - данное поле "осуждается", но ещё поддерживается. В тексте указывается, что предлагается в качестве замены. Рекомендуется переходить к замене по мере возможности, в следующей версии поле может уже не поддерживаться.
  • ABANDONED('текст') - поле "отменено". В тексте указывается, что есть взамен (если есть). Использование такого поля в коде уже невозможно, и вызывает ошибку компиляции.

 

 

Кроме явно указываемых выше модификаторов существуют так же неявная маркировка переменных и параметров функции:

  • Параметр, объявленный в заголовке функции, но не использующийся в теле функции, должен содержать в имени строку "dummy" (в любом регистре).
  • Переменная, объявленная в теле функции (и даже если ей присвоили значение), но не использованная в выражениях, должна иметь строку "dummy" (в любом регистре) в своём имени.
  • Объектная локальная переменная, которой присвоен новый объект, который не добавляется в массив сильных ссылок и для которого не указывается владелец, должна иметь в своём имени строку "temp" (в любом регистре). Например,
    load|er_Temp = {Text_file}(Path = path)

 

 

II. 2. c. Декларация именованных констант

 

Константы декларируются только на уровне класса, и только встроенных простых типов (INT, BOOL, REAL, STR).

  • Декларация одиночной константы в одной строке:
    CONST имя_типа : ИМЯ_КОНСТАНТЫ = выражение .
  • Декларация нескольких однотипных констант:
    CONST имя типа :
        ИМЯ_КОНСТАНТЫ_1 = выражение
        ИМЯ_КОНСТАНТЫ_2 = выражение
        ...
        ИМЯ_КОНСТАНТЫ_2 = выражение .

 

Рекомендуется использовать капитализированные имена констант (все буквы в верхнем регистре).

 

II. 2. d. Декларация массивов

 

После имен переменной, являющейся массивом, в квадратных скобках записывается размерность.

  • Массивы могут быть только одномерными.
  • Массив может быть фиксированным или динамическим.
  • В квадратных скобках динамического массива ничего не пишется при его декларации:
    STR Lines|_of_text[]
  • Для фиксированного массива при его декларации в квадратных скобках записывается либо целочисленное константное выражение, либо имя типа перечисления. Для целочисленного, значение выражения - размер массива (на 1 больше последнего индекса по данному измерению)..
    Пример (массив 16 элементов):
    REAL A|rray_of_values[16]
  • При использовании массива даже как цельного объекта (в отличие от его элементов), всегда указываются квадратные скобки.
    Например: A[]

 

 

Начальное значение для массивов обеспечивается обязательно, как и для всех остальных переменных.

  • Массив переменной размерности первоначально пуст.
  • Фиксированный массив объектов класса первоначально заполнен NONE-объектами соответствующего класса.
  • Фиксированный массив строк заполнен пустыми строками.
  • Фиксированный массив чисел, перечислений, коллекций заполнен арифметическими нулями.

 

II. 2. e. Конструктор массива

 

Конструктор массива - это перечень элементов массива в квадратных скобках в виде: [ значение, значение, ... значение ]

 

Конструктор массива может использоваться как второй операнд операции проверки на вхождение A IN B (и противоположной A !IN B).

 

II. 2. f. Использование массивов

 

Основные операции с массивами:

  • Чтение и запись элемента массива. В левой части оператора присваивания или в качестве операнда выражения указывается имя массива и в квадратных скобках записывается выражение, вычисляющее целочисленный индекс элемента по этой размерности.
    Например: A[i] = B
  • Добавление элемента в динамический массив оператором <<, пример: X[] << Value
  • Вызов одной из встроенных функций над динамическими массивами:
    • X[].Add(s)
    • X[].Insert(i, Value:s)
    • X[].Delete(i)
    • X[].Delete_range(i, Count)
    • X[].Remove(R)
    • X[].Find(v)
    • present = R IN X[]
    • CASE X[].Find(R) < 0 ? not_found ;
    • count = X[].Count
    • X[].Clear
  • При обращении за пределами массива возвращается NONE-объект соответствующего типа.
  • Присваивание значения за пределами массива игнорируется.

     

Передача массива как параметра в функцию:

  • Указываются квадратные скобки для массива.
  • На место динамического массива может быть передан только динамический массив.
  • На место фиксированного массива с целочисленными индексами может быть передан фиксированный (целочисленный) массив или часть динамического массива (с указанием диапазона индексов  в форме:
    A[first TO last]
  • Для указания того, что формальный параметр функции является массивом фиксированного размера с целочисленными индексами, в квадратных скобках записывается символ '*'.
  • В качестве параметра - фиксированного массива с размером, заданным перечислением, может быть передан только фиксированный массив с размером, заданным тем же перечислением.

 

Не допускается:

  • Присваивание целого массива как единого значения.
  • Возврат массива в качестве результата функции.

 

 

II. 3. Функции

II. 3. a. Заголовок функции

FUNCTION  имена (параметры) ==> тип_результата , модификаторы :
    операторы .

 

FUNCTION  имена (параметры) ==> тип_результата, модификаторы :
  • Статическую функцию начинает функцию слово FUNCTION или FUN.
  • Метод класса начинает ключевое слово METHOD.
  • Если метод переопределенный, используется ключевое слово OVERRIDE.
  • Для конструктора и деструктора класса используются ключевые слова CONSTRUCT и DESTRUCT, соответственно. И в этом случае уже нет имен функций, параметров, результатов, модификаторов - просто двоеточие, которое завершает заголовок.
  • Для переопределения стандартных операций +, -, *, / используется особая форма функции: начинается ключевым словом OPERATOR.
FUNCTION  имена (параметры) ==> тип_результата, модификаторы :
  • Функции могут иметь два имени (как минимум одно длинное имя не менее 8 символов)..
  • В случае двойного именования, сначала записывается короткое имя, далее, после разделителя '|' - остаток имени. При определении замещения метода класса-предшественника указывается только первое имя замещаемого метода.
  • Имя публичной функции начинается с прописной буквы. Если требуется сделать публичной функцию или метод, начинающийся со строчной буквы, может использоваться модификатор PUBLIC.
  • Имя функции, доступной только в самом классе (и его наследниках) - начинается только со строчной буквы.
  • OPERATOR не имеет имен, и его параметры задаются в форме
    ТИП ОПЕРАЦИЯ ТИП (или для унарного оператора "-": ОПЕРАЦИЯ ТИП), при этом являясь его уникальным именем (допускается переопределение несколькими операторами одной и той же операции - с разными типами данных).
  • В классе может быть объявлен один метод без имени (вместо имени в заголовке указывается точка), параметры для такого метода записываются в квадратных скобках. Если для такого метода объявлен сеттер, то такой метод может использоваться в левой части оператора присваивания.

 

 
FUNCTION имена (параметры) ==> тип_результата, модификаторы :
  • Параметры в круглых скобках в заголовке указываются при их наличии..
  • В случае их отсутствия записываются пустые круглые скобки.
  • Число параметров не ограничено, но если количество явных параметров (не считая THIS) превышает три хотя бы у одной функции, класс должен иметь модификатор BAD.

 

 

Параметры перечисляются через запятую.

  • Для параметра сначала записывается тип параметра, затем имена параметра, далее размерность (необязательно).
  • Параметр может иметь два имени (краткое имя отделяется от остальной части имени символом '|', как обычно).
  • Полное имя параметра должно быть не менее 8 символов.
  • Имена параметров должны начинаться с прописной буквы.
 

Для параметра-массива после имени указывается размерность в квадратных скобках.

  • Для динамического массива, в квадратных скобках ничего не записывается.
  • Для фиксированных массивов с целочисленными индексами записывается символ '*'.
  • Для фиксированных массив с перечислимыми индексами указывается имя типа перечисления (в фигурных скобках), например, A|rray_of_color[{color}]

 

 

Параметры не могут иметь модификаторы.

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

Скалярные параметры всегда принимаются по значению, и не могут изменяться в теле функции.

 

 

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

 

Для операторов, параметрами являются типы данных. Как минимум, один из типов данных должен быть классом или записью. В теле оператора (представляющим собой выражение) для ссылки на параметры используются буквы A и B, хотя в заголовке оператора эти буквы не пишут. Буква A соответствует первому операнду, B - второму. В случае переопределения унарной операции "-" для ссылки на единственный операнд используется имя A.

 
FUNCTION имена (параметры) ==> тип_результата, модификаторы :

Если тип результата не указывается (вместе со стрелочкой '==>'), то функция не возвращает результат.

  • Для присваивания значения результату функции, используется присваивание значения псевдо-переменной RESULT.
  • Как и все прочие локальные переменные, псевдо-переменная RESULT изначально инициализируется NONE-значением:
    • NONE - для объектов,
    • 0 - для чисел,
    • "" - для строк,
    • первый элемент - для перечислений,
    • FALSE - для типа BOOL.
  • Функция не может возвращать массив в качестве результата, только скаляр.
  • Специальный символ возврата ==> используется и как операция выхода из функции в теле функции (он может пристыковываться к концу любого простого оператора).

 

Например, подсчет символов точки в строке:
FUNCTION Ndots|_count (STR S) ==> INT :
    FOR i IN [0 TO S.Len-1] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        CASE S[i] == "." ? RESULT++ ; ▄▄▄▄▄▄ 
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 

FUNCTION имена (параметры) ==> тип_результата, модификаторы :
Модификаторы функции перечисляются через запятую. Возможны модификаторы:
  • RECURSIVE - для рекурсивной функции. Если функция уже использует некоторую рекурсивную функцию, и сама не вызывает себя непосредственно, то этот модификатор необязателен.
    • Если в процессе выполнения обнаруживается превышение некоторого порога рекурсивных вызовов (обычно - 128, но может быть меньше или больше), то происходит возврат к самому верхнему рекурсивному уровню, этот вызов функции игнорируется, а в качестве результата, если он требуется, возвращается NONE-значение.

 

  • NEW - функция возвращает новый объект (экземпляр класса).
    • Как минимум один оператор в функции представляет собой присваивание переменной RESULT конструктора нового объекта или результата вызова функции, имеющей модификатор NEW.
    • Функцию с модификатором NEW можно вызвать только особо (не в составе выражения), присвоив результат какому-либо внешнему владельцу, или обеспечив корректное владение нового объекта другими способами (см. раздел "Классы").

 

  • REPLACE - для переопреленного виртуального метода, не возвращающего результат сообщает, что виртуальный метод класса не использует вызов функции-предшественника, т.е. прототип функции полностью переопределен. Если такой модификатор отсутствует, то в теле требуется вызов метода предка (оператором BASE).

 

  • NATIVE - в качестве тела функции записывается строчная константа, которая, обычно, вставляется непосредственно в тело сгенерированной функции в откомпилированного коде. (Либо тело функции завершается оператором NATIVE "строка").
    • Класс, в котором используются NATIVE-функции, должен иметь модификатор NATIVE.
  • SETTER FOR identifier - функция является присваивателем для поля или получатель-метода, заданного идентификатором. Это поле или метод должно быть объявлено непосредственно перед функцией-присваивателем. В случае поля параметр должен быть единственным, того же типа, что и поле. В случае метода параметров должно быть на один больше, чем у метода-получателя, и тип последнего параметра должен совпадать с типом результата получателя (а прочие параметры - идентичны по типам).
  • CALLBACK - функция предназначена для вызова из кода самого класса и кода нативных функций, её не следует вызывать из пользовательского кода, в том числе из кода классов-наследников данного класса. Например, событие мыши на форме mouse_down: следует переопределять данный метод в своём наследнике формы, но не вызывать напрямую метод класса {Form}.
  • POP(Method2) - функция может быть вызвана только в операторе PUSH. По окончании блока PUSH, гарантированно вызывается Method2 (он не должен иметь параметров, и не может вызываться напрямую вообще никаким способом).
  • DEPRECATED('text') - функция "осуждается" и теперь необходимо использовать альтернативу, указанную в тексте. В будущих версиях функция, возможно, перестанет поддерживаться.
  • ABANDONED('text') - функция более не поддерживается. Используйте альтернативу, указанную в тексте в апострофах. Попытка вызова функции вызывает ошибку компиляции.
  • STORE(Parameter=value) - Специальный "невидимый" параметр целого типа, создающий на стороне вызывающего класса для каждого отдельного вызова функции скрытую целочисленную переменную, которая и передаётся в качестве параметра, причем по ссылке. См. подробнее в Дополнительной секции.
  • FORGET - при вызове такого метола, все сохранённые невидимые значения на стороне вызывающего объекта сбрасываются в их начальное состояние. См. подробности в Дополнительной секции.
  • TRAP - метод предназначен для использования в качестве отладочной ловушки, срабатывающей на то или иное событие по изменению поля класса. Для одного поля может быть установлено до трех методов-ловушек (на операции чтения, записи, и добавления/удаления элементов в поле, являющееся массивом). Список ловушек для поля задается в его модификаторе TRAP(ловушка1, ловушка2, ловушка3). Тип ловушки определяется параметрами:
    • для скалярного поля, метод без параметров вызывается при чтении значения поля, метод с параметром (тип которого должен совпадать с типом поля) вызывается перед присваиванием нового значения (которое передается в качестве параметра ловушке);
    • для поля-массива, добавляется параметр типа INT (получает индекс изменившегося или читаемого элемента); в случае первого параметра типа BOOL ловушка срабатывает на операции добавления / вставки / удаления элемента, при этом в случает добавления и вставки в этом поле передается TRUE, при удалении - FALSE; такая ловушка вызывается перед удалением либо после добавления элемента.
  • RESTRICT имя IN [список] или
    RESTRICT имя IN {имя_блока_констант} или
    RESTRICT имя IS CONST - ограничение на параметр с указанным именем: он может быть только константой (в первом варианте - только из указанного списка).
  • IF имя IN [список] - параметр функции, заданный именем, участвует в ограничении значения следующего за ним (модификатор THEN) параметра. Вместо списка может быть указано имя набора констант в фигурных скобках (см. описание оператора CONSTANT).
  • IF имя IS CONST - если параметр функции, заданный именем, получает константу в качестве значения, то работает следующее ограничение значения параметра (модификатор THEN).
  • THEN имя IN [список] - условное ограничение заданного именем параметра только указанными значениями (при условии, указанном предыдущим модификатором IF).
 

В случае, если в теле функции используются переопределенные операторы, то (независимо от вида функции - FUNCTION, METHOD, CONSTRUCTOR, OPERATOR и т.п.), должен присутствовать модификатор OPERATORS(список классов). В скобках должен задаваться список классов, в которых отыскиваются переопределенные операторы (именно в этом порядке).

 

II. 3. b. Тело функции

 

Тело функции состоит из операторов, и завершается символом '.' (точка).

  • Тело функции на любом уровне блока не должно содержать более 7 простых операторов и 7 блочных операторов.
  • Блок может быть разбит разделяющими комментариями вида
    ---------------- 'метка'
    (не более, чем 7 таких комментариев на блок), в этом случае подсчёт начинается после комментария заново.
 

Если обозначенные выше ограничения не соблюдены, в заголовке класса требуется указывать модификатор BAD.

 

В случае единственного оператора присваивания значения выражения псевдо-переменной RESULT, имя переменной может быть опущено и тело имеет форму
: = выражение .

 

II. 3. c. Вызов функций

 

Круглые скобки используются только при наличии параметров.

  • Параметры перечисляются в круглых скобках через запятую в порядке, соответствующем декларации параметров.
  • Параметр-объект передаётся в функцию неявно. Он может быть явно использован (THIS).
  • Если функция возвращает результат, её нельзя вызывать как самостоятельный оператор без присваивания её результата какой-либо переменной. Если результат не требуется, результат должен быть "присвоен" переменной NONE, например, в форме:
    NONE = объект.Функция(параметры)
  • Любая статическая функция может быть вызвана в префиксной форме, когда её первый параметр выносится в префикс, например,
    x.Sin - вместо Sin(x)
    s.Lower.Ending(".al4") - вместо
    Ending(Lower(s), ".al4")

     

 

 

III. Классы и объекты

III. 1. Классы

III. 1. a. Декларация класса

 

Синтаксис.

 

/*
CLASS
{имена} , модификаторы  :
    IMPORT: {Class1} ... ;
    BASE CLASS {базовый_класс}
    поле1
    поле2
    ...
    метод1
    метод2
    ...
    --------------- 'метка секции1'
    ...
    --------------- 'метка секции2'
    ...
END

HISTORY

...
*/

 

В одном файле располагается декларация одного класса.

Все прочие декларации (перечислимые типы, функции, константы, поля) существуют только в пределах классах.

Глобальных (статических) полей классов не бывает, все поля являются членами каких-либо экземпляров классов.

 

 

  • Код AL-IV всегда располагается между строками с символами конца и начала многострочного комментария /* и */.
  • Имена классов заключаются в фигурные скобки.
  • Имя класса начинается с прописной буквы.
  • Имя класса может разделяться на две части символом '|'. До этого символа находится краткое имя класса, после - длинное имя класса.
    Например: {My|_class} определяет два имени - краткое {My} и длинное {My_class} для одного класса.
  • Длинное имя должно быть не менее 8 символов (не считая фигурные скобки).
  • Имя файла класса (без расширения класса) должно совпадать с длинным именем класса (без фигурных скобок). Но нарушение этого правила не вызывает ошибку, только предупреждение.

 

Основная часть декларации класса - это определения полей и функций.

  • Поля декларируются в форме
    {тип} имена = значение, модификатор
  • Тип - это один из системных простых типов BOOL, BYTE, INT, REAL, STR, либо имя класса в фигурных скобках (начинается с прописной буквы), либо имя перечисления или записи в фигурных скобках (начинается со строчной буквы).
  • Если имена поля или метода начинаются с прописной буквы, то имя - публичное.
  • Со строчной буквы начинается имя скрытого поля или метода. Они доступны в самом классе, и в наследниках класса.
  • Декларации в классе могут разбиваться на секции с помощью оператора-разделителя:
    ----------- 'метка разделителя'
  • Внутри одной секции не должно быть более 7 полей.
  • Декларация функции так же завершает текущую группу.
  • Если правило "не более 7 полей в группе" не выполняется, модуль должен иметь модификатор BAD.

 

 

III. 1. b. Наследование, модификаторы

 

Для указания предка класса, первым в классе после оператора IMPORT записывается:
BASE CLASS {Base_class}

  • Если конструкция BASE CLASS ... отсутствует, то класс не имеет предков, кроме общего для всех классов предка - безымянного класса {}.
  • Если в классе переопределяются методы предка, то все такие переопределения начинаются словом OVERRIDE:
    OVERRIDE some_name(paramer) ==> result_type:
                    ...
    .
  • Для выполнения действий при создании каждого экземпляра класса, после присваивания значений полей, используется процедура CONSTRUCT|ION
    CONSTRUCT :
                    ...
    .
  • Для выполнения действий при уничтожении объекта, используется специальная процедура
    DESTRUCT :
                   ...
    .
  • Если классы использует деструктор, то он обязан иметь модификатор DESTRUCTORS в заголовке.

 

 

При переопределении метода в классе:

  • Используется префикс OVERRIDE вместо METHOD.
  • Указывается только одно имя метода.
  • Перечисляются (в скобках, через запятую) все определения параметров переопределяемой функции (должны совпадать первые буквы имен соответствующих параметров). Типы должны быть совместимы, размерности для массивов - совпадать.
  • Для методов, возвращающих значение:
    • указывается тип возвращаемого результата.
    • Пример:
      OVERRIDE enabled ==> BOOL :
            RESULT = o.Edit1.Text != "" .

 

Переопределённый метод может вызывать базовый метод (одноимённый метод родительского класса).

  • Для явного вызова базового метода в теле переопределённого метода используется запись BASE.
  • При этом все параметры передаются обычным способом, в том числе явно передаётся сам объект как неявный параметр:
    BASE(параметры).
  • Метод, не возвращающий значение, либо должен хотя бы раз вызвать базовый метод (обычно, вначале), либо ему следует добавить модификатор REPLACE.

 

 

Модификаторы для класса записываются после имен класса , через запятые. Имеются модификаторы:

  • ABSTRACT - класс предназначен только для наследования новых классов на его основе, создавать объекты этого класса нельзя.
  • BAD - класс не выдерживает требований по оформлению кода (не более 3 параметров, не более 3 уровней вложенности блочных операторов, не более 7 полей в секции класса, не более 7 простых + 7 блочных операторов в секции кода).
  • BITWISE - в классе используется поразрядная логика.
  • BYTES - в классе используются байты.
  • DESTRUCTORS - имеется деструктор.
  • NATIVE - имеются "нативные" функции.
  • OPERASTORS - переопределяются операторы.
  • RECURSIVE - имеются рекурсивные фукции.
  • STOPPING - используется оператор STOP.
  • UNTESTED - есть не оттестированный код.
  • SAFE - всё хорошо, класс оттестирован, не содержит никаких замечаний по безопасности или оформлению.
  • DEPRECATED('text') - класс поддерживается, но его использование осуждается. В скобках и апострофах указано, что использовать в качестве альтернативы. Желательно как можно быстрее отказаться от использования этого класса, т.к. в скором времени он может более не поддерживаться.
  • ABANDONED('text') - использование этой версии класса запрещено. В скобках и кавычках указано, что использовать в качестве альтернативы (не обязательно это класс, возможно, технический приём, или поле другого класса). Попытка объявить переменную с таким классом или функцию, возвращающую объект такого класса (и любая другая ссылка на класс) вызывает ошибку компиляции.

III. 1. c. Объекты. Сильные и слабые ссылки

 

Переменная класса (экземпляр класса) является объектом класса.

  • Объекты класса декларируются как переменные с именем класса в качестве типа данных.
    Например:
    {My_class} M|y_object = {My_class}
  • Поле или переменная, не имеющая в именах завершающего символа подчеркивания, является удерживающей ссылкой объекта.
  • Переменные_ с завершающим подчеркиванием в имени являются слабыми ссылками и лишь ссылаются на существующие объекты.
  • Слабая ссылка действительна лишь до тех пор, пока они объект существует (на него ссылаются переменные-сильные указатели, или жив владелец объекта).
  • Все локальные переменные функций являются сильными ссылками. Слабыми ссылками могут быть только поля класса.
  • При обнулении счетчика использования сильными ссылками объект уничтожается. Все использующие переменные немедленно начинают ссылаться на NONE.
  • Если в момент создания объекта для него явно указан владелец (OWNED BY), то наличие сильных ссылок на него уже никак не влияет на время жизни объекта: он будет уничтожен вместе со своим владельцем, при этом все ссылки на него, сильные или слабые, перенаправляются на NONE-объект.
  • NONE - объект - это специальный объект класса, имеющий все поля равными NONE (и 0 - для чисел, пустым строкам - для строк, и т.д.). Запись в поля этого псевдо-объекта игнорируется, чтение из его полей возвращает NONE-значения, вызов методов не выполняет никаких действий.
  • Все объектные переменные (как сильные, так и слабые ссылки) изначально имеют значение NONE, чтение за пределами массива объектов так же возвращает NONE.

 

ПРАВИЛО
  • Поле FA класса A, являющееся удерживающей ссылкой на объект, не может иметь тип класса B, являющегося предком по отношению к классу A.
  • Если класс А имеет удерживающее поле класса B, то класс B не может иметь удерживающих полей класса А
    или классов, родственных A по прямой линии (как предков, так и потомков).
  • Если класс А1 содержит удерживающее поле класса А2, класс А2 содержит удерживающее поле класса А3, и т.д., и класс А(n-1) содержит удерживающее поле класса А(n), то класс А(n) не может содержать удерживающих полей классов А1, А2, ..., А(n-1).
  • В частности, объект не может ссылаться на объекты своего класса.
  • В результате, невозможно создание цепочек взаимного владения.
  • Одноранговые сети, графы, и т.п. организуются с помощью слабых_ ссылок.

 

Вновь созданные объект должен быть
    либо присвоен внешней (по отношению к функции) объектной переменной без подчёркивания на конце имени (сильной ссылке),
    либо добавлен во внешний массив сильных ссылок (операция , >> массив[]),
    либо через запятую должен быть указан объект-владелец вновь созданного объекта, например, OWNED BY var_ref,
    либо присвоен переменной, в имени которой присутствует строка temp (в любом регистре, например, Example|_TempVar).
  • Переменная является внешней по отношению к функции, если она:
    • либо является полем самого класса,
    • либо является полем внешней переменной,
    • либо является псевдо-переменной RESULT,
    • либо является полем переменной RESULT,
    • либо является полем объектного параметра.
  • Если вновь созданный объект хотя бы раз в теле функции присваивается переменной RESULT, то функция должны быть помечена маркером NEW.
  • Функция с маркером NEW может вызываться только в отдельном операторе присваивания, как и для оператора создания нового объекта. При этом вновь созданный такой функцией объект так же должен быть присвоен внешней сильной ссылке или temp-переменной.
  • Для результата NEW-функции не может быть применена конструкция OWNED BY, в отличие от прямого создания объекта.

 

III. 1. d. Методы

 

Все функции класса являются виртуальными.

  • Имеет место полиморфизм: вызов метода приведёт к вызову метода, определённого для экземпляра класса или наследника класса, представляющего объект.

III. 2. Работа с объектами классов

III. 2. a. Оператор создания экземпляра

 

Конструкция объекта:

variable = {Class_name}(
    Field1 = expression, Field2[] << expression, ...)

  • Если значение присваивается переменной в операторе декларации этой переменной (который начинается с указания типа переменной в угловых скобках), и оператором создаётся объект именно этого типа, то в декларации может быть опущено имя типа декларируемой переменной:
    stream|_read_temp = {File_stream}(
           Path = source_path, Mode = 'READ')
  • Если при создании объекта не требуется инициализировать поля, то круглые скобки опускаются.
  • Если за именем типа и списком инициализации следует через запятую запись OWNED BY выражение, то тем самым созданному объекту назначается исключительный владелец.
    • Для такого объекта, счётчики использования строгими указателями не влияют на время жизни объекта: он существует, пока жив его владелец.
    • Если на момент создания объекта значение выражения в части OWNED BY равно NONE, то владелец не назначается, и при отсутствии других строгих ссылок на него, проживёт не долее, чем выполняется функция, в которой он создан.
  • Либо далее может располагаться операция добавления созданного объекта в один или несколько массивов объектов: >> переменная[].
  • Создаваемый объект должен быть присвоен либо внешнему для функции объекту (параметру или его полю, полю класса или его полю), или добавлен во внешний по отношению к функции массив строгих ссылок на объекты, либо ему должен быть назначен исключительный владелец (, OWNED BY). В противном случае, переменная, которой присваивается созданный объект, должен иметь в своём имени подстроку 'temp'.
 

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

 

III. 3. Операторы уровня класса

 

Первым в классе после заголовка должен быть указан оператор IMPORT, если он требуется. В операторе импорта перечисляются используемые классы (в том числе родительский класс, если класс образован не от базового класса {Object}).

IMPORT : {Класс1}, {Класс2}, ... ;

(В качестве завершения блока IMPORT может быть точка или точка с запятой).

 

Если класс имеет предка в иерархии, он должен быть указан оператором BASE CLASS {Имя}, но после оператора импорта, в котором указывается класс предка.

 

Завершается класс оператором END в отдельной строке.

 

До оператора END могут встречаться:

  • операторы определения перечислений
    ENUM {имя|уточнение} :
        'НАИМЕН|ОВАНИЕ1'
        'НАИМЕН|ОВАНИЕ2'

        ...  .
  • операторы описания структур
    STRUCTURE {имя|уточнение} :
        поле1
        поле2

        ... .
  • операторы описания таблиц
    TABLE Имя|уточнение : {структура}
        NAME "имя"
        COUNTER(список)
        NOTNULL(список)
        NAMES(поле = "имя", ...)
    .
  • декларации полей
    ТИП Имя|уточнение [размер] = инициализация, модификатор
  • декларация конструктора
    CONSTRUCT :
        ТЕЛО .
  • декларация деструктора
    DESTRUCT :
        ТЕЛО .
  • декларации статических функций
    FUNCTION Имя|уточнение(параметры) ==> ТИП_РЕЗУЛЬТАТА,
        модификатор1, модификатор2,
    ... :
        ТЕЛО .
  • декларации методов
    METHOD Имя|уточнение(параметры) ==> ТИП_РЕЗУЛЬТАТА,
        модификатор1, модификатор2,
    ... :
        ТЕЛО .
  • декларации переопределённых функций
    OVERRIDE Имя(параметры) ==> ТИП, модификатор1, ... :
        ТЕЛО .
  • функции тестирования
    TEST Имя|уточнение(параметры) ==> ТИП_РЕЗУЛЬТАТА :
         ТЕЛО .
  • переопределения операторов
    OPERATOR ТИП ОПЕРАЦИЯ ТИП ==> ТИП_РЕЗУЛЬТАТА :
         ВЫРАЖЕНИЕ .
  • блочные комментарии
    --------------------------- 'текст'
  • директивы NATIVE
    NATIVE: "код специфичный для платформы" .
  • операторы TODO
    TODO: "строка" .

 

III. 4. Модификаторы полей класса

 

Поля класса могут иметь модификаторы, перечисляемые после декларации поля через запятые.

 
  • PUBLIC - модификатор публичности поля. Обычно достаточно при объявлении поля начать его имя с заглавной буквы. Модификатор PUBLIC позволяет сделать публичным поле, имя которого начинается со строчной буквы. Не публичные (скрытые) поля могут видеть только методы самого класса, его потомки и друзья класса;
  • READ или READONLY - модификатор "только для чтения". Изменять такое поле смогут методы самого класса, его наследников и классов-друзей;
  • INIT или INITIALIZE - поле требует инициализации при создании экземпляра объекта. Если для создания экземпляра используется явный конструктор вида
    destination = {Class_name}(Field1 = value1, Field2 = value2, ...)
    и данное поле не перечислено, это вызывает ошибку компиляции. Если INIT-поле является еще и полем только для чтения, то явный конструктор можно будет использовать только в теле самого класса, его наследников и друзей;
  • TRAP(ловушка, ...) - задает список отладочных методов-ловушек для поля (до 3 ловушек на поле); в качестве ловушки может быть указан метод своего класса, не возвращающий значений в качестве результатов, при этом:
    • если поле скалярное, то в качестве допускаются либо методы без параметров (ловушка на чтение поля), либо методы с единственным параметров (тип которого совпадает с типом поля - ловушка на запись нового значения, вызывается перед записью);
    • если поле является массивом, то допускаются следующие сочетания параметров:
      • (INT) - ловушка на чтение элемента с указанным (параметром) индексом;
      • (INT, {тип поля}) - ловушка на изменение элемента массива с индексом, заданным первым параметром, при этом новое значение передается во втором параметре;
      • (BOOL, INT) - ловушка на изменение размера динамического массива: в булевом первом параметре передается TRUE в случае вставки нового значения (такая ловушка срабатывает после операции вставки), FALSE передается для операции удаления (в этом случае метод-ловушка вызывается перед удалением элемента).
    • ловушки не предназначены для изменения логики работы программы, и с их помощью невозможно изменить присваиваемое или возвращаемое значение, или отменить операцию (хотя и возможно добавить или удалить другие элементы в массив - но не нужно так делать). Задача ловушек - позволить упростить поиск проблемных операций, с целью отладки алгоритма;
  • DEPRECATED('текст') - поле является осуждаемым, не рекомендуется к использованию, и вызывает предупреждение компилятора;
  • ABANDONED('текст') - поле отменено, его использование будет вызывать ошибку компиляции;

 

III. 5. Завершение класса. История. Массив DATA[].

 

Класс завершается оператором END. Если после строки с оператором END первая непустая строка начинается ключевым словом HISTORY, то далее размещается блок истории изменений класса. Независимо от наличия истории изменений, при наличии непустого текста после директивы END, все эти строки доступны в коде класса через псевдо-массив строк DATA[].  

 

Если директива HISTORY имеется, то история должна иметь достаточно строгий формат. История состоит из блоков CREATED / UPDATED (блок CREATED может быть только первым, и только единственным, если присутствует). Формат заголовка блока:
( 'CREATED' | 'UPDATED' ) '(' YYYY-MM[-DD] ')' [ ',' 'VERSION' '"' text '"' ['BY' '"'text'"'] ':'

Содержимым блока являются сообщения ADDED / CHANGED / FIXED в форме:
( 'ADDED' | 'CHANGED' | 'FIXED' ) ':' ('"'text'"' | identifier) [ ',' ('"'text'"' | identifier) ]... ( ';' | '.' )

Последнее сообщение блока изменений должно завершаться точкой, или состоять из одной только точки.

 

История изменений может завершаться вместе с концом текста. Но если кроме истории изменений текст после директивы END содержит другую информацию, то история должна завершаться директивой
HISTORY ENDED

Например:
HISTORY:

UPDATED(2018-08-15), VER "1.0a":
    ADDED: "This history added";
    CHANGED: "No other changes yet".

IV. Структуры (STRUCTURE)

IV. 1. Декларация структур

 

Синтаксис.

 

STRUCTURE {name|_of_structure} :
    поле1
    поле2
    ...
    поле N .

 

Структуры декларируются внутри классов.

Имя структуры всегда начинается со строчной буквы.

Имя структуры может делиться значком '|' на части: часть до первого знака является кратким именем. Полное имя не должно быть короче 8 символов.

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

 

Запись состоит из:

  • полей простых типов фиксированного размера (BOOL, BYTE, INT, REAL, перечисления, STR)
  • полей типа структуры (любые другие структуры в области видимости - рекурсивное вложение не допускается)
  • ссылок на экземпляры классов
  • массивов из выше перечисленных элементов.

 

Декларация записи завершается точкой.

 

 

Структура, как и класс, может содержать поля любого типа. Но имеются ограничения на объекты классов:
поля структуры, ссылающиеся на объекты классов (т.е. имеющие тип класса), должны быть слабыми ссылками (и их имена должны завершаться подчеркиванием). Если же поле класса имеет тип структуры, то в этом случае они не может быть слабой ссылкой (т.к. любая переменная или поле типа структуры является ее единственным владельцем).

 

Все структурные переменные и поля автоматически инициализируются структурой, состоящей из нулевых полей (для чисел - это нули, для строк - пустые строки, для объектов классов - NONE). В отличие от объектов классов, присваивание значений полям структуры почти всегда возможно, и меняет состояние структурной переменной (тогда как присваивание значений полям NONE-объекта игнорируется). Однако, явное присваивание значения NONE структурной переменной имеет тот же результат, что и для объекта. NONE-значение так же может быть получено для структуры при чтении из массива за пределами доступного диапазона индексов, и в результате присваивания с использованием метода Dismiss.

 

 

IV. 2. Работа со структурами

 

Структура - это класс без методов, имеющий всегда только одну ссылку на него. Поэтому при создании экземпляра структуры нельзя использовать модификатор OWNED BY или правую запись в массив >> Array[], как для объектов классов.

 

Одной структурной переменной может быть присвоено значение другой структурной переменной, то при этом к переменной в правой части присваивания должна быть применена встроенная функция-метод Clone или Dismiss. В случае использования Clone значение исходной структуры дублируется, в случае Dismiss - исходная переменная может быть обнулена (получить значение NONE). Это касается всех видов присваивания или добавления в массив (элемент массива структур так же считается переменной).


Destination = Source.Clone
Destination = Source.Dismiss
Destination = Source_array[i].Clone
Destination_array[j] = Source_array[i].Clone
Destination_array[] << Source.Dismiss


 

 

Например:

STRUCTURE {person|_info} :
    STR F|irst_name
    STR L|ast_name
    INT Y|ear_birthday
    {education} E|ducation
    STR S|tate_province .

...

{person} Smith|_John = {person}(F = "John", L = "Smith",
               Y=1950, E='High', S = "NY")
 
 

Такой же конструктор может использоваться при передаче фактического параметра функции в позиции параметра типа запись.

При присваивании записи ее содержимое просто копируется.

 

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

Соответственно, при присваивании значения всей структуры-параметра, может (и должен) использоваться только псевдо-метод Clone, и не может быть применен метод Dismiss.

 

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

 

Структура может быть создана обычным для объектов оператором создания структуры:
{имя-структуры}(поле1=выражение, поле2=выражение, ...)
Например,
{complex} c|omplex_var = {complex}(Im=5)

 

 

 

V. Тестирование

V. 1.Основные положения 

 

Класс может содержать особые функции, которые начинаются ключевым словом TEST вместо FUNCTION. Они предназначены для тестирования и выполняются всегда на этапе компиляции.

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

 

 

Тестированию подлежат:

  • Все строки кода.
  • Все условные ветви кода.

 

Тест считается покрывающим, только если все подлежащие тестированию строки кода выполнялись при тестировании класса хотя бы один раз. Если это условие не выполнено, класс должен быть помечен как UNTESTED.

 

Все секции функции TEST должны иметь операторы ASSERT. Компилятор не может проконтролировать качество использования этих операторов, поэтому отсутствие таких операторов в секции вызывает только предупреждение.

 

Тестированию не подлежат:

  • Нативные функции.
  • Абстрактные классы.
  • Классы, не содержащие методов.
  • Методы, не содержащие операторов.
 

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

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

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

 

 

V. 2. Синтаксис 

 

Функция тестирования начинается заголовком, в котором слово TEST используется вместо слова FUN|CTION.

 

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

 

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

 

Как и другие функции, тестирующая функция разбивается на секции секционными комментариями
-------------- 'текст'

Но для тестирующих функций имеется дополнительное требование: секция должна содержать как минимум один оператор ASSERT.

 

Оператор ASSERT предназначен для проверки булевского выражения. Если значение выражения в операторе
ASSERT выражение
ложно, утверждение не выполняется. При наличии хотя бы одного проваленного утверждения класс считается не оттестированным.

 

 

VI. Дополнительно

VI. 1. Локализация строковых ресурсов 

 

AL-IV поддерживает механизм локализации строк с помощью псевдо-функций вида _идентификатор. Для строки это выглядит как вызов функции, например,
"Red square"._Rsq или _Rsq("Red square")

Но вызовом функции данная конструкция не является. Она лишь сообщает компилятору о том, что строку "Red square" следует поместить в массив локализуемых строк, индекс строки в этом массиве запомнить и использовать для извлечения строки из этого массива. Фактически, при этом может быть извлечена другая строка, записанная в этот массив вместо исходной строки "Red square" в результате работы функций локализации. Например, это может быть строка "Красный квадрат".

Все имена таких строковых ресурсов должны быть уникальными в пределах класса.

В качестве стандартного API для управления локализацией предлагается класс {Localize_str} (хотя это не обязательно, и всегда может быть изготовлен другой класс, работающий по другим алгоритмам).

 

При использовании класса {Localize_str} следует как минимум один раз (при инициализации программы) вызвать его статическую функцию Localize, указав краткое имя языка локализации. Предполагается, что имя языка приложение сохраняет само, в доступном ему месте. Для упрощения настройки приложения в плане выбора одного из доступных для локализации языков, имеется метод List_languages, возвращающий список полных и кратких имён языков, извлечённых их названий языковых файлов (предполагая, что они именуются в едином стиле English_EN.lng).

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

 

При обращении к методу Localize первым делом вызывается метод Save_untranslated, если до этого он не вызывался из программы. Пользователь-переводчик всегда может обнаружить первичный языковый файл, сохранённый этим методом, сделать из него копию, поименовав её в форме Имя-языка_ИЯ.lng и отредактировав в текстовом редакторе. Строки, подлежащие переводу, имеют форму NAME=Value. Заменять следует только значения Value, не трогая имя и знак равенства. В строке могут быть и другие знаки '=', они могут изменяться в соответствии с требованиями языка. Отредактированная версия языкового файла должна быть сохранена в формате UTF-8. 

 

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

 

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

 

VI. 2. Локализация ключевых слов языка 

 

AL-IV поддерживает возможность трансляции ключевых слов самого языка с использованием любого другого письменного языка. При этом частично используется техника локализации строк (см. выше).

Все ключевые слова канонической (английской) версии языка размещаются в текстовом файле Default_.lng в директории с исходными текстами компилятора. Достаточно скопировать этот файл и переименовать, например, в Klingon_KL.lng, после чего заменить строки на свои, и становится возможно использовать ключевые слова на соответствующем языке. Ключевые слова в языковом файле имеют имена, начинающиеся буквой K, и в основном сосредоточены в секции [{Translation_to_canonical_keyword}].

Для использования в классе национальных ключевых слов, класс должен начинаться со спецификации языка вида ['KL'] или [Language='KL'], после которой первое же ключевое слово должно быть записано уже на указанном языке. Например:


['RU'] КЛАСС {Привет_мир}, НЕТЕСТИРОВАН :

ФУНКЦИЯ Main|_главная : << "Привет, мир!" >> .

КОНЕЦ
 

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

Для этого в качестве перевода может использоваться более одного словосочетания, при этом словосочетания разделяются запятыми. Внутри словосочетания (которое может быть одним словом) вертикальный разделитель разделяет несколько возможных окончаний, которые могут продолжить основной корень. Например,
Kthis=ЭТОТ| ОБЪЕКТ,ЭТОМУ|_ОБЪЕКТУ| ОБЪЕКТУ,ЭТОГО_ОБ|ЪЕКТА,ЭТОГО ОБЪЕКТА

- позволяет использовать вместо THIS слова и словосочетания:

  • ЭТОТ
  • ЭТОТ ОБЪЕКТ
  • ЭТОМУ
  • ЭТОМУ_ОБЪЕКТУ
  • ЭТОМУ ОБЪЕКТУ
  • ЭТОГО_ОБ
  • ЭТОГО_ОБЪЕКТА
  • ЭТОГО ОБЪЕКТА

Примечание: в языке AL-IV ключевое слово THIS бывает необходимо в основном в двух случаях:

  • когда сам объект должен быть передан в качестве параметра,
  • или когда создаётся новый объект, и объект THIS указывается в качестве его владельца:
    x = {Тип}( ... ), ПРИНАДЛЕЖАЩИЙ ЭТОМУ ОБЪЕКТУ - прямой перевод
    x = {Type}( ... ), OWNED BY THIS
 

Вместе с ключевыми словами локализуются кодированные символы (#NL, #TAB и т.п.), а так же встроенные псевдо-функции (.Len, .Str, .Index и др.) Но в языке имеется так же возможность упростить перевод любых ранее изготовленных классов на национальные языки. Для этого для транслируемого класса создаётся класс-зеркало на соответствующем языке, с единственным модификатором в заголовке TRANSLATION OF {Class_name}. Он должен содержать только переводы наименований для полей, функций (и их параметров), перечислений, структур, констант. Переименования помещаются в соответствующие секции. Например:

['RU']
КЛАСС {Текст_файл}, ПЕРЕВОД {Text_file} :

ПОЛЯ:
   Path = Путь|_к_текстовому_файлу
   Encoding = Кодировка
.

МЕТОДЫ:
   Load = Загруз|ить(Строки|_массив[])
   Save = Сохран|ить(Строки|_массив[])
.

ФУНКЦИИ:
   Text_load = Текст_загруз|ить(
      Путь|_к_текстовому_файлу, Строки|_массив[] )
   Text_save = Текст_сохран|ить(
      Путь|_к_текстовому_файлу, Строки|_массив[] )
.

КОНЕЦ
 

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

 

Примечание для русскоязычных читателей. Возможность по русификации (или точнее, произвольной локализации) предоставляется как механизм, позволяющий развивать навыки программирования в любом возрасте, без необходимости начинать с изучения английского языка. Несмотря на довольно большой список слов в словаре, основа языка АЛФОР требует значительно меньше важных ключевых слов для того, чтобы можно было начать писать свой код или понимать уже написанный. Например, класс для вычисления вещественных корней квадратного уравнения:


['RU'] КЛАСС {Квадратное_уравнение}, НЕТЕСТИРОВАН:
ИМПОРТ: {Математика} .
ВЕЩЕСТВЕННОЕ A|_коэффициент_не_должен_быть_0
ВЕЩЕСТВЕННОЕ B|_коэффициент
ВЕЩЕСТВЕННОЕ C|_коэффициент

МЕТОД Решение\_квадратного_уравнения( Ответ\ы_в_массиве[] )
      :
      Ответ[].Стереть
      --------- 'A == 0 - неверное условие'
      ЕСЛИ Абсолютное_значение(A) < 0.000_000_001 ? ==> ;
      ВЕЩЕСТВЕННЫЙ д\искриминант = B * B - 4 * A * C
      ЕСЛИ ?
      [д == 0] ? Ответ[] << -B / (2 * A)
      [д > 0 ] ? ВЕЩЕСТВЕННЫЙ к\орень_из_д = д.Корень
                 Ответ[] << (-B - к) / (2 * A)
                 Ответ[] << (-B + к) / (2 * A) ; .

КОНЕЦ
 

Формат класса для перевода на другой язык, в форме диаграммы:


 

VI. 3. STORE - скрытые параметры 

 

Модификатор STORE для метода создаёт скрытый (для вызывающей стороны) целочисленный параметр, значение которого сохраняется на стороне вызывающего объекта.

В модификаторе задаётся имя параметра, как его видит функция, и может быть задано первоначальное значение:

, STORE(Имя = значение)

 

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

 

Для того, чтобы "забыть" сохранённые значения параметров, может использоваться вызов метода с модификатором FORGET. При этом будут сброшены в начальное состояние (указанное в модификаторе STORE) значения всех полей, которые создавались для передачи в скрытые параметры методов того же класса,  что и метод с модификатором FORGET.

 

 

Сочетание модификаторов STORE и FORGET позволяет ускорить доступ к массивам данных, индексируемым константными строками. Например, при получении значений полей записей в выборке SELECT из базы данных, или при доступе к колонкам элемента в listview по их заголовкам.

Пример реализации доступа к колонкам listview по именам:

 

FUN Cells|_by_constant_names(
    INT Row|_index,
    STR Name|_of_cell) ==> STR, STORE(I|ndex_of_name = -1),
                                RESTRICT Name IS CONST
    :
CASE I < 0 ? I = Column_index(Name) ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
RESULT = Subitems(Row, I) .
 

В данном примере, модификатор FORGET должны иметь все методы, меняющие состав колонок listview и их названия.

 

VI. 4. SQL запросы 

 

Если в классе определён метод Write(STR), то для объектов такого класса разрешена операция объект << строка, фактически вызывающая этот метод.

В случае класса {DB}, предоставляющего интерфейс для работы с базами данных, операция << может (и должна) использоваться для установки текста SQL-запроса (в поле SQL, которое доступно только по чтению).

 

 

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

 

Такие SQL-запросы ссылаются на таблицы, декларируемые утверждениями TABLE (ссылающимися на декларации структур STRUCTURE). Например:
 

STRUCTURE {abiturient}:
        REAL ID|entity_counter_64bit
        STR Surname|_last_name, MAXLEN[40]
        STR FirstName, MAXLEN[40]
        STR MiddleName, MAXLEN[40]
        INT Exam1|_scores
        INT Exam2|_scores
        INT Exam3|_scores
        {date_time} D|ate_in_documents
        BOOL OriginalDocuments
        BOOL AgreeToEnroll .


TABLE Students|_want_to_be:
        {abiturient}
        NAME "Students"
        COUNTER(ID)
        NOTNULL(Surname, FirstName, D)
        NAMES(D = "DateJoin") .
 

Запросы SQL-подобны, но их синтаксис несколько изменён по сравнению со стандартом SQL:

  • Все ключевые слова записываются в верхнем регистре: SELECT, UPDATE, INSERT, DELETE, DISTINCT, TOP, FROM, AS, INTO, WHERE, GROUP, ORDER, BY, IN, NOT, IS, NULL, JOIN, ON, LEFT, OUTER;
  • все части SQL-подобного утверждения разделяются запятыми (что упрощает перенос на другую строку, в соответствии с правилами AL-IV). Например:
    SELECT DISTINCT, FROM Students, (*)
  • часть FROM-JOINS следует в операторе SELECT до списка выбираемых полей (в начале оператора, как в DELETE и UPDATE);
  • Список выбираемых значений, а так же полей для сортировки и группировки, всегда заключается в круглые скобки;
  • Идентификаторы по возможности считаются ссылками на таблицы, алиасы таблиц, имена полей, и только при несовпадении со всеми ожидаемыми идентификаторами из этих списков, далее рассматриваются компилятором как части AL-IV-выражения (имена переменных, функций и т.п.). Например:
    UPDATE Students, SET Exam3 = 64,
    WHERE ID = {Ident_to_update}
  • При необходимости явно указать, что часть кода относится не к синтаксису SQL, а к синтаксису AL-IV, выражение AL-IV заключается в фигурные скобки. В части WHERE это практически единственный способ использования значений, вычисленных в коде AL-IV;
  • Все такие выражения должны иметь один из типов данных INT / REAL / BOOL / STR / {date_time}. Другие типы не допускаются (как исключение: тип BYTE может рассматриваться как целочисленный);
  • Для представления счётчиков (автоинкрементных полей) следует использовать тип REAL, т.к. разрядности обычного целого недостаточно для представления длинного целого, использующегося в счетчиках;
  • Выражения AL-IV автоматически преобразуются в строку, воспринимаемую БД. Нет необходимости (и запрещено) самому преобразовывать параметрические значения в строки с использованием функция Bool_sql, Int_sql, Str_sql, Date_sql, Real_Sql;
  • В операторе INSERT, в отличие от T-SQL и подобно UPDATE, список полей и присваиваемых значений задаётся парами
    ИМЯПОЛЯ = ЗНАЧЕНИЕ. Например:
    db << INSERT INTO Students,
          Surname = "Origatsu",
          FirstName="Pei",
          MiddleName="Q",
          Exam1=84,
          Exam2=81,
          Exam3=92,
          D=Date(2017, 7, 14),
          OriginalDocuments=TRUE

    db.Exec
  • В операторах INSERT и UPDATE, возможно указать переменную (или выражение) типа структуры, соответствующей таблице, для занесения содержимого этой записи в таблицу, вместо перечисления большого числа полей:
    db << INSERT INTO Students, BY Rec
    db.Exec
  • Если запрос использует более одной таблицы, то единственный способ подключить дополнительные таблицы - это утверждения JOIN, следующие за FROM (или за первой таблицей - в UPDATE). При этом все таблицы, включая основную, должны иметь уникальные алиасы, и далее обращение происходит к полям в виде алиас . поле, например:
    db << SELECT FROM Students a,
          JOIN Students b ON a.Exam1 = b.Exam1,
          (  a.ID         AS a_ID,
             a.FirstName  AS a_F,
             a.MiddleName AS a_M,
             a.Surname    AS a_S,
             COUNT(b.*)   AS CNT_b ),
          GROUP BY (a_ID)
    db.Open
  • Разрешены вложенные запросы в операциях X IN (SELECT ...)
  • Иногда необходимо в качестве операнда предоставить некий текст, который будет просто вставлен в соответствующую позицию в SQL-запросе, без анализа его синтаксиса. В этом случае используется псевдо-функция SQL(...), где вместо ... вписывается выражение AL-IV, возвращающее строку (или просто строковая константа). Например:
    ...WHERE x IN SQL("(1,2,3)"),
             AND SQL("GetDate()") BETWEEN b.D1 AND b.D2
  • Запрос может быть разбит на группы комментариями --- 'комментарий'. Существует метод в классе {DB}: Remove_after(STR Text), который удаляет из SQL-запроса текст запроса между двумя такими комментариями (точнее, после комментария --- 'Text' - до следующего комментария). Это можно использовать для отбрасывания опциональных частей запроса (обычно - условий в части WHERE).
  • Имеется возможность при чтении результатов запроса использовать особую форму оператора <<, в которой в качестве приемника используется переменная структурного типа, а справа указывается переменная типа {DB} (или унаследованного типа), с дополнительным указанием таблицы, в форме:
    x << Db, BY TABLE Tab
    При этом структура слева не обязана быть точно такой же, как и та структура, на которой основана таблица Tab - но для всех ее полей должно быть однозначное соответствие по именам с базовой структурой такой таблицы.
  • Для получения результатов запроса рекомендуется использовать либо цикл с предварительной проверкой того, что есть данные для считывания:
    db.Open
    CASE !db.Ended ?
         FOR i|dummy IN [1 TO 999_999] :
             {results_tab} r|esults1 << db
             {results_tab} all|_results[] << r
             CASE !db.Next ? BREAK i ;
         ;
    ;
    db.Close


    Либо цикл с проверкой на каждой итерации:
    db.Open
    FOR j|dummy IN [0 TO 999_999] :
        CASE db.Ended ? BREAK j ;
        id = db.CInt("ID")
        name = db.CStr("Name")
        STR r|esults_array[] << "id=" id " name=" name
        db.Forward ;
    db.Close
  • Если требуется получить единственное значение, для этого есть набор функций Open_VInt_close, Open_VStr_close и т.п. Например:
    REAL x|_some_value = db.Open_VReal_close
 

Ниже приводятся синтаксические диаграммы для операторов, формирующих SQL-запросы:

 

 

 

 

 

VI. 5. Отменённые и устаревшие классы, структуры, перечисления, поля, функции 

 

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

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

  • Эти модификаторы применимы к классам, структурам (STRUCTURE), полям классов и структур, перечислениям, функциям.
  • Текст должен содержать имя альтернативы (если такая имеется) или хотя бы краткое пояснение, почему возможность отменяется или устаревает. Содержимое текста компилятором, разумеется, не проверяется.
  • Модификаторы DEPRECATED и ABANDONED взаимоисключающие.
  • Предлагается сначала для устаревающих понятий добавить модификатор DEPRECATED, и сохранять его до тех пор, пока устаревшая (но как-то работающая) старая возможность ещё поддерживается.
  • При использовании устаревающей возможности в использующем коде, компилятор будет выдавать предупреждение, содержащее указанный в модификаторе текст.
  • Если добавлен модификатор ABANDONED, соответствующий элемент уже нельзя использовать: компилятор будет сообщать об ошибке. При этом для функций можно убрать тело, для класса - тело всех функций.
  • Смысл сохранять память об удалённой декларации заключается в том, чтобы предоставить использующему класс программисту информацию об альтернативе (сообщение 'текст' может содержать имя альтернативной функции, класса, или несколько таких имён, может быть, ссылку на Web-ресурс и т.п.).
 

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

Например, при создании класса {Dialog} как наследника класса {Form}, метод Show декларирован как ABANDONED('Show_modal'). Это сделано для того, чтобы при использовании форм класса {Dialog} программист получал от компилятора сообщение об ошибке при попытке вызова метода Show.

К сожалению, сообщение об ошибке от компилятора не будет получено в том случае, если программист присвоил свою форму класса {Dialog} переменной класса {Form}. В этом случае будет вызван отмененный метод (и если он не содержит код, этот вызов будет проигнорирован).
 

 

VI. 6. Ограничения на значения параметров

 

Функция может иметь модификаторы RESTRICT (безусловное ограничение на параметр) и IF/THEN (условные ограничения на значения одних параметров в зависимости от значений других параметров). При этом:

  • Все такие ограничения на параметры записываются после всех прочих модификаторов функции.
  • Количество ограничивающих модификаторов не ограничено.
  • Если устанавливается ограничение на параметр, или параметр участвует в части IF условного ограничения, то в качестве значения параметра можно передать только константу. Исключением является случай условия
    IF параметр IS CONSTANT, THEN ...
  • Ограничения на параметры контролируются компилятором на этапе компиляции кода. Если условия, указанные в ограничениях, не выполняются, компилятор выдаёт сообщение об ошибке.
  • Параметр может быть ограничен или участвовать в условии ограничения, если он имеет простой тип (BOOL, BYTE, INT, REAL, STR, или перечисление - ENUM), и не является массивом.
 

Безусловное ограничение имеет форму:
RESTRICT имя_параметра IN [список_значений]

или (для вещественных типов):
RESTRICT имя_параметра IN [N1 TO N2]

или (без указания значений):
RESTRICT имя_параметра IS CONSTANT

  • Второй вариант (с диапазоном) используется для вещественных параметров. Проверка идёт по условию
    N1 <= X <= N2,
    где:
    • X - переданное функции значение,
    • N1 и N2 - границы отрезка [N1 TO N2].
  • Третий вариант не определяет проверку значений, но обязывает передавать в функцию только константное значение.
 

Условное ограничение имеет форму:
IF параметр1 IN [список или диапазон],
THEN параметр2 IN [список или диапазон]

 

или
любая часть IN [...] может заменена на IS CONSTANT.

  • Оба параметра могут принимать только константы (кроме случая IF p1 IS CONSTANT)
  • При выполнении условия в части IF, обязано выполняться условие в части THEN, чтобы компилятор не вызывал ошибку.
  • Если условие в части IF не выполнено, часть THEN просто игнорируется (но параметр всё равно может принимать только константу).
 

зачем это может понадобиться?

 

Например, для реализации набора переходников к библиотеке OpenGL. Можно упростить работу, просто собрав переходники к соответствующим функциям, и передавать так же целочисленные константы, но добавить ограничивающие модификаторы, чтобы обеспечить проверку на недопустимые наборы значений параметров. Это намного проще, чем строить системы классов или использовать для "чистоты" перечисления, и обеспечивает тот же уровень защиты (если не лучший). И при этом работают классические примеры (того же NeHe), практически без изменений (достаточно убрать символы ';' в конце операторов).

 

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

 

VI. 7. Контроль зацикливания

  1. На каждой итерации любого цикла уменьшается внутренний глобальный счётчик. По достижении значения 0 вызывается внутренняя процедура, которая снова устанавливает значение глобального счётчика (обычно это 65535, и может вызывать дополнительные действия.
  2. Для визуальных приложений, имеющих графическую оболочку, после достаточно длительного выполнения начатой длинной операции (обычно более 2 секунд), может отображаться текущий прогресс и краткое описание начатой операции и её стадии выполнения. А так же кнопка для принудительной отмены операции.
  3. В случае, если программист не предусмотрел начало длинной операции, тем не менее нажатие на кнопку, пункт меню, и т.п., так же считается точкой начало длительной операции "по умолчанию". Для такой операции так же может отображаться "прогресс", и такая операция так же может быть отменена автоматически.

 

VI. 8. Оптимизация кода. INLINE-вставки

  1. Функции могут иметь модификатор INLINE, что является указанием для компилятора выполнять для данной функции непосредственную вставку кода вместо генерации вызова этой функции.
  2. При включенной оптимизации, компилятор самостоятельно может выполнять автоматическую INLINE вставку для достаточно коротких или однократно используемых функций. Опция компилятора /!autoinline запрещает автоматические INLINE-вставки (но не отменяет INLINE-вставки функций, имеющих модификатор INLINE).
  3. Функции в некоторых случаях не могут быть вставлены, вместо вызова их. Например, запрещена INLINE-вставка метода другого класса, или вставка функции (из другого класса), использующей декларации, не импортированные в данном классе. Так же, нельзя вставлять код, содержащий операторы ==>, BREAK и CONTINUE.
  4. В некоторых случаях, непосредственная вставка кода функций, вместо вызова их обычным способом, может ухудшать производительность. Например, если вставляется несколько функций, содержащих большое число локальных переменных, то суммарное число локальных переменных в функции-рецепиенте может оказаться слишком большим. Так как в AL-IV все переменные должны быть проинициализированы до начала выполнения функции, то в случае частого вызова этой функции (в цикле) будет получено существенное снижение быстродействия.

 

VI. 9. Оптимизация кода. UNROLL (раскрутка) для циклов FOR

  1. Циклы FOR, имеющие в качестве границ диапазон, заданный константами, может быть полностью развернут модификатором UNROLL без параметров (количество итераций не должно превышать некоторого предела, заданного компилятором, в текущей версии это 1024 итерации). Пример:
    FOR i IN [0 TO 19], UNROLL: ... ;
  2. Любые циклы FOR могут быть развернуты с фактором развертывания, заданным в скобках. Фактор должен быть целой константой со значением не меньше 2.
  3. При развертывании циклы увеличивается размер кода, но может сокращаться время выполнения. Но в некоторых случаях, производительность может заметно деградировать (в случае C#: возможно, это связано с особенностями работы его оптимизатора кода).
  4. Опция компиляции /!unroll запрещает все развертывания циклов.

 

VI. 10. Синтаксический сахар

 

a. Если имеется несколько подряд присваиваний (или добавлений в строку или в массив) одному и тому же получателю, то получатель в очередных операторах может быть заменен символом .. (две точки). При этом, в случае массива, квадратные скобки вслед за точками опускать нельзя. Например:
A[] << item1 << item2 << item3
..[] << item4 << item5


Строки с такими "продолжениями" присваиваний не учитываются при подсчете операторов, и количество продолжений не ограничено. Между полным присваиванием/добавлениям и продолжениями могут находиться другие операторы, не являющиеся присваиваниями/добавлениями (но дополнительные присваивания тому же получателю должны находиться в пределах одной функции). Например:
A[] << item1
CASE condition ? ..[] << item2 ;

 

b. Имеется возможность в случае присваивания полю, указанию которого предшествует цепочка разыменований (например, A.B.C(params).D[index].E), также указать, какое поле будет считаться целеприемником при последующих продолженных присваиваниях/добавлениях. Для этого вместо символа точки в соответствующей позиции (после интересующего поля, заменяемого далее удвоенной точкой) употребляется троеточие. Например:
A...B.C[] << item1
...Calculate(param) // здесь выполняется A.Calculate(param)

 

c. Если в пределах одного оператора встречается несколько выражений вида A.B[index].C(params).D, где последнее в цепочке имя D - это ссылка на поле (объекта или структуры), и далее цепочка повторяется, и только финальное поле  отличается, то второе и последующее выражения могут быть заменены на .E, .F и т.п. При этом лидирующие знаки -, !, ~ не относятся к выражению, и их не следует опускать. Например:
P = pt1.Offset(-PBox.Bounds.Loc.X, -.Y)

 

VI. 11. Краткая справка по "встроенным" функциям

 

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

 

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

 

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

 

Функция Расшифровка Группа
Index(Variable) ==> INT Индекс текущего элемента в цикле, когда переменная пробегает массив или часть массива циклы
Name({enum} Item) ==> STR Имя элемента перечисления Например, "'RED'" перечисления
First({enum} Item) ==> {enum} Первый элемент перечисления
Last({enum} Item) ==> {enum} Последний элемент перечисления
Prev|ious({enum} Item) ==> {enum} Предыдущий элемент перечисления
Next({enum} Item) ==> {enum} Следующий элемент перечисления
Int({enum} Item) ==> INT Преобразовать в целое (индекс элемента в перечислении начиная с 0)
Int(STR S) ==> INT Целочисленное значение из строки строки
Real(STR S) ==> REAL Вещественное значение из строки
Len(STR S) ==> INT Длина строки
Find(STR S, STR W) ==> INT Поиск индекса первой подстроки W в строке S (0 ... Len-1 или -1, если такой подстроки нет или W == "")
Str({INT,REAL,BOOL}) ==> STR или S({INT,REAL,BOOL}) ==> STR Преобразование в строку целого, вещественного, булевского значения
Count(A[]) ==> INT Количество элементов в массиве массивы
Allocate(A[], INT Size) Выделение необходимого количества элементов в массиве, чтобы размер был в итоге равен Size (если размер уже превышает Size, он не изменяется)
Find(A[], W) ==> INT Поиск индекса первого элемента в массиве (кроме массива структур). Результат 0 ... Count-1 или -1, если не найден.
Insert(A[], INT I, V) Вставка элемента V в позицию I в массиве A[]
Clear(A[]) Чистка динамического массива
Delete(A[], INT I) Удаление элемента с индексом I
Remove(A[], V) Удаление всех элементов V
Swap(A[], INT I, INT J) Обмен элементов с индексами I и J
Int(REAL X) ==> INT Отбрасывание дробной части (Truncate) вещественные

 

ShiftL(INT N, INT K) ==> INT Логический сдвиг N на K бит влево (если K<0, выполняется сдвиг на -K бит вправо). Выдвигаемые биты теряются, вдвигаются нули. побитовые операции сдвига
ShiftR(INT N, INT K) ==> INT Логический сдвиг вправо.
RotateL(INT N, INT M, INT K) ==> INT Циклический сдвиг влево на K бит младших M бит. Выдвигаемые биты вдвигаются справа.
RotateR(INT N, INT M, INT K) ==> INT Циклический сдвиг вправо
Clone({structure} structure) ==> {structure} Создание копии структуры структуры
Dismiss({structure} structure) ==> {structure} Отвязывание структуры от места хранения (перемещение структуры)
 

Все встроенные функции могут идентифицироваться полностью капитализированными именами, например: A[].COUNT

 

VII. Зачем нужен еще один язык ?

 

VII. 1. Настоящая многоплатформенность

 

Да, существует возможность создавать приложения, которые действительно будут многоплатформенными. Для этого нужно выбрать язык (С++, С#, Java, Pascal) и использовать какой-нибудь фреймворк (библиотека классов). На этом пути вас ждет масса приключений и разочарований.

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

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

 

В чем же фундаментальное отличие AL-IV в плане многоплатформенности, если это всего лишь надстройка над одним из существующих языков (над С#, Delphi или Free Pascal) ? Особенность AL-IV в том, что он изначально не ориентирован ни на какую платформу. Вы пишите код только один раз. Для запуска программы на другой платформе достаточно поправить конфигурационные файлы и вызвать компилятор.

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

На данный момент (июнь 2019) дерево поддерживаемых платформ выглядит примерно так:

 

 

 

Что, если какая-либо из поддерживаемых ветвей отпадет (например, перестанет поддерживаться желаемая целевая платформа, или средство разработки неожиданно подорожает) ?

На этот вопрос ответ есть. Он заключается в простоте языка AL-IV. Компилятор для него (компилирующий в промежуточный целевой язык) изготавливается за сравнительно короткое время. Всегда можно вернуться к генерации кода на языках C++ / Java / Python / ... или сделать новый генератор. Задача построения набора переходных нативных классов, реализующих функциональность базовых библиотек, может оказаться сложнее (но и этот вопрос решаем), т.к. базовая библиотека изначально невелика и спроектирована так, чтобы по возможности упростить такую работу.

 

VII. 2. Надежность 

 

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

Вы можете использовать т.н. "управляемую" (managed) память в С++ (в С# почти все объекты хранятся в такой памяти), или использовать none-объекты в objective-c. Но часть кода все равно будет написана в старом небезопасном стиле, и изменить эту часть вам, скорее всего, не удастся (даже если это ваш собственный код). Даже в случае современных С# / Java вы не будете избавлены от необходимости обеспечивать в своем коде проверки на равенство указателя значению null или предусматривать обработку исключений.

 

В случае AL-IV ситуация отличается кардинально. На уровне компилятора (и самого языка) контролируется выход за пределы массивов (возвращая фиктивный NONE элемент или предотвращая операцию записи), обращение к неприсвоенным объектам, гарантируется инициализация всех переменных, предотвращается зацикливание и бесконечная рекурсия. Имеются встроенные средства тестирования кода, с контролем степени "покрытия" кода тестами.  Программирование с AL-IV становится спокойным и монотонным занятием, без экстрима и авантюризма.

 

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

 

VII. 3. Зачем нужен новый синтаксис?

 

В действительности, всегда существует возможность остаться в рамках существующего синтаксиса, но при этом изменить семантику.

 

При разработке нового синтаксиса основной целью было упростить работу с исходным кодом в произвольном текстовом редакторе (с поддержкой UTF-8 - это единственное требование к такому текстовому редактору).

 

Именно отсюда - требование на запись ключевых слов только В ВЕРХНЕМ РЕГИСТРЕ. В этом случае исходный код проще читать (и ненамного сложнее редактировать).

 

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

 

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

Разместить блочный оператор для разбиения слишком длинной последовательности операторов - вообще не является чрезвычайно сложным пожеланием.

 

Остается вопрос только насчет ограничения на три параметра на функцию/метод. Первоначально, при вводе такого ограничения, предполагалось, что если оно окажется слишком сложным для постоянного следования, то будет снято или ослаблено.

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

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

 

VII. 4. Где цикл "while" ?

 

Циклы вида while / repeat ... until небезопасны, так как могут приводить к зацикливанию программного кода без какого-то шанса завершить этот цикл.

Циклы типа FOR i IN [...] безопасны в этом плане, т.к. рано или поздно завершаются (возможно, очень много придется ждать, но не вечность).

 

При программировании в Алфор, следует для каждого цикла, для которого в другом языке был бы использован while, использовать оператор FOR, задавая в качестве диапазона, например, [0 TO N], где N - верхнее допустимое число итераций. И первым оператором внутри цикла ставить выход по анти-условию продолжения цикла:
CASE !continue_cond ? BREAK i ;

 

Такой подход гарантирует прекращение цикла хотя бы по условию исчерпания заданного диапазона.

 

VII. 5. Зачем нужен новый способ управления памятью (настоящее программирование реального времени) ?

 

С тех пор, как была изобретена куча динамических данных, на самом деле, в плане управления памятью было сделано только одно нововведение: автоматическое уничтожение объектов при достижении счетчиком использования значения 0.

Правда, при этом выяснилось, что могут образоваться взаимные ссылки объектов друг на друга (а в более сложных случаях - кольцевые замкнутые маршруты взаимного использования), в результате чего автоматически удалить объект не получается. Для устранения возникшей проблемы была придумана т.н. "чистка мусора". К сожалению, данная процедура переводит абсолютно все системы, использующие эту методику для освобождения циклически связанных объектов, в разряд медленных и непредсказуемых. А это значит, что их становится нельзя использовать в системах реального времени. Или даже нежелательно использовать в системах массового обслуживания.

 

Да, всегда есть возможность для критичных подсистем отказаться от управляемой памяти, и вести разработку в "старом стиле", напрямую управляя выделением памяти. Но при этом исчезают преимущества автоматического освобождения памяти. И это, на самом деле сложный путь, так как давно уже программы не пишутся с нуля. Программисты постоянно используют уже существующие библиотеки функций, классов. Если нет возможности использовать управляемую память, то нет и возможности использовать классы/функции, использующие управляемую память. Возможности программиста резко сокращаются, разработка замедляется.

 

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

 

Конечно, в случае нового способа приходится несколько менять стратегию создания объектов. Как минимум, приходится думать о времени жизни объекта в точке программного кода, где он создается. И, конечно же, появляется необходимость держать массивы указателей на дочерние объекты. Но это небольшая потеря по сравнению с приобретением возможности полностью отказаться от чистки мусора (и от самого мусора в динамической куче).

 

 

VII. 6. Зачем встроенная поддержка операторов SQL?

 

Действительно, как сочетается стремление сделать язык максимально простым и в то же время встраивание прямой поддержки SQL-выражений?

Ответ: такая поддержка позволяет проверять значительную часть семантики SQL-запросов на этапе компиляции программы.

А именно: соответствия имен полей действительным именам полей (объявленным в декларации таблиц TABLE), корректность их использования (учет того, что поле может быть null, или является автоинкрементным (и, соответственно, ему нельзя присвоить значение, например). И проконтролировать собственно синтаксис SQL-запросов. Эти проверки выполняются на этапе компиляции, позволяя уменьшить вероятность того, что программа, предназначенная для работы с БД, будет запущена с явными ошибками в своем коде.

 

Этот принцип - возможность статического анализа корректности операций на этапе компиляции - значительно упрощает разработку. К сожалению, в отношении SQL в настоящее время этот принцип обычно не действует, хотя работа с БД является одной из ключевых в современной практике программирования. В AL-IV сделана попытка исправить эту тенденцию.

 

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

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

Но ознакомиться со специализированным расширением языка на порядок проще, нежели:

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

 

VII. 7. Почему нет возможности управлять разрядностью переменных?

 

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

Во многих современных языках имеется возможность двойственного определения типов переменных: для значений, для которых разрядность не очень важна, используется базовый тип (например, Integer), а в случаях, когда его явно недостаточно, или наоборот, требуется экономия разрядности, применяются типы с уточнением разрядности (Int64, Smallint, Shortint).

 

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

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

 

По этой причине в AL-IV нет беззнаковых типов данных (кстати, их нет, например, и в Java). И тип INT|EGER заведомо не годится для хранения слишком больших значений (обычно больше 2 млрд.) Требуется хранить больше данных - используйте тип REAL (который обязан быть по разрядности как минимум вдвое больше целого, и сможет сохранить как минимум не N двоичных разрядов, а N*2 - 8*k (где k - число разрядов для хранения экспоненты). Если тип REAL по каким-то причинам неприемлем (например, быстродействием, или и его разрядности недостаточно) - всегда есть возможность использовать структуры с массивами целых или байтов.

 

Ни более детализированное (как в современных архаичных языках), ни обобщенное задание разрядности (как в Алфор), не делает ваш код более независимым от платформы. Нельзя вообще полагаться на разрядность целых чисел, используемую по умолчанию например. Она может варьироваться в очень больших пределах - от 8 бит (очень редко) или от 16 бит (в микроконтроллерах, IoT, и т.п. и до 64 / 128 бит. А иногда, целых чисел в платформе может вообще не быть (теоретически), и для реализации целых используется вещественный тип данных.

 

VII. 8. Где тип данных CHAR?

 

Этот тип данных устарел, и не отражает современных реалий. В случае использования кодировки utf-8, один символ может кодироваться последовательностью байтов, от одного до шести. Т.е., по сути, для хранения одного символа, нужна строка. Что и сделано в AL-IV.

 

VII. 9. Почему нет указателей на структуры?

 

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

 

Структуры - это промежуточный тип данных между простыми типами и классами. С одной стороны, они содержат отдельные поля (и передаются в функции фактически по ссылке), с другой - они присваиваются практически как простые переменные (путем простого копирования), и не могут модифицироваться в функциях, если являются параметрами (т.е. всегда передаются только для чтения, в нотации C++/Java/Pascal языков - как константные параметры - const).

 

В AL-IV отсутствует возможность работать с указателями на структуры или на их части.

 

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

 

VII. 10. Краткость кода

 

a. Краткие имена

При написании кода на AL-IV, вы можете использовать краткие имена переменных, и код становится кратким. Как на заре программирования, когда программисты часто использовали сокращенные имена переменных, ограничиваясь одной-двумя-тремя буквами, и вообще не заботились о том, чтобы в будущем самим понять, что они там по-быстрому накалякали. С той разницей, что компилятор потребует предоставить при декларации переменной/функции/типа данных и более длинный вариант имени (не менее 8 символов в сумме), и при желании понять, для чего предназначена переменная, всегда можно найти ее декларацию. В случае использования специализированного IDE, достаточно кликнуть по имени переменной, чтобы ее декларация была продублирована в окне подсказки по текущему символу.

 

b. Префиксный вызов функций

Вы можете существенно уменьшить количество вложенных скобок в выражениях, заменяя классический вызов функции с параметром в скобках на префиксную форму. Например:
s.Replace_all(",", ".").Trim.Remove_ending(".").TrimR.Find_last("_")

Сравните:

Find_lastTrimR(Remove_ending(Trim(Replace_all(s, ",", ".")), ".")), "_")

В первом случае (префиксный вариант) код и короче, и намного понятней (второй вариант синтаксически так же верен для AL-IV, впрочем).

 

c. Удаление избыточных проверок

Если вы пишете на AL-IV, вам не требуется постоянно проверять на null (в случае AL-IV - на NONE, но в любом случае - не требуется). Компилятор сам выполнит необходимые проверки, и в случае, если объект является NONE, то обращение к его полям/методам не приведет к исключению/падению приложения/синему экрану смерти. Что произойдет? чаще всего, ничего не произойдет. Будет возвращено значение NONE для методов/функций, возвращающих объект, и для объектов-полей. А для чисел/строк/перечислений будет возвращен 0.

Вам так же не требуется заботиться о делении на ноль, или вычислении операций с операндом NaN. Будет возвращено значение NaN. Падение приложения в этом случае не предусматривается. Если полученное значение вас не устраивает, вы можете найти причину неудачи, и исправить ее. Возможно, речь идет о том самом делении на ноль, или об обращении к математической функции с недопустимыми параметрами. Но это точно не причина падать для всего приложения.

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

 

d. Операторы LIKE

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

--------------- 'не хочется переписывать', REUSED
...
--------------- 'end'

и далее в коде обратиться к этому фрагменту:
 

LIKE .......... 'не хочется переписывать'

Это как бы макрос (что было бы очень плохо - см. на C/C++ с их макросами), но без возможности вложенного вызова других операторов LIKE, или выхода за пределы текущего класса.

 

e. Оформление блочных операторов

В языке AL-IV нет скобок begin/end или { ... }. Вам не придется тратить пару лишних строк кода на это оформление вложенных блоков (у вас появится время на то, чтобы придумать, как уложиться в допустимые три уровня вложенности операторов FOR/CASE). Код становится практически таким же кратким, как в Python (но в случае, если у вас собьются ведущие пробелы/табуляции, код не будет испорчен - вложенность блоков определяется завершающим блоки символом ';'.

При переносе на новую строку не используются специальные символы, засоряющие текст - достаточно соблюсти пару простых правил (завершить предыдущую строку запятой, открывающей скобкой '(', '[', или начать строку продолжения с последовательности символов, которые не могут начинать новый оператор. Например, начать с кавычки или знака операции '+', '-', '&&' и т.п.).

 

В качестве символа завершения блока можно было бы, конечно, выбрать оператор END. Но тогда его пришлось бы требовать писать всегда в отдельной строке кода. (И кроме прочего, мне не нравится его перевод на русский язык. В том смысле, что слово КОНЕЦ на пару букв длиннее. Выбор слова CASE для условного оператора вместо IF тоже определялся, в том числе, возможностью перевести на русский язык словом такой же длины - ЕСЛИ). А точку с запятой переводить не нужно - это просто знак препинания.

 

f. Декларация локальных переменных

В AL-IV локальная переменная декларируется в том месте, где она впервые нужна (например, получает первое значение). И (это важно, и это действительно отличается от большинства других языков) действует до конца функции. Фактически, это означает, что локальные переменные были объявлены как бы в самом начале функции, и проинициализированы значением по умолчанию (нулем, пустой строкой, объектом NONE, а в случае динамического массива изначально пусты). Если получится так, что код не вошел в ту ветку, в которой вы декларировали эту переменную, она все равно является декларированной и имеет значение по умолчанию.

Это может выглядеть странно по сравнению с большинством других языков программирования. Но в реальности, не имеет (почти) никаких недостатков, и позволяет дополнительно сократить код. Например:
CASE x ?
[0]: ... some code here
[1]: BOOL res|ult_has_1 = TRUE
     ... some code here
[2]: ... some code here
[3]: ... some code here ;
[10]: res = TRUE
     ... some code here
CASE res ?
     << "We have 1 in the number !!!"#NL ;

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

 

g. Автоматическое освобождение объектных переменных

Да, для большинства современных языков это давно не новость. Но есть отличия: программе на языке AL-IV не требуется сборщик мусора. Это, как минимум, позволяет компилировать код для языков/систем, в которых нет автоматического освобождения объектов при обнулении счетчика использования (например, Delphi 32 + Windows). И при этом сохранять свойство автоматического освобождения ресурсов при исчерпании ссылок на них.

Для краткости кода это означает отсутствие дополнительного кода, занимающегося высвобождением переменной по окончании ее использования. (Причем, если в том же C# рекомендуется вызывать Dispose для тех же битмапов или MemoryStream, то для AL-IV этот Dispose вызывается автоматически, при завершении существования соответствующих экземпляров этих классов).

 

 

 

Содержание

Введение

I. Синтаксис    операторов

I.    1.    Форматирование операторов

I. 2. Оператор присваивания

I. 3. Выражения

I.  4. Прочие простые  операторы

I.  5. Условный  оператор CASE

I. 6. Операторы цикла FOR

I. 7. Блок  операторов PUSH

I. 8. Блок  операторов DEBUG

I. 9. Оператор LIKE

I. 10. Оператор REVERT

II. Переменные, константы и  типы данных

II. 1. Простые типы  данных

II.  2. Переменные,  массивы

II.  3.  Функции

III. Классы и объекты

III. 1. Классы

III.  2. Работа с объектами классов

III.  3. Операторы уровня класса

III.  4. Модификаторы полей класса

III.  5. Завершение класса. История. Массив DATA[].

IV. Структуры (STRUCTURE)

IV. 1. Декларация структур

IV. 2. Работа со структурами

V.  Тестирование

V. 1.Основные положения 

V. 2. Синтаксис 

VI.  Дополнительно

VI. 1. Локализация  строковых ресурсов 

VI. 2. Локализация  ключевых слов языка 

VI. 3.  STORE - скрытые параметры 

VI. 4.  SQL запросы 

VI. 5.  Отменённые и устаревшие классы, структуры, перечисления, поля, функции 

VI. 6.  Ограничения на значения параметров

VI. 7.  Контроль зацикливания

VI. 8.  Оптимизация кода. INLINE-вставки

VI. 9.  Оптимизация кода. UNROLL (раскрутка) для циклов FOR

VI. 10.  Синтаксический сахар

VI. 11.  Краткая справка по "встроенным" функциям

VII. Зачем нужен еще один язык ?

VII. 1. Настоящая  многоплатформенность

VII. 2.  Надежность 

VII. 3.  Зачем нужен новый синтаксис?

VII. 4.  Где цикл "while" ?

VII. 5.  Зачем нужен новый способ управления памятью (настоящее  программирование реального времени) ?

VII. 6.  Зачем встроенная поддержка операторов SQL?

VII. 7.  Почему нет возможности управлять разрядностью переменных?

VII. 8.  Где тип данных CHAR?

VII. 9.  Почему нет указателей на структуры?

VII. 10.  Краткость кода

Содержание

 

 

 


В начало