RUSSIAN
(Русский)
ENGLISH


This function requires cookies.
If the browser does not accept cookies,
the light theme is used always.

Introduction

 

Here a short description of the AL-IV (ALFOUR) programming language is represented.

At this link you can find more detailed language description.

 

 

1. Class is a module


 

 
/*                                                  //l

CLASS {Hello|_world}, UNTESTED : //abcd,j

FUNCTION Main|_hello_world :       //e,j
    << 'Hello, World!' >> .                         //fgh

END                                               //i,j

*/                                                  //l

 

 

Notes:
  1. Any identifier (except a for loop variable name) should be at least 8 characters length, or a rest of its long name should be specified after symbol '|'.
  2. Class name {Hello_world} can be shortened to {Hello} when the class is referenced in other class (but not in an IMPORT statement).
  3. Class name is always starting from an uppercase letter.
  4. Class attribute UNTESTED allows compiling the class without testing it.
  5. A single static method of a class Main (full name in the example is Main_hello_world) allows to create an application containing such class.
  6. An operator sign << outputs a line of code to a console (or adds an item to an array, or adds a string to an accumulating string). An operator sign >> waits an input of a string from a console operator.
  7. Operations << can be chained. An operator >> can be attached to a chain of << operations. E.g.:
    STR X_s|tring
    << 'Input X:' >> X_s
    REAL X|_input_value = X_s.Real
  8. A dot symbol '.' is ending a function.
  9. The statement END is finishing a class.
  10. All the keywords are written in the uppercase.
  11. Each class is placed in its own source text file.
  12. The AL-Iv code is always located between the end and the start multi-line comments symbols: /* and */. Take into attention, that symbols of the start ( */ ) and the end ( /* ) of comments in the AL-IV are contrary to such symbols in C-like languages (this allows to place text on the AL-IV and e.g. on the C# in the same text file).
     

 

2. Two rules continuing lines


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

 
 
Notes:
  1. A line of code should not exceed 80 characters (not taking into account comments which are starting with // and continues to the end of the line). Ending spaces are ignored, tabulating symbols are accounting as single symbols.
  2. Single line contains single statement
    (there are exceptions: operation ==> can finish any simple statement, operations << and >> can finish statement <<)
  3. A statement can be continued a next line in one of two cases:
    • A line of code is finished with one of characters '(', '[', ','.
    • The next line of code is not empty and it is not starting a new statement (i.e. it is not starting from an identifier or (without starting spaces) from a double quotation '"' (which are used to enclose a string constant).
  4. String concatenation does not require any special operation sign: string constants and expressions are written one by one or via spaces. Or a string constant concatenating is placed at the start of the next line of code (leading spaces are ignored).
  5. There are also operator comments in the AL/IV language:
    ------------------------------ 'text'
    which are used to divide blocks onto sections of allowable size (no more then 7 simple and 7 block statements in a section).
  6. Simple statement is not ending with a special symbols like ';' (unlike to many other programming languages). The semicolon ';' is used to finish a block statement, and the dot '.' is used to finish a function (or another class level declaration).

 

3. Conditional statement


/*

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

*/

 
Notes:
  1. Amount of function parameters should not exceed 3, either while calling the function other parameters should be passed in form of an assignment: Name= value;
  2. Names of function parameters, public fields of classes are starting from the uppercase letter. Names of local variables, hidden fields of the class are starting from a lowercase letters.
  3. Functions can not return an array as a result (pseudo-variable RESULT is always a scalar). When it is necessary to "return" an array as a result of a function, it should be passed as a parameter.
  4. Arrays are always written together with square brackets. E.g.:
    R[].Count
  5. A conditional expression type of the statement CASE can be BOOL, INT, STR or a enumeration (ENUM).
  6. A statement CASE with a Boolean expression has only a single branch corresponding to a positive expression value and can have an ELSE branch (there is not in the example).
  7. Only the entire block statement (like the CASE) is ending by a symbol ';'. Indentation is used to decorate source code but the main information (fro a compiler) about blocks nesting is contained in the source text (including symbols ending blocks ';' and functions '.').
  8. The CASE statement with integer, enumerated or string condition allows to specify several branches, in each of which an array of constants in square brackets is specified. A branch is executing if the condition expression is equal to one of its constants.
  9. After executing a brunch selected, the next statement following entire CASE block is executing. There are no special break statements after each branch.
  10. To exit the function (before ending its body) a statement ==> is used. It can be standalone or docked to a simple statement. E.g.:
    CASE A == B ? RESULT = TRUE ==> ;
  11. A function is ending with the dot ('.'). It is allowed to place such '.' symbol following the symbol ';' ending a block statement, in the same line. Though it is not allowed to place two or more semicolons ending nesting blocks in the same line, or to place any text following the ';' ending block statement except comments (//).

/*

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

*/
 
 
Notes:
  1. A condition can be omitted in the header of the CASE ? statement. Each its branch is defined by a Boolean expression in square brackets (ending by a question mark). It is possible to declare also the ELSE branch.
  2. All branch expressions are calculated sequentially until the expression found with the TRUE value. In that case, a correspondent branch is executed, and entire statement is finished.
  3. If there are no true expressions, an ELSE branch is executed if it is present.
  4. Data type STRUCTURE is declaring on a class level. Its name (in figure brackets) should be starting from a lower case letter. Therefore, all the structures declared in a class, are always accessible for all the classes importing the class having those structures declarations.
  5. In a class declaration, all its fields are declaring like class fields or local variables. Structure fields names should always start from capital letters (since structure fields could not be hidden).
  6. A STRUCTURE declaration is ending with the dot '.' (like any other declaration of a class level).

 

4. Local variables scope


 /*

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' ;
    ; .

*/



 
Notes:
  1. A condition in the CASE statement can be a string. Items to be compared in such case are string constants. These are required to be unique. It is allowed to use ELSE in such case.
  2. For simple types except REAL (i.e. BOOL, BYTE, INT, STR) and for enumerations it is allowed to use operations IN and !IN in which the second operand is an array of constants (of the correspondent type), or a variable array. Such operation checks out a presence (or an absence) of the left value in the right array.
  3. For strings also it is possible to use operations IN and !IN to check out presence (absence) of a substring in the right string.
  4. Special symbols can be encoded in form #XX, where XX is an integer constant or one of symbolic names (#NL, #CR, #LF, #TAB, #SP, #BK, #DEL, #BELL, #ESC).
  5. A local variable declared at any place in a function becomes available to read and write at any below line in the same function to the end of the function. It is guaranteed that all the local variable will be initialized with its default values (0 for numbers, empty string - for strings, the first item in enumerations, NONE safe value - fro objects of classes).

5. Five simple data types ...


/*
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
*/
 
Notes:
  1. Constants are declared in a class body (not in functions).
  2. In a single CONST statement it is possible to declare an arbitrary amount of constants of the same type - one constant in a single line of code.
  3. The block CONST is ending by '.'.
  4. While writing numeric constants underscore characters are ignored and can be used to separate groups of digits visually. E.g.:
    0xAB12_cd20, 0b1101_1100_0101_0011, 0o377_105_210_114, -0.376209e-3
  5. A capacity for simple data types is fixed and it is the same for a target platform. For integers usually 32 bits are used, for real numbers - maximum allowed (64 or 80 bits usually). Though actually precision depends mainly from a compiler implementation.
  6. There is no "character" data type.
  7. It is not recommended to work with bytes if this is possible: in some cases using bytes can lead to programmatic errors. If such type is used in a class, it should have an attribute BYTES.
 

... and enumerations

 


/*

CLASS {Car|_on_road}, UNTESTED :

ENUM {traffic|_colors} :
    'R|ED_LIGHT',                              //ab
    'Y|ELLOW_LIGHT',                           //b
    'G|REEN_LIGHT' .                           //b
*/
 
// can a car continue moving on a crossroad ?

/*

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

*/

 

Notes::
  1. A name of a enumeration is starting from a lower registry letter.
  2. A name of each enumerated item is enclosed into apostrophes.
  3. If a condition in the CASE statement is a enumeration, then it is not allowed to use ELSE, and all the members of the enumeration must be listed in branch selectors.
  4. All the enumeration items of all declared in a class enumerations must be unique .
  5. If same named items are imported from different classes, then a name of such item should be specified in form
    {enumeration_name}.'ITEM_NAME'
  6. If imported enumerations are same named with others imported ones, then it is necessary to specify its items in form:
    {Class_name}.{enumeration_name}.'ITEM_NAME'
 

 

6. Loop statement


/*

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

*/

 
Notes:
  1. A loop variable can have short only name.
  2. In the loop header, following the IN keyword, either a range of INT values is specified (in a form [n TO m] or [n DOWNTO m]), or an array.
  3. Loop variables need not (and do not use) a separate declaration.
  4. It can not be used out of the loop body, and next time it can be used only as a loop variable again (with an array of the same type).
 

/*

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


*/
 
Notes:
  1. A loop variable is always become of the same type as the loop array items.
  2. Operand '*' in an expression in square brackets is a pseudo function returning an integer index of the last array item (for a string it returns an index of the last character). For empty array (or string) it returns -1.
  3. Symbol '*' in brackets following a parameter declaration means that the parameter is a fixed array and can accept fixed arrays or slices of dynamic arrays as actual parameters. It is recommended to use fixed array parameters if it is not necessary to change their size in a function.
  4. A pseudo function Index is applicable only to a loop variable and returns an index of an item which is enumerating.
  5. Any loop FOR is finished at some time. This can not eliminate slowdown of execution in case of many nesting long running loops but eliminate always infinitive looping.
  6. If it is really necessary to run an infinitive loop, a special form of a loop can be used:
    FOR, INFINITE:
           body ;

    and the class should have an attribute 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 !just && S.[i-1 TO i] == #QU#QU ?                 //d
             RESULT = RESULT.[0 TO i-1] RESULT.[i+1 TO *]      //cd
             just = TRUE
        ELSE just = FALSE ;
    ; .

*/
 
Notes:

 

  1. A string can not be used as a looping array. A range should be used to iterate characters of a string (it does not occupy memory though it is concerned as an "array").
  2. If some items will be deleted in an array while iterating it, then it is necessary to use a range starting from the upper index and ending with the lower index value (using a keyword DOWNTO).
  3. All the function parameters of simple types are passed to a function by value and can not be changed in a function body. The RESULT pseudo-variable can be changed any time, its final state will be returned.
  4. It is not necessary to use any special symbol to concatenate string expressions: these are just written nearby, one following another (separating with spaces if necessary)

/*

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

*/
 
Notes:
  1. If a constructed array in brackets is enumerating in a loop, the form with increscent index [x TO y] or a form with decreasing index [y DOWNTO x] can be used.
  2. If an array variable is specified as enumerated, without any special range in indexes, then all its items are listed from the first (with index 0) to the last (*, or Count-1).
  3. In statements BREAK and CONTINUE a loop label is specified (a loop variable name). This allows to finish or continue a loop from a nesting loop body (but to jump to the start or to the end of an external loop it is required to BREAK first from all the nested loops).
  4. Amount of nesting blocks can not exceed 3 (actually 3.5 still it is allowed the 4th nested level with a single simple nesting statement), so in many cases interrupting or continuing a loop by a condition allowing to decrease amount of nesting blocks is more preferable then a conditional block at the end of a loop body.
 

/*

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

*/

Notes:
  1. Since there are no WHILE loops, these should be replaced with FOR loops. The best way is to specify bounds sufficient to execute all need iterations, but without too big reserve (to avoid a situation looking like a suspension in case of error in code). In the sample above a loop is used enumerating the array on which working is required, so amount of iterations can not exceed amount of its items. Though actually not N items were handled but LOG2(N), this is not bad approximation in this case.
  2. If a loop variable is not used in the loop to calculate expressions, it should have in its name (at least in its long version) a substring 'dummy' (in any register).
  3. The same rule is actual for a function parameter, which is not used in a function body: it should have a substring 'dummy' in its name too. Even if it is used in inherited or ancestral classes.
 

/*

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 ==> ;
    ; .


*/

Notes:
  1. If bitwise operations are used in a class ('|', '&', '^', '~') then the class should have an attribute BITWISE.
  2. It is not allowed to have both bitwise and arithmetic operations in a single statement.\
  3. There are no embedded shift operations. Functions should be used: ShiftL|eft, ShiftR|ight, RotateL|eft, RotateR|ight.
  4. All the variables (and the RESULT) are automatically initialized by zeroes of correspondent type.
  5. If it is necessary to specify an absence of any operations then a single constant NONE should be used.
 

 

// Output to screen using loops: 
// ##########
// ##      ##
// # #    # #
// #  #  #  #
// #   ##   #
// #   ##   #
// #  #  #  #
// # #    # #
// ##      ##
// ##########

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


 

// Get a string representation for an input array of integer numbers:
// [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. Recursion checkout


/*

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

*/

Notes:
  1. A recursive function calling itself directly, should have an attribute RECURSIVE. If the function is calling itself indirectly via a function which is always marked with the attribute RECURSIVE, it itself can be declared without the attribute.
  2. For recursive functions (with the RECURSIVE attribute) a control of recursion nesting level is added to the code. If some limit value was exceeded, a return to the first recursion level is occur, and then the function is returned without any other working. And if a result is required always NONE or correspondent type zero is returned.
  3. Static one parameter function always can be called in a form of object non-parameter method. E.g., x.Sin instead of Sin(x).

 

8. PUSH a variable

 
/*

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


*/

Notes:
  1. A value of a variable is saved and it gets a specified value (which is not necessary).
  2. A variable can be an array item, but it can not be an array.
  3. On the block PUSH exit, the variable is restored always (including a case when an execution of entire function is ended by a ==> statement).
  4. In a function it is allowed not more then 3 nesting levels of statements CASE / FOR (if more then 3 nesting levels are used, the class should have an attribute BAD), but statements PUSH are not accounting for nesting.
  5. There are no exceptions. Divide by zero for real number gives a special value INF|INITY, for integer number - maximum possible integer value of a corresponding sign.
  6. In case when by some serious reason it is not possible to continue working, an operator
    STOP "text"
    can be used to stop working entire thread (or entire program, in case of the main thread).
  7. A class should have an attribute STOPPING, if the operator STOP is using in its functions.
 

 

9. Array with indexes from a enumeration

// display a list of traffic lights burning

/*

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

*/

Notes:
  1. A variable can be declared at a moment of the first assigning a value to it, even in a nesting block. Then it is accessible to the end of entire function. This can lead to errors by a reason of absence of re-initialization of such variable (on further iterations). So, you will be warned if there are no preliminary initialization of an accumulator variable declared such way in the nesting loop.
  2. For a string an operation S.[i TO j] cuts a substring from index i to index j inclusive (indexing of arrays and strings is always starting from 0). E.g.:
    "ABCD".[1 TO 2] == "BC"
  3. An embedded pseudo function Name for an enumeration item is returning a short item name (in apostrophes)..
  4. A Boolean array with indexes from a enumeration can be used like a set in the Pascal language - as a set of named flags.
 

 

 

10. Class field attributes (INIT, READ). Strong and weak references.

// an astronomic example

/*

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

*/

Notes:
  1. An object class field which name (both short and long) is ending by trailing underscore character '_', is a weak reference to an object. In the aexample above:
    {Planet} Parent_|planet_or_star_
  2. A weak reference is never retaining. When a referenced object is destroyed, all weak references to it become referencing to a special NONE object of a correspondent class.
  3. NONE object - is a special constant object, having all the fields and methods same as a real object of the same class. But values of its fields are zeroes and NONE objects (fore strings - empty strings, for dynamic arrays - empty arrays, for BOOL - value FALSE, for enumerations - the first item of a enumeration). Writing to a NONE object is not changing its value (always ignored).
  4. A class can not contain object fields which are strong references to objects which are ancestors or inheritances of the class. Nets and graphs can be built only using weak references.
  5. If a class {A} has strong references to a class {B}, then class {B} can not have strong references to a class {A}. If a class {A[1]} is referencing strongly a class {A[2]}, {A[2]} is referencing to {A[3]}, ..., {A[n-1]} to {A[n]}, then the class {A[n]} can not have strong references to objects of any of classes {A[1]}, {A[2]}, ..., {A[n-1]}. This rule guarantee impossibility of creating a ring of keeping each others objects. In result, a program is not requiring a garbage collector, and all objects are destroyed exactly at the moment when its strong referencing counter become zero (but: see about object "owners" below).
  6. An attribute INIT|IALIZE for a class field requires an initialization of the field while creating an instance of a class.
  7. An attribute READ protects a field from writing to it from other classes (except the class itself and its inheritance).

 

11. Object life time


/*

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


*/


Notes:
  1. In an AL/IV program a lifetime of an object is defined at the moment when its created (but not only by a garbage collector or a programmer decision who could desire to destroy an object at some time he wants). For an object it should be defined would it be assigned to an external (for a function) field or variable, or would it be added to an array of strong references, or would it have its owner assigned to it.
  2. If at least once in a function body a new object created is assigned to a pseudo variable RESULT, then the function should have an attribute NEW.
  3. Such factory function can be called only by a separate statement (like a direct object creating statement).
  4. And an object returned by such function also can be added to an array of strong references (using the operation >>). Either the destination variable fro a new object should have a substring 'temp' in its name.
  5. If an object is creating by a constructor and following comma an owner is specified (OWNED BY xx), then the object created will exist until its owner is destroyed (independently of its usage counters).
  6. If an object constructed by a factory function or a constructor is added to an array of references, then such array must be an array of strong references (not weak). When an object is added to an array, its usage counter is incremented and when it is removed from the array, the usage counter is decremented (very like to assigning a reference onto it to a variable or a field of an object).
  7. If a function body contains only  a single statement, and it is an assignment a value to the pseudo-variable RESULT, then the keyword RESULT can be omitted.
 

 

12. Inheritance. Polymorphysm.

 

/*

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

*/

 

Notes:
  1. There are no variables in the AL/IV language which could play role of pointers to functions. A single way to call a function which is unknown at design time (except its parameters) is virtual methods of a class.
  2. All the methods in a class are virtual. While redefining a method:
    1. a keyword OVERRIDE is written instead of the keyword METHOD;
    2. a short only name of the method is specified;
    3. parameters should be declared in the same order but its names could be other though the first letters must match;
    4. types of object parameters must be compatible, for all other types these must match.
  3. If a class is using other classes (to declare variables or fields, to call methods, to access constants etc.), then all used classes must be listed in an IMPORT statement.
  4. The IMPORT statement is written immediately following the class header and contains a list of used classes, finished by a dot or semicolon symbol.
  5. While descending a class from another class, following the IMPORT statement a statement
    BASE CLASS {Ancestor_class} should be written.
  6. In the BASE CLASS construction in parentheses a list of assignments to fields of the ancestor class object  can be provided. Such assignments are working before assignments in a statement of the object creating:
    {Type}(assignments_list)
  7. To initialize an object after its creation a constructor of the class is called. The constructor is defined by a special function with the keyword CONSTRUCT as the header.
  8. The constructor is called after creating the object: at that time an initialization of base class fields in the statement BASE CLASS(assignments) is done, and (important!) all the assignments in a constructing statement
    {Type}(assignments_list) are also executed, and all the ancestor constructors was called already.
  9. If a function value is not required, it should be "assigned" to a constant NONE.

 

13. Destructors are dangerous


/*

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

...

*/

Notes:
  1. In most cases destructors are not necessary (embedded destructor is enough to release strings, arrays and objects belonging to the object). Really user defined destructors are actually necessary only in cases when a class is keeping a descriptor of some "external" (relatively to the AL/IV language) object.
  2. Still time of destructor firing can not be predicted well, using it can lead to unstable or unpredictable work of an application. A class containing a destructor, should have an attribute DESTRUCTORS.

14. Native functions


/*

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

*/

Notes:
  1. If a class is using native functions of a target language, then such class should have an attribute NATIVE in the header of the class. All such classes are depending on a target platform. The best way to provide as most independency as possible is in moving all such classes into separate subdirectories and specifying certain directories for a compiler depending on a target platform. E.g., MyProject\_Delphi, MyProject\_C#, etc.
  2. A function having an attribute NATIVE ahs a string constant as its body. Such constant is inserted into resulting compiled code without changes, and a correctness of the code written in the string is depending on a programmer only. To write a native code, it is necessary to know a target language and understand rules of a compiler from AL/IV to that language.
  3. A native function can start from a regular AL-IV code. In such case it therefore should end with a NATIVE "code" statement with a native code in a string constant.
  4. If a compiler converts a parameter name A|ttribute in the translated code into X_A (taking short name and adding a prefix X_ to it), then in the native code it should be referenced by an identifier X_A. But this can depend on a certain compiler implementation.
  5. Additionally to functions with attribute NATIVE, separate statements
    NATIVE: "string" .
    can be placed in a source code of a class. These are using to insert additional code between functions, on a level of a class as a textual unit.
  6. Statements NATIVE placed before the first function in a class can be used for special purposes. E.g. to add directives "using" or "uses". But this rule depends on a certain compiler implementation.
  7. To simplify writing native code, a compiler can automatically create an inner local variable RESULT (like the Pascal), initialize it by the default NONE value, and return it at the end. It is better to assign a value to the RESULT variable rather then to use native return expression statement (less warnings from the end compiler).
  8. String literals in quotations '' ... '' (with double apostrophes) are very useful to write native code still it is possible to write double quote symbols there even without any mirroring. But it is necessary to remember that such literal should be the single in a line (after removing ending comments following //).
  9. Native classes do not require testing (though these also can be tested). Only its creator is responsible for its correctness. He (she) at least should provide enough safety to use such classes and to satisfy following requirements:
    1. there should no be any exceptions fired: always errors should be ignored, but without any bad "consequences" for a user's files or other data;
    2. information on important errors should be stored in a queue of errors accessible by the program via system functions;
    3. while testing it is not allowed to change any files except those having a substring 'TEST$' (in any register) in its name or path;
    4. also while testing it is not allowed to change any data bases and tables in data bases except those having a substring 'TEST' in the name (of a DB or of a table).

 

 

 

Content

Introduction

1. Class is a module

2. Two rules continuing lines

3. Conditional statement

4. Local variables scope

5. Five simple data types ...

... and enumerations

6. Loop statement

7. Recursion checkout

8. PUSH a variable

9. Array with indexes from a enumeration

10. Class field attributes (INIT, READ). Strong and weak references.

11. Object life time

12. Inheritance. Polymorphysm.

13. Destructors are dangerous

14. Native functions

Content

 

Home