CalcIt language supports Object Oriented Programming (OOP). The Class is a definition and Class variables are instances based on Class definitions. Every Class has construction code and optionally automatically called destruction code. Classes can have public functions (or methods), properties or variables (members) that can be accessed through Class variables using the dot character syntax. Classes can inherit any number of other Classes (multiple inheritance) and Class parameters (in functions or properties) can accept Class variables of any derived Class of the Class type of the parameter (polymorphism). Virtual methods are not supported.
The text which follows will not try to explain OOP terminology. The reader must know it already and read here just to see how this terminology maps to the OOP implementation provided by the CalcIt language.
A Class definition is a CalcIt element like UDF, Application, Expression etc. They are created and stored in their own list, the Classes List.
A Class definition is created much like a UDF is created. Even if visually are stored in separate lists actually share the same user interface for creation and modification. So the reader has to be familiar with UDFs before proceed in this topic.
Even the Class definition is created the same as a UDF it is used in a completely different way. A Class cannot be executed directly. The Class definition name is used to create one or more Class variables using a specific syntax. Using the Class variable, a number of functions, properties or global variables (in Class definition scope), declared as PUBLIC, can be accessed.
The usual UDF parameter's interface, in case of Classes is the interface of the constructor.
When a Class variable is defined then the construction code of the Class is called. The execution starts from the start of the Class code through its end much like the execution of a UDF. Every global variables (Class members) will be initialized and any code available will be executed. Every action that represents the construction of the Class instance must be executed at this point.
A Class can have an optional destruction. If the code includes a non public function with the name Destroy then this function will be called automatically when CalcIt performs the cleanup of a code that has finish its execution.
The first reason to offer the Class capability in CalcIt was the need to handle with a more convenient way the APIs needed to import. In this case, an "Import Class" wraps APIs logic and offers a CalcIt like interface to the end user which is much more convenient.
For example to import ODBC we need a number of steps. First to connect to the database, create a number of handles, check error codes, fill some buffers, decode the information in others. A lot and not easy work and error prone, especially for the inexperienced one. Why not just to create a Class variable and execute a public function, say ExecSelect which takes our SQL statement and returns in an array all requested information decoded as needed? This way we do not even need to know how to handle ODBC API, only how to create a Class variable and call a routine/method. This looks much more easy.
set c=CLASS ODBC('MYDB');
set o(0)
c.ExecSelect('Select * from Customers where Debit<1000',o);
PrintArr(o);
The above code opens a database MYDB, executes an SQL select statement and returns the requested information in array o. Finally prints the contents of o at the screen. The ODBC Class is given as a sample and contains more than 400 lines of code.
Classes and Class variables are not solely used to import APIs. Can be used beneficially to create useful libraries in CalcIt. Example of that is the CArray Class given as a sample too. Implements a two dimensional array I have used a lot.
We write a Class the same way we write a UDF function. Visually a Class differs from a UDF only at the fact that some of the Class local function, Properties or global variables are declared to be PUBLIC. For example in Classes will see functions declared in the following way:
function AddTwoNumbers(n1,n2);public;
n1+n2;
end;
Additionally we can see properties to be declared as public.
property Item(i);public;
i*2;
GETVALUE:
v(i);
SETVALUE:
v(i):=RESULT;
end;
In a Class only one of its properties can be declared as DEFAULT.
property Item(i);public;default;
i*2;
GETVALUE:
v(i);
SETVALUE:
v(i):=RESULT;
end;
We will see later what a default property is.
Writing a class we need to have three things in mind:
Besides having public functions we can declare public constants or variables too:
public const WM_MOUSEMOVE
= $0200;
public const WM_PAINT
= $000F;
The above code fragment defines two public constants, WM_MOUSEMOVE, WM_PAINT.
To mark variables as public the following syntax is used:
set c=CLASS AnyClass;
set bf=BUFDEF(a,b,c:atINT);
a:=100;set r(100);
public(c, bf, a, r);
//c, bf, a, r marked as public
The above syntax cannot create new variables. Variables must be defined/declared before marked as public. Simple variables, Class variables, Buffer variables and array variables can be marked as public.
When our Class is ready and stored in the Classes List, we can create instances of it in any piece of CalcIt code. A Class instance is a Class variable declared with a special syntax of the SET command.
set c=CLASS MyFirstClass(10,20);
The above code creates a Class variable c as an instance of the Class MyFirstClass. The creation of Class variable invokes the construction code of the Class. In the example above the construction uses the values of two parameters.
When a Class variable is created, memory is allocated for all the global variables it contains. We can create more than one Class variables for the same Class in one code. Each one has its own private memory for the same global variables.
NOTE that, when many Class variables are declared in the same code then their destruction routines will be called in the reverse order than their declaration, when the code which contains them finishes its execution.
It is important to note here that by contrary with all other variables created with the use of the SET command, Class variables cannot be redefined.
Through a Class variable we can call public functions or properties of the Class. The dot syntax is used in the expected way:
c.PrinMyName;
In case of the default property in a Class (if there is
one) we can omit its name. For example lets suppose that there is a default property Item
in Class MyFirstClass
which takes as a parameter an index
value. We can write:
a:=c.Item(10);
c.Item(10):=100;
and equally
a:=c(10);
c(10):=100;
Public constants are not accessed through a Class variable as it is the case for public functions or properties. They are accessed always through the Class name using the dot syntax. If Class CExpample has the public constants exam1 and exam2, then we can access them in any piece of Calcit code in the following way:
i1:=CExample.exam1;
i2:=CExample.exam2;
To use public constants, defined in any Class, is not needed to create Class variables for this Class. It is possible to create Classes that contain only public constants definitions and never create a Class variable for these Classes in any piece of CalcIt code.
BUFFER IDs created by BUFDEF statements or FORM IDs created by FORMDEF statements can be passed as public constants or via public functions in any other piece of CalcIt code and be used there normally.
At the very start of the Class code a block of commands can be placed between keywords INHERITS and END. The inherited Classes are listed as in the example below:
Inherits
CArray(10,2);
CAssoc; //two Classes
will be inherited
end;
For every inherited Class all parameters needed for calling their constructors has to be passed. In the block of commands between INHERITS and END only Class declarations like in the example can be placed and also a limited number of other commands that can be used for initialization purposes to the values passed to the parameters of the constructors and also for the definition of constructor parameters of the new Class. These commands can only be assignment statements, the SET command and INTERN and EXTERN. See the INHERITS block of the provided Class C1Query:
inherits
extern(DSN,UserName='',Password=''); //new Class
constructor parameters
set db=CLASS ODBC(DSN,UserName,Password);
CQuery(db);
end;
In this code the C1Query has the same functionality as CQuery but with a different constructor interface. Doesn't add anything more. These two Classes are provided in the standard CalcIt Library at the Library page.
After the inheritance block of commands all public functions, properties and variables of the inherited Classes can be accessed directly without the need of a Class variable, like they belong to the new, derived Class.
The derived Class can create a new function, property or variable using the names found in the inherited Classes. This way can override any inherited element. The inherited element will not disappear although. Changes only which element the compiler selects first. The symbols defined later are selected first. To access any of the inherited (overridden) elements, then, the Class name, where they belong, can be used.
CArray.Add([10,100]);
This way the compiler will know what function to call.
In case of Class type parameters (in functions, properties) every Class variable of a derived Class of the parameter, can be passed.
In the tree list of the new Class definition, all inherited elements will appear with the Class they belong.
CArray is a simple but very useful Class. It implements a two dimensional array. If your code needs the services of a two dimensional array you can create a Class variable for the CArray:
set a=CLASS CArray(0,2);
The above code creates Class variable a. The two parameters of its construction define the dimensions of the array. In our case we initialize a to have 0 rows and two columns per row. This is not strange. The CArray implements a dynamic array. We can add items to it at any moment using the public function Add. Lets see the interface of this public function:
function Add(arr dt);public;
Function Add has an array parameter. This is because we want in one command to initialize all the columns of the added row:
a.Add(['James','Jones']);
In the construction we have define rows with two columns. The above command adds a row where the first column takes the value 'James' and the second column the value 'Jones'.
We can access any element of the array for read or write through the property Item.
a.Item(3,1):='Bill';
a.Item(3,2):='Gates';
The above code modifies row 3. In the first column puts 'Bill', in the second put 'Gates'.
Because property Item is the default property of the Class its name can be omitted:
a(3,1):='Bill';
a(3,2):='Gates';
Class CArray has also the following public functions and properties:
CArray is based on a normal CalcIt array (bf in the code). Every CArray row reserves a consecutive number of elements in bf for all its columns. To find out where a row starts in bf we make a calculation found in the non public local function ArrPos.
function ArrPos(r,c);
if(r>RowSize);Error('Rows index out of range(1-'&RowSize&')');end;
if(c>ColSize);Error('Columns index out of range(1-'&ColSize&')');end;
r--;
r*ColSize+c;
end;
As we can see, if in the given row (r) the column parameter (c) points outside the current array dimensions then an error is generated.
To access specific row-column in the array the default public property Item is used. This property uses its r and c parameters to calculate the position of the requested row/column in the linear array bf. This is accomplished in its common section. Then this value is used for read or write operations directly on bf.
property Item(r,c);public;default;
v:=ArrPos(r,c);
GETVALUE:
bf(v);
SETVALUE:
bf(v):=RESULT;
end;
Function Add, adds more rows
at the end of the array. Because we do not know the number of columns per row we
use an array variable (dt) to pass all columns
information of the row. Using an inline array we
simulate a variable number of parameters interface. The function has
code that check if we try to enter more column data than the current
configuration of the array. If the size of the array
parameter is bigger then generates an error. Function Add returns the row position of the
added
row.
function Add(arr dt);public;
SetSize(RowSize+1,ColSize); //increase row dimension
if(Size(dt)>ColSize);
Error('Attempt to enter more data than the columns (',
Size(dt),' instead of max
',ColSize,')');
end;
x:=ArrPos(RowSize,1);
for(i,1,size(dt));
bf(x+i-1):=dt(i);
end;
RowSize;
end;