RUSSIAN
(Русский)
ENGLISH


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

Introduction

A programming language AL/IV is a

   minimal

   imperative

   object oriented

   platform independent

high-level programming language

   with static types,

claiming to a very high level of safety and stability

   of a controllable level of code protection.

 

Important semantic things of the language are:

NONE-objects instead of NULLs

 
  • fields of NONE objects are always equal to NONE.
  • Writing to fields of NONE-objects is ignored.
  • Reading of fields gives always zeroes and NONE-objects of correspondent types.
  • NONE-objects are returned in a case when arrays are accessed out of the array bounds,
  • and when the object referenced was lost .
  • All object variables are always initialized with NONE
  • and never have value NULL.

 

Separating references to objects onto strong (owning) and weak_ (using) references

 
  • Strong references can not be used to create closed link chains between objects.
  • Garbage collection is not necessary at all.
  • All objects are automatically destroyed when its usage (by strong references) counters becomes zero.
  • When the object is destroyed, all weak references onto it immediately become referencing to NONE objects of corresponding class.
  • New objects must be addressed either a strong pointer reference or an existing object should be specified becoming an owner of the creating object.

Total thread data isolation from other simultaneously executing threads (Note: parallel is working but the efficiency was not confirmed)

 
  • Special mechanisms to provide exclusive access to shared data are not necessary.
  • A thread can access only its own objects and even does not see objects of other threads.
  • There are not possible deadlocks due to data access conflicts.
  • There are no global variables so there are no problems with simultaneous access them from different threads.

Detection of infinitive loops and endless recursions

 
  • Loops are possible only in form
    FOR var IN arr[] : ...
    (there are no loops like WHILE/REPEAT-UNTIL). There are loops FOREVER, but its usage is restricted hardly. Any loop FOR is finished obligatory, so infinitive looping become impossible.
  • Recursive functions always are marked with a modifier RECURSIVE, and a recursion deep is controlled: in case of danger of too deep recursion an immediate return to the first level of the recursion in the calls chain is performed and it returns NONE value.

PUSH statement for variables and objects

 
  • In a PUSH statement for a variable its value is stored and new value can be assigned.
  • On a block end, stored value of a variable is restored always.

Parameters passed by value, can not be changed in a function

 
  • Reusing input parameters to store intermediate values is not allowed.
  • Value of a scalar parameter can not be changed until the function ends.
  • For an array parameter it is allowed to change items, and for dynamic arrays - adding and deleting items.

"Read only" fields and module variables

 
  • Simplifies creating of "properties" which can be modified only by the owner (class and its descendants).

Enumerations and collections

 
  • These are very similar to enumerations and sets in Pascal-like languages.
  • These allow to work with named constants and flags.
  • It is possible to use Boolean arrays with such enumerations as indexes to implement collections of flags.
  • Enumeration items are included into apostrophes.
  • The CASE statement with an enumeration as a condition requires to list all possible values in its branches. If a enumeration used in CASES was extended with new values, the compiler is complaining on all the CASE statements where such new values are not yet listed.

There are no handling exception instruments, since there are no exceptions

 
  • There are no errors which could change execution flow immediately (branching to another execution point).
  • Passing control to a top level recursive function when a maximal allowed level of a recursion is achieved is performed only to speed up malfunction detection and to prevent stuck of execution in case of a big amount of nested loops.
  • Most of errors are ignored by replacing a supposed result with the NONE value.
  • In case of errors (working with files, exceptions in native code, etc.) these are collecting (in a system array of errors) and later can be handled in the code.

Testing engine embedded to the language

 
  • TEST functions allows to test code.
  • Covering of the code by tests is measured by the compiler.
  • A unit insufficiently covered by tests must be marked by the modifier UNTESTED.

It is possible to redefine arithmetic operations

 
  • Only arithmetic operations can be redefined (+, -, *, /).
  • Any function using redefined operators, must declare in its header a list of classes where such operators are from.
 

Important syntax things of the language are:

One class - one module

 
  • Each source text file contains a single class definition.
  • All public names and function parameters have names starting from uppercase letter, all the hidden names and local variables are starting from a lowercase letter.
  • Underlining at the end of a name is used only for object fields of class which are weak references to the object.

Double naming of types, variables, functions, constants

 
  • Most of objects (variables, functions, data types) have two names.
  • In declarations, full name is separated onto 2 pars via a '|'. The part before '|' is representing a short name of an item.
  • All named things (excluding FOR loop variables) should have long names (at least 8 characters).
  • Language keywords are written in the uppercase and can not be used to name functions or variables.

 

Names of classes, enumerations and records are always written in figure brackets

 
  • The are 4 simple data types: BOOL, INT|EGER, REAL, STR|ING.
  • Classes and enumerations are named using figure brackets, e.g. {My|_class}.
  • Classes have names in figure brackets starting from uppercase. Enumeration and record names are always starting from a lowercase letter.
  • Type like "char" is not present (STR is used).
  • Implicit conversion is allowed only for converting INT to REAL.

Source code string length restriction

 
  • Source code should not contain lines with length greater then 80 characters (not taking into account comments and ending spaces).
  • The rule is not affecting lines after the final directive END.

A rule to continue line of code

 
  • There is no a special symbol to end a simple statement.
  • A statement usually occupies a single line of code.
  • A line of code is continuing in the next line if it is ending with one of character ',', '(', '['
  • or if the next line is not empty and do not start new statement, i.e. it is not start from '{', '<', '[' or a letter,
  • and it is not ending a block or a function, i.e. it is not starting from symbols ';', '.'

Static function with a single parameter can be called always in a postfix form

 
  • Calling in form sin(x) can be always replaced with x.sin()

Arrays are always used together with its square brackets: A[ ]

 
  • Only single dimension arrays are present.
  • An array can be either dynamic or a fixed.
  • For fixed arrays enumerations can be used as indexes (as data types).

A line of code contains only a single statement

 
  • Symbol ';' is used to end a block statement (CASE, FOR, FOREVER, PUSH).
  • Symbol '.' is ending a function, a enumeration, a constant block or an import list.
  • Ending symbol ';' or '.' must be the last such symbol in a line (so, two semicolons are not possible but '; .' is allowed).
  • Any simple statement (except BREAK, CONTINUE or STOP) can have a statement '==>' attached (return from a function statement).

Restrictions on a code quality:

 
  • It is allowed not more then 3 nested levels of block statements CASE / FOR / FOREVER, (though this is not concerning statements PUSH and DEBUG).
  • It is allowed not more then 7 simple and 7 block statements in a single block, after that a block comment is required (--------- 'comment').
  • It is allowed not more then 3 explicit parameters of a function (without implicit THIS parameter). The exception: the restriction is not concerning NATIVE functions.
  • In case of abandoning any of rules listed above, a class must be marked with a bad code modifier (BAD).

Overloading operators

 
  • It is possible to redefine basic arithmetic operators for classes and record. Using redefined operators is declaring in a function header.
 

The language has lack of:

Preprocessor

 
  • There are no non-typed macros
  • EXCLUSION: there is a LIKE statement, but it is restricted and can not be used nested or out of class bounds.
  • There are no conditional compilations.
  • There are no file includes.

Function pointers

 
  • There are no function pointers. Use instead virtual methods.

Overloading

 
  • There are no same named functions in a module.
  • While importing same named static functions and procedures it is required to specify explicitly a class where these are from: {class}.function(parameters).
  • EXCLUSION: redefined OPERATORs are overloaded automatically (still its input argument types are actually a part of its names).

Array as a result of a function

 
  • Function can return only a scalar value.
  • If it is necessary to return an array, the result is passed is a parameter.

Multiple inheritance

 
  • Class can be inherited only from one ancestor class.

go to statement

 
  • There is no GOTO statements. (There is the GOTO statement in the AL-IV language, but it is purposed not tojump between statements but to activate a scenario).
  • It is possible to continue or break a loop from a deeply nested block (NEXT x, BREAK x).

Iterators defined by methods

 
  • It is not possible to define custom enumerators.
  • There are array item enumerators FOR EACH m IN M[] DO

"Getters" and "setters" with variable-like access

 
  • A field or a variable can not have a setter which handles assignment of new value, or setter which implements reading from value. There are getters and setters but its purpose is in providing with capability to use the statement REVERT.
  • Reading a field always is simple reading a value from memory.
  • Writing to a field always just is simple writing a value to memory.
  • EXCLUSION: setters are possible but used usually for the REVERT statement, or in assignment to a .[] method (used to implement classes {Vector}, {Matrix}).

Character data type

 
  • To represent characters, string variables and constants of length 1 are used.

Precision specification for simple data type variables

 
  • Precision is not specified.
  • It is always same for embedded data types <int>, <real>, <str>, <bool>.

Implicit type conversions for distinct data types

 
  • There are no implicit data type conversions except converting INT to REAL.
  • There are no type casts.

Low-level programming

 
  • Special NATIVE functions are used to implement wrappers between high level language (AL-IV) and a target language, to which source code is translated.
  • Any programming language can be a target (e.g., C++, Objective C, Java, C#, Delphi, Python, Rubi, LUA, Shell, Algol, Fortran, Oberon, etc.).
  • Entire low level support is written on a target language, and just a set of wrapper functions is specified with attribute NATIVE. This allows to use any programming language as a target - from the Algol or BASIC to Zonnon.

Exceptions

 
  • Exceptions due to an incorrect addressing are almost not possible (because of NONE objects, instant initializations of variables, checking arrays bounds).
  • In most cases when in other environments an exception is generated, in AL/IV no actions are fired, and an empty result is returned (NONE).
  • So, exception handling is not required, so exception handling is not provided.

I. Syntax

I. 1. Formatting statements

I. 1. a. Continue statement lines

 

Statements need not any finalization symbol (like ';').

Symbol ';' is used to finish a block of statements.

E.g.:
d|escriminant = B * B - 4 * A * C
CASE d.Sign ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
     [-1]: NONE                              
     [0] : R[] << -B / 2 / A                 
     [+1]: R[] << (-B - d.Sqrt) / 2 / A      
           R[] << (-B + d.Sqrt) / 2 / A ; ▄▄▄█

 

 

A line of a code should not exceed 80 characters (without ending comments and spaces and tabulations).

Statement can be continued in several lines only in following cases:

  • a line is finished with ',', '(', '[';
  • the next line is starting with one of symbols (without quotas):
    • '"' (double quotas - starts string constant)
    • '+'
    • '-' (single only, not '--')
    • '*'
    • '/'
    • '%'
    • '|'
    • '&'
    • '>'
    • '='
    • '#'
 

Another way to remember the rule is that a statement is continued only if:

  • either the previous line is finished by one of symbols '(', '[', ','
  • or the next line is not empty and it can not a start of a new statement, that is, it is not start from:
    • a letter,
    • a symbol '{' (a variable declaration can start from a type name in figure brackets),
    • a symbol '[' (not a condition for a CASE statement),
    • a symbol '<' (an output statement can start from a symbol '<<'),
    • symbols '.' or ';' (it is not a separate ending of a code block or a function),
    • a symbol '--' (so it is not a block comment -------------- 'text').
 

Or use the following figure:

 

I. 1. b. Block statements

Block statements always are starting in a new line.

Nesting statements block is ended with the symbol ';'.

Functions and other blocks of a class level are ending with a symbol '.'. Such blocks are: a class header, an import list, enumeration declaration, constants definition block. (Though it is allowed to use ';' for an import list, too).

The last line in a nested block can not be ended with two same ending symbols ';' or '.'. But it is allowed to end nesting block with its upper leveled function by two symbols '; .' at the end of the last line in the function.

So, if it is required to finish two nesting blocks, then the ending symbol of the external block will be placed in a separate line of code. E.g.:

 

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

 

 

There is only a single keyword for a conditional statement (CASE), a single keyword for a basic loop statement (FOR), a single save and restore statement (PUSH). There is also an infinitive loop statement (FOREVER) using it is restricted. For debugging, special blocks DEBUG  can be used also.

All other statements are simple: BREAK, CONTINUE, STOP, LIKE, REVERT, function calls, variable declarations and modifications, input and output (>>, <<). The BREAK statement can be used only to exit a FOR loop and not for any other purposes.

 

A compiler requires to mark with a modifier BAD classes in which rules of code structuring are not satisfied:

  • Number of nested levels of block statements CASE and FOR should not exceed 3 (blocks PUSH / DEBUG are not considered, blocks FOREVER are treated as FOR).
  • Number of simple statement in a section of a block should not exceed 7.
  • Number of nested block statements in a section of a block also should not exceed 7.
  • A special block comment
    --------- 'text'
    can separate groups of statements (number of signs '-' at least 2) treated as sections in a block.

 

 

I. 1. c. Comments

  • Multi-line comments */ ... /*,  starting brackets are always placed at the begin of a line (may be following leading spaces) from the symbol
    */
    and finishing with a string
    started from symbols
    /*

  • A text file containing the AL-IV source code is always treating to be started from multi-line comments. A line containing at the end the symbol /* becomes the end of such leading comments lines. I.e. the AL-IV code should be located always between lines with symbols /* and */ (as it would be comments for some C-like programming language around).

See the figure:

 

  • Comment to the end of line (starting from //). Such comments do not finish current statement.
  • Comments starting with two minuses:
    ----------- 'text'
    are used to separate blocks of code onto sections. These also are used in class declarations and in module headers.
    A block comment statement can start a reused block of code (in statements LIKE / REVERT). In such case it should have a modifier REUSED (which is written after the label comma separated). E.g.:
    --------------- 'start extracting', REUSED

 

 

I. 1. d. Case sensitivity for keywords, types and other identifiers

  • KEYWORDS can be written in the UPPER case only.
    CASE if a keyword but case, cAsE and Case - not.
  • {Type names} are enclosed into figure brackets.
  • simple type names are reserved keywords: BOOL, BYTE, INT, REAL, STR (and there no simple types except these 5 listed here).
  • {Class names} are starting from uppercase letters.
  • {enumeration_and_record} names are starting from lowercase letters.

Type names create a name space not intersecting other names at all.

  • CONSTANT names are case sensitive.

It is desired to be written CAPITALIZED.

  • Names of 'CONSTANTS' which are items of enumerations and collections are always enclosed into apostrophes.
  • Variable and field names (of structures and classes) are case sensitive.

So, A and a are different variables (or constants).

Names of Public fields, functions, variables, types are always starting from a capital letter.

All other names are local in a module. (This does not concern local variables which are always local in its functions and can be written from any case).

  • The underscore at the end of a name_ is an integral part of the name (and used as a marker of a pointer to a structure or of a weak pointer to an object).

 

I. 1. e. Naming

 

While declaring a variable, a function, a data type, those things can have two names - a short and a long. The first the short name is written, then, if present, a long name is following separated by the symbol '|'. So there are following rules:

  • The whole name length should not be less then 8 characters: S|ample_val
  • Only one exclusion for variable names: FOR loop variables can have only short name, even short, e.g.:
    FOR x IN M[] : ... ;
  • If a name should be finished by an underscore character (which is a mark for a weak reference in the AL-IV), then both variants of the name should be ended with the underscore, e.g.:
    List_|of_items_[]
  • For class names, records and enumerations the same rule is active, and figure brackets are not accounting in names lengths:
    {My_class|_to_do_something}

 

I. 1. f. Modifiers

 

Many language declarations (module header, statement, variable of data type declaration) can have modifiers.

  • Modifiers are written in form , NAME at the end of a declaration (usually).
  • If there are several modifiers then these are listed via ','.
  • Modifiers are written in the upper case. E.g.:
    CLASS {My|_class}, BITWISE, RECURSIVE :

 

Modifiers do not affect main code semantics.

These just help to compiler to provide more security and sometimes help to optimize resulting code.

If to remove all modifiers in a ready program (and suppose that a language do not require it), this do not change a result of the program (but can take an effect of changing its size / working speed, either increasing or decreasing it).

 

 

I. 2. Assignment

I. 2. a. Simple assignment statement

x = y

where x is a variable or a field of a structure or an object and y is an expression.

 

To compare for equality of two operands a "C"-like operator == is used.

 

I. 2. b. Assignment in combination with arithmetic, logic or bitwise operation

 

Additional operations +=, -=, *=, /=, %=, |=, &=, ^=, ||=, &&= can be used as an equivalent of the assignment of a result of a correspondent operation +, -, *, ... between the variable left to the symbol of the operation and the right-sided expression.

 

I. 2. c. Data sending operations

Operation << is used to append a string to a string or to append an item to an array, or to output text to a current output console.
  • For a string variable A and a string expression B, a statement A << B is equal to A = A B
  • In case of an array A[] and an expression B, statement A[] << B is equal to a function call A[].Add(B)
  • If the left operand is not present, then such statement means writing a string to an output stream (to the console for a console application).

 

A similar operation >> is used as a replacement of a call of a function read for the left operand as a source (and also to add a newly created object to an objects array, see in a section devoted to classes and objects).

  • Input-output operators << and >> can be combined in a single statement in a form:
    << string [>> variable] [>>variable] ...
    E.g.:
    << 'Enter number:' >> Number

 

I. 2. d. Repeating assignment and sending data operators

 

If a statement is starting from a symbol '..' (without quotas), then this represents a reference to the left side of the previous assignment statement. So, three dots are meaning such reference with a following "dot operation" used to either name a field or a method of an object or to name a function giving the operand referred by the '..' symbol as the first parameter.

For such repeating assignment statement (or data sending statement):

  • statements are not counting as simple or nesting (there are no restrictions on such repeating assignments);
  • an expression is fixed to be a destination for following repeating assignments if:

     

    • it is used as the left side of a usual assignment (a = b, a += b, a *= b etc.);
      E.g.:
      Text = New_memo(THIS"TEXT""VH")
      ...Set_width(200)
      ...Set_anchor_bottom(TRUE)
    • it is used as a destination in a usual sending data statement (a << b, a[] << b), including a case when the sending operator is replacing a method Write call;
      E.g.:
      Lines[] << "#!/bin/bash"
      ..[] << "echo run WinE"
      ..[] << "wine"
    • if a pseudo-operator '...' is used in place of a single "dot" operation in a chain of unnamings/indexation operations applied to an expression. E.g.:
      A.Field1[indexA]...Do_something(params)
      ...Color = RED
      ...Foo(params)

      In the example above, in two last lines, the symbol '..' is replaced by a compiler with an expression A.Field1[indexA]

 

I. 2. e. Increment and decrement

 

Operators ++ and -- can be used for integer variables in a postfix form:

  • as separate statements
  • or in array indexes
  • in other parts of expression.
  • Changing the variable value is provided only after the statement is executed.
  • Changing the same variable more then once via ++ / -- in a single statement is not allowed.
  • It is not allowed to use operations ++/-- in an expression containing also operations && / ||.

 

I. 3. Expressions

 
Operator Description Group
- Negation Arithmetic operations (with numbers), result is a number
* / % Multiplication, division, rest of division
+ - Adding, subtracting
IN !IN Check for presence a value in a set or array, check for an object belonging to a class Comparing numbers, strings, classes, result is Boolean or integer (for ??)
LIKE !LIKE Compares two strings without case sensitivity and interpreting special symbols '?' and '%' in the second operand as patterns 'any character' and 'any amount of any symbols'
< <= > >= == != ?? Compares
~ Bitwise NOT Bitwise operations with integers, result again is integer

 

& ^^ Bitwise AND, eXclusive OR (xor)
| Bitwise OR
! Logical NOT Logical operations with Boolean, result also is Boolean
&& Logical AND (&&)
|| Logical OR (||)
~ Internal assignment Always in form (expression ~NName)
  • Concatenation operation has no special sign (use space).
  • If any bitwise operations are used in a module, then a modifier BITWISE must be added for the module.
  • Using both bitwise and arithmetic operations in the same expression is not allowed.
  • bitwise shift and rotate operations are implemented with embedded functions ShiftL|eft, ShiftR|ight, RotateL|eft, RotateR|ight, there are no separate binary operation signs for them.

 

 

I. 3. a. Checking conains of an item

 

Operators IN and !IN can be used in expressions:

  • for checking if a scalar value is present in an array of values (and an array to the right can be either an array variable or a construction in form
    [ value1, value2, ..., valueN ])
  • to check is a substring is present in a string.

 

In case of a constructed array, all its items must be constant expressions. And in case when the left value is a string, it is not allowed to construct an array from a single string value. Either a string should be to the right of IN / !IN ( the case 2).

Operations IN / !IN are not applicable to REAL numbers (still it is not allowed to compare REAL values for equality/inequality).

 

I. 4. Other simple statements

  • To return from a function before its code ends, a statement ==> used.
    • Such statement can be written either separately or following another simple statement (in the same line of code). E.g.:
      CASE R > 0 ? RESULT = R ==> ; ▄▄▄▄

 

  • To terminate or continue a loop, keywords  BREAK and CONTINUE are used, together with a loop label, e.g.:
    FOR x IN [1 TO 100] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        do_something                   
        CASE condition ? BREAK x; ▄▄▄▄ 
    ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
  • It is not allowed to use BREAK / CONTINUE in the FOREVER loop. It is only possible to terminate the FOREVER loop by the return statement (==>) or in result of an exception firing.
  • Using a label in BREAK / CONTINUE statements allows to continue or terminate an outer loop from an inner loop directly.
 

 

I. 5. Conditional statement CASE

  • An expression in the CASE statement should be of Boolean, integer,  enumerated or string type.
  • Expressions of other types (e.g., REAL, record, objects, etc.) are not allowed.
  • The header is always finished by the symbol '?'.
  • In case of the Boolean condition, a block of code follows which is executed in case when the condition is true. And an ELSE block can be following (see below).
  • In other cases each branch is starting from a constant array of constant values in square brackets:
    [array of values]: code
  • Constants (literal or named) should be listed in the array having a type correspondent to the type of the condition in the header.
  • The value can be a single, or it can be represented with a list of values, or by a range X TO Y (which is allowed in case of integer condition).
  • All the items in all sets or arrays must be distinct.
  • In case of enumerated condition, all the enumeration items must be listed in arrays starting branches, and in such case it is not allowed to use ELSE branch.
  • If a common part ELSE is present, it is executed if the expression in the header were not matching any constant listed, and there was no a branch executed.

 

A BREAK statement is not used to finish a branch.

After any branch execution entire CASE statement is finished.

 

Classic example - solving a square equation for real numbers:


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

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

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

 

 

Especial version of the CASE ? statement - with a condition in the header but with sequential calculation of conditions in square brackets until either the first one is found with value TRUE (in such case a correspondent branch is executed only), or until the ELSE part (or the end of the CASE statement) is found.

In such case symbols '?' are written after each expression rather then ':'.


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


I. 6. FOR loop statements

 

 A loop statement FOR enumerating array items - is a single possible controlled loop statement:

 

FOR variable IN array[range] : body ;
or
   FOR variable IN [range]: body ;
 

  • The loop variable of such loop statement is always has the same type as a enumerating array items data type.
  • In case of a range without an array, the variable type is integer;
  • It:
     
    • Does not require declaration.
    • Does not require a long synonym.
    • Can not be used out of the loop, except in another FOR loop.
    • It is used as a label in statements BREAK variable and CONTINUE variable, related to the loop.
  • It is possible to apply an embedded function Index to it (it returns an index of the variable in the enumerating array).
  • In case of empty range all the items are enumerated from the start (index 0) to the end (index *).
  • To indexes change direction and bounds, it is necessary to use a constructed array [X TO Y] or [Y DOWNTO X], where X and Y - are integer expressions.
  • A variable of a FOR loop with a range can not be changed in a loop body (and used as a usual variable).
  • Bounds of indices enumerating are calculated before the loop, and even if size of an array is changed during a loop, this does not affect enumerated indices. As a result, this can lead to violating array bounds, and an array variable will be set to NONE in such cases.
 

To create an unguided infinitive loop, a special block statement is used:
FOREVER : loop body ; ▄▄▄▄

  • The FOREVER loop can be terminated only by a return from the function statement ==>.
  • Statements CONTINUE / BREAK can not be used.
  • A class where statements FOREVER are used, must be marked with a modifier INFINITIVE.

 

 

I. 7. PUSH statements block

A PUSH statement is purposed to create procedural "brackets" with saving some state before and obligatory restoring this state after the block.

 

Restoring is always guaranteed:

  • When the block is left by a BREAK / CONTINUE statement.
  • When a function is left by a ==> (return) statement.

 

Syntax:
PUSH variable = expression : body ; ▄▄▄▄

or:
PUSH variable : body ; ▄▄▄▄

 

  • On entry into the block a value of a variable is saved.
    E.g. in a local stack or in a local dynamic array.
  • Then new value is assigned to it (if specified).
  • When the finishing bracket is achieved (it is executed even in case of a failure), the value of the variable is restored.
  • Any simple or object data type scalar variable or an array item is allowed to be used as a PUSH block variable.
 

A special method with modifier POP(method2) can be called in the PUSH statement. At the end of such PUSH block, the method2 specified in parentheses (of the POP modifier of a method called in the PUSH), is called obligatory.

Such method2 should not have parameters, and it can not be called directly (as well as opening method, having the POP modifier, which therefore can be called in the PUSH statements). E.g.:


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

 


 

I. 8. DEBUG statements block

 

A DEBUG block is intended for a temporary code injection which is required for debug purposes. In such blocks, levels of nesting blocks are not controlled. A compiler is always warning about such blocks not removed from a code. Such blocks should be removed when debugging is finished.

In DEBUG blocks it is possible to declare local variables as usual. But it is not allowed to use such variables out of DEBUG blocks (though it is allowed to use it in another DEBUG block in the same function).


DEBUG : ▄▄▄▄▄▄▄
    body ; ▄▄▄█

 

If there are required additional classes to compile DEBUG blocks, it is recommended to use a special directive IMPORT with the modifier DEBUG.

 

I. 9. LIKE statement

 

(Do not mean the comparison operation between two strings in an expression!)

 

The LIKE statement is intended to insert earlier written code without changes in a position of the LIKE statement. An inserted code should be placed between two block comments (on the same nesting level). A label of the first block comment (together with a function name) is used to identify the code to insert. There are rules:

  • A reused block of code should be marked with a modifier REUSED.
  • If a block is inserted from another function, the name of the function is written following the LIKE keyword.
  • A multi-dot is written next (amount of dots is not restricted, at least three should present).
  • It is possible to insert code only from the same class.
  • Inserted code should be located above the LIKE statement.
  • An inserted block should not have LIKE statements itself (recursion or nesting are not possible like in macro).
  • An inserted code is injected "as is", its syntax and semantics are controlled only while compiling the LIKE statement. So do not duplicate variable names, declarations etc. to avoid compiler errors.
  • A reused block can be inserted so many times as it is needed, without restrictions.
  • For a time while a block is inserted by the LIKE statement, a counter of nesting FOR/CASE levels is reset to zero, so there are no problems of total nesting level exceeding allowed amount (3 nesting levels).
 

An example:


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

 

Additionally, while calling the LIKE statement, it is possible to replace some fragments of code with other:

 

LIKE [method].......'label', BUT (pattern1 ==> replacement1) [*N1] [, (pattern2 ==> replacement2) [*N2] ]

 

Lists of replacements are enclosed into parentheses and listed via comma. A replacement should have a multiplier following its right bracket in form * number if there are several such patterns in code. The multiplier can be omitted only in case when only a single such pattern is there.

All the replacements are done just before compiling the LIKE statement having such replacements defined.

Both pattern and replacement strings can be enclosed into apostrophes. Only the string right to the symbol ==> can be empty, but patterns always should not be empty strings.

 

And please remember:

  • It is better to use LIKE ..... 'name' then just to copy/paste a code.
  • BUT It is better to move a common code into separate functions if this is possible (then to use LIKE).

So do not overuse the LIKE statement without necessity.

 

I. 10. REVERT statement

 

The REVERT statement is intended to insert earlier written sequence of assignments reverting its direction of assignment in a position of the REVERT statement. An inserted code should be placed between two block comments (on the same nesting level). A label of the first block comment (together with a function name) is used to identify the code to insert. There are rules:

  • A reused block of code should be marked with a modifier REUSED.
  • If an inverting code block is from another function, the name of the function is written following the REVERT keyword.
  • A multi-dot is written next (amount of dots is not restricted, at least three should present).
  • It is not possible to revert code from another class.
  • Inverted code should be located above the REVERT statement.
  • An inverted block should contain only assignment statements, in those destination and source parts can be reverted (at least using correspondent getters / setters).
  • An inverted code should not use local variables (including parameters and a special variable RESULT).
  • A reused block can be inserted so many times as it is needed, without restrictions.
An example:
 

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

 

 

II. Variables, constants and data types

II. 1. Simple data types

II. 1. a. Names of data types, data types classification

 

There are predefined simple data types which can not be redefined:

 

  • BOOL - Boolean data type, with values TRUE and FALSE.
  • BYTE - byte data type, with values from 0 to 255. Using bytes in a class requires adding a modifier BYTES to the class header.
  • INT|EGER - integer numbers of usual precision.
  • REAL - real numbers with default precision.
  • STR|ING - string type (chain of characters).

 

For data types a precision is never specified:

  • For variables of base data types always a precision is used which is defined by a compiler developer (or controlled by project options, if the compiler allows to do so).

 

 

 

 

II. 1. a. Writing constants of base data types

  • Integer constants in decimal base system: [-|+]<0...9_>...
    1_345 = 1345
    (underscores are ignored and can be used to separate groups of digits).
  • In binary system:
    0b_0100_1010_0011_1001
    (Case of a letter used to define a base, is ignored: B=b, G=g, X=x)
  • In hexadecimal system (x=X):
    0xFFEA_d44a
  • In octal system (only small letter 'o'):
    0o_7_342_001
  • Real constants:{0...9}.{0...9}[e[+|-]{0...9}]
    E.g.:
    -2.73e3
    11_E-04

  • String constants: "{symbol except "}"
    where symbol is any printable character or space,
    double quotas are never written. To write it, it is necessary to use a symbol constant #QU). E.g.:
    #QU "This text is enclosed into double quotas" #QU
  • To concatenate character constants and literal strings there are no operation symbols using.
    E.g.:
    "This text is finished by a caret return" #CR
  • If a line of code is starting from a quotation symbol, the line is a continuation of the previous line. So, to write a very long string constant, not fit into a single line of code, it is sufficient to divide the string onto two (or more) strings, and write its continuation in another string. E.g.:
    "This is very long string of text. It is so long that "
    "it can not be written in a single line of code."

  • If a string constant is starting from a symbol '@', then for all continued lines symbol #NL is inserted in between, so new line characters are inserted into a long string constant in places where the line is continued in next lines. E.g.:
    @"This string constant contains 3 lines: the 1st,"
     "the 2nd,"
     "and the 3rd"

  • A symbol @@ before a string literal cancels previous symbol @ - until the end of the concatenation of strings or until the next symbol @.
  • As a continuation of a string constant in a separate line of code a literal in form ''text'' can be used (bounded with doubled apostrophes). Such string literal can contain any characters except a combination [''] (doubled apostrophes).

 

 

II. 1. c. Enumerations

 

A enumerated data type defines a set of named integer constants. The set itself is also named to make it possible to refer to it by the name.

  • In enumeration declaration, its items are written comma separated. A declaration:
    ENUM {names|_of_enumeration} : values .
  • A enumeration type name is always enclosed into figure brackets and starts from a lowercase letter.
  • Each enumeration item name is enclosed into apostrophes.
  • An item name can have short and small form as usual (separating short name and the rest part by a symbol '|').
  • Names must be unique in the class (among all the enumerations in the class).
ENUM {color|s_of_traffic_light} : 'R|ED_COLOR',
                                  'Y|ELLOW_COLOR',
                                  'G|REEN_COLOR' .
 

Operations on enumerations:

  • It is not allowed to assign an integer value or value from another enumeration to a variable of such type.
  • Value of variable (or constant, or expression) of a enumerated type can be converted to an integer using embedded function Int.
  • Back conversion of a numeric value to an enumerated type is not possible except via a custom function (e.g. using CASE statement).
  • Embedded function Name returns for an enumerated item its name by value (the first name of an item if there are several there).

 

 

A enumeration can be used as a range for indexes of a fixed array:
STR LName|s_of_lights[{color}]

In such case the enumeration items only can be used as indexes of such fixed array:
LName['R'] = "Red color"

 

Enumeration items are not ordered and can not be compared using operations <, <=, >, >= (only == and != are allowed). Enumeration items can form a constant array, and for pair "item - array of items" it is possible to use operations IN and !IN:
CASE A IN ['R''Y'] ? ... ; ▄▄▄▄

In case of naming conflict between enumeration items from different enumerations, those should be qualified using names of enumerations:
{enumeration_name}.'EUMERATION_ITEM'.

In case when names of enumerations are matching, a class name should also be added to a specification:
{Class_name}.{enumeration_name}.'EUMERATION_ITEM'.

 

 

When a CASE statement condition has a enumerated type:

  • it is not allowed to use the ELSE branch
  • all the items from the enumerated data type must be listed
  • more then a single constant can be used in a single branch:
    CASE Greek ? ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        ['ALPHA''BETHA']: ...                               
        ['GAMMA''DELTA']: ...                               
        ['EPSILON']: ...                                      
        ['DZETHA''ETHA''TETHA''IOTHA''KAPPA']: ...    
        ... ; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 

II. 2. Variables and arrays

II. 2. a. Declaration of variables

 

A declaration of a variable is starting from a specification of its data type in {figure} brackets (or from one of reserved data type names: BOOL, BYTE, INT, REAL, STR).

  • When a variable is declaring its full name should be at least 8 characters length.
    • EXCLUSION: Loop variables (FOR) name length is not restricted.
  • Short part of the variable name is separated from the rest of name with a symbol '|'.
    • For dynamic arrays brackets are empty.
    • For fixed arrays indexed by integer index, a constant is specified defining the array size (index of the last item + 1).
    • For fixed arrays with indexes from a enumeration, a name of the enumerated type is specified, in figure brackets as usual:
      BOOL My_arr|ay_of_flags[{traffic}]
  • For arrays its dimension is following in [square] brackets.
  • Then variable MODIFIERS are following if these are necessary (READ, INIT, MAXLEN[n]).
  • A declaration can continuing with an assignment of an initial constant value, if this is suitable (not in all cases, see about classes, structures, global object variable).

 

A variable always has an initial value. If there are no explicit initial value specified, a default initial value is set.

  • For an object, this is NONE-value of a correspondent class (see "Classes").
  • For a string this is an empty string.
  • For a dynamic array this is an empty array.
  • For enumerations, collections, numbers this is arithmetic 0 value (for enumeration types this means always the first item in the enumeration).
  • A structure is initialized with a structure having zero-valued fields.

 

 

Naming variables rules:

  • A short name is in identifier of 1 or more characters,
  • Along name is an identifier from 8 to 80 characters,
  • In the code both short and long names can be used equally.
 

Case of letters in a variable name:

  • A Public variable or class field name is starting from an uppercase letter (and can have modifier READ making it read-only).
  • CONSTANTS usually contain only capital letters.
  • private variables and class fields usually start from lowercase letter.

 

II. 2. b. Field modifiers

 

If some modifiers are required for a variable, these are written in a declaration following it names and dimensions.

  • READ|ONLY - is used for module level variables, fields of classes (and also for function parameters).
    • For a module variable it enables only reading the variable from other modules.
    • For a class field, it becomes read only except its own module and code of methods of inherited classes.
  • INIT|IALIZE - for a class field, it is required to be initialized while creating an object (see "Classes").
  • MAXLEN|GTH[n] - for record fields, such modifier allows to specify a maximal number of characters in a fixed string. Only fixed strings with such modifier can be used in records. Class having fixed strings and operating with such strings must have a modifier SHORT_STRINGS in its header.
  • DEPRECATED('text') - such field is deprecated though it is still is supported. Text should contain information about an alternative (this can be not only a filed, but a function, or another class etc.) It is recommended to use an alternative as quick as possible, still in following versions the field can become unsupported.
  • ABANDONED('text') - the field is abandoned. Using it leads to a compiling error. The text should contain information about an alternative.

 

 

Additionally to explicit modifiers above, there are also implicit variables modifiers:

  • A function parameter declared in the function header but not used in the function body, should have a substring "dummy" temp in its name (in any register case).
  • A variable declared in a function body and (even if a value was assigned to it) not used in expressions also should have the substring "dummy" in its name.
  • Object local variable, to which a new object variable was assigned, which were not added to an array of strong references, has not an owner specified, should have a substring "temp" in its name (in any register case). E.g.,:
    load|er_Temp = {Text_file}(Path = path)

 

II. 2. c. Named constants declaration

 

Constants can be declared only on a module level, and only of embedded simple data types (BOOL, INT, REAL, STR).

  • Declaration of a single constant in a single line:
    CONST type_name : CONSTANT_NAME = expression .
  • Declaration of several constants:
    CONST type_name :
        CONSTANT_NAME_1 = expression
        CONSTANT_NAME_2 = expression
        ...
        CONSTANT_NAME_2 = expression .

 

It is recommended to use uppercased names for constants.

 

II. 2. d. Arrays declaration

 

Array dimensions are written in square brackets following the variable names.

  • An array variable can be fixed or dynamic.
  • Dynamic array always has a single dimension.
  • In square brackets nothing are written for a dynamic array:
    <str> Lines | Lines_if_text[]
  • Fixed array can have several dimensions.
  • For each dimension of a fixed array a constant expression is written in square brackets. Its value is an array size (it is greater by 1 then an index on the given dimension).
    An example (an array 3x4):
    <real> A | Array_of_values[3][4]
  • While using an array even as a whole object all its square brackets are listed always.
    E.g., A[][] for a two-dimensional fixed array.

 

 

Initial values for items of arrays are always provided as well as for all other variables.

  • A dynamic size array initially is empty.
  • A fixed array of class objects initially is filled by NONE objects of the correspondent class.
  • A fixed array of numbers, enumerations, collections is initially filled with arithmetic zeroes.

 

 

 

II. 2. e. Array constructor

 

An array constructor is a sequence of items in square brackets in a form: [ value, value, ... value ]

 

An array constructor can be used as the second operand of an operation checking if the first operand is present in the second array A IN B (and in the opposite case: A !IN B).

 

II. 2. f. Using arrays

 

Main specific operations with arrays:

  • Reading and writing items of an array. In the left side of an assignment statement or as an operand of an expression a name of an array is specified and for each of its dimensions an expression is written in square brackets, which represents an integer index of an item of the array.
    E.g.: A[i][1] = B
  • Adding an item to the end of a dynamic array with an operation <<, e.g.: X[] << Value
  • Calling one of embedded functions designed under dynamic arrays::
    • X[].Add(s)
    • X[].Insert(i, Value:s)
    • X[].Delete(i)
    • X[].Delete_range(i, Count)
    • X[].Remove(R)
    • X[].Find(v)
    • present = R IN X[]
    • CASE X[].Find(R) < 0 ? not_found ;
    • count = X[].Count
    • X[].Clear
  • When an item is read out of array bounds, then a NONE value of correspondent type is returned.
  • Assigning a value out of array bounds is ignored.

 

Passing an array as a parameter to a function:

  • Square brackets for an array parameter (dynamic or fixed).
  • In place of a dynamic array only a dynamic array can be passed.
  • In place of a fixed array formal parameter only a fixed array or a range of a dynamic array can be passed:
    A[first TO last]
  • To specify a formal parameter of a function as a fixed array with integer indexes, the symbol '*' is written in square brackets.
  • In place of a fixed array with indexes given by a enumerated data type, only a fixed array with same type of indexes can be passed only.

 

It is not allowed:

  • Assigning an array as a whole variable.
  • Returning an array as a result of a function.

 

II. 3. Functions

II. 3. a. Function header

FUNCTION names (parameters) ==> result_type , modifiers :
    statements
.

 

FUNCTION names (parameters) ==> result_type , modifiers :
  • A static function is starting from a keyword FUNCTION or FUN.
  • A class method is starting with the keyword METHOD.
  • If the method is overriding its predecessor method from its class ancestor, the keyword OVERRIDE is used.
  • For a constructor and a destructor, a keyword CONSTRUCT or DESTRUCT are used, correspondently (and in such case there are no function name(s), parameters, result types and so on - just a colon symbol.
  • To redefine standard operations +, -, *, / special form of a function is used starting from the keyword OPERATOR.
FUNCTION names (parameters) ==> result_type , modifiers :
  • Functions can have two or more names (at least one name should be not less then 8 characters).
  • If a function has two names, the short name is written first, than after a separator '|' - the rest of the name. When a method of a predecessor class is overriding, only the first (short) name of the method overriding is specified.
  • Public function name starts from an uppercase letter. To make public a method or function which name is starting from a lowercase letter, use the modifier PUBLIC (see below).
  • Function accessible only in the class itself (and its descendants) is named from a lowercase letter always.
  • OPERATOR has not names, and its parameters are specified in the form TYPE OPERATION TYPE (or, for unary "-" operation, in the form OPERATION TYPE), and so its parameters definition is used as its unique name (it is allowed to redefine operators several times for the same operation but with different types of parameters).
  • It is possible to declare a single no-named method in a class (in the header the dot is used in place of name). Parameters for such method are enclosed in square brackets. If a setter is defined for such method, then the method call can be used in the left side of assignment statements.

 

 
FUNCTION names (parameters) ==> result_type , modifiers :
  • Parentheses must be always present.
  • For empty parameters list empty parenthesis are written.
  • Amount of parameters is not restricted but if at least one function declared in a class has more then three parameters, the class must have a modifier BAD.
 

Parameters are separated with comma.

  • First a type of a parameter is written, then its names, then optional: dimensions, modifiers, default value.
  • Parameter can have two names (short name is separated from the rest of the long name with the symbol '|' as usual) .
  • Full parameter name should be at least 8 characters length.
  • Parameter names should start from the uppercase letter.
 

For an array parameter its dimensions are listed following its names.

  • For dynamic array, square brackets are empty.
  • For fixed arrays with integer indexes, the symbol '*' is written in brackets.
  • For fixed arrays indexed by a enumeration, the name of the enumerated type is written, in figure brackets as usual, e.g.:
    A|rray_of_color[{color}]
 

Parameters can not have its own modifiers.

Parameters which are not used in the function body, should have a substring "dummy" in its name (in any registry case).

Scalar parameters are always passed by the value (formally) and can not be changed in the function body.

 

Parameters can have restrictions on values passed or be used in restrictions to other parameters. Parameters can be restricted with lists of constant values of ranges independently, or dependently from values of other parameters passed.. See function modifiers RESTRICT, IF, THEN (below).

 

For operators, input data types are its parameters. At least one of input data types should be a class or a record. In the operator body (which is always a single expression) to refer to that type parameters, letters A and B are used (though there are no such letters on the operator header). The "A" letter is used to refer to the first parameter, "B" - to the second. In case of redefining the unary operator "-", the "A" letter is used to refer to the single input data type.

 
FUNCTION names (parameters) ==> result_type , modifiers :

If a result type is not specified (together with the symbol '==>'), then the function is not returning a result.

  • To assign a result to a function, an assignment to a pseudo variable RESULT is used.
  • As well as for other local variables, the RESULT variable is initialized initially with a NONE value:
    • NONE - for objects,
    • 0 - for numbers,
    • "" - for strings,
    • first item - for enumerations,
    • FALSE - for BOOL data type.
  • A function can not return an array as a result, only a scalar.
  • Special symbol ==> is also used as an operation of returning from a function. It can follow any simple statement (assignment, function call, etc.)

 

A sample of a function counting number of dots in a string:
FUNCTION Ndots|_count (STR S) ==> INT :
    FOR i IN [0 TO S.Len-1] : ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
        CASE S[i] == "." ? RESULT++ ; ▄▄▄▄▄▄ 
    ; . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

 

FUNCTION  names (parameters) ==> result_type ,modifiers
Function modifiers are listed via comma. Following modifiers are possible:

 

  • RECURSIVE - function is recursive. If a recursive function does not call itself and it is recursive via a call to a function which is already marked as recursive, the modifier can be omitted.
    • If too deep recursion is detected (usually more then 128 call levels), then execution is returned to the first recursive call level and a function called on that level is ended (returning NONE value corresponding to its result type - if the function should return a value).

     

  • NEW - function is returning a new object instance.
    • At least one statement in the function is an assignment of a new object creation (or a result of calling another function with the NEW modifier) to the pseudo variable RESULT.
    • A function with the NEW modifier can be called only separately (not as an ordinary operand in an expression), and it is necessary either to assign a result to an owning object reference, or to provide a correct owner by some other way (see "Classes" section).

 

  • REPLACE - for an overridden virtual method of a class not returning a value, to inform that the function does not call a base method, so it is totally override. If there are no such modifier for such kind of an overrode function, then it is requred to call a base method (using operator BASE).
  • NATIVE - a body of the function is a string constant containing a code inserted into resulting (compiled) function as is. (Either a function body is ending with a statement NATIVE "string").
    • A class, having NATIVE functions must have the modifier NATIVE.
  • SETTER FOR identifier - function is a setter for a field or a getter method specified by the identifier. Such field or method must be declared just before the setter function. In case of a field, a setter should have a single parameter of the same type as the field. For a case of the getter method, the setter should have number of parameters greater by one then a getter. The last parameter should have the same type as a result of a getter, all other parameter data types should match for getter and setter.
  • CALLBACK - function is intended to call it from the class code (and native functions) only, not for direct call of it from the customer code (even not from descendant classes of the class containing the callback function). E.g. an event mouse_down in a form: it is necessary to override the method rather than to call it in your form implementation class.
  • POP(Method2) - function is intended to be called only in the PUSH statement. When such PUSH block is ending, the Method2 specified in parenthesis is called anyway (the Method2 should not have parameters, and it can not be called any other way except this).
  • DEPRECATED('text') - the function is deprecated and it is necessary to use an alternative specified in a 'text'. In further versions of a class it is possible that the function will not be supported (and become abandoned or removed).
  • ABANDONED('text') - the function is no more supported, use an alternative specified in the text in apostrophes. Calling such function leads to a compilation error.
  • STORE(Parameter=value) - creates a special "invisible" parameter of an integer data type, which creates on a caller class side correspondent integer variable (separately for each call to the function). Such variable is passed implicitly by reference at call. See more detailed in the Additional section.
  • FORGET - When such method is called, all the stored invisible values in the caller object are reset to its initial values. See details in the Additional section.
  • TRAP - a method is intended to use as a debugging trap, which is called when some event on data change occur for a class field. For a field, up to three traps can be set (for operations: read, write and add/insert/delete a value to a dynamic array field). A list of traps installed for a field is specified in its modifier TRAP(trap1, trap2, trap3). A trap type is defining on base of its parameters:
    • for a scalar field, method without parameters is called when the field is accessed for read operation, and a method with a single parameter is called before changing the field, in such case the parameter should be of the same type as the field and it receives a new value assigning to the field;
    • in case of an array field, an INT parameter is added where an index of an item accessing is passed; in case when the first parameter is of type BOOL, the trap is called before delete / after insert/add operations, and the first BOOL parameter receives TRUE in case of add/insert operation, or FALSE in case of delete operation.
  • RESTRICT name IN [list] or
    RESTRICT name in {constants_block_name} or
    RESTRICT name IS CONST - restricts a parameter with the given name: it can be only a constant (in the first case only from the list of values specified).
  • IF name IN [list] - a function parameter with the given name is used to restrict values of another parameter (specified in the next restriction modifier THEN). A list can be replaced with a name of a constants block in figure brackets (see CONSTANT statement definition).
  • IF name IS CONST - if a function parameter with the given name gets a constant, then the following restriction (modifier THEN) is active.
  • THEN name IN [list] - a conditional restriction of a given parameter by only values specified in the list (a condition is specified by the previous IF modifier).
 

In case when some redefined operators are used in the function body, it should have the modifier OPERATORS(list of classes). This does not depend on kind of a function (FUNCTION, METHOD, CONSTRUCTOR, OPERATOR, etc.) A list of used classes should be specified in parenthesis, in the order in which classes are enumerated while searching an appropriate operator.

 

II. 3. b. Function body

 

Function body consist of statements located and it is finished by a dot symbol.

  • On any block level the body should not have more then 7 simple statements, 7 declarations of local variables and 7 block statements.
  • A block can be separated by comments
    ---------------- 'label'
    (not more then 7 such comments for a block), in such case counting statements of different kinds starts again.
 

If the requirements above are not satisfied. then it is necessary to mark entire class with the modifier BAD (in the class header).

 

In case of a single assignment statement, in which an expression is assigned to the pseudo variable RESULT, the RESULT keyword can be omitted, and entire function body has a form
: = expression .

 

II. 3. c. Calling functions

 

Parenthesis are used only if actual parameters are present.

  • Actual parameters are listed in parentheses comma separated in the same order as correspondent formal parameters are declared.
  • Object parameter is passed implicitly. It can be used explicitly via a pseudo-variable THIS.
  • If a function is returning a result, it can be called in a separate statement not assigning returning result. If the result is not required, it should be "assigned" to a NONE, e.g. in form:
    NONE = object.Function(parameters)
  • Any static function can be called using a prefix style, when its first parameter is moved to a function prefix, e.g.:
    x.Sin - instead of Sin(x)
    s.Lower.Ending(".al4") - instead of Ending(Lower(s), ".al4")

 

III. Classes and objects

III. 1. Classes

III. 1. a. Class declaration

 

Syntax.

 

/*
CLASS
{names}, modifiers :
    IMPORT: {Class1} ... ;
    BASE CLASS {Base_class}
    field1
    field2
    ...
    method1
    method2
    ...
    --------------- 'section label1'
    ...
    --------------- 'section label2'
    ...
END

HISTORY

...


*/

 

One file contains exactly one class definition.

All other declarations (enumerations, functions, constants, fields) are located only  in classes and belong them.

The are no global (static) class fields, all the fields are members of certain objects.

 

  • The AL-IV code is always located between lines containing symbols of the end and the start of multi-line comments: /* and */.
  • Class names are enclosed into figure brackets.
  • Class names always start from an uppercase letter.
  • Class name can be separated onto two parts by the symbol '|'. Before the symbol a short name of the class is defined, after the '|' symbol - the rest of a long name of the class.
    E.g.: {My|_class} defines short name {My} and full name {My_class} for the same class.
  • A long name must not be shorter then 8 characters.
  • Class file name (w/o extension) should much the long class name (w/o figure brackets). But violating this rule leads only to a warning, not to an error.

 

The main part of a class declaration consists of declarations of fields and methods.

  • Fields of a class are declared in form
    {type} names = value, modifier
  • Type - is one of system simple types BOOL, BYTE, INT, REAL, STR or a class name in figure brackets (its name is starting from an uppercase letter) or a enumeration or a record name in figure brackets (name is starting from a lowercase letter).
  • If field and method names are starting from an uppercase letter, then the name is Public.
  • If the first letter of a name is in lowercase then the name is hidden. Such methods and fields are accessible only in the module where the class is declared, and in inherited classes.
  • Groups of declarations in a class can be separated onto sections with a separator in form:
    ----------- 'separator label'
    (at least two '-' signs)
  • Each section should not contain more then 7 fields.
  • Function declaration also ends a group of declarations.
  • If the rule "maximum 7 fields in a section" is not satisfied, then such class must be marked with a modifier BAD.

 

 

III. 1. b. Inheritance, modifiers

 

To specify an ancestor for a class its declaration is written in a form (immediately after IMPORT):
BASE CLASS <Base class name 1>

  • If a construction BASE CLASS ... is absent, then the class has no other ancestors except the common for all classes unnamed class {}.
  • If the are methods in a class which are overriding correspondent methods of a base class then all those methods are declared starting from a keyword OVERRIDE:
    OVERRIDE some_name(paramer) ==> result_type:
                    ...
    .
  • To assign other initial values to fields of a base class and to do something else on a create time of an instance of a class, a procedure is used
    CONSTRUCT :
                    ...
    .
  • To do something special when an object is destroyed, a procedure is used
    DESTRUCT :
                   ...
    .
  • If a destruction procedure is used in a class it is necessary to add a modifier DESTRUCTORS to the header of the class.

 

 

When a method is redefined in a class:

  • A prefix OVERRIDE is used instead of the METHOD.
  • Only one name of the method is specified.
  • All the parameters of a base method are listed (in parenthesis, comma separated). First letters of correspondent parameter names must much. Types must be compatible by types and by dimensions (for arrays).
  • For methods returning a value:
    • data type of a returning value is specified.
    • An example:
      OVERRIDE enabled ==> BOOL :
            RESULT = o.Edit1.Text != "" .
       

A redefined method can call a base method (the same named method of the base class).

  • To call explicitly a base method in a body of a redefined method a syntax is used: BASE.
  • All the parameters are passing by a usual way, and the object itself is passed implicitly as usual : Base(parameters).
  • A method not returning a value, should at least once call a base method (usually, at the start), else it is necessary to add a modifier REPLACE to the overriding method.

 

 

A modifier for a class is written following the class names. Such as:

  • ABSTRACT - the class is intended only for deriving new classes on base of this class, it is not allowed to create objects of the class itself.
  • BAD - the class is not satisfying requirements on code style (not more then 3 parameters for a function, not more then 3 nesting levels of block statements, not more then 7 fields in a section of a class, not more then 7 simple statement + 7 block statements in a section of a block in a function code).
  • BITWISE - bitwise logical operations between integer values are used.
  • BYTES - bytes are used in the class.
  • DESTRUCTORS - the class has a destructor.
  • NATIVE - there are native functions in the class.
  • OPERATORS - there are operators redefined.
  • RECURSIVE - there are recursive functions in the class.
  • STOPPING - a STOP operation is used in the class.
  • UNTESTED - there is an untested code in the class.
  • SAFE - all is OK, class is tested fully and there are no warnings on a safety or style.
  • DEPRECATED('text') - the class is still supported but its using is deprecated. The text in apostrophes contains information about an alternative which should be used as soon as possible still in future the class can become not supported.
  • ABANDONED('text') - the class is abandoned. Any reference to it leads to a compilation error. The text contains information about an alternative.

 

 

III. 1. c. Objects. Strong and weak references

 

A variable of a class type (a class instance) is an object of the class.

  • Objects are declared by specifying a correspondent class name in figure brackets as a type name of the variable.
    E.g.:
    {My Class} M|y_object = {My_class}
  • A field or a variable not having trailing underscore characters in its names, is a keeping reference to an object.
  • If an object_ name has a trailing underscore character, it is defined not to own an object but to be just a weak reference to to another object.
  • Such weak object reference variable links to it just while it exist and there are object variables referencing such object strongly (or an owner of the object still exists).
  • All the local variables in a function are strong references always.
  • When a count of strongly referencing variables becomes zero, the object is destroyed and all the weak pointers start pointing to NONE value immediately.
  • If at create time for an object an owner was specified explicitly (OWNED BY), then presence of strong references to it has no effect: it will be destroyed together with its owner, and at that moment all the references to it, either strong or weak will be redirected to the NONE object.
  • NONE - is a special object of a class, having all the object fields equal to NONE (and 0 for numbers, "" - for strings, etc.). Writing to fields of such pseudo-object is always ignored, reading is always returning NONE and 0 values, calling its methods does not lead no any valuable operations.
  • All the object variables initially are set to NONE, and if an object is read out of bounds of an array, also NONE is returned.

 

THE RULE
  • A field FA of a class A which is a strong reference to an object can not belong to a class B which is a direct ancestor of the class A.
  • If a class A has an object field of class B which is a strong reference, then the class B can not have object fields which are strong references to class A
    or other classes which are direct ancestors or inheritances of A.
  • When a class A1 references strongly an object of a class A2, and the class A2 references strongly an object of a class A3 and so on, and a class A(n-1) references strongly an object of a class A(n), then the class A(n) can not contain object fields which are strong references to any of previous classes A1, A2, ..., A(n-1).
  • It is not possible to refer with a strong reference to an object of the same class, in part.
  • As a result, it is not possible to create circled chains of strong references of objects to each others.
  • Different kinds of peer networks of objects, graphs, lists etc. can be easy formed using weak_ only pointers.

 

Just created object should be either
    assigned to an external (for a function) object variable without an underscore at the end of its name (i.e. a strong reference to it should be created),
    or it should be added to an external array of strong references (an operation , >> array[]),
    or an owner object should be specified (an operation , OWNED BY var_ref),
    or it should be assigned to a (local) variable which has substring 'temp' in its name (in any case, e.g., Example|_TempVar).
  • A variable is an external to a function if it is:
    • either is a field of the class itself,
    • or it is a field of an external variable,
    • or it is a pseudo-variable RESULT,
    • or it is a field of the RESULT,
    • or it is a field of an object parameter.
  • If a newly created object at least once in a function body is assigned to the RESULT variable, then the function should be marked as NEW.
  • Function marked as NEW can be called only in a standalone statement like a statement where a new object is created directly. And the new object created with a function marked as NEW, also must be assigned to an external variable, added to an external array or assigned to a temporary variable.
  • For a NEW function result it is not allowed to use OWNED BY instruction.

 

III. 1. d. Methods (events)

 

All the class functions are virtual.

  • The is a polymorphism there: calling a method leads to call a method assigned for a certain class instance, representing an object.

 

 

III. 2. Working with objects of classes

III. 2. a. Creating an object instance

 

Object construction:

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

  • If a value is assigned to a variable in a declaration statement and actually the same type object is created as the variable declared, then type name of the declared variable can be omitted:
    stream|_read_temp = {File_stream}(
           Path = source_path, Mode = 'READ')
  • If fields initializations are not necessary, parentheses are omitted.
  • If an operation OWNED BY expression is followed (comma separated), this assigns an owner for the object created.
    • For such object, its strong usage counter does not affect its life time: it exists until its owner is destroyed.
    • If at the creation time the object assigned as an owner is referencing NONE, then the object owner is not assigned and it is most possible that the object created will be destroyed on a function end.
  • Either an operation of adding the object created to an array of strong references may follow (comma separated):
    , >> variable[]
  • An object created should be either assigned to an external (for a function) object field or variable,
    or it should be added to an external array of strong references using postfix operation: , >> array[]
    or an owner should be assigned to an object using postfix operation: , OWNED BY value.

    In other cases a variable on the left side of an assignment should have a substring 'temp' in its name.

 

A direct creating of an object become impossible if a field is present in an object, which is mandatory to initialize (has a modifier INIT|IALIZE), but it is not accessible (hidden for other modules) or made read-only using a READ modifier. In such cases the only way to create such objects is using a special construction function.

 

III. 3. Class level operators

 

The first in a class (following the class header) should be specified a statement IMPORT if it is required. In an import statement classes are listed which are used (including an ancestor class if the class is not descending from the common class {Object}).
IMPORT : {Class1}, {Class2}, ... ;
(the semicolon or the dot can be used to end the IMPORT statement).

 

If the class has an ancestor in its hierarchy, this should be specified with the statement BASE CLASS {Name}, but after the IMPORT statement where the ancestor class is listed.

 

A class is always ended with a standalone statement END located in a separate line.

 

Before the END statement following statements/blocks can be found:

  • enumeration declarations
    ENUM {name|detailed} :
         'NAME|DETAILED1'
         'NAME|DETAILED2'

         ...  . 
  • structure definitions
    STRUCTURE {name|detailed} :
           Field1
           Field2

           ...   .
  • table definitions
    TABLE Name|detailed : {structure}
        NAME "name"
        COUNTER(list)
        NOTNULL(list)
        NAMES(field = "name", ...)
    .
  • field declarations
    TYPE Name|detailed [size] = initialization, modifier
  • constructor declaration
    CONSTRUCT:
        BODY .
  • destructor declaration
    DESTRUCT:
        BODY .
  • static functions declarations
    FUNCTION Name|detailed(parameters) ==> RESULT_TYPE, modifier1,
    modifier2,
    ... :
        BODY .
  • methods declarations
    METHOD Name|detailed(parameters) ==> RESULT_TYPE, modifier1,
    modifier2,
    ... :
        BODY .
  • overridden methods
    OVERRIDE Name(parameters) ==> TYPE, modifier1,
    ... :
        BODY .
  • test functions
    TEST Name|detailed (parameters) ==> TYPE :
         BODY .
  • operator redefining
    OPERATOR TYPE OPERATION TYPE ==> TYPE :
         EXPRESSION .
  • block comments
    --------------------------------- 'text'
  • native code directives
    NATIVE: "code specific for a platform" .
  • to-do statements
    TODO "text" .

 

III. 4. Class fields modifiers

 

Class fields can have modifiers listed comma separated following its declarations.

 
  • PUBLIC - a field is forced to be public even if its name is starting from a lowercase letter (by default it is sufficient to name a field starting from a capital letter to make it public). Non-public (hidden) field of a class can be visible only for the class itself methods, its descendants and its friends;
  • READ or READONLY - a field is declared accessible "only for read". Such field can be changed only by methods of the class itself, by its descendants and friend classes;
  • INIT or INITIALIZE - a field requires to be initialized due to object creation. If to create an instance of a class a standard constructor is used in form
    destination = {Class_name}(Field1 = value1, Field2 = value2, ...)
    and such field is not listed in the assignments list in the parenthsezes, this leads to a compiling error. If an  INIT-field is also a read only field, then such explicit constructor can be used only in methods of the class itself, of its descendants or its friends;
  • TRAP(method, ...) - specifies a list of trap methods for a field (up to 3 traps for a field); it is possible to list only methods of the class itself which do not return any result and:
    • if the field is scalar, then it is allowed to use methods without parameter (a read trap), or methods with a single parameter having the same type as the field itself (a writ trap which is called before assigning a new value, which is passed in the parameter);
    • if a field is an array then it is allowed to use following combinations of parameters:
      • (INT) - a trap for read an item with an index (specified by the parameter);
      • (INT, {field type}) - a trap for writing a value into an array item (with a certain index specified by the first parameter, the value assigning is passed as the second parameter);
      • (BOOL, INT) - a trap on changing array size: the first BOOL parameter is TRUE in case of add / insert operation (and such trap is called after insertion operation); in case of delete operation the first parameter receives the FALSE value (and delete trap is called before deleting the value).
    • traps are not purposed to modify program logic, and it is not correct to modify values assigning or reading (though it is still possible to change array size - but do not do so). A task for traps is to simplify searching problem operations while debugging only;
  • DEPRECATED('text') - field is deprecated, you are warned if use it;
  • ABANDONED('text') - field is abandoned, the compiled fires an error in case when it is used in code.

 

III. 5. Ending class. History. DATA[] array.

 

A class is ended with the directive END. If following the END statement the first non-empty line starts with the keyword HISTORY, then the history of changes of the class is located there. Independently of changes history present or not, all the lines after the END directive if these are not empty become accessible from the class code via the pseudo-array of strings DATA[].

 

If the HISTORY directive is present, then the history should be in sufficiently strong  form. It consists of blocks CREATED / UPDATED (where the CREATED can be the first and a single only if it is present). The block header format is:
( 'CREATED' | 'UPDATED' ) '(' YYYY-MM[-DD] ')' [ ',' 'VERSION' '"' text '"' ['BY' '"'text'"'] ':'

The block consists of messages ADDED / CHANGED / FIXED in form:
( 'ADDED' | 'CHANGED' | 'FIXED' ) ':' ('"'text'"' | identifier) [ ',' ('"'text'"' | identifier) ]... ( ';' | '.' )

The last message of a block should end with the dot, or it should consist from the dot only.

 

The history of changes can be ended together with the end of the class text. But in case when the text after the END directive contain also another information additionally to the history, it should be ended with the special directive
HISTORY ENDED

E.g.:
HISTORY:

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

IV. Structures (STRUCTURE)

IV. 1. Declaration of a structure

 

Syntax.

 

STRUCTURE {name|_of_structure} :
    field1
    field2
    ...
    field N .

 

Structures are declared in a class body.

Name of a record is always start from a small letter.

The name can be separated with the symbol '|': the first part is a short name of the record. Full name of a record can not be shorter then 8 characters.

All the records declared in a class are always accessible for all the classes importing that class.

 

A structure contains of:

  • fields of simple data types of fixed size (BOOL, BYTE, INT, REAL, enumerations, STR)
  • fields of structure types (any other structures in a scope of view but recursive nesting is not allowed)
  • references onto class instances
  • arrays of fixed size of the listed above items.

 

A record declaration is ended with the dot symbol '.'.

 

A structure like a class can contain fields of ant type. But there are restrictions on fields which are objects of classes:
such fields of structures must be weak references onto objects (and its names should be ended with the underline character). If a class field is a structure, it must not be a weak reference since any variable of a structure type is a single reference onto its structure.

 

All the structure variables and fields are automatically initialized with a structure (of the corresponding type), which consists of zero fields (for numbers these are zeroes, for strings theses are empty, for objects - NONE values). In difference to objects of classes, assigning values to structure fields is almost always possible and changes its value (unlike to objects with NONE value for which assigning to its fields is just ignored). Therefore explicit assigning the NONE value to the structure variable has the same result as for an object. Also NONE value for a structure can be achieved in result of reading from an array out of its bounds or in result of assigning a value to another structure variable using the method Dismiss.

 

 

IV. 2. Working with structures

 

Structure is a class without methods, objects of which are always referenced by a single reference. So when an instance of a structure is created, it is not possible to use the OWNED BY modifier or >> Array[] operation unlike for classes.

 

One structure variable can be assigned from a value of another structure variable, and in such case the right side of the assignment should be ended with a call to the embedded function Clone or Dismiss. In case when Clone is called, the source structure is duplicated while assigning, in case of the Dismiss the source variable can become equal to NONE.


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

 

E.g.:

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

...

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

A similar constructor can be used to pass an actual parameter to a function in place of a record parameter.

When a record is assigning (to another RECORD variable or field), its content just is copied.

 

When passing a structure as a function parameter, it can be used in the called function only for read, and it is not allowed to change its fields. This differs from rules of using objects of classes, for which it is possible to change its fields in a called function.

Correspondently, when assign entire structure to another structure variable, its possible (and necessary) to call only Clone pseudo-method and not Dismiss.

 

A structure can be used as a result of a function. When working with the RESULT variable of a structure type it is not necessary to initialize it still it is always initialized by the default value.

 

A structure can be created with an operator creating a new object of certain class:
{structure-type-name}(field1=expression, field2=expression, ...)
For example:
{complex} c|omplex_var = {complex}(Im=5)

 

 

V. Testing

V. 1. General rules

 

Class can contain special functions starting with a keyword TEST in place of the FUNCTION. Such functions are intended to test and executed always on a compilation stage.

  • If tests are not executed successfully, a class should be marked with an attribute UNTESTED.
  • If not all lines of code were executed on a test stage, the class should be marked as UNTESTED.
  • If at least one statement ASSERT was failed, the class also should be marked as UNTESTED.
  • If a class should be marked as UNTESTED but it has no such attribute, this concerned as an error, and a project is not compiled.
 

Which code should be tested:

  • All the lines of code.
  • All the conditional branches.

 

A set of tests is concerned to be "covering" only if all the lines which should be tested were executed during a class testing at least once. If this condition was not satisfied, the class should be marked as UNTESTED.

 

All the sections of code in a TEST function should have ASSERT statements. The compiler can no control correctness of usage of such statements though, so an absence of ASSERT statements leads just to warnings.

 

What is not necessary to test:

  • Native functions.
  • Abstract classes.
  • Classes not having methods.
  • Methods not having statements.
 

Therefore while testing a class inherited from an abstract class all the not redefined methods of an abstract ancestor should be tested to ensure that a derived class is tested.

It is allowed to place in an abstract class some testing functions with parameters, intended to test partially (or totally) functions of an abstract class.

Tests with parameters are not called automatically but these can be called from other TEST functions. In part if to create such parameterized tests in an abstract class these can be used to simplify testing the abstract class together with derivatives of it.

 

V. 2. Syntax

 

Testing function starts from a header which differs from a function header by a keyword FUN|CTION.

 

Testing functions can have parameters but such parameterized tests are not called automatically: these can only be called from other tests.

 

Testing function can start from its individual IMPORT statement, where additional classes are listed, necessary for the test to launch it.

 

Like other functions testing function also is separated onto sections with section comment
------------ 'text'

But for testing functions an additional requirement is there: each section should contain at least one ASSERT statement.

 

An ASSERT statement is intended to check a Boolean expression value. If a value in an
ASSERT expression
is false, then assertion is failed. When at least one failed assertion is present then the class is concerned untested.

 

 

VI. Additionally

VI. 1. Localization of string resources

 

The AL-IV programming language supports localization of strings via preudo-functions in form of
_identifier("Red square")
or
"Red square"._identifier

This construction looks like a function call but it is not a call to a function. But it tells to a compiler that a sting "Red square" should be placed into an array of localized strings, remember its index in the array and use it to extract the string from the localization array. Actually, another string can be extracted, if it was replacing the original one in result of working of localization operations.

All the names of such string resources (pseudo-functions) should be unique in a class.

A special class {Localize_str} is suggested to control localization process (though this is not obligatory and it is always possible to write another class to do this).

 

When using the class {Localize_str} it is necessary at least once (on an application initialization) to call a static method Localize, specifying a localization language short name. It is supposed that the name of a current language selected an application itself is storing at some place where it is get it when necessary. To simplify a setup an application to allow selecting a language by user, a method List_languages is provided returning a list of full and short names of available languages (semicolon separated) extracted from names of language files (supposing these have names in form like English_EN.lng).

The method Localize has an additional parameter Prefix which allows translating not all the strings, but only those having names starting from a certain prefix (the Prefix is provided without leading underscore character). An empty string means translating all the strings independently of resource names. 

 

When the Localize method is called it is first is calling a method Save_untraslated, if it was not called before from the application. A translator always can found the primary language file saved by this method, make a copy from it, rename it to Some-language_SL.lng and edit it. Strings which should be translated has a form NAME=Value. It is necessary to replace only values leaving names as is. An edited version of a language file should be saved in UTF-8 format.

 

By default a directory where an application is located is used as a place to store language files. Or if it is impossible to write there a special application data directory is used. Or it is possible to set such directory explicitly (calling Set_directory_lang). 

 

It is important that if new strings to translate were added while developing an application, then these are spread not only to an original language file stored by Save_untranslated but to all other (already translated) language files (though not yet translated).

 

VI. 2. Localization of the language keywords

 

The AL-IV supports translation of its keywords from any human language. For this purpose a localization technique (from above section) is used partially.

All the English keywords are stored in a text file Default_.lng in a directory with source files of the compiler. It is sufficient to copy this text file, rename it e.g. to Klingon_KL.lng and replace string values from English to some other. Language keywords have names starting from the letter "K" and these are mainly in the section [{Translation_to_canonical_keyword}].

To use national keywords in your code, a class should be started from a specification ['KL'] or [Language='KL'], following which the first keyword (CLASS) is already translated into the language specified.

 
 

Since a translation language can have some word morph rules or have some variations depending on a lexical context, especially for keywords it is allowed to relate to a single canonical keyword more then a single identifier.

A translation can have a form of several word sequences comma separated, where each sequence have a form root|suffix1|fuffix2|..., and any suffix can have a space or start from a space. In result, all the words root, rootsuffix1, rootsuffix2, ... are corresponding to the keyword specified (and a space in a suffix means that two identifiers spaces/tabs separated are treated as the target keyword, too).

 

Together with the language keywords, encoded characters are translated (#NL, #TAB etc.) and some embedded pseudo-functions such as (.Index, .Len, .Str). And it is allolwed to translate whole classes to national languages. To do so, for a translating class a special mirror class is created which have a single modifier TRANSLATION OF {Class_name}. It contains only translations for fields, functions, methods, enumerations, structures, constants, placed in correspondent sections.

 
 

Then it is sufficient to include such translation class into import and call its methods, functions, fields etc. using translated names.

 
 
 

A class format for a translation class, in form of a diagram:

 


 

VI. 3. STORE - hidden parameters

 

A modifier STORE for a method creates hidden (for a caller side) integer parameter which value is stored on a caller side.

A name of a parameter is specified in parenthesis, and also its initial value can be specified additionally:

, STORE(Name = value)

 

For each method call in a calling class a hidden integer field is created for the stored parameter, and this field is passed to the method by a reference implicitly. Passing by a reference means that if the method changes a STORE-parameter, then after it is finished, new value become the value of the hidden field. And on the next call of the same method in the same line of code, this new value will be passed as an additional parameter.

 

To reset stored hidden parameter values it is possible to call a method having a modifier FORGET. Stored parameters created to pass them to methods of the same class as the FORGET method will be set to its initial values (specified in the STORED modifier).

 

Modifiers STORE and FORGET together allow speed up accessing data arrays indexing by constant strings. E.g., while obtaining values of fields of records in SELECT results from database, or while accessing items and sub-items of a list view by headers of columns.

An example of accessing listview by column names:

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

In the above example, the FORGET modifier should be assigned to all the methods changing list view columns set and its names.

 

VI. 4. SQL queries

 

If a class has a method Write(STR), then for its objects it is possible to use operation

OBJECT << STRING

 

It is actually just calls the Write method.

In case of class {DB} (which is intended to work with databases), the operation << should be used to set up an SQL query text.

 

When a text of SQL is set and string expression is started from one of keywords INSERT / DELETE / UPDATE / SELECT, then entire expression is treated as an SQL-like statement which creates a final SQL text at run time. And if not all but many of parameters of such SQL-like statement are checked at compile time, minimizing possible problems at run time later.

 

Such SQL queries a referencing "tables" declared by TABLE declarations (on base of STRUCTUREs). E.g.:

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


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

Queries are similar to SQL but syntax is different a bit:

  • All the keywords are written in uppercase: SELECT, UPDATE, INSERT, DELETE, DISTINCT, TOP, FROM, AS, INTO, WHERE, GROUP, ORDER, BY, IN, NOT, IS, NULL, JOIN, ON, LEFT, OUTER;
  • All the parts of an SQL-like statement are separated with comma to simplify splitting long queries onto lines. E.g:

    SELECT DISTINCT, FROM Students, (*)
  • A FROM-JOINs part is located always in the beginning of the statement, even for SELECT;
  • A list of selected fields and values as well as lists of grouping and ordered values is enclosed into parentheses;
  • If it is possible, all identifiers first are treated as references to tables, table aliases and field names. And only in cases when these are not matching correspondent identifiers, these are attempted to use as AL-IV variables, functions etc. E.g.:

    UPDATE Students, SET Exam3 = 64,
    WHERE ID = {Ident_to_update}
  • If it is necessary to specify explicitly that part of text in a statement is an AL-IV text, the AL-IV expression is enclosed into figure brackets. In a WHERE clause this is the only way in many cases to use values calculated at run time;
  • All AL-IV expressions used directly in an SQL expression should have types INT / REAL / BOOL / STR / {date_time}. Other types are not allowed (but BYTE an many cases is treated as INT);
  • To represent counter fields (auto-incrementing) it is necessary to use type REAL, still ususal integer type precision is not enough to represent long integer value, which are used for counters in databases;
  • AL-IV expressions are automatically converted to SQL compatible strings, to treat those in SQL queries as indirect constants. So it is not necessary (and not allowed) to convert these into strings using such functions as Bool_sql, Int_sql, Str_sql, Real_sql, Date_sql;
  • In the INSERT statement, a list of fields and assigned values is specified like in the UPDATE:

    db << INSERT INTO Students,
          Surname = "Origatsu",
          FirstName="Pei",
          MiddleName="Q",
          Exam1=84,
          Exam2=81,
          Exam3=92,
          D=Date(2017, 7, 14),
          OriginalDocuments=TRUE
    db.Exec
  • In INSERT and UPDATE statements it is allowed to specify a variable (or other expression) of correspondent to table STRUCTURE to set values, instead of listing all the fields and its new values. E.g.:

    db << INSERT INTO Students, BY Rec
    db.Exec
  • If a query contains more than a single table, the only way to specify additional tables is in adding JOIN parts just after the first table. In such case, all the tables should have unique aliases specified to make it possible to reference its fields. E.g.:

    db << SELECT FROM Students a,
          JOIN Students b ON a.Exam1 = b.Exam1,
          (  a.ID         AS a_ID,
             a.FirstName  AS a_F,
             a.MiddleName AS a_M,
             a.Surname    AS a_S,
             COUNT(b.*)   AS CNT_b ),
          GROUP BY (a_ID)
    db.Open
  • Nested queries in operations IN are allowed: X IN (SELECT ...)
  • When it is necessary to insert some text into a query, use the pseudo-function SQL(...) which operand is an AL-IV expression returning a string (or just a string literal). For example:
    ...
    WHERE x IN SQL("(1,2,3)"),
          AND SQL("GetDate()") BETWEEN b.D1 AND b.D2
  • A query text can be separated on groups by comments
    --------- 'Text'
    and there is the method in the {DB} class:
    Remove_after(STR Text)
    which removes a text between two such comments (from the line next to the comment ------ 'Text', and to the next comment). This option can be used to exclude some optional parts of a query (usually in the WHERE clause).
  • It is possible to read query results the special form of the assignment statement << in which in the left part a variable is specified of a STRUCTURE type, and at the right side a {DB} class variable (or of an inherited data type), with additional reference to a table, in form:
    x << Db, BY TABLE Tab
    In such statement a variable at the left side must not be the same STRUCTURE type as the base of a table Tab but all its fields must have non-conflicting correspondence to that STRUCTURE type.
  • To get results it is recommended to use either a FOR loop with a preliminary check if there are results actually:
    db.Open
    CASE !db.Ended ?
         FOR i|dummy IN [1 TO 999_999] :
             {results_tab} r|esults1 << db
             {results_tab} all|_results[] << r
             CASE !db.Next ? BREAK i ;
         ;
    ;
    db.Close


    or a loop with a check on each iteration:
    db.Open
    FOR j|dummy IN [0 TO 999_999] :
        CASE db.Ended ? BREAK j ;
        id = db.CInt("ID")
        name = db.CStr("Name")
        STR r|esults_array[] << "id=" id " name=" name
        db.Forward ;
    db.Close
  • If it is necessary to get just a single value as a result of a query, there is a set of functions Open_VInt_close, Open_VStr_close etc. E.g.:
    REAL x|_some_value = db.Open_VReal_close


 

 

There are syntax diagrams below for statements generating SQL queries:

 

VI. 5. Abandoned and deprecated classes, structures, enumerations, fields, functions

 

While your classes and libraries are developing, you can find it necessary to re-write some things. Some functions, fields and so on becomes too old, supporting it costs more and more, some things are replaced with others etc.

To make it possible to control on a language level of a process of a distinction of old entities and provide smooth transition to new features, following modifiers are added to the AL-IV:

DEPRECATED( 'text' ) and ABANDONED( 'text' ).

  • These modifiers are applicable to classes, structures (STRUCTURE), fields of classes and structures, enumerations and functions.
  • A text in parentheses (and apostrophes) should contain a name of an alternative or at least a short clarification why the feature is marked as DEPRECATED or ABANDONED).
  • Modifiers ABANDONED and DEPRECATED are mutually exclusive.
  • It is suggested for all things become obsolete to add a modifier DEPRECATED, and keep it until the feature become not supported at all, and then replace it to ABANDONED.
  • When a deprecated feature is used in code, a compiler generates a warning containing the text specified in the modifier.
  • If the modifier ABANDONED is added then correspondent feature can not be used: compiler will generate an error. For an abandoned function, it is possible to remove its body, for abandoned class - to remove bodies of all the functions.
  • The sense in storing information about abandoned features in a class code is in providing an information about an alternative (in the text in parentheses which should contain name of such alternative function, class or other way to get desired functionality).
 

There is another possible usage of modifiers DEPRECATED and ABANDONED: these can be used an inherited class to refuse from same functionality of the predecessor class, since it is not working in the descendant class.

For example the class {Dialog} which is a descendant of the class {Form} do not use the method Show which is declared as ABANDONED('Show_modal'). This is done so to prevent a programmer using incorrect method to show a dialog form.

Unfortunately the message from the compiler will not be firing in case when an instance of the class {Dialog} is assigned to a variable of the class {Form}. In such case the abandoned method actually will be called (and it just will be ignored since an abandoned method always overriding the ancestor method replacing it.

 

VI. 6. Restrictions to parameter values

 

A function can have modifiers RESTRICT (unconditional restriction on a parameter) and IF/THEN modifiers (conditional restrictions on parameter values depending on values of other parameters).

  • All such restrictions on parameters are written following all other function modifiers.
  • Amount of restrict modifiers is not restricted.
  • If a restriction is specified for a parameter, or the parameter is used in the IF part of a conditional restriction, then it is possible to pass only a constant as a value of such parameter. The only exclusion is a special conditional restriction
    IF parameter IS CONSTANT, THEN ...
  • Restrictions on parameters are controlled by a compiler at a compile stage. If conditions specified in restrictions are not satisfied, the compiler fires an error.
  • A parameter can be restricted or participate in a condition of a restriction only if it has a simple data type (BOOL, BYTE, INT, REAL, STR) or it has a enumeration data type, and is not an array.
 

Unconditional restriction has a form
RESTRICT parameter_name IN [list_of_values]

 

or (for REAL data type):
RESTRICT parameter_name IN [N1 TO N2]

 

or (without specifying certain value):
RESTRICT parameter IS CONSTANT

  • When a range is used (for real values), a condition is checking N1 <= X <= N2, where:
    • X - a value passed to a function,
    • N1 and N2 - bounds of a range [N1 TO N2].
  • The last variant does not specify any value checks but requires passing only a constant value as the parameter specified.
 

 

A conditional restriction has a form:
IF parameter1 IN [list or range],
THEN parameter2 IN [list or range]

 

or part IN [...] can be replaced with IS CONSTANT.

  • Both parameters listed accept only constant values (except the case IF parameter1 IS CONSTANT).
  • If a condition in the IF part is satisfied, then a condition in the THEN part must be satisfied to avoid compiler errors.
  • If the IF part condition is not satisfied, then the THEN part is just ignored (but the parameter 2 anyway can accept only a constant).
 

 When this can be useful ?

 

E.g., to implement wrappers to the OpenGL library. It is possible to simplify the work just creating simple wrappers to correspondent OpenGL functions and pass just integer constants, but to add restrictions for such parameters to provide checking for only allowed sets of constants. This is much more simple then to create a set of classes or to use enumerated values (since the same values can be used in different functions for different purposes). Restrictions are providing working of classic OpenGL code (e.g. samples from NeHe etc.), almost without any changes (it is sufficient to remove symbols ';' at ends of lines).

 

This restrictions feature can be used (in future) to optimize code: if the compiler "knows" that only constants can be passed, it can create for each constant a special function version optimized for such constant value.

 

VI. 7. Infinitive loop control

  1. On each iteration of any loop, an internal global counter is decreased. When it becomes equal 0, an internal procedure is calling which restores that counter to an initial value (usually 65536), and an additional function can be called in such case.
  2. For visual applications having graphics shell after sufficient time of long operation execution (usually more then 2 seconds) a progress of the operation can start displaying which is showing a percentage an a description of the long operation. And it is possible to cancel the operation by a button on the progress panel.
  3. In case when a programmer did not provide such long operation therefore pressing a button or a menu always is considered as a start of possible such long operation by default. So for such operation also it is possible to display a progress automatically allowing to cancel the operation if it becomes to work too long.

 

VI. 8. Code optimizations. INLINE insertions

  1. Functions can have the modifier INLINE. This is an instruction for the compiler to insert the function code in place a call of it.
  2. If the optimization is on, the compiler itself can replace calls of some functions to its immediate code for sufficiently short functions or for functions used only once. The compiler option /!autoinline disables such automatic in-lining (but do not prevent in-lining functions marked with the modifier INLINE).
  3. In some cases functions can not be in-lined. E.g. it is not allowed to INLINE methods of another classes, or functions of another classes which use declarations not imported in the target class. Also, it is not allowed to in-line the code having statements ==>, BREAK or CONTiNUE.
  4. In some cases, in-lining of functions can lead to degradation of performance. E.g. if several functions having many local variables are in-lined into a single function, then its total amount of local variables can become too large. Since in AL-IV all the local variables must be initialized before running the function code, using such function very often (e.g. in a loop) can lead to slow down of the execution.

 

VI. 9. Code optimizations. UNROLL for FOR loops

  1. FOR loops based on a range defined by constant values can be totally unrolled with the modifier UNROLL without parameters (but amount of iterations should not exceed some limit defined by the compiler, current limit is 1024 for C#, Delphi and Free Pascal). An example of code:
    FOR i IN [0 TO 19], UNROLL: ... ;
  2.  Any FOR loops can be unrolled with an unroll factor specified in parenthesis. The unroll factor should be an integer constant not less then 2.
  3. While unrolling a FOR loop, total code size can increase but execution time mostly reduces. But in some cases the performance can reduce rather then increase (in case of C#, this may be a result of its code optimizer which may be can not handle long code sequences).
  4. The compiler option /!unroll disables any UNROLLs for FOR loops.

 

VI. 10. Syntax sugar

 

a. If there are several sequential assignments in code (or append to a string or to an array) with the same destination variable, then in following statements can be replaced with the .. symbol (two dots). Note that in case when the destination is an array (in an append statement array[] << item), then it is not allowed to omit brackets. For example:
A[] << item1 << item2 << item3
..[] << item4 << item5


Lines of code with such continued assignments are not accounting while restricting amount of statements in a block, and amount of continued assignments/appends is not limited. And it is allowed to add other language constructions in between the stating statements and continued statements (and between continued statements, too), if such statements are not assignments / appends. E.g.:
A[] << item1
CASE condition ? ..[] << item2 ;

 

b. It is allowed in case of an assignment to a filed specified after a chain of dereferencing operations (separated with dots, e.g. A.B.C(params).D[index].E), to specify also which field will be a target for following continued assignment / append statements. For this, instead of a dot in a correspondent position (after the default target field replaced later with double dots), three dots symbol is used. E.g.:
A...B.C[] << item1
...Calculate(param) // equals to: A.Calculate(param)

 

c. If in a single statement there are several expressions of form A.B[index].C(params).D where the last name D is a reference to a field (of a class or on object), and then entire chain is replicated (except the last field) then the second and following expressions can be written in very short form: .E, .F etc. Note that leading operation signs like '-', '!', '~' are not parts of such expressions and should be written. E.g.:
P = pt1.Offset(-PBox.Bounds.Loc.X, -.Y)

 

VI. 11. Short help on "embedded" functions

 

The reason why such "embedded" functions exist is that it is not possible staying in the language rules frames to define strictly their parameters / results. E.g. the syntax & semantics do not allow to specify a parameter as "an array of any type". At the same time it is convenient to have a set of same named common functions to manipulate with arrays independent of its items types.

 

Note: with introducing functions "overloading" the main such reason can be partially eliminated. But therefore there is a problem to refer to "any structure" or "any enumeration" type: the AL-IV is not ready to supply such templates.

 

Since all the functions in the below table are static, these can be called either in the classic form foo(x, y, z) or in the prefix form x.foo(y, z).

 

Function Definition Group
Index(Variable) ==> INT The index of the current item in a FOR loop, when the Variable is enumerating all or part of array loops
Name({enum} Item) ==> STR An enumeration item name. E.g., "'RED'" enumerations
First({enum} Item) ==> {enum} The first enumeration item
Last({enum} Item) ==> {enum} The last item
Prev|ious({enum} Item) ==> {enum} The previous item
Next({enum} Item) ==> {enum} The next itwem
Int({enum} Item) ==> INT Convert the enumeration item to an integer (an index of the item based on 0)
Int(STR S) ==> INT Get an integer from a string strings
Real(STR S) ==> REAL Get a real value from a string
Len(STR S) ==> INT String length
Find(STR S, STR W) ==> INT Search an index of the first substring W in the string S (0 ... Len-1 or -1, when there is no such substring or W == "")
Str({INT,REAL,BOOL}) ==> STR or S({INT,REAL,BOOL}) ==> STR Convert value (BOOL/BYTE/INT/REAL) to the string.
Count(A[]) ==> INT Amount of items in the array arrays
Allocate(A[], INT Size) Allocating additional items to provide size of the array to be at least given Size. If the size of larger already, it is not changed.
Find(A[], W) ==> INT Search an index of the  first item W in the array A[] (except a structures array). The result is between 0 ... Count-1, or -1 if not found.
Insert(A[], INT I, V) Inserting the value V at the position I in the array A[]
Clear(A[]) Clear the dynamic array A[]
Delete(A[], INT I) Deleting the item at index I
Remove(A[], V) Removing all the values V from the array
Swap(A[], INT I, INT J) Exchange items with indexes I and J
Int(REAL X) ==> INT Truncate the fractional part of the real value real numbers

 

ShiftL(INT N, INT K) ==> INT Logical shift the N onto the K bits left (if the K<0, shifts onto -K bits right). Bits shifted out are lost, zeroes shifting at the right side. bitwise shift/rotate operations
ShiftR(INT N, INT K) ==> INT Logical shift right.
RotateL(INT N, INT M, INT K) ==> INT Cyclic shift left onto K bits. Bits shifted out left are shifted in from the right side.
RotateR(INT N, INT M, INT K) ==> INT Cyclic shift right
Clone({structure} structure) ==> {structure} Copying a structure structures
Dismiss({structure} structure) ==> {structure} Releasing a structure from its previous store, returning the structure
 

All the embedded functions can be identified by capitalized names, e.g.: A[].COUNT

 

VII. Why one more programming language is necessary ?

 

VII. 1. Real multi-platform support

 

Yes, it is always possible to create applications which really are multi-platform. To do this, it is necessary to select a language (C++, C#, Java, Pascal) and to use some framework (or classes library). On that way a lot of adventures and disappointments.

Adventures - because authors or these languages and frameworks very often have its own mind about what is important and what is not necessary for you. They go ways which were not known for you before. Be ready that to solve a tiny problem you will spend weeks digging entire Internet, reading dozens of forum pages etc.

Disappointments - because sometimes you will have to redesign entire project removing desired features due to restrictions of a selected tool. Or even stop using the tool and to search another more comfortable (may be more expensive), and then to re-write all earlier written code.

 

And what fundamental distinction of the AL-IV in case of multi-platform support, if it is just an extension over one of existing programming languages (under C#, or Delphi, or Free Pascal) ? The pcularity of the Alfour is that it is initially is not oriented on a certain platform.

You writ your Alfour code only once. And to launch the application on another platform it is sufficiently to correct configuration files of a project and to call the compiler.

It is just necessary to provide a support of a desired platform. Fortunately, there are not too many platforms. There is a probability that in a finite time we will have support of all the desired platforms.

For this moment (June, 2019) the platforms supported are:

 

 

What if one if branches supported disappear suddenly (e.g. a desired target platform become not supporting or a developer tool become more expensive to pay for it) ?

There is an answer on that question. It is in the simplicity of the AL-IV. The compiler for it (which is compiling to a some intermediate language) can be prepared for a short enough time. It is always possible to return to a code generationh for C++ / Java / Python / ... or to make another generator. A task of creating wrapper native classes implementing base libraries functionality for a certain platform can be harder. But this also can be solved since base library also is simple enough and projected to simplify such task as much as possible.

 

VII. 2. Safety

 

Actually all the modern programming languages were developed from ancient primitive assembling languages, allowing not safe operations, address arithmetic, not controlling array bounds etc. There are no existing high level programming languages in which it could not be possible to get an exception in result of an erroneous access to a zero address or in some hard cases to corrupt an arbitrary memory block.

You can use so called "managed" memory (in C++ this i an option, in C# the most of objects are in such memory), or to use none-objects (in the Objective-C). But part of code always will be written in a not safe old style, and you could not fix this (even in case if that code is yours). Even in modern C# and Java you are not free from necessity to provide checking if an object passed to a function is equal to null, or to provide exceptions handling in your code.

 

In case of t AL-IV, the situation is different totally. On the compiler level (and the Alfour language itself) an index value is checking for a bounds when an array is accessing (providing dummy item or ignoring write to the array operation if bounds are exceeding), accessing unassigned objects, it is guaranteed initialization  of all the variables, it is preventing infinitive looping and recursion.

There are there embedded testing capabilities (with a control of a covering of code with tests). Programming with AL-IV really become relaxing and regular work, without extreme and adventures.

 

Exclusions are not possible in the AL-IV. When developing native methods / functions it is recommended to follow this paradigm   providing in the final code in case of an exception handling it to provide working with it in a style of post-handling (when errors just are collected in a system array and can be accessed later by a final code, just to get know if there were errors while a function was called. In many cases it is sufficient to make a message about errors or just log it, and it is not necessary to crash).

 

VII. 3. Why new syntax ?

 

Really, it is always possible to stay in frames of some of existing syntax rule set, but to change semantics.

 

While developing new syntax the main purpose was to simplify working with source code using an arbitrary text editor (supporting UTF-8, this was an only requirement for such text editor).

 

It is actually from here a requirement to write keywords only in the UPPER CASE. In such case source code is much easier to read (and too hard to edit).

 

What about an absence of a starting bracket of a block statement, and a special symbol ending each simple statement. The absence of these makes text more clear, and simplifies editing it (for operations like re-factoring). And this does not make reading harder.

 

What about restrictions on amount of nesting blocks, on amount of statements between block comments. These restrictions do not effect a programmer freedom a lot. When we have too many nesting levels, this make code harder to read and understand. When using several indentation levels, accessible line width becomes too small to fit sufficiently long statements, and we have more often to split lines of code. So, the requirement to move deeply nesting blocks of code to outer methods/functions is very correct and useful.

It is not hard at all to split too long sequences of statements Separating these with block comments i not too hard requirement at all.

 

The only question left is about a restriction on three only parameters for AL-IV methods/functions. Initially, when such requirement was introducing it was supposed that if the rule become too hard to follow it, this can be changed or removed at all.

But while developing sufficiently complex applications (such as the al-IV compiler, its IDE text editor and some other) this was found that the requirement is not impossible to satisfy, and very useful to make developing easier. For functions / methods having not more then 3 parameters it is mach simpler to remember its parameters rather then to use special IDE to pop-up help on method parameters each time it is used.

The restriction finally was removed and replaced with a requirement to specify parameter names in form of assignment. But this was done just to simplify code re-factoring (by moving pieces of code from internal blocks out to separate functions) and adaptation of code earlier written on other programming languages.

 

VII. 4. Where is "while" loop statement ?

 

Loops of form "while" / "repeat...until" are not safe, since these can lead to infinitive loops without any chance to leave such loop.

Loops of form FOR i IN [...] are safe for that since these earlier or later but finished (it is possible that it could be a lot of time but not the eternity).

 

While programming in the ALfour, in place where it could be suitable to use "while" in other languages, use instead the FOR statement setting as a range (for example) [0 TO N] where N is a maximum possible amount of iterations. And the first statement of such FOR statement should be a conditional BREAK on the anti-condition of the loop continuation:
CASE !continue_cond ? BREAK i ;

 

 

VII. 5. Why new memory management rules (real real-time processing programming) ?

 

Since the time when a heap of dynamic data was invented, there were only one actually new useful change in memory management: automatic objects destroying on its usage counter value becoming zero.

Therefore it was found immediately that in many times closed chains of references can be created (references from objects to objects increasing its usage counters), which in result prevent objects from automatic freeing still its usage counters never achieve zero value. To fix the problem the so named garbage cleaner was introduced. Unfortunately, such procedure translates all the systems built on base of this technology for releasing actually unused objects into a category of slow and unpredictable. And this means that such programming environments can not be used to create real time systems or even to use in time-critical systems of mass service.

 

Yes it is always possible to refuce for critical subsystems from the managed memory and to develop in the old style directly controlling all the memory usage. But in such case totally disappear all the advantages of automatic memory management. And this is much more difficult still programmers usually do not do all the work from the scratch but use external libraries of classes and functions. If they can not use automatic releasing objects, then in many cases these should refuse from a lot of libraries. In result, capabilities of programming become restricting, and developing become more expensive and slow.

 

It is contrary another situation in case when you can use automatically releasing objects but it is not necessary to refuse from real time systems developing. Still a garbage cleaning is not necessary, objects are releasing exactly at the moment when this is necessary. Is not this a dream for a programmer.

 

Certainly in case of new way it is necessary to change a bit a strategy of objects creating. At least a programmer should think about a life time of an object at the point where it is creating. And we should reserve arrays to store references to dynamic child objects. But this is not too great loose in comparison to a possibility to refuse at all from the garbage collector (and from the garbage in the heap).

 

 

VII. 6. Why embedded SQL statements ?

 

Really, how a desire to make the programming language as simple as possible is combining with embedding of a direct support for SQL expressions?

An answer is following: such support allows to check a lot of SQL semantics on the stage of compiling (rather then at run-time).

Among them we can check: a correspondence of field names in queries to real fields (declared in TABLE definitions), a correctness of its usage (null fields, auto-increment fields, which could not be set). And certainly to check the SQL syntax, too. Such checks are done on a compiling stage reducing a possibility that a program which works with data bases, can be run with explicit errors in SQL code.

 

Such principal - a possibility of a static analysis of a correctness of operations on a compiling stage - makes developing much simpler. Unfortunately what is concerning SQL in modern programming such principal is not applying though working with data bases is one of key technologies in a modern practice. In the AL-IV this is fixed.

 

In differ to a generic programming the embedding some specialized features into the language (such as SQL, or complex numbers) actually do not increase a minimal necessary programming level.

While writing the code, you just do not use such features, and even could be not know about these existence. While reading an alien code, you should to learn the possibility if you found it in the code.

But learning some additional possibility is much easier than:

  • to get know rules of writing generic functions / methods / classes;
  • to learn a certain definition of a variable declaration and operations with its data type, programmed in a certain generic class including:
    • methods,
    • redefined operations,
    • data type conversions,
    • and often in combination of inheritance from other generic data types.

 

VII. 7. Why there are no possibility to control which precision to use for (numeric) variables ?

 

Because this makes your code simpler (and minimizes amount of errors lead from using in calculations of data of almost the same type but with different data precision).

In many of modern programming languages it is possible to define data types dual ways: for values for which the precision is not important, a base data type is used (e.g. Integer). But in cases when it is not enough or otherwise if it is necessary to economy memory, data types with special precision are used (Int64, SmallInt, ShortInt).

 

In result, a lot of data types and its combinations are present in a code. And we have to take into account situations when a precision of one variable or result of some calculations is not enough to fit in another variable. (Actually, nobody takes into account such things, instead if a possibility is to get incorrect results, then such erroneous results will be obtained, without any reasonable explanation, what was occur and how it can be fixed).

Why such thin specifications are necessary if these are just used actually to economy memory. It is much better to reject from those forever, making life simpler for a programmer since he (she) has not to decide each time which variation of a type to use now.

 

By similar reason there are no unsigned data type in the AL-IV (like in Java, too). And the type INT|EGER is inot suitable to store too large values (usually greater then 2 billions). In case when it is necessary to store greater values, use data type REAL (hich should be larger in precision and be capable to store at least N * 2 - 8 * k binary digits (where k - is a number of binary digits used to store an exponent). If the type REAL by some reasons is not acceptable (e.g. due to its slow speed, or again its precision is not enough) - you always can use arrays of integers or bytes.

 

Neither more detailed (as in modern archaic languages) nor in case of more generic data type definition, code programmed is not becoming more independent from a platform. It is not possible at all to suppose on a precision of integer numbers, which is used by default. It can be vary in a wide range - from 8 bits (very rare) or from 16 bits (in microcontrollers, IoT and so on) and up to 64 / 128 bits. And sometimes it is possible that a platform as no integers at all and to implement all the numbers real data type is used.

 

VII. 8. Where is CHAR data type ?

 

This type became too old and it is not actual for modern conditions. In case of using UTF-8 encoding, one character can occupy in memory more then one byte, so to store it we have to use a string. What we finally have in AL-IV.

 

VII. 9. Why there are no pointers to STRUCTUREs ?

 

Because records are introduced into the language only to allow data aggregation without dynamic data allocations. And these guarantee that a structure is always a single references so when the control flow leave its scope, it immediately releases the occupied memory.

 

Records are intermediate data types between simple data types and classes. From one side these contain separate  fields (and passed to functions by reference actually). From another side these are assigned like simple variables (just copying its content) and can not be modified in functions where these are passed as parameters (i.e. these are always passed as constant values, in notation of C++/Java/Pascal - const).

 

In the AL-IV it is not possible to work with pointers to structures or its parts.

 

An absence of pointers and addressing arithmetic essentially increases code safety. Indexing of items in arrays is much more safe operation since the compiler have an information which object (array) code is accessing, and it can provide a control on its bounds exceeding, at least at run time.

 

VII. 10. Code brittleness

 

a. Short names

 

 

 

b. Prefix functions calls

 

c. Removing surplus checks

 

d. LIKE statements

 

e. Formatting block statements

 

 

 

f. Declaring local variables

 

g. Automatic releasing of objects no more used

 

 

 

Content

Introduction

I. Syntax

I. 1.    Formatting statements

I. 2. Assignment

I. 3. Expressions

I. 4. Other  simple statements

I. 5. Conditional  statement CASE

I. 6. FOR loop statements

I. 7. PUSH statements block

I. 8. DEBUG statements block

I. 9. LIKE statement

I. 10. REVERT statement

II. Variables, constants and  data types

II. 1. Simple data types

II. 2. Variables and arrays

II. 3. Functions

III.   Classes and objects

III. 1.    Classes

III. 2.    Working with objects of classes

III. 3.    Class level operators

III. 4.    Class fields modifiers

III. 5. Ending class. History. DATA[] array.

IV.    Structures    (STRUCTURE)

IV. 1. Declaration of    a structure

IV. 2. Working with structures

V. Testing

V. 1. General rules

V. 2. Syntax

VI. Additionally

VI. 1. Localization of string resources

VI. 2. Localization of the language keywords

VI. 3. STORE - hidden parameters

VI. 4. SQL queries

VI. 5. Abandoned and deprecated classes, structures, enumerations, fields,    functions

VI. 6. Restrictions to parameter values

VI. 7. Infinitive loop control

VI. 8. Code optimizations. INLINE insertions

VI. 9. Code optimizations. UNROLL for FOR loops

VI. 10. Syntax sugar

VI. 11. Short help on "embedded" functions

VII. Why one more programming language is necessary ?

VII. 1. Real multi-platform support

VII. 2. Safety

VII. 3. Why new syntax ?

VII. 4. Where is "while" loop statement ?

VII. 5. Why new memory management rules (real real-time processing    programming) ?

VII. 6. Why embedded SQL statements ?

VII. 7. Why there are no possibility to control which precision to use    for (numeric) variables ?

VII. 8. Where is CHAR data type ?

VII. 9. Why there are no pointers to STRUCTUREs ?

VII. 10. Code brittleness

Content

 

 

 


Home