version 2003 (Modified)
Data types of variables
4D has three categories of variables:
Local variables,
Process variables,
Interprocess variables.
For more information about this point, refer to the Variables section. Process and interprocess variables are structurally the same for the compiler.
Since the compiler cannot determine the process in which the variable will be used, process variables should be used with more care than interprocess variables. All process variables are systematically duplicated when a new process begins. A process variable can have a different value in each process, but it has the same type for the entire database.
Variable types
All variables have a type. As described in the Data Types section, there are 12 different types of variables:
Boolean
Fixed string
Date
Integer
Longint
Graph
Time
Picture
Number (or Real)
Pointer
Text
BLOB
There are nine different types of arrays:
Boolean Array
String Array
Date Array
Integer Array
Longint Array
Picture Array
Real Array
Pointer Array
Text Array
Creation of the symbol table
In interpreted mode, a variable can have more than one data type. This is possible because the code is interpreted rather than compiled. 4D interprets each statement separately and comprehends its context. When you work in a compiled environment, the situation is different. While interpretation is performed line by line, the compilation process looks at a database in its entirety.
The compiler's approach is the following:
The compiler systematically analyzes the objects in the database. The objects are database, project, form, trigger and object methods.
The compiler scans the objects to determine the data type of each variable used in the database, and it generates the table of variables and methods (the symbol table).
Once it has established the data types of all variables, the compiler translates (compiles) the database. However, it cannot compile the database unless it can determine the data type for each of the variables.
If the compiler comes across the same variable name and two different data types, it has no reason to favor any particular one. In other words, in order to type an object and give it a memory address, the compiler must know the precise identity of that object (i.e., its name and its data type). The compiler determines its size from the data type. For every compiled database, the compiler creates a map that lists, for each variable, its name (or identifier), its location (or memory address), and the space it occupies (indicated by its data type). This map is called the symbol table. An option in the Preferences lets you choose whether to generate this table in the form of a file during compilation.
This map is also used for the automatic generation of compiler methods.
Typing variables
The compiler must respect the identification criteria of the variables.
There are two possibilities:
If the variables are not typed, the compiler can do it for you automatically. Whenever possibleas long as there is no ambiguitythe compiler determines a variable's type from the way it is used. For example, if you write:
V1 := True
the compiler determines that variable V1 is of data type Boolean.
By the same token, if you write:
V2:= "This is a sample phrase"
the compiler determines that V2 is a Text type variable.
The compiler is also capable of establishing the data type of a variable in less straightforward situations:
V3:= V1 `V3 is of the same type as V1 V4:= 2*V2 `V4 is of the same type as V2
The compiler also determines the data type of your variables according to calls to 4D commands and according to your methods. For example if you pass a Boolean type parameter and a Date type parameter to a method, the compiler assigns the Boolean type and the Date type to the local variables $1 and $2 in the called method.
When the compiler determines the data type by inference, unless indicated otherwise in the Preferences, it never assigns the limiting data types: Integer, Longint or String. The default type assigned by the compiler is always the widest possible. For example, if you write:
Number:=4
the compiler assigns the Real data type to Number, even though 4 happens to be an integer. In other words, the compiler does not rule out the possibility that, under other circumstances, the variable's value might be 4.5.
If it is appropriate to type a variable as Integer, Longint or String, you can do so using a compiler directive. It is to your advantage to do so, because these data types occupy less memory and performing operations on them is faster.
If you have already typed your variables and are sure that your typing is coherent and complete, you may explicitly ask the compiler not to redo this work, using the compilation Preferences. In case your typing was not complete and exhaustive, at the time of compilation, the compiler will return errors requesting you to make the necessary modifications.
The compiler directive commands enable you to explicitly declare the variables used in your databases.
They are used in the following manner:
C_BOOLEAN(Var)
Through such directives, you inform the compiler to create a variable Var that will be a Boolean.
Whenever an application includes compiler directives, the compiler detects them and thus avoids guesswork.
A compiler directive has priority over deductions made from assignments or use.
Variables declared with the compiler directive C_INTEGER are actually the same as those declared by the directive C_LONGINT. They are, in fact, long integers between 2147483648 and +2147483647.
When to use compiler directives
Compiler directives are useful in two cases:
The compiler is unable to determine the data type of a variable from its context,
You do not want the compiler to determine a variable's type from its use.
Furthermore, using compiler directives allows you to reduce compilation time.
Cases of ambiguity
Sometimes the compiler cannot determine the data type of a variable. Whenever it cannot make a determination, the compiler generates an appropriate error message.
There are three major causes that prevent the compiler from determining the data type: multiple data types, ambiguity on a forced deduction and the inability to determine a data type.
Multiple data types
If a variable has been retyped in different statements in the database, the compiler generates an error that is easy to fix.
The compiler selects the first variable it encounters and arbitrarily assigns its data type to the next occurrence of the variable having the same name but a different data type.
Here is a simple example:
in method A,
Variable:=True
in method B,
Variable:="The moon is green"
If method A is compiled before method B, the compiler considers the statement Variable:="The moon is green" as a data type change in a previously encountered variable. The compiler notifies you that retyping has occurred. It generates an error for you to correct. In most cases, the problem can be fixed by renaming the second occurrence of the variable.
Ambiguity on a forced deduction
Sometimes, due to a sequence, the compiler can deduce that an object's type is not the proper type for it. In this case, you must explicitly type the variable with a compiler directive.
Here is an example using the default values for an active object:
In a form, you can assign default values for the following objects: combo boxes, pop-up menus, tab controls, drop-down lists, menu/drop-down lists and scrollable areas using the Edit button for the Value List (under the Entry Control theme of the Property List) (for more information, refer to the 4D Design Reference manual). The default values are automatically loaded into an array whose name is the same as the name of the object.
If the object is not used in a method, the compiler can deduce the type, without ambiguity, as a text array.
However, if a display initialization must be performed, the sequence could be:
Case of : (Form event=On Load) MyPopUp:=2 ... End case
In this case, the ambiguity appearswhen parsing methods, the compiler will deduce a Real data type for the object MyPopUp. In this case, it is necessary to explicitly declare the array in the form method or in a compiler method:
Case of : (Form event=On Load) ARRAY TEXT(MyPopUp;2) MyPopUp:=2 ... End case
Inability to determine a data type
This case can arise when a variable is used without having been declared, within a context that does not provide information about its data type. Here, only a compiler directive can guide the compiler.
This phenomenon occurs primarily within four contexts:
- when pointers are used,
- when you use a command with more than one syntax,
- when you use a command having optional parameters of different data types,
- when you use a 4D method called via a URL.
- Pointers
A pointer cannot be expected to return a data type other than its own.
Consider the following sequence:
Var1:=5.2 (1) Pointer:=->Var1 (2) Var2:=Pointer-> (3)
Although (2) defines the type of variable pointed to by Pointer, the type of Var2 is not determined. During compilation, the compiler can recognize a pointer, but it has no way of knowing what type of variable it is pointing to. Therefore it cannot deduce the data type of Var2. A compiler directive is needed, for example C_REAL(Var2).
- Multi-syntax commands
When you use a variable associated with the function Year of, the variable can only be of the data type Date, considering the nature of this function. However, things are not always so simple. Here is an example:
The GET FIELD PROPERTIES command accepts two syntaxes:
GET FIELD PROPERTIES(tableNo;fieldNo;type;length;index)
GET FIELD PROPERTIES(fieldPointer;type;length;index)
When you use a multi-syntax command, the compiler cannot guess which syntax and parameters you have selected. You must use compiler directives to type variables passed to the command, if they are not typed according to their use elsewhere in the database.
- Commands with optional parameters of different data types
When you use a command that contains several optional parameters of different data types, the compiler cannot determine which optional parameters have been used. Here is an example:
The GET LIST ITEM command accepts two optional parameters; the first as a Longint and the other as a Boolean.
The command can thus either be used as follows:
GET LIST ITEM(list;itemPos;itemRef;itemText;sublist;expanded)
or like this:
GET LIST ITEM(list;itemPos;itemRef;itemText;expanded)
You must use compiler directives to type optional variables passed to the command, if they are not typed according to their use elsewhere in the database.
- Methods called via URLs
If you write 4D methods that need to be called via a URL, and if you do not use $1 in the method, you must explicitly declare the text variable $1 with the following sequence:
C_TEXT($1)
In fact, the compiler cannot determine that the 4D method will be called via a URL.
Reducing time needed to compile
If all the variables used in the database are explicitly declared, it is not necessary for the compiler to check the typing. In this case, you can set the options so that the compiler only executes the translation phase of the method. This saves at least 50% in compilation time.
Optimizing code
You can speed up your methods by using compiler directives. For more details on this subject, refer to the Optimization Hints section. To give a simple example, suppose you need to increment a counter using a local variable. If you do not declare the variable, the compiler assumes that is a Real. If you declare it as a Longint, the compiled database will perform more efficiently. On a PC, for instance, a Real takes 8 bytes, but if you type the counter as a Longint, it only uses 4 bytes. Incrementing an 8-byte counter obviously takes longer than incrementing a 4-byte one.
Where to place your compiler directives
Compiler directives can be handled in two different ways, depending on whether or not you want the compiler to type your variables.
Variables typed by the compiler
If you want the compiler to check the typing of your variables or to type them itself, it is easy to place a compiler directive for this purpose. You can choose between two different possibilities, depending on your working methods:
Either use the directive in the method in which the variable first appears, depending on whether it is a local, proces or interprocess variable. Be sure to use the directive the very first time you use the variable, in the first method to be executed. Keep in mind that during compilation, the compiler takes the methods in the order of their creation in 4D, and not in the order in which they are displayed in the Explorer.
Or, if you are systematic in your approach, group all the process and interprocess variables with the different compiler directives in the On Startup Database Method or in a method called by the On Startup Database Method.
For local variables, group the directives at the beginning of the method in which they appear.
Variables typed by the developer
If you do not want the compiler to check your typing, you must give it a code to identify the compiler directives.
The convention to follow is:
Compiler directives for the process and interprocess variables and the parameters should be placed in one or more methods, the names of which begin with the key word Compiler.
By default, the compiler lets you automatically generate five types of Compiler methods, which group together the directives for variables, arrays and method parameters (for more information about this point, refer to the Design Reference manual).
Note: The syntax for declaring these parameters is the following:
Directive (methodName;parameter). This syntax is not executable in interpreted mode.
Particular parameters
Parameters received by database methods
If these parameters have not been explicitly declared, they are typed by the compiler. Nevertheless, if you declare them, the declaration must be done inside the database methods.
This parameter declaration cannot be written in a Compiler method.
Example: On Web Connection Database Method receives six parameters, $1 to $6, of the data type Text. At the beginning of the database method, you must write: C_TEXT($1;$2;$3;$4;$5;$6)
Triggers
The $0 parameter (Longint), which is the result of a trigger, is typed by the compiler if the parameter has not been explicitly declared. Nevertheless, if you want to declare it, you must do so in the trigger itself.
This parameter declaration cannot be written in a Compiler method.
Objects that accept the "On Drag Over" form event
The $0 parameter (Longint), which is the result of the "On Drag Over" form event, is typed by the compiler if the parameter has not been explicitly declared. Nevertheless, if you want to decalre it, you must do so in the object method.
This parameter declaration cannot be written in a Compiler method.
Note: The compiler does not initialize the $0 parameter. So, as soon as you use the On Drag Over form event, you must initialize $0. For example:
C_LONGINT($0) If (Form event=On Drag Over) $0:=0 ... If ($DataType=Is Picture) $0:=-1 End if ... End if
The C_STRING compiler directive
The C_STRING command uses a different syntax than the other directives because it accepts an additional parameterthe maximum string length.
C_STRING(length;var1{;var2; ;varN})
Since C_STRING types fixed-length strings, you specify the maximum length of such strings. In a compiled database, you must specify the length of the string with a constant rather than with a variable.
In an interpreted database, the following sequence is acceptable:
TheLength:=15 C_STRING(TheLength;TheString)
4D interprets TheLength, then replaces TheLength with its value in the C_STRING compiler directive.
However, the compiler uses this command when typing variables with no specific assignment in mind. Thus, it is not in a position to know that TheLength equals 15. Not knowing the string's length, the compiler cannot keep a space for it in the symbol table. Therefore, with compilation in mind, use a constant to specify the length of the declared character string. For example, use a statement such as this:
C_STRING(15;TheString)
The same rule applies to declaring fixed string arrays, which are typed with the command:
ARRAY STRING(length;arrayName;size)
The parameter that indicates string lengths in the array must be a constant.
However, you can specify the length of the string with a 4D constant or a hexadecimal value in these two compiler directives. For example:
C_STRING(4DConstant;TheString) ARRAY STRING(4DConstant;TheArray;2) C_STRING(0x000A;TheString) ARRAY STRING(0x000A;TheArray;2)
Do not confuse the length of an Alphanumeric field, which has a maximum of 80 characters, with a fixed string variable. The maximum length of a string declared by a C_STRING directive, or belonging to an ARRAY STRING, is between 1 and 255.
Note: The syntax of this command allows you to declare several variables of the same length in a single line. If you want to declare several strings of different lengths, do so on separate lines.
A certain liberty permitted by the compiler
Compiler directives remove any ambiguity concerning data types and, in the case of alphanumeric strings, lengths. Although a certain rigor is necessary, this does not necessarily mean that the compiler is intolerant of any and every inconsistency.
For example, if you assign a real value to a variable declared as an Integer, or if you assign a string of 30 characters to a variable declared as a 10-character string, the compiler does not regard either assignment as a type conflict and assigns the corresponding values according to your directives. So, if you write:
C_INTEGER(vInteger) vInteger:=2.5
The compiler does not regard it as a data type conflict that will prevent compilation; instead, the compiler simply rounds off to the closest integer value (3 instead of 2.5).
Similarly, if you declare a string that is shorter than the one you are dealing with, the compiler will only take the number of characters declared in the directives. Therefore, in the following sequence:
C_STRING(10;MyString) MyString:="It is a beautiful day"
the compiler takes the first ten characters of the constant, i.e. "It is a be".
See Also
Error messages, Optimization Hints, Syntax Details, Typing Guide.