7.3 Short Intro to x4GL
-
4GL Programs
-
Structure of a program
-
DATABASE section
-
GLOBALS section
-
Functions
-
MAIN block
-
DEFINE section
-
4GL Commands
7.3.1 4GL Programs
A 4GL program consists of a series of modules and forms. Each 4GL module can contain functions and reports and each program must contain exactly one ’main’ section and must end in a .4gl extension. C modules can also be included in programs.
7.3.1.1 Structure of a program
database section
globals section
function/report/main block
.
.
.
.
function/report/main block
7.3.1.2 DATABASE section
This section is optional and is of the format :
DATABASE database-name
The database name is actually the DATA SOURCE NAME (DSN) from the ODBC drivers.
7.3.1.3 GLOBALS section
This optional section allows you to define variables which are accessible to all modules. There is normally a single file (typically called ’globals.4gl’) where variables are defined. All other modules which need these variables then include that file using the GLOBALS statement .eg.
globals.4gl:
GLOBALS
DEFINE a INTEGER
END GLOBALS
module.4gl:
GLOBALS "globals.4gl"
Note: In Aubit 4GL the ’globals’ module (containing the GLOBALS / END GLOBALS) must be compiled first.
A function in 4GL is a sequence of commands which are executed when called from another block of code. A function can accept parameters and can return values.
A function is defined :
FUNCTION function-name ( parameter-list )
define-section
commands
END FUNCTION
Values are returned using the RETURN keyword:
RETURN value
Each program must contain a main section - it is the starting point in any program.
MAIN
define-section
commands
END MAIN
7.3.1.6 DEFINE section
This optional section allows you to define variables which may be used subsequently. In its simplest form:
DEFINE variable_names datatype
or
DEFINE CONSTANT constant_name "Value"
DEFINE CONSTANT constant_name Number-Value
More than one variable can be defined as any type in the same statement by separating the names with a comma:
DEFINE a,b,c INTEGER
Available datatypes are :
SMALLINT (2 byte integer)
INTEGER (4 byte integer)
CHAR (Single character ’string’)
CHAR(n) (n character string)
MONEY
DECIMAL (These are not fully implemented)
FLOAT (8 byte floating point number - (C double))
SMALLFLOAT (4 byte floating point number - (C float))
DATE (date - number of days since 31/12/1899)
DATETIME
INTERVAL
BYTE
TEXT
VARCHAR Unimplemented yet
LIKE tablename.columnname
RECORD LIKE tablename.*
- can only be used when the module has a DATABASE statement. These copy the datatypes directly from the database either for a simple column, or to generate an entire record (see below)
Special datatypes are :
ARRAY[n] OF datatype defines an array
RECORD .. END RECORD defines a record structure
ASSOCIATE [CHAR](m) WITH ARRAY[n] of datatype .... defines an associative array (hash table).
7.3.1.7 Arrays Syntax:
DEFINE vars ARRAY[n] datatype eg.
DEFINE lv_arr ARRAY[200] OF INTEGER defines an array of 200 elements each being an integer. Elements of an array are indexed from 1 to the number of elements specified.
IMPORTANT: No bounds checks are made. Accessing elements which are outside those defined (i.e. <1 or > n) will result in an error (Usually a core dump). Eg
LET lv_arr[1]=1
LET lv_arr[200]=200
LET lv_arr[201]=201 # this will cause a program fault!
Records are structured groups of data, with the entries separated by commas. Elements within a record are accessed via the syntax: record name ’.’ element name.
DEFINE recordname RECORD
element datatype,
element datatype
...
END RECORD
eg.
DEFINE lv_rec RECORD
elem1 CHAR(10),
elem2 INTEGER
END RECORD
defines a record with two elements. eg.
LET lv_rec.elem1="string1"
Records may also be nested and used in conjunction with arrays. The following are all therefore valid:
DEFINE lv_record ARRAY[20] OF RECORD
elem1 CHAR(20),
elem2 INTEGER
END RECORD
DEFINE lv_record RECORD
a ARRAY[200] of INTEGER,
b CHAR(20)
END RECORD
DEFINE lv_record RECORD
subrecord1 RECORD
elem1 CHAR(10),
elem2 INTEGER
END RECORD,
subrecord2 RECORD
elem2 DATE
END RECORD
END RECORD
7.3.1.9 Associative Arrays
These are an Aubit4GL extension.
Associative arrays allow you to access data from an array using a string as a subscript rather than an integer. For example:
LET age<<"bob">>=40
DISPLAY age<<"bob">>
This can be especially useful when dealing with codes and code desciptions:
LET lv_desc<<"A">>="Active"
LET lv_desc<<"I">>="Inactive"
LET lv_desc<<"R">>="Running"
LET lv_desc<<"D">>="Deleted"
LET lv_state="A"
.
.
DISPLAY lv_desc<<lv_state>>
(This is for illustration, the data would normally be read from a database!)
To define an associate array:
DEFINE name ASSOCIATE [CHAR] (nc) WITH ARRAY [nx] OF datatype
Where nc is the number of characters to use for the index, and nx is the total number of elements that may be stored.
Internally, associate arrays are stored using hash tables, for performance reasons always declare ’nx’ much larger than is actually required. A factor of two is optimum in most cases.
Again the datatype used in this form of array may be a RECORD, ARRAY etc. Eg.
DEFINE lv_asoc1 ASSOCIATE CHAR(10) WITH ARRAY[10] OF INTEGER
DEFINE lv_asoc3 ASSOCIATE (10) WITH ARRAY[10] OF INTEGER
DEFINE lv_asoc2 ASSOCIATE CHAR(10) WITH ARRAY[10] OF RECORD
element1 CHAR(10),
element2 CHAR(20)
END RECORD
Constants are defined using:
DEFINE CONSTANT name value eg.
DEFINE CONSTANT max_num_vars 30
DEFINE CONSTANT err_string "There is an error"
IF num_vars>max_num_vars THEN
ERROR err_string
END IF
It is also possible to use constants
in any subsequent define sections:
DEFINE CONSTANT num_elems 20
DEFINE lv_arr ARRAY [num_elems] OF INTEGER
IF num_vars<=num_elems THEN
LET lv_arr[num_vars]=1
END IF
You can think of DEFINE CONSTANT statements as being equivalent to C #define statements (except that you cannot use them to define macros as you can with C).
As from version 1.2 of Aubit4GL you can define new datatypes with syntax:
DEFINE NEW TYPE mytype typestatement
e.g.
DEFINE NEW TYPE contract RECORD
level smallint,
denomination char(8)
END RECORD
DEFINE mycontract contract
LET mycontract.level = 3
LET mycontract.denomination = ”Spades”
The new type definition saves the programmer the tedium of replicating a RECORD .. END RECORD definition for instances such as passing records to functions or reports.
The current system allows programs to call shared libraries using the syntax:
call library::function(..)
or
call library.function(..)
(See tools/test/file.4gl or lib/extra_libs/pop/pop_killer.4gl for some example usage)
You can use a . as a synonym for :: in the above library calls.
Packages take this one step further in that the calls are coded like any other functions. They are detected at compile time by referencing a list of possible function name mappings specified by an import package statement. Syntax :
IMPORT PACKAGE packagename
or
USE packagename
The packagename should be the name of a file in the $AUBITDIR/etc/import directory.
A file called default exists in this directory which is included for all compilations - this allows you to add calls to your own subroutines just as if they were builtin functions with no need to add them to the compile line as object or library modules..
This file should contain a series of lines, each containing:
library functionname
(In this way a package can contain functions from more than one library...)
e.g.
A4GL_pcre pcre_match
A4GL_pcre pcre_text
Whenever the compiler sees a call to pcre_match it will call pcre_match in the A4GL_pcre library - in this way it’s equivalent to A4GL_pcre::pcre_match
So a full .4gl may look like :
import package a4gl_pcre
main
if pcre_match("cat|dog","There was an old cat") then
display "Matches to ",pcre_text(1)
else
display "No match"
end if
end main
Compile and run
$ 4glpc pcre_test.4gl -o pcre_test
$ ./pcre_test
Matches to cat
(Note - you don’t need to link against the library - it’s done at runtime!)
(If you’ve got pcre installed - you can compile up the pcre library by doing a make in the lib/extra_libs/pcre directory)