CalcIt Language

Overview

Code written in CalcIt language can be as simple as a mathematical expression that returns a single numeric value or multi-statement code of any complexity. People with basic programming skills can use the full power of CalcIt language. If you ever had programmed in languages like BASIC or PASCAL, to program CalcIt will be no problem.

If CalcIt code is more than one expression then it is considered multi-statement. Every statement is separated by other statements using semi colon character (;). E.g.

a:=1000;
v:=a*56;
print(a+v);

Any piece of CalcIt code returns a value, numeric or alphanumeric. Any expression which doesn't assigns its value to  a variable (using assignment operator (:=)), defines or redefines the return value of the code. E.g.

a:=1000;
v:=a*56;
print(a+v);
a+v;

In the above example the last statement (a+v) doesn't assign its value to any other variable and so defines or redefines the return value of the whole code. Many such statements can be executed in a code redefining always the return value of the code. We can read the current returned value using the command RESULT. E.g.

a:=1000;
v:=a*56;
print(a+v);
a+v;
RESULT*1000;

The last statement in the example above reads the current returned value of the executed code and multiplies it by 1000 and redefines again its returned value.

To add some comments in the text of a computation, we can use {} symbols. E.g. {This is a comment}.
Using // we can comment out the text after it in the same line. E.g.

a:=1000;      //assigns 1000 to the variable a
v:=a*56;      //assigns a*56 (56000) to the variable v
print(a+v);   //Prints in output window the value of expression a+v (57000)
a+v;          //Defines the return value of the code as the result of expression a+v (57000)
RESULT*1000;  //Redefines the return value of the code taking its previous value (57000) and
              //multiplies it by 1000. The new returned value is 57000000

Inside a piece of CalcIt code local functions can be defined using keyword function.

function AddTwoNumbers(n1,n2);
 n1+n2;
//defines the returned
        //value of the function
       
//as the sum of n1 and n2 
end;

AddTwoNumbers(10,5); //returns 15 and defines code's returned value

Inside local functions any change of RESULT refers to the returned value of the function and not of the whole CalcIt code.

Any number of variables can be created and used in CalcIt code if their names follow some conventions. There are seven kind of variables:

  1. Simple variables. Any assignment in a first time used name creates a new variable of this type. Contents can be numeric or alphanumeric. Optionally, every simple variable can hold more than one value using fields. See Using fields. Optionally every simple variable can be extended to contain an array and behave like an array variable, participate in array expression etc, through @ symbol. See more at Array extensions.
  2. Array Variables. Array variables need to be declared before use by using the SET command. Declared arrays are always one dimensioned. Optionally, every array element can hold more than one value using fields. See Using fields. Optionally every array element can be extended to contain an array and behave like an array variable, participate in array expression etc, through @ symbol. See more at Array extensions.
  3. Automation variables. Automation variables need to be declared first by using a special syntax of SET command. Are used to access COM/Automation objects installed in your system.
  4. File variables. File variables are declared using a special syntax of SET command. Using a file variable we can access for read or write any kind of file, at binary level, in a way similar of the one of arrays.
  5. Form variables. Form variables are declared using a special syntax of SET command. Every Form variable handles an instance of a data entry Form. Via the Form variable we can access the properties of the data entry Form or the properties of the controls it carries. The way the Form looks like, the controls it carries, etc is specified using the command FORMDef. See also the Visual Form Designer (VFD).
  6. Buffer variables. This kind of variables is a mechanism to access a piece of memory (buffer) in a structured way using fields of specific type, like integer, string, double, etc. They work as a bridge between CalcIt (which uses variables of no specific type) and the outside "world" that always express memory or function interfaces with specific data types. Buffer variables is the only way to express function calls of DLLs (API) which the user wants to call in its CalcIt code. Also they are used in collaboration with File variables. They are declared with a special syntax of the SET command.
  7. Class variables. Every Class variable handles an instance of a Class definition. A Class definition is an element stored in the Classes list. It looks like a normal UDF but with the additional capability to declare some of its local functions as PUBLIC. Using Class variables we can call these PUBLIC functions. Additionally Classes can be used as "Import Classes", as a way to wrap the API calls of a DLL to a more convenient user interface. They are declared with a special syntax of SET command.

NOTES:

  1. CalcIt language compiler do not generate native processor's code. Generates a code that it is executed by an interpreter, a special program that acts as a kind of virtual processor. Additionally every piece of CalcIt code  is autonomous and is not part of any other piece of code written in any other place in CalcIt. For example the code in an Application item is separate from the code of a User Defined Function (UDF) even the first calls the second. They have separate initialization and termination procedures (execution environment). For example every time a UDF is called, its code is initialized and when it finishes execution is terminated by the same cleanup operations as the code that calls it.
  2. Standard or User Defined symbols are not case sensitive.
  3. We can stop a lengthy CalcIt code execution at any time pressing LEFT SHIFT + RIGHT SHIFT together. For performance reasons it is checked every two seconds so keep it pressed until the code stops.
  4. In Local functions, Properties, UDF and standard functions we can omit optional parameters (with default values) in any position just placing the appropriate comma. e.g. AnyFunc(1,,4,,5).
  5. A quick way to pass values between codes is the global variable Global. Is a simple variable, described above.
  6. To access the parameters passed at the command line either of the main CalcIt application or in case CalcIt executables, especially in the case console CalcIt executables, use the global array variable Params.
  7. To ensure the execution of a code regardless the use of EXIT, BREAK or BREAKALL, TRY...FINALLY syntax can be used.
  8. When copying multi-field values or arrays from one variable to another, or passing (by value) as parameters in functions etc, the data are not really copied. It is copied only a reference to the data. So at any point many variables can point to the same data. When one of these variables tries to modify the data only then a real copy of data takes place. This makes the handling of multi-field values and arrays much faster. A reference counting mechanism is used.
  9. In case we need to ignore the returned value of a function and the same time protect the returned value of the whole code (RESULT, see above) we can assign the value to a variable or use the special command NORET.

    i:=IgnoreReturnedValue(10,200,300);    //the RESULT is not changed
    noret IgnoreReturnedValue(10,200,300);
    //the same but using NORET

Language Features

Contents

Feature Description
Dynamic Arrays Any number of single dimensioned dynamic arrays can be created and used in CalcIt code. The SET command is used to define, redefine or assign values to a dynamic array. To create an array with an initial specific size we use the following syntax:

SET ar(100); //or SET ar=ARR(100);

The above syntax declares an array variable ar of 100 elements. This size can be changed by the use of SET command again or other special commands that Add, Insert or Delete elements. For example ArrAdd(ar,'New value') adds one more value at the end of the array and so makes its size of 101 elements. At any moment the command SIZE can be used to find out the current number of elements an array variable currently contains. In our example the SIZE(arr) will return 101. 

Another way to declare/redefine/initialize an array is to use the SET command to load a text file on it very easily and in one step.

E.g. SET ar=READ('c:\calcit\readme.txt');

We can easily declare/redefine/initialize an array with specific values in the following way:

set ar=[100,200,'CalcIt','Computer',100+56];

Once an array variable is declared the assignment operator (:=) can be used as well, to assign the result of an array expression to the variable:

set ar(0);
ar:=[100,200,'CalcIt','Computer',100+56];

Every array element can be accessed for read or write using an index in parentheses:

ar(3):=1000;
print(ar(3));

The first statement changes the third (3) element of the array ar. The second reads and prints on output window the contents of the third (3) element of the array ar.

NOTE: The index is one based, the first item is at index 1.

To access an array in the reverse order a negative index can be used. For example ar(-1) accesses the last element of the array, and so on.

Intersection, Union, Difference and Concatenation operations can be performed over whole arrays and the use of symbols *, +, -, &. Also with UNIQUE we can get the sorted unique elements of an array. See array expressions.

Inclusion operator IN can be used to check if a value is contained in an array.

Additionally there are commands to search (SEARCH, BINSEARCH), sort (SORT, SORTN), summarize (SUM) the contents of array variables. See also commands  MIN, MAX, AVRG, PRINTARR.

Every array element can contain delimited text which can be accessed for read or write. E.g.

arr(3)#(2)

See more about this feature in Delimited Text.

To Add, Insert or Delete elements from an array we use commands ARRADD, ARRBINADD, ARRINS and ARRDEL.

A quick syntax to add an element at the end of a dynamic array is the one using a set of empty parentheses: arr():='New element at the end'; 

The contents of an array can be written to disk as a text file using the WRITE or APPEND commands.

The syntax below is valid:

set Arr(0);

Creates a new array with zero elements or empties (redefining) another previous declared.

Check the special FOR loop syntax that is used exclusively to process dynamic arrays and offers speed and convenience.

See also Fine tuning Dynamic arrays operation.

Every array element is not limited to hold only one value. Can be extended to hold more using fields. See Using Fields below.

See also Array Extensions on how every array element can be extended to contain another array.

For extended array manipulation see also Array Expressions below and inline arrays

Array Expressions Array expressions look like normal CalcIt expressions but instead of additions, subtractions etc over numeric or alphanumeric values, whole arrays are used in a different set of operations.

The result of array expressions can be assigned in array variables (using SET command) or can be passed in functions array IN parameters.

The operations that can be performed in such expressions are:

Operation Symbol
Intersection *
Union +
Difference -
Concatenation &

e.g. set a=a1+a2-a3*a4

where a, a1, a3, a4 are array variables. In the example SET command is used to assign the result of the array expression (a1+a2-a3*a4) to the array variable a.

  • Union (+): Combines and returns the elements of two arrays. Elements common to them are not duplicated. This of course presumes that arrays contain unique elements before the operation.
  • Intersection (*): Returns the elements common in two arrays.
  • Difference (-): In the difference operation between two arrays (e.g. set ar=ar1-ar2) it is returned all elements of ar1 not existed in ar2. TIP: You can use this operation to remove a number of specific values out of an array. e.g. set ar1=ar2-[10,20,12]. This statement returns in ar1 an array which contains all values of ar2 except 10, 20 or 12 (if any).
  • Concatenation (&): Returns all elements contained in two arrays. e.g. set ar1=ar2 & ar3. Similar operation can be performed by ArrAdd.

In case of alphanumeric values the operations are not case sensitive.

In case we want to add an array at the end of another (set arr1=arr1&arr2) prefer to use the faster and economical alternative syntax of set arr1&=arr2 or ArrAdd command. Also arr&=arr2.

A number of functions that return arrays can be used in array expressions. These are:

NOTE:

Commands LEFT, MID, RIGHT have alternatives that work with strings in string manipulation.

The precedence of the operators used in array expressions is the same. Parentheses can be used where this is needed.

See also about Inline arrays below.

Inline arrays Inline arrays are arrays represented by their contents directly and not by some array variable. Inline arrays appear in the code as a list of simple expressions separated by commas and enclosed by brackets. e.g.

set ar=[1,2,v1,v2,v3+5]

The above represents an inline array of 5 elements which is used to initialize array variable ar. Every element can be the result of an expression. 

Inline arrays can participate normally in array expressions like anything is or returns an array. Inline arrays that contain only constant values are created at compile time and so introduce no performance penalty at run time.

An example code:

Clear;
set a1=[1,2,3,4];
set a2=[10,20,30];
set a3=sortn([1000,2000,3000] & a1 & a2 & [100,200,300]);
PrintArr(a3);

Remember that sortn is for numerical sorting.

TIP: Inline arrays can be used in case we need an interface of variable numbers of parameters in local functions and UDF. In a local function or UDF we can declare an array (IN) parameter. This parameter can accept an inline array:

function MyFunction(arr p);
...
...
end;

MyFunction([1,2,3,4,5]);   
//five values passed
MyFunction([100,200,300]); 
//three values passed
MyFunction([5]);           
//one value passed

NOTE: In array expressions of only one inline array that contains only one element, the brackets can be omitted. For example the following statement,

set a1=[123]; //only one inline array with one element

can be written as:

set a1=123; //only one value without brackets

The same is valid for IN (by value) array parameters in local or standard functions.

See also array expressions above.

Fine tuning Dynamic arrays operation Dynamic arrays is an important part of the CalcIt language and an explanation of the way they work is needed.

The main characteristic of the dynamic arrays is their ability to increase their size dynamically. The programmer can add any number of elements at any moment in a dynamic array. This is possible because CalcIt language reallocates the memory occupied by a dynamic array so the array is able to accommodate more elements.

Every time a reallocation occurs the data existed in the previous allocation are copied to the new greater in size allocation.

The reallocation operation is a very expensive operation in terms of execution time and memory. At the time of the reallocation an array has to occupy more that the double of the memory it is needed. Then a copy takes place to move data from the old allocation to the new which is very costly. The previous allocation, after the copy, has to be freed involving another access to the generally slow memory manager. Additionally the free memory pool is at risk to be fragmented and become unusable.

For the above reasons CalcIt language tries to avoid frequent reallocation as possible. The technique used is simple. Every array reserves more elements than needed. This way when a new item is added to the array one of the preallocated reserved elements is used and the reallocation is avoided. The reallocation occurs when all the reserved space is exhausted. Every reallocation adds to the array the elements requested plus the reserved space.

By default the reserved space is 500 elements. This in most codes is ok. But some times will prefer a different number. An array in local function which is reinitialized every time the local function is called is slower if it has to allocate every time 500 elements. We know that this space is never used so is possible to prefer a smaller number. Other times we have to add in a array a very big number of elements that we know is not bigger than a specific number. Then we prefer a reserved space equal to this number instead relying in too many reallocations that slow down the operation considerable and also, because of memory fragmentation, can cause a "memory full" error when plenty of memory seams available.

Using the syntax below is possible to define array's reserved space size:

set a(ArrSize, ReservSpace)

The above syntax defines (or redefines) an array variable a to ArrSize elements using ReservSpace elements reserved space.

Both parameters are optional. If ReservSpace is not provided then 500 is assumed.

An alternative syntax can be used, as well:

set a=arr(ArrSize, ReservSpace)

Using Fields This feature added for reasons of code readability and flexibility. Applies to every simple variable and array element. Every such variable/array element normally holds only one value. But sometimes it is needed to keep in one such variable more than one values through the use of fields. Better explain that with an example.

Handling Fields at Compile Time (when the field's name is known at compile time):

Let's say, a variable MyComputer. With the assignment below it takes a value:

MyComputer:='TEAC TFT ';

What if is needed to add more values in the variable MyComputer regarding our hard disk, keyboard, mouse, CPU etc, like subcategories of MyComputer? This is a nice case to use fields and add in one and only one MyComputer variable all these values:

MyComputer.Monitor:='TEAC TFT';
MyComputer.HD:='WD 200GB';
MyComputer.Keyboard:='Microsoft natural';
MyComputer.CPU:='Intel Pentium';
...

Just using these assignments the fields Monitor, HD, Keyboard and CPU are created for the variable MyComputer and of course it is possible to refer to them the same way.

print(MyComputer.Monitor);

At the first writing to a variable's field, the field is created and the variable goes to a multi-value-field mode. It is possible to go back in a single value mode using again an assignment and not using fields any more:

MyComputer:='Is an example';

In this assignment any field values will be destroyed and only one value will be forced for the MyComputer. The same happens in case of field assignments. Any previous single value is destroyed.

Generally, copying a variable to another always makes a perfect copy whatever mode was the variable that receives the copy, before the copying.

HINTS: Fields can be used to reduce the number of parameters in local functions or UDF. Many values can be passed in only one parameter. Especially, in case of one IN/OUT (by reference parameter) many values can be returned at once. Also a local function or UDF can return many values in its result.

NOTE: It is possible to create fields on fields in an endless chain. For example:

MyComputer.MotherBoard.cpu:='P4';
MyComputer.MotherBoard.Memory:='512MB';

This feature will be very useful and handy for array elements. Imagine an array where every element have a number of values accessed through the same fields. Of course every element can have different fields, there is no limitation here, but occasion to be such thing useful will be, probably, somewhat rare.

In case of arrays the use of fields has some consequences. What happens with array expressions where array elements have different fields or participate arrays with different or no field structure at all? What about searching, sorting etc array with element in any field or not configuration?

For the arrays only is introduced the concept of the default field. For every array variable can be defined a default field using the command SetArrField.

Clear;

set Computers(0);

function addv(&v,cpu,monitor);
 v.cpu:=cpu;
 v.monitor:=monitor;
end;

addv(Computers(),'INTEL P4 2.26MHz','TEAC TFT');
addv(Computers(),'AMD ATHLON64','EIZO CRT 19');

SetArrField(Computers,'cpu');

Sort(Computers);
PrintArr(Computers:20,true);

Lets explain the above code. There is an array variable Computer. We want to add elements where each one holds two values for always the same fields cpu and monitor. A local function (addv) helps this purpose. At every execution of addv is passed the Computers() (in the place of an IN/OUT parameter - observe symbol &). This syntax adds a new element to the array and returns it. So in every execution of addv a new elements is created, is passed to addv code and it is initialized in the two fields, cpu and monitor. So the above codes adds two elements.

Command SetArrField sets the default field for Computer to be the field cpu. So the Sort command bellow will sort the array using the field cpu. The above code prints:

1. AMD ATHLON64       TEAC TFT
2. INTEL P4 2.26MHz   EIZO CRT 19

Really, the sorting is by cpu field. Lets now change the command SetArrField to set as default field the monitor.

SetArrField(Computers,'monitor');

The new execution of the code prints:

1. INTEL P4 2.26MHz   EIZO CRT 19 
2. AMD ATHLON64 TEAC  TFT

Indeed, the sorting is now by monitor field.

All array expressions operators, sorting, searching and the UNIQUE command are affected by the selection of the default field.

In case there are array elements with no field structure, then, their single value is always considered in the operation.

In case of array expressions where two arrays participate through one of the operators (Intersection, union, etc) the resulting array has as default field the one of the first operant.

It is possible to clear the default field with the following syntax:

SetArrField(Computer);

In case there is a need to create an array with the values of one field of another, the command ArrCol can be used.

set cpus=ARRCOL(Computer,'cpu'); 

The field parameter is optional. If omitted then the default field will be used.

To save and read back arrays with multi-field elements the commands XWrite and XRead can be used.

Handling Fields at Run Time:

The above description speaks about fields which their names are known at compile time. There is also the capability to create/access fields that are not known at compile time. In this case a special syntax is used to read or write to these fields. Lets say a variable vr. At run time a field computer is created/accessed with the following statement:

vr.('computer'):=100;

The syntax uses a DOT (like the compile time fields) and then follows an expression enclosed in parentheses which returns the name of the field.

Commands specific to Fields handling

Every field can be extended to contain an array. See more at Array extensions below.

Array Extensions Every simple variable, array element or field, can be extended to contain an array. This way very complex hierarchical data structures can be created.

Normally the compiler recognizes an array because a variable is declared as such through set command. In case of simple variables, array elements and fields, another way is needed to provide a similar distinction at compile time. So wherever any of the previous contains an array and has to be passed as a whole to a command or to participate in an array expressions, then the symbol @ is used to denote this. For example @a, where a is a simple variable, can be used like a declared array variable. @r(5) is an array element (of array variable r) that can be used as an array, by itself, in array expressions. The same with @a.f1 in case of a field (f1). The only exception is with set r(10) syntax (where any positive number can be in place of 10). In case of array extensions this syntax cannot be used. Use set @a=arr(10) in its place.

To access one element at a specific index, the parentheses are used in exactly the same way as with declared arrays. For example if a is a simple variable then a(10) accesses the 10nth element of the array it contains. In case an array is not assigned to this variable before, then an error will stop the program at run-time. So parentheses with an index, applied directly to any simple variable, field or array element always mean access of an array element at the specified index. Exactly the same way as with declared array variables.

This way very complex data hierarchies can be created and handled. Can be printed on screen through PrintArr command, saved to disk through XWrite and loaded back through XRead.

let's say a is a simple variable. To be used as an array first must be initialized as as array. Assigning the result of an array expression is the more direct way:

@a:=arr(100);
set @a=[100,200,300,400];
PrintArr(@a);
set x=@a & [1,2,3];

In the above code first and second lines assign the result of an array expression to @a with two equivalent ways. The third line prints @a like it was an array variable. The fourth assigns to array variable x the result of an array expression where @a participates in an array concatenation operation.

@a:=[10,20,30,40];
for(i,1,size(@a));
 a(i)+=1;
end;

The above code adds 1 on every element of the array contained in @a; The special array FOR syntax can be used, to the same purpose, in the code below:

@a:=[10,20,30,40];
for(@a,s);
 s+=1;
end;

In all these examples @a is used like any other usually declared array variable.

Another example below demonstrating how an array element can be extended to contain another array and how in this case the individual elements are accessed.

set ar=[100,200,300];
@ar(2):=[1,2,3,4];
//equivalent to set @ar(2)=[1,2,3,4];
ar(2)(3)+=1000;
PrintArr(ar);
print('************');
PrintArr(@ar(2));

The above code declares an array variable ar assigning to it the contents of an array expression (in this case is just an inline array). Then takes element at index 2 and overwrites its previous contents (previous value 200) to contain by itself an array, again assigning the contains of an array expression. Symbol @ is used to denote that this elements has to be handled like an array variable. Then adds 1000 to the third element of this new array. The rest of the lines of the above code prints the result of the root array variable ar and after that the contains of the array contained in its second element only. It prints:

100
+1
 2
 1003
 4

300
************
1
2
1003
4

NOTE: Through array extensions, functions can return arrays.

Automation Objects Through automation variables, objects exposed by automation servers (like Excel) can be accessed. 

SET o=OBJC('Excel.Application');

This creates an Automation variable connected with the Excel application. Using o we can access Excel and "borrow" its functionality. 

E.g. When the Excel runs execute the code below.

set o=OBJC('Excel.Application');
for(i,1,10);
 o.Cells(i,5):=i*100;
end;

The example above fills with numbers some cells of the active sheet.

The SET command for automation first tries to connect in an instance of a server already running. If the requested server is not running then creates a new instance of it.

Alternatively we can write

SET o=OBJC;

This creates a new "empty" automation variable. With the use of SET command we can assign to it content later in the code. This syntax creates "placeholders" for intermediate objects returned by properties or methods of automation objects that are objects too.

To declare an automation variable and the same time assign to it an object returned by a property or method of another object, we can use the syntax below:

SET no=OBJV(o.Range('A1:A10'));

This will assign the Range object (in Excel) A1:A10 to the variable no. And so on.

If the automation variable is already declared as such, is posible to use a simple assignment to pass a value to an automation variable:

no2:=o.Range('B1:B10');

We can create automation object variables through a automation object pointer returned by an imported (via DLL) routine using OBJP keyword:

SET o=OBJP(optr);

We can pass automation objects in object methods that require such a parameter.

We can denote omitted parameters simply placing only the comma (,) character.

If a parameter in a method is IN/OUT, its value is not returned except we use the character & as the following text describes:

o.AnyMethod(123,v,100);

Lets say that the second parameter can return a value (IN/OUT parameter). With the above syntax this value will not be returned (but the value of v will be passed normally to the automation server). If we want to get the returned value then we can do that with the following syntax:

o.AnyMethod(123,&v,100);

File Variables. We can create File variables that permit us to access for read or write any kind of file.

Using a file variable we can access a file in many ways like an array (a memory object) either for read or write. This makes their use very convenient.

This feature can be used to explore a file of any structure, to patch a file, use a file for persistence storage of the data we handle on it or create binary files of any structure and content.

We declare a file variable simply using the SET command:

set f=FILE('c:\test.dat',false);

This creates (or redefines) a file variable named f. Using this variable we can access the file it points: 'c:\test.dat'. The access to this file can be read-only or read-write. This is defined by the second parameter which is optional. (Default is read-only, TRUE). In our example the access is read-write.

The way to access the file is similar with arrays. We use an index:

a:=f(100,atBYTE);

With this statement we read a byte value (atBYTE) at index 100. The meaning of the index by default is a byte index from the start of the file (the first byte is at index 0). Its meaning can be changed using the command FileRecDef which helps to break the file in equal sized pieces of any size (records). Read more at Using File Variables topic.

Whatever kind of indexing we accept or define eventually we will conclude in a byte index from the start of the file. There we can access for read or write the file using the syntax that the above example implies. The memory in the requested file position can be accessed as byte (in the example), as a word (atWORD), as double word (atDWORD), as integer (atINTEGER), as float (atFLOAT), as a string of a fixed size or as a null terminated string (of no fixed size) (atCHAR). These access types are fully explained at Using File Variables topic.

To write a value in a file variable we make normal use of the assignment operator:

f(100,atBYTE):=230;

If variable f is not in read-only mode then the value of 230 will be written at position 100 of the file (zero-based index).

NOTE: Because atBYTE is the default access type (if not altered by FileRecDef), can be omitted.

f(100):=230;

Here is a reference of commands used to handle binary files:

For a full description and tutorial of this feature see Using File Variables topic and alphabetical keywords list.

NOTE: We can handle files of up to 2GB in size.

Buffer Variables Buffer variables is the only way, in CalcIt, to access a piece of memory using "fields" of a specific type, like integer type, string type, double type, etc.

For every Buffer variable exist two things:

  1. The memory address of the buffer we want to access, and
  2. The definition of the way we want to access it.

The way we want to access a buffer is a number of fields of a specific type one after another. We call this group of fields a "structure". To define a structure definition we use the command BUFDEF:

const TelephoneBook=BUFDEF(Name:atCHAR[30], 
                           Address:atCHAR[30], 
                           Telephone:atCHAR[30]);

The above defines a structure of three string fields of 30 character each. We can define fields of the following types:

  • atINTEGER or atINT. Signed 4 bytes integer.
  • atWORD. Unsiged 2 bytes integer.
  • atDWORD. Unsigend 4 bytes integer.
  • atSHORT. Signed 2 bytes integer.
  • atBYTE. Unsigend 1 byte integer.
  • atFLOAT. Double floating point number (8 bytes)
  • atINT64. 64 bit signed integer (8 bytes)
  • atCHAR. String of fixed number of characters.
  • atPOINTER. Memory address.
  • atBUFFER. Substructure definition.
  • atCALLBACK. Denotes a function pointer (CALLBACK).

BUFDEF command is always evaluated at compile time and so we can pass its value in a local constant.

Having define a structure we can now create a Buffer variable.

set bf=BUFFER(TelephoneBook);

The above creates a buffer variable of type TelephoneBook. When a buffer variable is created this way, the correct size of memory is allocated. We can access this memory for read or write using the following syntax:

bf.Name:='Dimitris';
bf.Address:='Klaras';
bf.Telephone:='777888999';

The dot character is used to separate the field name from the Buffer variable.

In an alternative syntax we can merge the structure definition with the declaration of the Buffer variable:

set bf=BUFDEF(Name:atCHAR[30], 
              Address:atCHAR[30], 
              Telephone:atCHAR[30]);

Buffer variables allocate their own memory automatically but this is not the only case. We can declare a buffer variable to point in any valid memory address. For this purpose we use the optional part ADDR in any Buffer variable definition:

set bf=BUFFER(TelephoneBook)ADDR(AnyMemAdr);

or

set bf=BUFDEF(Name:atCHAR[30], 
            Address:atCHAR[30], 
            Telephone:atCHAR[30])ADDR(AnyMemAdr);

Where we get AnyMemAdr? AnyMemAdr can be:

  • Any memory address (pointer) returned by an API function call
  • Can be the address of a field of another Buffer variable
  • Can point in a part of a file variable

set bf=BUFDEF(Name:atCHAR[30], 
              Address:atCHAR[30], 
              Telephone:atCHAR[30])ADDR(&bb.fld);

The above creates a buffer variable that points to the address of bb.fld of another Buffer variable bb. Character & means "Address of". You can pass there a File variable in exactly the same syntax:

set bf=BUFDEF(Name:atCHAR[30], 
            Address:atCHAR[30], 
            Telephone:atCHAR[30])ADDR(&FileVar(500));

Doing so it is possible to access a file (controlled by the File variable - FileVar in our example) through the Buffer variable and its fields.

The whole contents of a Buffer variable can be printed on screen using PrintBuf command. e.g.

set bf=BUFDEF(a,b,c:atINT,x:atCHAR[20])=[10,20,30,'Computer'];
PrintBuf(bf);

This will print:

A = 10
B = 20
C = 30
X = Computer

Additionally Buffer variables can be used to describe API calls:

Lets say that there is a DLL math.dll with the following exported C function:

int SumTwoNumbers(int n1,int n2);

This can be described with the following Buffer variable:

set AddTwoNumbers=BUFDEF(n1:atINTEGER, n2:atINTEGER)API('math.dll');

The name we choose for the Buffer variable must be the same with the one of the imported function. To make the API call we use the command APICall:

sm:=APICall AddTwoNumbers([100,200]);

Using this syntax the call to AddTwoNumber is made, the numbers 100 and 200 are added and the result (300) is returned to the variable sm.

For complete information about Buffer variables see "Using Buffer Variables" topic.

Classes and Class Variables To create a Class variable we have, first, to create a Class definition. A Class is created almost the same way as a UDF. Is a piece of CalcIt code and contains global variables and local functions. Some of the functions or variables can be declared as public and this way, through a Class Variable, can be called directly in any other piece of CalcIt code. Additionally in Class definitions we can declare public constants.

Multiple inheritance is supported. A Class can inherit any number of other Classes.

Class definitions, like UDF are stored in a special list, the Classes list.

Every Class has an optional construction and an optional destruction. In construction generally we initialize our global variables. Destruction is useful mainly when a class is used as a wrapper of an API. In such a case, for example in a DATABASE API, we connect in construction and disconnect in destruction.

The construction code is actually all the main code if it was a UDF (the code not included in local functions) and the construction parameters are actually the parameters of UDF, if it was a UDF.

To have destruction code to be called automatically by CalcIt when the Class variable is destroyed, we just have to include in our Class code a non public local function with the name "Destroy".

Every Class variable is an instance of one of the defined Classes in Classes list and has its own private memory for the global variables it contains. Lets say that there is a Class with the name CArray which implements a two dimensional dynamic array. In its construction takes two parameters that define the dimensions of the array. We create an instance of this Class with the following syntax:

set ar=CLASS CArray(10,2);

The above creates a Class variable ar for the Class definition CArray and calls its construction code to initialize a two dimensioned array 10X2.

The Class CArray contains a public function with the name Rows. This function returns the number of rows currently in the array. We can print the number of rows using the following syntax:

print(ar.Rows);

Using the dot character we separate the Class variable from the name of the public function we call.

Classes and Class variables is a more powerful and versatile way to extend Calcit functionality instead of using "monolithic" UDFs. Their use is important as "Import Classes" to wrap APIs in the most convenient, for the user, interface.

For a complete description of Classes and Class variables see "Using Classes and Class Variables" topic.

Delimited Text Any kind of text which contains parts separated by a specific character not used for any other purpose in the text, is a kind of delimited text. CalcIt can handle efficiently and easily such kind of text which is very common in text processing. For example consider the following statement:

a:='House|Computer|Pentium|Music|Mouse|Tarzan';

Assigns a kind of delimited text to the variable a. We use character | as the separator character. This character is also the default separator character for CalcIt. There are 6 separate parts in this text. We can access any of these parts easily either for  read or write. See how:

a#(3):='ZIP drive';
print(a#(3));
print(a);

The first statement changed the third part of the text and replaces the previous value of 'Pentium' with the new variable of 'ZIP drive'. The second statement prints in output window the contents of the third part (now 'ZIP drive'). The third statement print the now changed contents of a, that is:

House|Computer|ZIP drive|Music|Mouse|Tarzan

As we can observe an index kind of syntax is used to access the parts of a delimited text, following symbol #. In case of an array element the following syntax is used:

arr(3)#(2):='test';

Assigns in the third position, second delimited part the value 'test'. 

We can count the number of  parts in a delimited text using the PartsCount command. E.g.

PartsCount(a) will return 6.

Handling delimited text that way the default delimiting character is used. This character is | or chr(124). We can change the default delimiting character with the command DELIMITER. e.g.

DELIMITER:=' ';

The above changes the delimiting character to the space (' ') character. That way any text containing English  (or in any other language, of course) can be accessed at a word level.

Additionally we can define the delimiting character to be used in a specific command using another optional parameter in the access syntax. E.g.

a#(2,','):='test';

The above accesses the second part using comma (,) as the delimiting character

We can access individual characters in a part of delimited text for read and write using [ ] symbols and an index:

a#(2)[3]:='w'; //write character w in third position of second part ('Computer')
k:=a#(2)[3];  
//Read third character of the second part and assigns
              
//it in variable k
.

Also, in one statement, we can apply delimited text syntax as many times as needed using different delimiter every time. e.g.

s:='<NAME>CalcIt</NAME>';
s#(2,'>')(1,'<');
//this statement returns CalcIt

The second line of the above code returns the value part of any string in the form <something>value</something>. The first time the > is used as a delimiter that returns 'CalcIt</NAME>' and the second the < is used as a delimiter to return 'CalcIt'.

NOTES:

Parts of delimited text can not be passed in IN/OUT parameters of Local Functions or UDFs. Can be passed normally in IN parameters of any kind.

Also PACK and UNPACK commands can be used to handle delimited text. PACK packs the contents of an array in one value of delimited text. UNPACK does the reverse. See more about these commands in the alphabetical list of CalcIt symbols.

Delimited text for  dynamic arrays (see above) are handled with a second  index inside a new consecutive pair of parentheses.

Delimited text syntax (and also character access [ ] syntax) can be applied the same way in symbolic constants and function results (Local, Standard or UDF). Of course only for read. 

See also Text Processing below.

Text Processing CalcIt language is very flexible in handling text files and data. There are many commands to handle text data:

CADJ,DELETE, FIND, INSERT, LADJ, LEFT, LEN, LTRIMLOCASE, MID, RADJ, REPLACE, RIGHTRTRIM, TRIM, UPCASE.

Not to forget the easy handling of delimited text! Also commands REPLACE and FIND have versions that use powerful regular expression syntax to handle text data: REReplace and REFind. See regular expressions syntax and how these commands can be used in alphabetical keywords list.

Plus operator (+) or even better (faster) operator & can be used to concatenate easily text values and create composite strings.

We can use all comparison operators (<,>,<>) to compare two alphanumeric values. Faster operators specifically for alphanumeric comparison are available: eq, ne, gr, ls, ge, le.

We can access individual characters in the content of a variable or array element using [ ] symbols and an index:

a[2]:='w';  //Writes w at 2 of a
k:=a[2];   
//Reads second character of a and
           
//assigns it to variable k

Using READ and WRITE commands we can read or write a text file in a single step. Text files are always loaded in array variables which makes their processing very easy.

See also String formatting below.

String formatting Inherent in any expression is the ability to format easily its result as regards width, justification, fractional digits for numeric values, the character to be used for the justification and thousands separation. Consider the following example:

PRINT('Total is ',(1234567.34+10)*2:'15.3R*,');

In the second expression after (:) follows a string that defines the formatting to be used. The output of the above statement is:

Total is **2,469,154.680

The part of the statement in bold red formats  (1234567.34+10)*2 result in a with of 10 characters where the 3 is for fractional digits, Right justified, using asterisk character (*) to fill the empty places and using comma (,) to separate thousands.

All sections in formatting string are optional and can be omitted but the order to put them is specific as below:

W.FJNST

Where:

  1. W: Is a number that determines the total width the formatted string will take.
  2. .F: Represents the fractional digits. After the point character follows a number to designate fractional digits.
  3. J: Determines the justification. Can be L for left justification, R for right justification and C for center justification. If omitted then L is assumed.
  4. N: Determines Hex format (H), Binary format (B), Engineering format (E), Date format (D) or Normal numeric format if this option is omitted. Numeric values for Hex or Binary format have to be in the range of unsigned 32 bit integers.
  5. S: Represents a single character to fill the "unused" spaces of the resulting string. If omitted then the space character is assumed.
  6. T: Thousands separation. You can put there only the comma character (,) to request thousands separation. Always the comma character is used for thousands separator.

TIP: If you want to format data in width and/or fractional digits then you can omit single quotes in the formatting string. e.g. print(1000.1:10.2);

NOTE: Formatting string can be the result of an expression.

For more string formatting capabilities see also STR, STRF, PRINT, PRINTF.

Creating and Using Forms: Form variables With Form variables we can create and handle data entry forms. To use a Form in our code we have first to define its layout using the command FORMDef. This command returns an ID representing a Form layout and we can use it to create running instances of it.

id:=FORMDef(POSITION, DIMENSION, PROPERTIES LIST, CONTROLS LIST);
set frm=FORM(id,100);

With the first statement we create a form layout and with the second we create a Form instance based on this layout. The constant 100 is an ID we choose to name the newly created Form instance. Using the variable frm we can handle the Form, access its properties and the properties of controls it contains.

frm.Border:=bsSINGLE;

The above statement changes the border of the Form to a single line with no resize capability.

frm('MYNAME').Text:='Dimitris';

The above assignment statement changes the text of an edit control in frm. The constant MYNAME is the ID we gave to this controls in its definition via the FORMDef command.

The user interaction with a Form fires events and the code needs to handle them and take appropriate actions. For example the user presses a button "Calculate" and our code must catch it, make the calculation based in the user input in some edit controls and display it in others suitable for display purposes. To handle events we just have to place in our code a local function with the name EventProc. This function must have a specific parameter interface that passes all the information needed to identify the type of the event and its source so to take the correct action. The interface is the following:

function EventProc(FrmID,ControlID,Action,&Data);

Using the parameters we can distinguish which form fired the event (FrmID), by which control (ConrolID) and what kind this event was (Action). Some events carry additional information and this is the purpose of the Data parameter. Supported events are:

  • OnChange
  • OnClick
  • OnKeyPress
  • OnMouseMove
  • OnTimer
  • OnClose
  • OnAfterDel
  • OnAfterIns
  • OnValidValue

To make a Form visible and start editing we use command ShowForm.

ShowForm(frm);

To hide\close a Form we use the command CloseForm.

CloseForm(frm);

When our code uses its own Forms maybe is not desirable to have the usual CalcIt windows behind and we can hide them using the command Hide. We can make them visible again using Show. Note that when the code finishes the CalcIt windows become visible automatically.

If CalcIt windows is hidden, still we have the ability to see the output of PRINT, PRINTF,  PRINTARR redirecting their output to any Memo control on our Forms. We use SetOutput command.

SetOutput(frm,MemoID);

Here is a summary of commands related with Form variables:

In bold are the special commands that define controls in the controls definition list. See more about them at Defining Controls.

See a complete description of this feature in Using Form variables topic.

Visual Form Designer (VFD) can handle FORMDef creation in behalf of the user who designs his/her Forms interactively in a WYSIWYG editor.

Commands CONFIRM and SHOWMESSAGE can benefit Form programming.

See also CalcIt executables.

Printing in color and style (on screen and printer) Is possible to print a text with sections in various colors and styles. For this to be possible a form with a ctrRTFMEMO (Rich Text Format) control is needed and this control has to be set as default output through SetOutput command. Then the usual PRINT or PRINTF commands are used with a... twist!

To those commands, the additional information for the color and style has to be passed. So a multi-field value is used which passes the value to print in one field and the color and style information in others.

The Fields are:

  • V for the Value
  • C for the Color
  • S for the Style

The color information can be any RGB color (returned by the RGB command). Also any of the predefined RGB constants (like clRED etc) can be used.

The style information is passed through a string value which contains a combination of the letters B, U, S, I. B stands for Bold, U for Underline, S for Strikeout and I for Italic.

VCS command can help to pass easily the additional information. For example VCS(100,clRed,'BUI') will return a multi-field value with the following field arrangement:

V=100
C=clRed
S='BUI'

The command

print( vcs(100,clRed,'BUI') )

will print something like this:

100

PRINTF can be used in the following way:

printf(vcs('Test %  %  %',clBlue,'B'), [vcs(100,clGreen,'U'), vcs(200,clNavy,'I'), vcs(300,clYellow)]);

The above will print:

Test 100 200 300

NOTE that ctrMEMO control is faster and consumes much less system resources than ctrRTFMEMO. Of course if only few lines has to be print, there is no much a difference.

TOPRINTER command can send the contents of any ctrMEMO or ctrRTFMEMO control, currently selected as output, to the default printer.

Local Functions Inside CalcIt code local functions can be defined. These functions must not confused with User Defined Functions (UDF).

Parameters can be of the following types:

  • Simple (IN or IN/OUT)
  • Array (IN or IN/OUT)
  • Automation (IN or IN/OUT)
  • Buffer (IN or IN/OUT)
  • Function parameter (IN)
  • Form (IN/OUT)
  • Class (IN/OUT)
  • File (IN/OUT)

For simple and array type IN parameters default values can be assigned.

NOTE: IN parameter passing is what usually call BY VALUE and IN/OUT is what usually call BY REFERENCE.

See more in alphabetical list of CalcIt symbols.

See also Properties below.

Properties A property is a special kind of local function. Offers a syntactical convenience.  Can be described as "programmatic variable". We can read the value of a property the same way we can call a local function and get the value it returns BUT we can write to a property using the assignment operator (:=).

The code of every property has three parts.

  1. Common part. It is called either for read or write operation.
  2. Read part. It is called only for read operation
  3. Write part. It is called only for write operation. In this case the RESULT contains the value we intent to write.

Properties can be created in any piece of CalcIt code and are especially useful to Classes. In Classes we can have public properties. See Using Classes and Class variables topic.

A property code looks like below:

property test(i);
 
//common part
 i*2;
GETVALUE:
 
//read part
 x(i);
SETVALUE:
 
//write part
 x(i):=RESULT;
end;

Where x is a global array.

For complete information about properties see Property in Alphabetical list of CalcIt symbols.

User Defined Functions (UDF) User Defined functions can be defined to extend easily the set of available commands in CalcIt. These functions are written with no different way from any other piece of CalcIt code and customize the CalcIt command set to the special needs of every user. See more in the specific topic.

Parameters of UDF can be of the following types:

  • Simple (IN or IN/OUT)
  • Array (IN or IN/OUT)
  • Automation (IN or IN/OUT)
  • Buffer (IN or IN/OUT)
  • Function parameter (IN)
  • Form (IN/OUT)
  • Class (IN/OUT)
  • File (IN/OUT)

Array, Automation and Function parameters can be declared through command extern only. Additionally only through extern we can declare IN/OUT parameters when this is permitted. Default values can be assigned to simple type parameters through extern command.

Simple variables of the code become automatically parameters when the code tries to read their value before any assignment operation to them. This way the parameters are always IN.

Loops Loop commands fully supported in CalcIt language. FOR, WHILE, REPEAT. See alphabetical list.
Program Flow control IF...ELSE...END and SELECT...CASE...ELSE...END constructs can be used.
Clipboard We can exchange data easily with clipboard. See CLIPREAD and CLIPWRITE commands in alphabetical list.
Operators CalcIt supports many operators for numeric,  alphanumeric or logical processing. For the logical operators AND and OR Short Circuit Boolean evaluation is supported. See more in the specific topic.
Date handling There are commands to support date data. Dates can be represented by a number which can be processed as any number. Also there are commands to switch from numeric to human readable format and reverse. See commands DATE, DAY, MONTH, YEAR, TODAY, DATESTR, WEEKDAY, STRDATE, VALIDDATE

Date constants can be inserted directly in the code. See CalcIt constants.

TODAY function returns the system date in various convenient formats in a multi-field value.

TIME Use TIME command to get the current system time
Directory Handling Commands to handle files on disk. See commands READDIR, CURDIR, DELFILE, RENFILE, GETFILE, GETPATH, FILEEXISTS, MAKEDIR, COPYFILE, DIREXISTS.

Command READDIR loads the contents of a directory in an array variable in a single step.

Text Output Commands PRINT, PRINTF CLEAR and MSG/DESCR can be used to write text in special output windows.
Debugger See specific topic of how we can debug CalcIt code with a full featured source level debugger.
Trigonometric functions SIN, COS, TAN, ARCSIN, ARCCOS, ARCTAN. Also we can select between DEGREES, RADIANS or GRADIENTS using ANGLESUNIT command.
Hyperbolic functions SINH, COSH, TANH
Numerical, Statistical ABS, FRAC, INT, ROUND, SQRT, MIN, MAX, AVRG, SUM
Random number generation RANDG, RANDOM functions, RANDOMIZE command and RANDSEED system parameter.
Constants There are many and powerful ways to define constants in CalcIt. Also there is a number of predefined constants. Additionally the user is able to create his/her own symbolic constants in Constants list. Also we can declare symbolic constants local in a code using the command CONST.

  See specific topic.

Commands to handle Calculation Items The commands below handle Calculation items in the Calculations list (Calculating part)