AL-IV - Detailed description

| RUSSIAN(Русский) | ENGLISH |  |
Эта функция использует куки. Если браузер не принимает куки, используется светлая тема.

## Введение

Здесь представлено краткое описание языка AL-IV (АЛФОР) в виде небольших примеров и примечаний к ним. По этой ссылке имеется более подробное описание языка.

## 1. Класс есть модуль

`/* //l CLASS {Hello|_world}, UNTESTED : //abcd,j FUNCTION Main|_hello_world : //e,j << 'Hello, World!' >> . //fgh END //i,j */ //l`
Пояснения: Любой идентификатор (кроме имени переменной цикла) должен быть либо не менее 8 символов длиной, либо через символ '|' указывается остаток длинного имени. Имя класса `{Hello_world}`, может быть сокращено до `{Hello}` при ссылке на класс при его импорте (но не в операторе `IMPORT`). Имя класса всегда начинается с прописной буквы. Атрибут класса `UNTESTED` разрешает компиляцию класса без тестирования. Единственный статический метод класса `Main` (полное имя в примере `Main_hello_world`) позволяет создать программу, содержащего этот класс. Оператор `<<` выводит в консоль строку текста (или добавляет элемент в массив или строку в накопительную строку), оператор `>>` ожидает ввода строки текста от оператора консоли. Операторы `<<` могут состыковываться в цепочку. Оператор `>>` может пристыковываться к такой цепочке справа. Например: `<< 'Введите X:' >> STR X_s|tring REAL X|_input_value = X_s.Real` Точка завершает функцию. Оператор `END` завершает класс. Все ключевые слова языка записываются буквами верхнего регистра. Каждый класс размещается в отдельном текстовом файле. Код AL-IV всегда располагается между строками, содержащими символы конца и начала многострочных комментариев: `/*` и `*/`. Обратите внимание, что символы начала и конца многострочных комментариев противоположны тем, которые используются в C-подобных языках. Это позволяет размещать код на AL-IV, и, например, C#, в одном файле. Операторная скобка END, относящаяся к классу, должна отделяться от предыдущих строк как минимум одной пустой строкой (чтобы отличать ее от операторной скобки END внутри функций).

## 2. Два правила продолжения строки

`CASE any_changed ? // SQL-query to update a record in a database //e // corresponding to new values in input controls {DB} db|_TEMP_object = New_ole_db(connection_s(path), "", "") //f db << UPDATE Students, //c SET Exam3 = {Result_set}, //c WHERE ID = {Id_student} //c -------------------------- 'do update now' //e db.Exec ==> ; //b`

Пояснения: Строка кода не должна превышать 80 символов, не считая комментариев до конца строки (начинающихся символами '`//`'). Пробелы на конце строки игнорируются, символы табуляции считаются за один символ. В одной строке - один оператор (исключения: оператор ==> может пристыковываться к любому простому оператору, операторы << и >> могут пристыковываться к оператору <<) Оператор может быть продолжен на следующую строку в одном из двух случаев: Строка кода завершается открывающейся скобкой '`(`', '`[`' или запятой '`,`'. Следующая строка кода не пуста и не начинает новый оператор (т.е. не начинается с идентификатора или символов '{', '<', '==>') и не является одним из символом ';', '.' . Конкатенация строк не требует специального символа: строковые константы и выражения записываются рядом или через пробелы. Или конкатенируемая строковая константа записывается в начале следующей строки (не считая ведущих пробелов). В языке AL/IV имеются так же операторные комментарии вида `---------------------- 'текст'` - они используются для деления кода блока на секции приемлемого размера (не более 7 простых и не более 7 блочных оператора в секции). Простой оператор не завершается каким-либо специальным символом (как ';' во многих других языках). Символ ';' используется для завершения блочного оператора (CASE, FOR, PUSH, и др.), а символ '.' - для завершение функции (и других деклараций уровня класса).

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

`/* CLASS {Equation}, TESTED : TEST Of_Square_eq : REAL r|esults_array[] Square_eq(1, 0, -1, R= r[]) //acd ASSERT r[].Count==2 && r[0].Near(-1) && r[1].Near(1) ----------------- 'D == 0' Square_eq(1, 2, 1, R= r[]) //acd ASSERT r[].Count==1 && r[0].Near(-1) ----------------- 'A == 0' Square_eq(0, 1, 1, R= r[]) ASSERT r[].Count==0 . -------------------- 'Ax^2 + Bx + C = 0' METHOD Square_eq|uation( REAL A|_coefficient, //b REAL B|_coefficient, REAL C|_coefficient, REAL R|esults_list[]) //c : //abcd R[].Clear //dk CASE A.Abs <= 1e-10 ? ==> ; //efgj REAL d|escriminant = B * B / 4 - A * C //b CASE d.Sign ? //eh [0]: R[] << -B / (2 * A) //ik [1]: REAL q|sqrtD_div2A = d.Sqrt R[] << -B / 2 / A - q //k R[] << -B / 2 / A + q //k ; . //g END */`
Пояснения: Если число параметров функции более 3, то при обращении к функции параметры после третьего должны прописываться в форме присваивания Имя= значение; Имена параметров публичных функций, публичных полей класса начинаются с прописной буквы. Имена локальных переменных, скрытых полей класса начинаются строчной буквой. Функция не может возвращать массив в качестве результата (псевдо-переменная `RESULT` - всегда скаляр). При необходимости "вернуть" массив, он должен быть передан в качестве параметра. Массивы всегда употребляются с квадратными скобками. Например: `R[].Count` Тип выражения в условии оператора `CASE` может быть `BOOL`, `INT`, `STR` или перечислением (`ENUM`). Оператор `CASE` с булевским выражением имеет одну ветвь для положительного результата, либо дополнительную ветвь `ELSE` (нет в этом примере). Только весь блочный оператор (такой, как `CASE`) завершается символом '`;`'. Отступы используются для оформления исходного кода, но основную информацию (для компилятора) о вложенности блоков задаёт текст, в том числе символы завершения блоков ('`;`') и функции ('`.`'). Оператор `CASE` с целочисленным, перечислимым или строковым условием позволяет указать несколько ветвей, задавая для каждой массив констант в квадратных скобках, при равенстве которым выполняется соответствующая ветвь. После выполнения ветви управление передаётся следующему за `CASE` оператору, какие-либо специальные операторы вроде `BREAK` для выхода не требуются. Для выхода из функции (до завершения её тела) используется оператор `==>`. Он может быть самостоятельным или пристыковываться к любому простому оператору. Например: `CASE A == B ? RESULT = TRUE ==> ;` Функция завершается символом '`.`'. Допускается точку размещать вслед за последним символом '`;`', завершающим блочный оператор. Не допускается размещать таким образом два или более подряд символа '`;`', или размещать какой-либо текст (кроме комментариев (`//`) после символа '`;`', завершающего блок.

`/* STRUCTURE {results|_of_square_equation} : //d REAL X1|_result //e REAL X2|_result //e INT N|umber_of_results . //f -------------------- 'Ax^2 + Bx + C = 0' FUNCTION Square_eq|uation( REAL A|_coefficient, REAL B|_coefficient, REAL C|_coefficient) ==> {results} : ---------------- 'single CASE ? version' REAL d|escriminant = B * B - 4 * A * C REAL aa|_A_doubled = 2 * A CASE ? //a [A.Near(0) || d < 0] ? ==> //ab [d.Near(0)] ? RESULT.N = 1 //ab RESULT.X1 = -B / aa RESULT.X2 = RESULT.X1 ELSE REAL q|sqrt_of_D = d.Sqrt //c RESULT.X1 = (-B - q)/ aa RESULT.X2 = (-B + q)/ aa RESULT.N = 2 ; . */`

Пояснения: Условие может отсутствовать в самом операторе `CASE ?`. Каждая ветвь задаётся булевским выражением в квадратных скобках (которые завершаются знаком вопрос). Допускается использование `ELSE`. Выражения вычисляются последовательно, пока не встретится истинное выражение. В этом случае выполняется соответствующая ветвь кода, и блок завершается. Если истинных выражений нет, выполняется ветвь `ELSE`, при её наличии. Тип данных "структура" (STRUCTURE) декларируется на уровне класса, ее имя (в фигурных скобках) начинается со строчной буквы. Тем не менее, все структуры, декларированные в классе, считаются доступны всем классам, импортирующим этот класс (структуры не могут быть скрыты). В декларации структуры ее поля декларируются так же, как поля класса или локальные переменные. Имена полей структур должны начинаться с прописной буквы (поля структур не могут быть скрытыми). Декларация структуры завершается точкой (как декларация уровня класса).

## 4. Область видимости локальных переменных

`/* METHOD Enter_command|_typed_on_console : STR c|ommand_entered << #NL "Enter command: " >> c //d CASE "stop" IN c ? //c << #NL "Really stop? " >> c CASE c.Lower IN ["yes", "y", "ok", "+"] ? //b Want_stop = TRUE ==> ; ; STR w|ords_of_command[] Split(w[], c, " ") ------------------------ 'recognize command' Command = 'NOTHING' Direction = 'FORWARD' Speed = 1 Volume = 2 Text = "" CASE w[0] ? //a ["go"]: Command = 'GO' ["run", "rn"]: Command = 'GO' Speed = 3 ["jump", "jm"]: Command = 'GO' Speed = 2 ["rotate", "rt"]: Command = 'ROTATE' ["look", "lk"]: Command = 'LOOK' ["take", "tk"]: Command = 'TAKE' ["put", "pt"]: Command = 'PUT' ["say", "s"]: Command = 'SAY' INT text_from = 1 //e CASE w[1] ? ["loud"]: Volume = 3 text_from = 2 //e ["quiet"]: Volume = 1 text_from = 2//e ; ["whisper", "ws"]: Command = 'SAY' Volume = 1 ELSE << " Unknown command" ==> ; CASE text_from > 0 ? //e FOR i IN [text_from TO w[].Count-1] : //e Text = _ w[i] " " ; //f Text = Text.Trim ; ------------------------ 'direction ?' CASE Command IN ['GO', 'LOOK', 'TAKE', 'PUT'] ? //b CASE w[1] ? //a ["forward", "f", ""]: NONE ["left", "l"]: Direction = 'LEFT' ["right", "r"]: Direction = 'RIGHT' ["back", "b"]: Direction = 'BACK' ELSE << " Unknown direction: " w[1] Command = 'NOTHING' ; ; . */`

Пояснения: Условие в условном операторе `CASE` может быть строковым. Элементами для сравнения в этом случае являются строковые константы. Требуется уникальность всех констант. Допускается использование `ELSE`. Для простых типов данных кроме `REAL` (т.е. `BOOL`, `BYTE`, `INT`, `STR`) и для перечислений могут использоваться операции `IN` и `!IN`, в которой второй операнд - это массив констант в квадратных скобках или переменная-массив. Эти операцию проверяют наличие (и, соответственно, отсутствие) левого значения в правом массиве. Для строк так же могут использоваться операции `IN` и `!IN` - для проверки наличия (отсутствия) подстроки в строке. Специальные символы могут кодироваться в форме #XX, где XX - целочисленная константа или одно из символических имён (`#NL`, `#CR`, `#LF`, `#TAB`, `#SP`, `#BK`, `#DEL`, `#BELL`, `#ESC`). Локальная переменная, декларированная в любом месте функции, становится доступной для чтения и записи до конца тела функции. При этом гарантируется инициализация всех локальных переменных, декларированных в теле функции, значением по умолчанию (для чисел - 0, для строк - пустая строка, для перечислений - первый элемент перечисления, для объектов - безопасное значение NONE соответствующего класса). Одиночный символ '_' справа от знака присваивания считается ссылкой на выражение слева от знака присваивания.

## 5. Всего 5 простых типов данных ...

`/* CONST BOOL: Flag|_boolean = FALSE . CONST INT : //e Count|er_integer = 1_000 //d IValue|_arbitrary = -2_222_222 . //bd CONST REAL: Value|_number = 1.3E+10 . CONST STR : Chain|_of_characters = "ABC" . //fc CONST BYTE: Byte|_value = 255 . //g */`

Пояснения:: Константы декларируются в теле класса (не функции). В одном операторе `CONST` может декларироваться произвольное число однотипных констант - по одной константе на строку кода. Блок определения констант завершается символом точки '`.`'. При записи числовых констант символы подчёркивания используются для разделения групп цифр. Например:: `0xAB12_cd20`, `0b1101_1100_0101_0011`, `0o377_105_210_114`, `-0.376209e-3` Разрядность простых типов данных фиксирована и одинакова для целевой платформы. Для целых обычно используется 32 разряда, для вещественных - максимум возможного (64 или 80 разрядные числа). Хотя в общем, разрядность всех типов зависит только от реализации компилятора. Типа данных "одиночный символ" нет. Рекомендуется не использовать тип BYTE, если есть такая возможность. Использование байтов в некоторых случаях может приводить к ошибкам в вычислениях. В случае, если в классе используется тип `BYTE`, в его заголовке должен присутствовать атрибут `BYTES`.

## ... и перечисления

`/* CLASS {Car|_on_road}, UNTESTED : ENUM {traffic|_colors} : 'R|ED_LIGHT', //ab 'Y|ELLOW_LIGHT', //b 'G|REEN_LIGHT' . //b */`

`// может ли автомобиль продолжать/начинать движение, // в зависимости от того, какой свет горит на светофоре`

``/*` METHOD Can_move( {traffic} C|olor_lighting, BOOL A|lready_move_before) ==> BOOL : CASE C ? //c ['R']: RESULT = FALSE //bd ['G']: RESULT = TRUE //bd ['Y']: RESULT = A ; . //bd END */`

Пояснения: Имя перечисления начинается со строчной буквы. Имя каждого элемента перечисления заключается в апострофы. Если условие в операторе `CASE` имеет тип перечисления, то нельзя использовать `ELSE`, и должны быть перечислены все возможные значения этого типа при определении возможных ветвей исполнения. Все имена элементов объявленных перечислений в пределах класса уникальны. Если импортируются одноимённые элементы из нескольких классов, то имя элемента указывается в виде `{имя_перечисления}.'ИМЯ_ЭЛЕМЕНТА'` Если импортированные перечисления являются одноимёнными, необходима квалификация имени класса: `{Имя_класса}.{имя_перечисления}.'ИМЯ_ЭЛЕМЕНТА'`

## 6. Оператор цикла

`/* // print Fizz instead of numbers multiply to 3, // print Buzz - if a number is multiple to 5, // FizzBuzz - if it is multiple to 3 and 5, // All other numbers print as is. FOR i IN [1 TO 100] <----------------------------- //abcdе CASE ? [i % 3 == 0 && i % 5 == 0] ? << "FizzBuzz" #NL [i % 3 == 0]: << "Fizz" #NL [i % 5 == 0]: << "Buzz" #NL ELSE << i.Str #NL ; -------------------------------------------------> //е */`

Пояснения: Переменная цикла не обязана иметь длинное имя. В заголовке цикла после `IN` указывается либо диапазон целочисленных значений (в форме `[n TO m]` или `[n DOWNTO m]`), либо массив. Для переменной цикла не требуется и не используется отдельная декларация. Она не может использоваться за пределами цикла, и повторно может использоваться только в другом цикле, с массивом элементов того же типа. В качестве операторных скобок в любом блоке или в самой функции могут использоваться паты `: ;`, `{ }`, `<-- -->`, `BEGIN END` или `begin end`. В случае стрелочных скобок `<-- -->` количество минусов может быть более двух.

`/* FUNCTION Invert_array( REAL A|rray_of_numbers[*]) //c { FOR R IN A[0 ТО */2] <-------------------------- //ab REAL t|emp_number = R A[R.Index] = A[* - R.Index] //bde A[* - R.Index] = t ------------------------> } //bde */`

Пояснения: Переменная индекса всегда получает тот же тип, что и элементы массива. Операнд '`*`' выражения в квадратных скобках является псевдо-функцией, возвращающей индекс последнего элемента массива (для строки - индекс последнего символа строки). Для пустого массива возвращает -1. Символ `'*'` в квадратных скобках параметра-массива сообщает, что параметр принимает фиксированные массивы (и срезки динамических). Если в функции не требуется изменять размеры параметра-массива, то рекомендуется объявлять его именно как фиксированный. Псевдо-функция `Index` применима только к переменным цикла и возвращает индекс перечисляемого элемента. Любой цикл `FOR` когда-нибудь завершается. Это не устраняет задумчивость ("подвисание") в случае большого числа длинных вложенных циклов, но устраняет однозначно "вечные" циклы. Если действительно необходим бесконечный цикл, используется специальная форма цикла `FOR, INFINITE : тело ;` и класс должен иметь атрибут `INFINITE`.

`/* FUNCTION Undouble_quotes( STR S|tring_containing_double_quotes) ==> STR BEGIN BOOL just|_unquoted = FALSE RESULT = S //c FOR i IN [S.Len-1 DOWNTO 1] <------------------------ //ab CASE !just && S.[i-1 TO i] == ''""'' ? //d RESULT = RESULT.[0 TO i-1] RESULT.[i+1 TO *] //ce just = TRUE ELSE just = FALSE ; ----------------------------------------------------> END */`

Пояснения: Строка массивом не является, поэтому для итерации по символам строк необходим просмотр диапазона индексов (условного массива целых чисел, который на самом деле не занимает места в памяти). Если во время выполнения цикла в массиве будут выполнены удаления элементов, то просмотр элементов (и символов в строке) следует выполнять в обратном порядке, используя диапазон значений от верхнего индекса к нижнему (ключевое слово `DOWNTO`). Все параметры простых типов передаются в функцию по значению и не могут изменяться в теле функции. Переменная `RESULT` может изменяться произвольное число раз, возвращено будет ее финальное значение. Строка может заключаться либо в символы двойной кавычки, либо в двойные апострофы. В первом случае строка может содержать апострофы, во втором - символы двойной кавычки. Для конкатенации строк не используется никакой специальный знак: строковые выражения записываются подряд (например, через пробелы);

`/* FUNCTION Simple_numbers|_from_1_to_Last( INT A|rray_results[], INT Last|_number) : A[].Clear A[] << 1 << 2 << 3 << 5 << 7 << 11 << 13 << 17 FOR i IN [A.Count-1 DOWNTO 0] <------------ //a CASE A[i] > Last ? A[].Delete(i) ; ------------------------------------------> FOR i IN [19 TO Last] <-------------------- //a CASE i % 2 == 0 { CONTINUE i } //cd FOR n IN A[] <----------------------------- //b CASE i % n == 0 ? BREAK n, CONTINUE i ; //c ------------------------------------------> A[] << i -----------------------------> . */`

Пояснения: Если в цикле перечисляется конструируемый массив, то может быть задана форма с нарастанием индексов `[x TO y]` либо с убыванием индексов `[y DOWNTO x]`.. Если массив указан в качестве перечисляемого, без указания особого диапазона, то всегда просматриваются элементы в порядке нарастания индексов начиная с 0-го и заканчивая последним (*, или Count-1). В операторах `BREAK` и `CONTINUE` указывается метка цикла, который следует завершить или продолжить. Это позволяет выполнить эту операцию из тела вложенного цикла (но для указания скачка на начало или конец внешнего цикла сначала требуется выполнить BREAK для всех вложенных циклов). Количество вложенных блоков не должно превышать трёх (точнее трех с половиной - допускается четвертый уровень с единственным простым оператором в качестве вложенного), поэтому во многих случаях прерывание или продолжение цикла по условию, позволяющие уменьшить число вложенных уровней - более предпочтительно, чем условное выполнение остатка блока кода.

`/* FUNCTION Binary_search|_in_array( INT A|rray_data[], INT W|hat_searching, BOOL Optimize|_median_DUMMY) //c : INT L|eft_bound = 0 INT R|ight_bound = A[].Count-1 FOR i|dummy IN A[] : //ab CASE L >= R-1 ? BREAK i ; INT M|iddle_index = L + (R - L) / 2 CASE (A[M] - W).Sign ? [0]: RESULT = M ==> [1]: R = M ELSE L = M ; ; CASE ? [W == A[L]] ? RESULT = L [W == A[R]] ? RESULT = R ELSE RESULT = -1 ; . */`

Пояснения:: Т.к. циклов вида WHILE не существует, они должны быть заменены циклами `FOR`. Наилучший способ - задать границы, достаточные для выполнения всех итераций, но запас не должен быть слишком большим (чтобы не получить ситуацию, похожую на "зависание" в случае ошибки в коде). В примере использован цикл по массиву, с которым идёт работа: и хотя фактически будет просмотрено не N элементов, а LOG2(N) элементов, это достаточно хорошее приближение сверху. Если переменная цикла не используется внутри цикла для вычисления выражений, она должна иметь в своём имени (хотя бы длинном) подстроку '`dummy`' (в любом регистре). То же правило действует для параметров функций, которые не используются в теле своей функции: они должны иметь подстроку '`dummy`' в полном имени, даже если они используются в наследуемых классах или классах-предках.

`/* FUNCTION Binary_to_int|eger( STR S|tring_binary) ==> INT : FOR i IN [0 TO S.Len-1] : CASE S[i] ? ["0"]: RESULT = _.ShiftL(1) //df ["1"]: .. = _.ShiftL(1) | 1 //abcdfg ["_"]: NONE //ce ELSE ==> ; ; . */`

Пояснения: Если в классе используются операции поразрядной логики '`|`', '`&`', '`^`', '`~`', то класс должен иметь атрибут `BITWISE`. В одном операторе не допускается одновременное использование операций арифметики и поразрядной логики. Операции сдвигов не имеют собственных обозначений. Используются функции `ShiftL|eft`, `ShiftR|ight`, `RotateL|eft`, `RotateR|ight`. Все переменные (и `RESULT`) автоматически инициализируются нулями соответствующего типа. Если в качестве ветви необходимо указать отсутствие каких-либо действий, используется одиночная константа `NONE`. Операнд _ (одиночное подчеркивание) означает повторение переменной (или поля) слева от присваивания. Символ '..' (две точки) слева от знака присваивания означают повтор той же целевой переменной (или поля), что и в предыдущем операторе присваивания (в той же функции).

// Вывести на экран с использованием циклов:

// ########## // ## ## // # # # # // # # # # // # ## # // # ## # // # # # # // # # # # // ## ## // ##########
`FUNCTION Crossed|_rectangle( INT N|umber_lines) : INT x|_position_of_1st_internal_dot = 2 FOR i IN [1 TO N] : CASE i == 1 || i == N ? FOR j IN [1 TO N] : << "#" ; << #NL CONTINUE i ; << "#" INT k|ount_spaces_outer = x - 2 INT s|paces_inner = N - 2 * (k + 2) FOR j IN [1 TO k] : << " " ; << "#" FOR j IN [ 1 TO s] : << " " ; CASE s > 0 || N % 2 == 0 ? << "#" ; FOR j IN [1 TO k] : << " " ; << "#"#NL CASE ? [i * 2 == N] ? NONE [i > N/2] ? x -= 1 ELSE x += 1 ; ; .`
`FUNCTION Crossed2|_with_string_variable( INT N|umber_lines) : INT x|_position_of_1st_internal_dot = 1 FOR i IN [1 TO N] : CASE i == 1 || i == N ? STR s|tring_line = "" FOR j|dummy IN [1 TO N] : s << "#" ; << s #NL CONTINUE i ; s[] << "#" FOR j IN [1 TO s.Len-2] : s << " " ; s[] << "#" s.[x] = "#" s[* - x] = "#" << s #NL CASE ? [i * 2 == N] ? NONE [i > N/2] ? x -= 1 ELSE x += 1 ; ; .`

// Для входного массива целых чисел получить представление в виде строки: // [0,1,2,3,4,7,8,10] ==> "0-4,7-8,10" // [4,7,10] ==> "4,7,10" // [2,3,8,9] ==> "2-3,8-9"

`FUN get_ranges(INT A|rray_sorted[]) ==> STR : RESULT = A[0].S FOR i IN [1 TO A[].Count-1] <----------------- CASE A[i] == A[i-1] + 1 ? STR suffix|_to_append = "-" A[i].S CONTINUE i ; RESULT << suffix "," A[i].S suffix = "" -----------------------------> RESULT << suffix .`

## 7. Контроль рекурсии

`/* FUNCTION Factorial(INT N|umber_argument) ==> INT, RECURSIVE : //ab CASE N <= 1 ? RESULT = 1 ELSE .. = N * (N - 1).Factorial ; . //cd */`

Пояснения: Рекурсивная функция, вызывающая себя, должна иметь атрибут `RECURSIVE`. Если функция вызывает себя опосредованно, через функцию, которая уже помечена этим атрибутом, атрибут `RECURSIVE` для неё необязателен. Для рекурсивных функций (с атрибутом `RECURSIVE`) в программу добавляется контроль глубины рекурсии. При превышении некоторого заданного уровня вложенности рекурсивных функций (например, 128) выполнение прерывается, происходит возврат к первому уровню вложенности, и функция на этом уровне завершается, не выполняет более никаких операций. При этом, если требуется возврат значения, всегда возвращается `NONE` или нуль соответствующего типа. Статическая однопараметрическая функция может быть вызвана в стиле объектного метода без параметров. Например, `x.Sin` вместо `Sin(x)`. Допускается использовать оператор `CASE ... ELSE ... ;` в одной строке кода (если он достаточно краток, чтобы разместить его в одной строке.

## 8. Сохранение переменной (PUSH)

`/* CASE A == 0 ? PUSH A = B : //ac PUSH B = C : //ac Results[] << Linear_eq ==> ; //d ; ; */ ... /* ---------------------- 'Ax = B' METHOD Linear_eq|uation ==> REAL : RESULT = B / A . //e */`

Пояснения: Значение переменной сохраняется, и ей присваивается указанное значение. Присваивание нового значения необязательно. Переменная может быть элементом массива, но не может быть массивом. При выходе из блока значение сохранённой переменной гарантированно восстанавливается (в том числе, при выходе из цикла оператором BREAK, или при выходе из функции оператором `==>`). В функции разрешается не более трёх уровней вложенных блочных операторов `CASE` / `FOR` (если использовано более трёх уровней, класс обязан иметь атрибут BAD), но операторы `PUSH` при подсчёте вложенных уровней не учитываются. Исключения отсутствуют. Деление на ноль для вещественного числа даёт значение `INF|INITY`, для целого - максимально возможное целое число соответствующего знака. В случае, если по каким-то серьёзным причинам дальнейшая работа невозможна, работу всего потока (в случае главного потока - всей программы) может быть прервана оператором `STOP "текст"` Класс, в теле которого используется оператор `STOP`, должен иметь атрибут `STOPPING`.

## 9. Массив с индексами из перечисления

`// вывести список горящих лампочек светофора`

``/* BOOL Is_lighting[{traffic}] //d ... FOR li|ghting IN Is_lighting[] : STR wh|at_lighting << li.Name ", " ; //ac ; CASE wh != "" ? << wh.[0 TO *-2] " are lighting" //b ELSE << "There are no lights which are lighting" ; */``

Пояснения::: Переменная может декларироваться в момент первого присваивания ей значения, даже внутри блока. После чего она доступна до конца тела функции. Это может приводить к ошибкам из-за отсутствия инициализации накопительных переменных, при работе с несколькими вложенными циклами, поэтому при таком использовании переменной во вложенном цикле следует предупреждение компилятора. Для строки операция `s.[i TO j]` вырезает подстроку от индекса `i` до индекса `j` включительно (индексация массивов и строк начинается с нуля). Например, `"ABCD".[1 TO 2] == "BC"` Встроенная псевдо-функция `Name` для элемента перечисления возвращает краткое имя элемента (в апострофах). Булевский массив с индексами из перечисления может использоваться аналогично множеству в Pascal - как набор именованных флажков (элементов множества).

## 10. Атрибуты полей класса (INIT, READ). Сильные и слабые ссылки.

`// пример из астрономии`

``/* CLASS {Star_system}, SAFE : IMPORT: {Planet_or_star} ; {Planet} All_planets|_and_stars[] //e STR Name|_of_star_system END CLASS {Planet|_or_star}, SAFE : IMPORT: {Star_system} ; {Star_system} Star_|system_, INIT //abcef {Planet} Parent_|planet_or_star_, INIT //abcdf STR Name|_of_planet_or_star {Planet} Satellit_|s_list_[] BOOL Is_star -------------------------------- 'Rings' BOOL Is_ring|_of_particles, READ INT Color|_of_ring, READ //g METHOD Set_is_ring(INT C|olor_of_ring) : Is_ring = TRUE Color = C . //h ----------------- 'Main characteristics' REAL Dist|ance_to_parent_AU_perigely REAL Year|_time_years REAL Ecliptic_incl|ination_degrees REAL Ecliptic_exc|entricity REAL Apogee_days|_from_2000BC ------------------ 'Own characteristics' REAL Size|_relative_to_Earth REAL Mass|_relative_to_Earth REAL Inclination|_degrees REAL Day|_time_hours REAL Atmosphere|_dencity REAL Atmosphere|_height REAL Magnetic_f|ield_strength END */``

Пояснения: Объектное поле класса, имя которого (и краткое, и длинное) заканчивается символом подчёркивания '_', является слабой ссылкой на объект. В примере: `{Planet} Parent_|planet_or_star_` Слабая ссылка не является удерживающей. Когда уничтожается объект, на который ссылается слабая ссылка, она автоматически начинает указывать на NONE-объект соответствующего класса. `NONE`-объект - это особый константный объект, имеющий все поля и методы - те же, что и реальный объект. Но значение всех его полей - нули и объекты `NONE` (для строк - пустые строки, для `BOOL` - значения `FALSE`, для перечислений - первый элемент перечисления, для динамических массивов - пустой массив). Запись в `NONE`-объект не изменяет его значение (игнорируется). Класс не может содержать объектные поля, являющиеся сильными ссылками на родственные объекты (т.е. на своих потомков или своих предков). Сети и графы из родственных объектов могут быть образованы только с помощью слабых ссылок. Если класс {A} имеет сильные ссылки на класс {B}, то класс {B} не может иметь сильных ссылок на класс {A}. Если класс {A[1]} ссылается строго на {A[2]}, {A[2]} на {A[3]}, ..., {A[n-1]} на {A[n]}, то класс {A[n]} не может иметь сильных ссылок на любой из классов {A[1]}, ... {A[n-1]}. Данное правило обеспечивает отсутствие возможности создания кольца взаимно удерживающих друг друга объектов. В результате, в программе не требуется сборщик мусора, и объекты уничтожаются в точности в тот момент, когда исчерпывается счётчик сильных ссылок на объект (но: см. ниже о "владельцах" объектов). Атрибут `INIT|IALIZE` для поля класса требует инициализации поля при создании экземпляра класса. Например: Атрибут `READ` защищает поле от записи из других классов (кроме классов-наследников и собственного класса).

## 11. Время жизни объекта

`/*
`CLASS {Star_system}, SAFE : IMPORT {Planet_or_star} ; {Planet} All_planets|_and_stars[] STR Name|_of_star_system {Planet} Current_parent|_for_new_planets_and_stars METHOD Last|_planet ==> {Planet} := All_planets[*] . //g METHOD New_planet(STR Name|_planet) ==> {Planet}, NEW : //a RESULT = {Planet}(Star_ = THIS, //acd Parent_ = Current_parent, Name = Name), >> All_planets[] Current_parent.Satellit[] << RESULT CASE All_planets[].Count == 0 ? Current_parent = RESULT ; . METHOD Make|_planet(STR Name|_planet) : CASE "," IN Name ? STR names|_splitted[].Split(Name, ",") FOR n IN names[] : NONE = New_planet(n) ; ELSE NONE = New_planet(Name) ; . //... `FUNCTION Create_Sol_system ==> {Star_system}, NEW : //a RESULT = {Star_system} //a {Star_system} R|esulting_shortcut = RESULT {Planet} sun|_star = R.New_planet("Sun") ...Is_star = TRUE --------------------------------- 'inner planets' R.Make("Mercury,Venus") R.Make("Earth"]) PUSH R.Current_parent = R.Last : R.Make("Moon") ; PUSH R.Current_parent = R.New_planet("Mars") : R.Make("Fobos,Deimos") ; --------------------------------- 'asteroid belt (>200km size)' PUSH R.Current_parent = sun: R.Make("Ceres,Vesta,Pallas,Hygiea,Interamnia,Europa,Davida,Sylvia") R.Make("Eunomia,Euphrosyne,Cybele,Hektor,Juno,Camilla,Patientia,Banberga") R.Make("Psyche,Thisbe,Doris,Fortuna,Themis,Amphitrite,Egeria,Elektra,Iris") R.Make("Diotima,Aurora") ; --------------------------------- 'Jupiter + satellites > 100km' PUSH R.Current_planet = R.New_planet("Jupiter") : R.Make("Io,Europa,Ganimede,Callisto,Himalia") ; --------------------------------- 'Saturn + satellites > 100km' PUSH R.Current_planet = R.New_planet("Saturn") : R.Make("Prometheus,Pandora,Epimetheus,Janus") R.Make("Mimas,Enceladus,Tethys") R.Make("Dione,Helene,Rhea,Titan,Hyperion,Iapetus,Phoebe") ; --------------------------------- 'Uranus + satellites > 100km' PUSH R.Current_planet = R.New_planet("Uran") : R.Make("Juliet,Portia,Miranda,Ariel,Umbriel,Titania,Oberon,Sycorax") ; --------------------------------- 'Neptune + satellites > 200km' PUSH R.Current_planet = R.New_planet("Neptune") : R.Make("Despina,Galatea,Larissa,Proteus,Triton,Nereid") ; --------------------------------- 'Pluto + all known satellites' PUSH R.Current_planet = R.New_planet("Pluto") : R.Make("Charon,Styx,Nix,Kerberos,Hydra") ; --------------------------------- 'trans-pluto > 500km only' R.Make("Orcus,2002 XV93,2003 VS2,2007 JH43,Ixion,2004 GV9,2005 RN43") R.Make("1995 SM55,2002 MS4,Haumea,2014 US224,2002 AW197,2014 EZ51") R.Make("2004 XR190,2013 FY27,Sedna") //... .` END`
*/`

Пояснения: В программе AL/IV время жизни объекта определяется в момент его создания (а, например, не алгоритмом работы сборщика мусора, и не произволом программиста, который мог бы пожелать "разрушить" объект тогда, когда пожелает). Для объекта должно быть определено, будет ли он присвоен внешнему по отношению к функции полю или переменной, будет ли добавлен в массив сильных ссылок, или жизнь объекта будет полностью связана с указанным для него объектом-владельцем. Если псевдо-переменной `RESULT` в теле функции хотя бы раз присвоен вновь создаваемый объект, то такая фабрикующая функция должна иметь атрибут `NEW`. Фабрикующую объекты функцию можно вызвать только отдельным оператором присваивания (так же, как сам оператор создания объекта). Полученный из фабрикующей функции объект так же должен быть либо присвоен внешней переменной или полю (хотя бы `RESULT`), либо добавлен в массив сильных ссылок (операция `, >>`), либо переменная, которой присвоен этот объект должен иметь в имени подстроку "`temp`". Если при создании объекта вызовом конструктора через запятую указан владелец атрибутом `OWNED BY`, то этот объект будет существовать, пока существует владелец, независимо от числа сильных или слабых ссылок на него. Если объект в момент создания конструктором или фабрикующей функцией добавляется в массив, то этот может быть только массив жестких ссылок. При добавлении объекта в массив, его счетчик ссылок увеличивается, при удалении из массива - уменьшается (так же, как при присваивании ссылки из переменной или поля объекта). Если тело функции состоит из единственного присваивания значения псевдо-переменной `RESULT`, то ключевое слово `RESULT` может быть опущено.

## 12. Наследование. Полиморфизм.

`// к примеру из п.2:`

`/* CLASS {Input_person|_characteristics}, UNTESTED : IMPORT {Form}, {Dialog}, {Control} ; IMPORT {Label}, {Edit}, {Button} ; //cd BASE CLASS {Form} //ef {Edit} Surname|_person {Edit} Name|_first {Edit} Patro|nymic {Button} OK|_input_finished {Button} Cancel|_input CONSTRUCT: //gh Caption = "Input person information" NONE = New_label(THIS, "Surname:") //i Surname = New_edit(THIS, "Surname") NONE = New_label(THIS, "Name:") Name = New_edit(THIS, "Name") NONE = New_label(THIS, "Patronymic:") Patro = New_edit(THIS, "Patro") -------------------- 'Buttons panel' new_column("Buttons", 'CLIENT', DEFAULT_SIZES) OK = New_button(THIS, "OK", "OK"), >> grayable[] Set_default(OK) Cancel = New_button(THIS, "CANCEL", "Cancel") Set_cancel(Cancel) . OVERRIDE disabled ==> BOOL : //ab CASE sender_alias == "OK" ? RESULT = Surname.Text == "" || Name.Text == "" ; . END */`

Пояснения: В языке AL/IV нет переменных, которые играли бы роль указателя на произвольную функцию. Единственный механизм, позволяющий вызвать функцию, о которой может быть практически ничего не известно (кроме параметров) на этапе разработки класса - это виртуальные методы класса. В классе все методы являются виртуальными. При переопределении метода родительского класса: вместо ключевого слова `METHOD` записывается `OVERRIDE`; указывается короткое или длинное имя метода; должны быть объявлены параметры в том же порядке, но имена могут быть другими - достаточно совпадения первой буквы имени; типы объектных параметров и результата должны быть совместимы, для прочих типов данных - совпадать. Если в классе используются другие классы (для декларации переменных или полей, для обращения к методам, для доступа к константам и т.д.), то все использованные классы должны быть импортированы оператором `IMPORT`. Оператор `IMPORT` записывается непосредственно вслед за заголовком класса, и содержит перечень используемых классов, завершаемый точкой или точкой с запятой. При наследовании класса от некоторого класса-предка вслед за оператором `IMPORT` записывается конструкция `BASE CLASS {``Класс_предка}` В конструкции `BASE CLASS` в круглых скобках может быть набор присваиваний начальных значений полям, определённым в классах-предках. Эти присваивания выполняются до того, как отработают присваивания значений в конечном операторе создания объекта `{Тип}(присваивания)` Для инициализации объекта после его создания вызывается конструктор класса. Конструктор определяется специальной функцией с заголовком `CONSTRUCT`. Конструктор вызывается после создания объекта: к этому времени выполнена инициализация полей в операторе `BASE CLASS {Имя_предка}(список присваиваний)`, и выполнена предварительная инициализация полей, указанных в операторе создания объекта `{Имя_класса}(список присваиваний)` , и отработаны все конструкторы классов-предков (начиная с самого "древнего"). Если значение функции не требуется, оно должно быть "присвоено" константе `NONE`.

## 13. Деструкторы считаются опасными

`/* CLASS {Bitmap|_image}, NATIVE, DESTRUCTORS : //b NATIVE: "using Windows;" . INT native_handle METHOD Handle ==> INT : CASE native_handle == 0 ? create_native_handle ; RESULT = native_handle . DESTRUCT: //a CASE native_handle != 0 ? release_native_handle ; . //a ... */`

Пояснения: В большинстве случаев, пользовательские деструкторы не требуются. Встроенный деструктор обеспечивает освобождение подчинённых объектов (а так же строк, динамических массивов) автоматически. Без деструкторов нельзя обойтись только в случаях, когда класс удерживает дескриптор некоторого "внешнего" (по отношению к языку) объекта. Так как временем срабатывания деструктора управлять может оказаться затруднительно, их использование считается возможной причиной нестабильной (или не предсказуемой) работы приложения. Класс, содержащий деструкторы, должен иметь атрибут `DESTRUCTORS`.

## 14. Кодовые (NATIVE) функции

`/* CLASS {Command_line}, NATIVE : //a NATIVE: "using System.Diagnostics;" . //f FUNCTION Get_command_line ==> STR, NATIVE : @ "" ''" if (test_command_line != "")"'' //bh " RESULT = test_command_line;" //g " else RESULT = return Environment.CommandLine;" . //g NATIVE: @ "private static string test_command_line = " #QU #QU ";" . //e FUNCTION Change_command_line(STR Val|ue_to_set), NATIVE : STR pr|efixed_by_application_name = #QU Application_name #QU //c " " X_Val NATIVE " test_command_line = X_pr;" . //c END */`

Пояснения: Если в классе используются кодовые функции (содержащие прямые вставки кода целевого языка), то такой класс должен иметь атрибут `NATIVE`, явно прописанный в заголовке класса. Все такие классы зависят от целевой платформы. Лучший способ обеспечить большую степень независимости, при наличии таких классов - это выделить их в отдельные поддиректории, и прописывать компилятору пути только к тем директориям, которые нужны для его целевой платформы. Например, MyProject\_Delphi, MyProject\_C#, и т.п. Функция, имеющая атрибут `NATIVE`, в качестве тела имеет строковую константу. Эта константа будет вставлена без изменений в результирующий код, и корректность прописанного в ней кода целиком зависит от программиста. Для написания нативного кода требуется знание целевого языка программирования, и понимание правил работы компилятора из AL/IV в этот язык программирования. Тело кодовой функции может начинаться с обычных операторов языка AL-IV. В этом случае оно должно заканчиваться директивой `NATIVE "код"` с кодом на целевом языке программирования в виде строковой константы. Если компилятор превращает имя параметра (или локальной переменной) `A|ttribute` в оттранслированном коде в идентификатор `X_A` (берёт краткое имя параметра, и добавляет к нему префикс `X_`), то в натуральном коде для обращения к параметру `A` следует записывать его имя как `X_A`. Для собственных полей класса используется префикс `F_`. Но это может зависеть от конкретной реализации компилятора. Кроме функций с атрибутом `NATIVE`, в классе могут встречаться отдельные операторы `NATIVE: "строка" .` Они используются для вставки дополнительного кода между функциями, на уровне текстовой единицы целевого языка (на уровне модуля, файла исходного кода и т.п.). Операторы `NATIVE`, расположенные перед самой первой функцией, могут использоваться для специальных целей. Например, для добавления директив "`using`" или "`uses`". Но это правило так же зависит от конкретного компилятора. Часто для упрощения написания нативного кода компилятор автоматически оформляет пролог и эпилог, в которых инициализирует локальную переменную `RESULT`, а (в эпилоге) возвращает ее значение (для нативных функций, возвращающих результат). В этих случаях лучше не использовать прямые операторы `return выражение` языка, а выполнять присваивание значения переменной `RESULT` (компилятор конечного языка не делает лишних предупреждений о недостижимом коде, о неиспользованных переменных). Строковые литералы в двойных апострофах `'' ... ''` удобно использовать как часть многострочной константы в записи нативного кода, именно потому, что при этом легко записываются двойные кавычки в строке (не требуется никакое зеркалирование - строка кода целевого языка записывается как есть, без изменений). Но следует помнить, что такой литерал должен быть единственным в строке (не считая комментариев `//` в конце строки). Нативные классы не требуют обязательного тестирования (но необязательное тестирование не запрещено). За корректность их работы полностью несёт ответственность их разработчик. Он, как минимум, должен обеспечить достаточную степень надёжности и безопасности при использовании таких классов: не должно происходить никаких исключений - должно происходить молчаливое игнорирование ошибок без "последствий" (для файлов и данных пользователя); Информация о важных ошибках должна сохраняться в очереди последних ошибок (доступной программе через интерфейс системных функций); при работе на этапе тестирования не должны изменяться какие-либо файлы, кроме как по путям, содержащим подстроку "`TEST$`" (в любом регистре); в тестовом режиме не должны изменяться никакие базы данных и таблиц, кроме содержащих подстроку "`TEST`" в своём имени (либо вся БД, либо таблица).

## Содержание

Введение

1. Класс есть модуль

2. Два правила продолжения строки

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

4. Область видимости локальных переменных

5. Всего 5 простых типов данных ...

... и перечисления

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

7. Контроль рекурсии

8. Сохранение переменной (PUSH)

9. Массив с индексами из перечисления

10. Атрибуты полей класса (INIT, READ). Сильные и слабые ссылки.

11. Время жизни объекта

12. Наследование. Полиморфизм.

13. Деструкторы считаются опасными

14. Кодовые (NATIVE) функции

Содержание

`` В начало