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

 

 

Пояснения:
  1. Любой идентификатор (кроме имени переменной цикла) должен быть либо не менее 8 символов длиной, либо через символ '|' указывается остаток длинного имени.
  2. Имя класса {Hello_world}, может быть сокращено до {Hello} при ссылке на класс при его импорте (но не в операторе IMPORT).
  3. Имя класса всегда начинается с прописной буквы.
  4. Атрибут класса UNTESTED разрешает компиляцию класса без тестирования.
  5. Единственный статический метод класса Main (полное имя в примере Main_hello_world) позволяет создать программу, содержащего этот класс.
  6. Оператор << выводит в консоль строку текста (или добавляет элемент в массив или строку в накопительную строку), оператор >> ожидает ввода строки текста от оператора консоли.
  7. Операторы << могут состыковываться в цепочку. Оператор >> может пристыковываться к такой цепочке справа. Например:
    STR X_s|tring
    << 'Введите X:' >> X_s
    REAL X|_input_value = X_s.Real

  8. Точка завершает функцию.
  9. Оператор END завершает класс.
  10. Все ключевые слова языка записываются буквами верхнего регистра.
  11. Каждый класс размещается в отдельном текстовом файле.
  12. Код AL-IV всегда располагается между строками, содержащими символы конца и начала многострочных комментариев: /* и */. Обратите внимание, что символы начала и конца многострочных комментариев противоположны тем, которые используются в C-подобных языках. Это позволяет размещать код на AL-IV, и, например, C#, в одном файле.

 

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), """")    
                                                                      
          db << UPDATE Students,                                       //c
                SET Exam3 = Result_set,                                //c
                WHERE ID = Id_student                                  //c
          -------------------------- 'do update now'                   //h
          db.Exec ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ //b

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

 

3. Один условный оператор


/*

CLASS {Equation}, UNTESTED :

REAL A|_coefficient                                     //b
REAL B|_coefficient
REAL C|_coefficient

-------------------- 'Ax^2 + Bx + C = 0'

METHOD Square_eq|uation(
    REAL R|esults_list[])
    :                                                   //abcd
    R[].Clear                                           //dk
    CASE A.Abs <= 1e-10 ? ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ //efj
    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                         //gk
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

END

*/

 
Пояснения:
  1. Число параметров функции не должно превышать 3, либо класс должен иметь атрибут BAD.
  2. Имена параметров функций, публичных полей класса начинаются с прописной буквы. Имена локальных переменных, скрытых полей класса начинаются строчной буквой.
  3. Функция не может возвращать массив в качестве результата (псевдо-переменная RESULT - всегда скаляр). При необходимости "вернуть" массив, он должен быть передан в качестве параметра.
  4. Массивы всегда употребляются с квадратными скобками. Например:
    R[].Count
  5. Тип выражения в условии оператора CASE может быть BOOL, INT, STR или перечислением (ENUM).
  6. Оператор CASE с булевским выражением имеет одну ветвь для положительного результата, либо дополнительную ветвь ELSE (нет в этом примере).
  7. Только весь блочный оператор (такой, как CASE) завершается символом ';'. Отступы используются для оформления исходного кода, но основную информацию (для компилятора) о вложенности блоков задаёт текст, в том числе символы завершения блоков (';') и функции ('.').
  8. Оператор CASE с целочисленным, перечислимым или строковым условием позволяет указать несколько ветвей, задавая для каждой массив констант в квадратных скобках, при равенстве которым выполняется соответствующая ветвь.
  9. После выполнения ветви управление передаётся следующему за CASE оператору, какие-либо специальные операторы вроде BREAK для выхода не требуются.
  10. Для выхода из функции (до завершения её тела) используется оператор ==>. Он может быть самостоятельным или пристыковываться к любому простому оператору. Например:
    CASE A == B ? RESULT = TRUE ==> ;
  11. Функция завершается символом '.'. Допускается точку размещать вслед за последним символом ';', завершающим блочный оператор. Не допускается размещать таким образом два или более подряд символа ';', или размещать какой-либо текст (кроме комментариев (//) после символа ';', завершающего блок.

/*

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
    CASE ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ //a
    [A.Almost_eq(0) || d < 0] ? ==>                 //ab
    [d.Almost_eq(0)] ? RESULT.N = 1                
                       RESULT.X1 = -B / (2 * A)     //ab
                       RESULT.X2 = RESULT.X1       
                                                   
    ELSE REAL q|sqrtD_div2A = d.Sqrt / 2 / A        //b
         RESULT.X1 = -B / 2 / A - q                
         RESULT.X2 = -B / 2 / A + q                
         RESULT.N = 2 ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

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

 

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] " " ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
        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' ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

*/



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

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


/*
CONST BOOL: Flag|_boolean = FALSE .
CONST INT :
    Count|er_integer = 1_000               //d
    Value|_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
*/
 
Пояснения::
  1. Константы декларируются в теле класса (не функции).
  2. В одном операторе CONST может декларироваться произвольное число однотипных констант - по одной константе на строку кода.
  3. Блок определения констант завершается символом точки '.'.
  4. При записи числовых констант символы подчёркивания используются для разделения групп цифр. Например::
    0xAB12_cd20, 0b1101_1100_0101_0011, 0o377_105_210_114, -0.376209e-3
  5. Разрядность простых типов данных фиксирована и одинакова для целевой платформы. Для целых обычно используется 32 разряда, для вещественных - максимум возможного (64 или 80 разрядные числа). Хотя в общем, разрядность всех типов зависит только от реализации компилятора.
  6. Типа данных "одиночный символ" нет.
  7. Рекомендуется не использовать тип 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

*/

 

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

 

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 ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

*/

 
Пояснения:
  1. Переменная цикла не обязана иметь длинное имя.
  2. В заголовке цикла после IN указывается либо диапазон целочисленных значений (в форме [n TO m] или [n DOWNTO m]), либо массив.
  3. Для переменной цикла не требуется и не используется отдельная декларация.
  4. Она не может использоваться за пределами цикла, и повторно может использоваться только в другом цикле, с массивом элементов того же типа.

/*

FUNCTION Invert_array(REAL A|rray_of_numbers) :
    FOR R IN A[] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ //a
        CASE R.Index > A[].Count / 2 ? ==> ; ▄▄▄▄▄▄▄  //c
        REAL t|emp_number = R                        
        A[R.Index] = A[* - R.Index]                   //bcd
        A[* - R.Index] = t ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ //bcd


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

    и класс должен иметь атрибут INFINITE.

/*

FUNCTION Undouble_quotes(
    STR S|tring_containing_double_quotes) ==> STR
    :
    BOOL just|_unquoted = FALSE
    RESULT = S                                                 //c
    FOR i IN [S.Len-1 DOWNTO 1] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ //ab
        CASE S[i-1 TO i] == #QU #QU && !just ? ▄▄▄▄▄▄▄▄▄▄▄▄▄ 
             RESULT = RESULT[0 TO i-1] RESULT[i+1 TO *]      //c
             just = TRUE                                    
        ELSE just = FALSE ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

*/
 
Пояснения:
  1. Строка массивом не является, поэтому для итерации по символам строк необходим просмотр диапазона индексов (условного массива целых чисел, который на самом деле не занимает места в памяти).
  2. Если во время выполнения цикла в массиве будут выполнены удаления элементов, то просмотр элементов (и символов в строке) следует выполнять в обратном порядке, используя диапазон значений от верхнего индекса к нижнему (ключевое слово DOWNTO).
  3. Все параметры простых типов передаются в функцию по значению и не могут изменяться в теле функции.

/*

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 ? CONTINUE i ; ▄▄▄▄▄▄▄▄▄▄▄▄   //c
        ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
        A[] << i ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

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

/*

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 ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄
    CASE W == A[R] ? RESULT = R ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄
    RESULT = -1 .

*/

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

/*

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


*/

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

/*

FUNCTION Count_sp|aces_in_string(
    STR S|tring_analyze) ==> INT
    :
    FOR i IN [0 TO S.Len-1] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        CASE S[i] == " " ? RESULT += 1 ;   //ac
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

*/

Пояснения:
  1. Операции ++ и -- допускаются только постфиксные. Применимы только к целочисленным переменным (полям объектов, элементам массивов). Записываются справа от переменной и выполняются после выполнения всего оператора.
  2. Не допускается применять операции инкремента/декремента к одной и той же переменной более одного раза (или к такому же полю даже у двух разных объектов).
  3. Запрещено использование операций ++/-- в виде отдельного оператора.

 

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

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 ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ 
        FOR j IN [1 TO s.Len-2] : s[j] = " " ; ▄▄▄▄▄▄▄▄▄ 
        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 ==> ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
    RESULT = N * (N - 1).Factorial .         //c

*/

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

 

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

// к примеру из п.3 (функция Square_eq):

/*

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


*/

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

 

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


/*

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

*/

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

 

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

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

/*

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" ;

*/

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

 

11. Встроенное тестирование

// к примеру из п.3 (функция Square_eq)
// При добавлении данной функции в класс, атрибут UNTESTED
// может быть заменён на SAFE

/*

TEST of_equations : //abc
     IMPORT {Mathematics} ; //ee
     ------------------- 'x^2+2x+1=0 single solution' //d
     solve|eq_temp = {Equation}(A = 1, B = 2, C = 1) //fg
     REAL r|esults_found[]
     solve.Square_eq(R[])
     ASSERT Almost_eq(R[].Count == 1 && R[0], -1) //d
     --------------------- 'x^2-2x+1=0 two solutions'
     solve.A = 1
     solve.B = 2
     solve.Square_eq(R[])
     ASSERT R[].Count == 2
     ASSERT Almost_eq(R[].Min_item, 0)
     ASSERT Almost_eq(R[].Max_item, 4/3.0)
     ------------------------- 'x^2+1=0 no solutions'
     solve.B = 0
     solve.Square_sq(R[])
     ASSERT R[].Count == 0
     ------------------------------ 'linear equation'
     solve.B = 1
     ASSERT Almost_eq(solve.Linear_eq, 1)
     .

*/

Пояснения:
  1. Функции без параметров, которые начинаются в ключевого слова TEST, позволяют тестировать класс.
  2. При наличии таких функций на этапе компиляции выделяется шаг тестирования, при котором формируется специально подготовленный вариант программы (и исполняется), после чего анализируются результаты тестирования.
  3. Компилятор контролирует степень покрытия класса тестами. Класс считается полностью протестированным, если при тестировании все операторы всех функций (кроме "нативных") выполнились хотя бы по одному разу. А так же, все утверждения ASSERT были выполнены.
  4. Утверждение ASSERT в блоке тестирования позволяет проверить истинность некоторого утверждения. Каждая секция внутри функции TEST, отделённая операторными комментариями
    -- 'текст', должна содержать хотя бы один оператор ASSERT.
    Операторы ASSERT могут использоваться только в функциях тестирования. Функции тестирования компилируются только на специальном проходе компилятора - тестирующем. В результирующем коде функции TEST и операторы ASSERT отсутствуют.
  5. Функции тестирования могут содержать собственные операторы импорта, дополняющие общий список импорта класса на время тестирования такой функции. В примере импортируется класс {Mathematics}.
  6. Если локальная переменная при первом упоминании получает значение как результат операции создания нового объекта вида
    {Тип}(список присваиваний),
    то это считается её декларацией как объектной переменной с указанным справа классом.
  7. Если значение нового объекта в момент его создания присвоено только локальной переменной (как в примере), то локальная переменная обязана иметь подстроку 'temp' (в любом регистре) в своём полном имени. Если значение этой переменной не будет присвоено другому объекту (полю, переменной, существующей вне функции, не будет добавлено в массив объектов и т.п.), то по завершении функции, объект будет уничтожен. Наличие флага'temp' сообщает компилятору о том, что программист знаком с таким поведением этой переменной, и объект создаётся временно, до окончания выполнения данной функции.

 

12. Атрибуты полей класса (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

*/

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

 

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


/*

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) :
    {Planet} temp|_planet = 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")
    sun.Is_star = TRUE
    --------------------------------- 'inner planets'
    R.Make("Mercury")
    R.Make("Venus")
    R.Make("Earth"])
    PUSH R.Current_parent = R.Last : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         R.Make("Moon"; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
    PUSH R.Current_parent = R.New_planet("Mars") : ▄▄▄▄▄▄▄
         R.Make("Deimos")                                
         R.Make("Fobos"; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

    --------------------------------- 'Jupiter'
    PUSH R.Current_planet = R.New_planet("Jupiter") : ▄▄▄▄
         R.Make("Io")                                    
         R.Make("Europe")                                
         R.Make("Ganimed")                               
         R.Make("Callisto")                              
    ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
//...
    .

END


*/


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

 

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

// к примеру из п.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

*/

 

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

    , и отработаны все конструкторы классов-предков (начиная с самого "древнего").
     
  9. Если значение функции не требуется, оно должно быть "присвоено" константе NONE.

 

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


/*

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

...

*/

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

16. Кодовые (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

*/

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

 

 

 

Содержание

Введение

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

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

3. Один условный оператор

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

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

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

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

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

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

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

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

11. Встроенное тестирование

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

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

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

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

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

Содержание

 

В начало