4D v16.3

Using Compiler Directives

Home

 
4D v16.3
Using Compiler Directives

Using Compiler Directives  


 

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.

All variables have a type. As described in the Data Types section, there are several different types of variables:

Boolean
Date
Longint
Graph
Time
Picture
Number (or Real)
Pointer
Text
BLOB
Object

Here are the different types of arrays:

Boolean Array
Date Array
Integer Array
Longint Array
Picture Array
Real Array
Time Array
Object Array
Pointer Array
BLOB Array
Text Array

Notes:
- The Object type is available since 4D v14.
- The former Alpha (fixed string) and Integer types are no longer used for variables. In existing code, they are automatically redirected to Text and Longint types.

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.

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.

When variables are typed by means of a compiler directive, they receive a default value, which they will keep during the session as long as they have not been assigned. 

The default value depends on the variable type and category, its execution context (interpreted or compiled), as well as, for compiled mode, the compilation options defined on the Compiler page of the Database settings: 

  • Process and interprocess variables are always set "to zero" (which means, depending on the case, "0", an empty string, an empty Blob, a Nil pointer, a blank date (00-00-00), etc.)
  • Local variables are set:
    • in interpreted mode: to zero
    • in compiled mode, depending on the Initialize local variables option of the Database settings:
      • to zero when "to zero" is chosen,
      • to a set random value when "to a random value" is chosen (0x72677267 for numbers and times, always True for Booleans, the same as "to zero" for the others),
      • to a random value (for numbers) when "no" is chosen. 

The following table illustrates these default values:

TypeInterprocessProcessLocal interpretedLocal compiled "to zero"Local compiled "random"Local compiled "no"
BooleenFalseFalseFalseFalseTrueTrue (varies)
Date00-00-0000-00-0000-00-0000-00-0000-00-0000-00-00
Longint00001919382119909540880 (varies)
Graph00001919382119775946656 (varies)
Time00:00:0000:00:0000:00:0000:00:00533161:41:59249345:34:24 (varies)
Picturepicture size=0picture size=0picture size=0picture size=0picture size=0picture size=0
Real00001.250753659382e+2431.972748538022e-217 (varies)
PointerNil=trueNil=trueNil=trueNil=trueNil=trueNil=true
Text""""""""""""
BlobBlob size=0Blob size=0Blob size=0Blob size=0Blob size=0Blob size=0
Objectundefinedundefinedundefinedundefinedundefinedundefined

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.

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 appears––when 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.

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.

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.

Compiler directives can be handled in two different ways, depending on whether or not you want the compiler to type your variables.

The compiler must respect the identification criteria of the variables.

There are two possibilities:

1) If the variables are not typed, the compiler can do it for you automatically. Whenever possible––as long as there is no ambiguity––the 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.

2) 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 _o_C_INTEGER are actually the same as those declared by the directive C_LONGINT. They are, in fact, long integers between –2147483648 and +2147483647.

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

Compiler directives remove any ambiguity concerning data types. 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, 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_LONGINT(vInteger)
 vInteger:=2.6


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.6).



See also 

Error messages
Optimization Hints
Syntax Details
Typing Guide

 
PROPERTIES 

Product: 4D
Theme: Compiler

 
HISTORY 

 
ARTICLE USAGE

4D Language Reference ( 4D v16)
4D Language Reference ( 4D v16.1)
4D Language Reference ( 4D v16.2)
4D Language Reference ( 4D v16.3)