show TOC

Aubit 4GL enhancements to standard I4GL language


In this chapter:


FIXME:

RPC calls Very sexy - Is this the same as RFC? Should we call ir RFC or RPC ?

Dynamic Library Interface Again - very useful - WHAT IS THIS in English please

Curses Windows handling Normally a bitch to use - HOW DOES THIS AFFECT a4gl?

Add new GUI related syntax in menu files, form files, menuhandler, etc...


A4GL enhanced syntax summary table

For this a4gl Syntax...

...See this feature details...

LET Age<<"Fred">>=30 Associative Arrays
DEFINE .... ASSOCIATE ... WITH ... Associative Arrays
SET PAUSE MODE ON|OFF PAUSE Screen Handling
SET SESSION | CURSOR OPTION SET SESSION OPTION/SET CURSOR OPTION
OPEN SESSION ... TO DATABASE ...AS USER ... PASWORD ... Multiple concurrent connections
SET SESSION TO ... Multiple concurrent connections
USE SESSION ... FOR ... Multiple concurrent connections
CLOSE SESSION Multiple concurrent connections
DEFINE CONSTANT ... Application Constants
_variable(x) Variable IDs
code { ....} endcode Embedded "C" code
HIDE | SHOW | MOVE WINDOW Move/Show/Hide Window
WHENEVER SUCCESS | SQLSUCCESS Whenever success/sqlsuccess
FGL_SET_ARRLINE | FGL_SET_SCRLINE Extended Display Array Control
LOCAL FUNCTION ... Local functions
CALL get_info(...) get_info function
f000=..., dynamic size=x (.PER files) Dynamic Screen Fields
START EXTERNAL FUNCTION[x] FOR ... Remote Function Calls
CALL EXTERNAL ...[x] (y) Remote Function Calls
DEFINE ... LINKED TO ...(...) Select/delete/update using
SELECT | DELETE | UPDATE USING ... Select/delete/update using
ON ANY KEY On any key
TEMPLATE ... END TEMPLATE A4GL Wizard and program templates
ENABLE/DISABLE fields ENABLE/DISABLE fields

Go to top of page...


Associative Arrays

Allows arrays and/or record arrays to be indexed by character strings :

            Define lv_str CHAR(20)
            LET Age<<"Fred">>=30
            LET Surname<<"Fred">>="Smith"
            LET lv_str="Fred"
            Display Age<<lv_str>>," ",Surname<<"Fred">>

Assoc. arrays let you use strings instead of integers for the subscripts - great if you have a lookup table, eg country codes. But then you cannot move trough them with, say

FOR x=1 to 20
let a_dummy[x].something = x
end for

Quite often you don't need to, but maybe this is something we need to look at.

With a4gl:

DEFINE lv_arr ASSOCIATE CHAR(10) WITH ARRAY [20] OF CHAR(80)
LET lv_arr<<"Informix">>="Top"
LET lv_arr<<"Oracle">>="No so top"
DISPLAY lv_arr<<"Oracle">>

Assoc. arrays let you use strings instead of integers for the subscripts - great if you have a lookup table, eg country codes

LET desc<<"UK">>="United Kingdom"
LET desc<<"NZ">>="New Zealand"
LET lv_code="NZ"
DISPLAY lv_code," ",desc<<lv_code>>

Result:

NZ New Zealand

Go to top of page...


PAUSE Screen Handling

SET PAUSE MODE ON|OFF - pause screen updates

Use this to turn off screen redraws during a large number of screen updates, SET PAUSE MODE ON, then open all windows, forms etc. then SET PAUSE MODE OFF to redraw the screen in a single pass, great for displaying information on a window that is obscured slightly by the another window.

Screen drawing can be slow on some low speed links (eg. over a modem on a tty).If there are a lot of changes to be made to a screen, eg. opening a few windows, it may be advantagous to group the changes into a single  block and then refresh the screen. This can be also be used to stop the  screen being updated at all.

To stop the screen being updated:

SET PAUSE MODE ON

To refresh the screen

SET PAUSE MODE OFF

This can also be useful when you need to display something to a window  which is currently overlapped:

 

   Window w1

 

 

   
 
    Window w2

 

 

   

 

SET PAUSE MODE ON
CURRENT WINDOW IS w1 # brings w1 over w2
DISPLAY "Something" AT 1,1
CURRENT WINDOW IS w2 # brings w2 over w1
SET PAUSE MODE OFF

Normally

 

   Window w1

 

 

   
 
    Window w2

 

 

   

 

     Window w1

 

 

   
 
   

 

 Window w2

     

 

   Window w1

 

 

   
 
    Window w2

 

 

   

 

This sequence would normally be fairly quick and appear as a flash on the  screen

With pause mode

   Window w1

 

 

   
 
    Window w2

 

 

   

 

   Window w1

 

 

   
 
    Window w2

 

 

   

Go to top of page...


ODBC Compliance

Uses ODBC to connect to data. This allows access to heterogeneous data and increased portability of application. See also SET SESSION OPTION/SET CURSOR OPTION

Using ODBC allows access to any data source for which an ODBC driver is available. This requires:

1) An ODBC driver manager
2) (UNIX Only) ODBC sources file (~/.odbc.ini)
3) Username/Password combination for the required data source.

Notes:

UNIX
The ODBC driver for UNIX is compiled into the 4GL application.

Windows
The ODBC driver is a core part of the operating system

ODBC Sources file - Unix

This file should normally reside in the users home directory and is called '.odbc.ini' .This file has the following format :

[ODBC Data Sources]
sample=Aubit Computing Ltd


[sample]
Driver = /home/odbcdrivers/driver.so
Host = localhost
Database = mja1
Options =
ReadOnly = No
FetchBufferSize = 60

The entries under "ODBC Data Sources" compromise the Data Source Names (or DSNs). Each entry here has a correspnding group of options which are dependant on  the driver being used. Aubit 4GL uses these Data Source Names as the "Database  Name" in the modules.

For the example above we might use:

DATABASE sample
DEFINE ...

In order to make a connection to a data source, you generally need to specify a Username and Password. Depending on the ODBC driver, you may need to specify a valid Unix Login/Password or Database Login/Password. In Aubit 4GL these may be specified in one of two ways : 

Using the SQLUID and SQLPWD environment variables & the DATABASE statement: 

eg.

DATABASE informix

or in the program

eg.

OPEN SESSION s_id1 TO DATABASE informix as user "username" password "password"

or

OPEN SESSION s_id1 TO DATABASE informix as "username", "password"

Using the Open Session allows you to obtain the username and password at runtime from the user more easily than using the DATABASE statement and is more secure.

During development it is easier to use the DATABASE statement and SQLUID/SQLPWD, and this format MUST be used when using the DEFINE..LIKE  statement.

Sessions

Using the DATABASE statement opens a connection or 'session' to the database with the name 'default'. It is possible to open more than one session to the same, or different databases, and because ODBC makes available databases which are not even of the same type, it is possible to open two sessions to two completly different databases.

The syntax for using sessions is as follows

OPEN SESSION session_name TO DATABASE database_name AS USER "username" PASSWORD "password"

If you do not specify a username and password then these will be read from the SQLUID and SQLPWD environment statements. To make use of sessions you may either:

1) Change the current session
2) Temporarily change the current session. 

SET SESSION TO session_name
USE SESSION session_name FOR
[SQL STATEMENT]

You can remove close a database session by using the CLOSE SESSION  statement:

Eg. 

CLOSE SESSION session1

An example:

MAIN
DEFINE l_rec RECORD
var1 INTEGER,
var2 CHAR(20)
END RECORD

OPEN SESSION original_db TO DATABASE db1 AS USER "m123456" PASSWORD "a1234"

OPEN SESSION copy_db TO DATABASE db2 AS USER "m654321" PASSWORD "b2345"

USE SESSION original_db FOR
DECLARE c1 CURSOR FOR SELECT col1,col2 FROM tab1

FOREACH c1 INTO l_rec.*
USE SESSION copy_db FOR
INSERT INTO copy_tab VALUES(l_rec.*)
END FOREACH
CLOSE SESSION copy_db
CLOSE SESSION original_db
END MAIN

Go to top of page...


Multiple concurrent connections 

Using the new OPEN/USE/CLOSE SESSION it is possible to open multiple connections to the same or to different data sources. For example, run two transactions against a database or copy from one type of database to another. Use Informix and Access to gather data for a report. Or Oracle and SQL-Server for data entry screens. It ideal for copying data too.

allow access to multiple databases at the same time.

    OPEN SESSION 
    USE SESSION
    CLOSE SESSION

Go to top of page...


Application Constants

        DEFINE CONSTANT name "Fred"
        DEFINE CONSTANT cv_arraysize 20
        DEFINE m_array ARRAY[cv_arraysize] OF INTEGER

These can then even be used for array sizing

DEFINE CONSTANT cv_arrsize 100
DEFINE m_array ARRAY[cv_arrsize] OF INTEGER

FOREACH some_cursor into m_array[l_counter]
    LET l_counter=l_counter+1
    IF l_counter>vc_arrsize THEN
        ERROR "There May be more rows than can be displayed"
        EXIT FOREACH
    END IF
END FOREACH

Go to top of page...


Map Files

It is possible to instruct the compiler to generate a map file describing where variables, functions, cursors, windows etc. are defined and used. Detailed list of the usage of cursors, windows, variables, tables, columns etc.

4glpc -map hello_db.4gl

this will produce a hello_db.map file in the format I described

It gives answers to :

I'll try and make a full list sometime soon..

Have you looked at map files yet ? (Do a grep for addmap in the rules: $ grep addmap *.rule)

type|object|Place|Line|Module

Where type is :

d for a define
M is for the MAIN
L is for an assignment (LET)
v is for a variable being used
W is for a window name
C is for a function call
F is for a function definition
t is for a table being referenced
D is for a declare cursor

(I think these are right - have a look for 'add_map' in the 4glc/rules).

Object is the name of the variable/cursor/window etc

Place is 'Module' for functions reports and 'MAIN' Its 'MAIN' for things in 'MAIN'..'END MAIN' and its the Function name/report name for things in those areas.

for hello_db.4gl :

d|ifx_sess|Module|2|hello_db.4gl|
d|pg_sess|Module|4|hello_db.4gl|
M|MAIN|MODULE|10|hello_db.4gl|
d|p_tabname|MAIN|13|hello_db.4gl|
d|dsn_informix|MAIN|14|hello_db.4gl|
d|dsn_postgres|MAIN|16|hello_db.4gl|
L|ifx_sess|MAIN|18|hello_db.4gl|
L|pg_sess|MAIN|19|hello_db.4gl|
v|dsn_informix|MAIN|21|hello_db.4gl|
v|dsn_postgres|MAIN|22|hello_db.4gl|
W|w1|MAIN|29|hello_db.4gl|
w|w1|MAIN|33|hello_db.4gl|
C|db_select|MAIN|36|hello_db.4gl|
F|db_select|MODULE|49|hello_db.4gl|
d|dsn_informix|db_select|52|hello_db.4gl|
d|dsn_postgres|db_select|54|hello_db.4gl|
C|ifx_session|db_select|77|hello_db.4gl|
v|dsn_informix|db_select|77|hello_db.4gl|
C|pg_session|db_select|84|hello_db.4gl|
v|dsn_postgres|db_select|84|hello_db.4gl|
C|pg_session_scroll|db_select|89|hello_db.4gl|
v|dsn_postgres|db_select|89|hello_db.4gl|
F|ifx_database|MODULE|104|hello_db.4gl|
d|dsn_informix|ifx_database|107|hello_db.4gl|
d|p_tabname|ifx_database|109|hello_db.4gl|
t|systables|ifx_database|120|hello_db.4gl|
D|"hello_db:c1"|ifx_database|120|hello_db.4gl|
v|p_tabname|ifx_database|121|hello_db.4gl|
v|p_tabname|ifx_database|123|hello_db.4gl|
F|ifx_session|MODULE|132|hello_db.4gl|
d|dsn_informix|ifx_session|135|hello_db.4gl|
d|p_tabname|ifx_session|137|hello_db.4gl|
L|dsn_informix|ifx_session|145|hello_db.4gl|
v|ifx_sess|ifx_session|150|hello_db.4gl|
L|ifx_sess|ifx_session|153|hello_db.4gl|
t|systables|ifx_session|160|hello_db.4gl|
D|"hello_db:c4"|ifx_session|160|hello_db.4gl|
v|p_tabname|ifx_session|161|hello_db.4gl|
v|p_tabname|ifx_session|163|hello_db.4gl|
F|pg_database|MODULE|174|hello_db.4gl|
d|dsn_postgres|pg_database|178|hello_db.4gl|
d|p_tabname|pg_database|180|hello_db.4gl|
t|pg_type|pg_database|190|hello_db.4gl|
D|"hello_db:c2"|pg_database|190|hello_db.4gl|
v|p_tabname|pg_database|191|hello_db.4gl|
v|p_tabname|pg_database|193|hello_db.4gl|
F|pg_session|MODULE|202|hello_db.4gl|
d|dsn_postgres|pg_session|205|hello_db.4gl|
d|p_tabname|pg_session|207|hello_db.4gl|
v|pg_sess|pg_session|213|hello_db.4gl|
L|dsn_postgres|pg_session|213|hello_db.4gl|
v|dsn_postgres|pg_session|215|hello_db.4gl|
L|pg_sess|pg_session|227|hello_db.4gl|
t|pg_type|pg_session|236|hello_db.4gl|
D|"hello_db:c3"|pg_session|236|hello_db.4gl|
v|p_tabname|pg_session|237|hello_db.4gl|
v|p_tabname|pg_session|239|hello_db.4gl|
F|pg_session_scroll|MODULE|251|hello_db.4gl|
d|dsn_postgres|pg_session_scroll|254|hello_db.4gl|
d|p_tabname|pg_session_scroll|256|hello_db.4gl|
v|pg_sess|pg_session_scroll|264|hello_db.4gl|
L|pg_sess|pg_session_scroll|266|hello_db.4gl|
t|pg_type|pg_session_scroll|275|hello_db.4gl|
D|"hello_db:c3"|pg_session_scroll|275|hello_db.4gl|
v|p_tabname|pg_session_scroll|276|hello_db.4gl|
v|p_tabname|pg_session_scroll|278|hello_db.4gl|
>|"hello_db:c3"|pg_session_scroll|281|hello_db.4gl|
v|p_tabname|pg_session_scroll|282|hello_db.4gl|
v|p_tabname|pg_session_scroll|284|hello_db.4gl|
>|"hello_db:c3"|pg_session_scroll|284|hello_db.4gl|
v|p_tabname|pg_session_scroll|285|hello_db.4gl|
v|p_tabname|pg_session_scroll|287|hello_db.4gl|

Go to top of page...


Variable IDs

(For use with Form/cursor/window/Prepare statement names etc.) Many of these compile time 'names' may now be variables or string constants. They are also compiled in such a way that one module can access cursors etc. defined in another. Each ID name is processed by the module name at compile time, cursor,  c_cursor1 in module mod1.4gl becomes "mod1:c_cursor1" when called from a different module. In this way normal processing is not affected. In order to use strings or variables use the _variable keyword in 4GL :

            DEFINE a CHAR(20)
            LET a="Window1"
            OPEN FORM _variable(a) FROM "filename"
            DISPLAY FORM _variable("Window1")

4GL uses a lot of IDs, session names, cursor names, form names etc.

These may be written using the normal identifier method or via the special function '_variable' (note the important '_')

OPEN WINDOW _variable("e1") AT 1,1 ....

is roughly equivalent to (see globbing) :

OPEN WINDOW e1 AT 1,1

The advantage of this is that IDs can now be passed to functions as  strings.

You dont need the _variable for these instances, the _variable is for naming things, eg. windows, cursors etc. where YOU give it a name

The informix syntax for database is

DATABASE dbname

OR

DATABASE variable-name

This is the same in a4gl:

DEFINE xx CHAR(20)
LET xx="mydbname"
DATABASE xx #1

DATABASE yy #2

If it can't resolve the name as a variable - it assumes its the name of the database

#1 - Connect to "mydbname"
#2 - Connect to "yy"

The same goes for the sessions. The only difference is that you GIVE the sessions a name:

OPEN SESSION mysession TO DATABASE xx #1 again

or

OPEN SESSION mysession TO DATABASE yy #2 again

Would work the same as before.

You can use the _variable for the open session - but only for specifying the name of the session :

DEFINE zz CHAR(20)
LET zz="mysession"

OPEN SESSION zz TO DATABASE xx # would open a session called 'zz' #1
OPEN SESSION _variable(zz) TO DATABASE xx # would open a session
called 'mysession' #2

In fact - its a little more involved than that - if you look at the generated code, the code in #1 would actually 'clobber' the session id to be 'modulename:zz', whereas #2 would not do any clobbering and so could be easily referenced from a different module...

_variable (cursor_name)
_variable (session_id)
_variable (window_name)
etc...

DEFINE lv_winname CHAR(20)
DEFINE lv_cnt INTEGER
    FOR lv_cnt=1 to 10 STEP 4
    LET lv_winname="w_",lv_cnt using "<<"
    OPEN WINDOW _variable(lv_winname) 
        at lv_cnt,lv_cnt with 3 columns,3 rows 
        attribute(border) ## check syntax for the rows/columns bit 
END FOR


_variable is NOT supported in that place because it is not REQUIRED in that place!

WRT the etc. - I'll check the source and get you a definative list - bit it is basically any 'NAMES' like statements, windows, etc.

OPEN WINDOW _variable(...

OPEN _variable(cursor)

But I'll generate a list:

> > They use some spec char (I forgot) to signify that the following is
> > the name of the variable, like OPEN WINDOW @var_winname AT ...
>
> Do you want to change _VARIABLE to be @variable ?

Go to top of page...


Passing IDs to functions

module1.4gl:

DECLARE c1 CURSOR FOR SELECT * FROM systables
CALL open_cursor("module1:c1")

anymod.4gl:

FUNCTION open_cursor(p_cursor)
DEFINE p_cursor CHAR(20)

OPEN CURSOR _variable(p_cursor)

IF SQLCA.SQLCODE!=0 THEN
ERROR "There was some error opening the cursor!"
END IF

END FUNCTION

Go to top of page...


Embedded 'C' code

Whilst C code may hamper portability, this feature does allow embedding of C statements within the 4GL code. This means that you do not need to compile and call separate C functions in C modules:

            MAIN
                code
                {
                char buff[256];
                FILE *f;
                f=fopen("filename","r");
                fgets(buff,255,f);
                }
                endcode

                DISPLAY "Read : ",buff clipped AT 1,1

            END MAIN

You can embed C code directly into a 4GL program by using the code/endcode  block. The code and endcode MUST be in lowercase (This is the only  statement that is case-sensitive and MUST begin at the start of a line and  the line may not contain any other characters (including spaces and tabs) :

code
f=fopen("FILE","r");
ENDCODE # this will not work!!!

Everything between the code and endcode is passed directly into the C  module created when the 4GL module is compiled.

You can use code/endcode :

1) Any where you can use any other 4GL statement
2) Before/After any section

You cannot use the code/endcode in a:

1) globals section
2) in a define section

Variables

You can define variables in a code section as in normal C after a '{'.  It is therefore common to use the following code segment:

code
{
.
.
}
endcode

Any variables defined in the code segment are only available IN that code  segment. Normal C rules apply (eg. define the variable as 'static' if you  want to keep its value between calls.)

You can also access the variables define in the 4GL module. INTEGERs  normally translate to long, SMALLINTs to short etc. The most common  mistake made is with strings, strings in 4GL are padded to fill the size  of the string with spaces ' '.When using the strings in C you normally  need to remove these spaces.

E.g.:

FUNCTION open_file(p_filename,p_mode)
# This is a bad example because it doesnt work properly
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER
code
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION

Calling this with "fgl.log","r" will result in a call to fopen of ("fgl.log ","r ") which will most likely fail.

The easiest way of resolving this is to remove the extra spaces using the  trim 'C' function:

FUNCTION open_file(p_filename,p_mode)
# This is a better example
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER

code
trim(p_filename); /* must be in a code/endcode block */
trim(p_mode); .
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION

Calling 4GL functions from embedded C code

4GL function names are 'globbed' when compiled in the above example

FUNCTION open_file
will be translated into
aclfgl_open_file()

You need to pass the number of parameters as the parameter, and push the  actual parameters onto the 4GL stack.

Eg:

code
{
int a;
push_char("fgl.log");
push_char("r");
a=aclfgl_open_file(2);
}

Numbers can be pushed using push_double, push_float, push_int,push_date  etc.

The number of values returned from the function is returned as an integer  from the function and can be poped from the 4GL stack using the pop..  group of functions

Calling C functions from 4GL

Use a code/endcode block (see the example above). Some C functions  required the inclusion of another file via a '#include .." statement.  These can be put in a code/endcode block at the top of the program

eg. full file open example

code
#include <stdio.h>
endcode

FUNCTION open_file(p_filename,p_mode)
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER

code
trim(p_filename); /* must be in a code/endcode block */
trim(p_mode);
/* need to convert this to a long only works if sizeof(long)=sizeof(void 
*) */
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION

Most 4gls allow calls to C, but doing that for a novice is not easy, tried calling fopen ? You have to write all the interface code yourself to pop off the stack and push back on it...

FUNCTION fopen(p_filename,p_mode)
    DEFINE p_filename CHAR(128)
    DEFINE p_mode CHAR(5)
    DEFINE p_fileid INTEGER

    code
        trim(p_filename); // library function to remove trailing spaces
        trim(p_mode);
        p_fileid=fopen(p_filename,p_mode);
    endcode

    RETURN p_fileid
END FUNCTION

Could it be any simpler ?

(A4GL can also link to .so's looking up functions names dynamically...)

Go to top of page...


Move/Show/Hide Window

Allow Moving, hiding and redisplaying of windows:

    HIDE WINDOW
    SHOW WINDOW
    MOVE WINDOW

Go to top of page...


WHENEVER SUCCESS/SQLSUCCESS

Might come in handy, but costly in terms of performance!

    WHENEVER SUCCESS/SQLSUCCESS

Go to top of page...


Multi-level Menus

            MENU "Main Menu"
                COMMAND "SubMenu 1"
                     MENU "Menu 2"
                        COMMAND "Option 2.1
                        COMMAND "Exit'
                             EXIT MENU
                      END MENU

                COMMAND "SubMenu 2"
                    MENU "Menu 2"
                        COMMAND "Option 2.1
                        COMMAND "Exit'
                             EXIT MENU
                   END MENU
            END MENU

Go to top of page...


Extended Display Array Control

    BEFORE/AFTER ROW in DISPLAY ARRAY

    Use Before row/After row in display arrays to display highlight bars or associated information.

    FGL_SET_ARRLINE / FGL_SET_SCRLINE

    Use with display/input arrays to set current array and screen lines - Great for searches.

Go to top of page...


User overridable and extended USING functionality for use with dates

Added in 'dddd','mmmm' for full weekday names and month names, also added 'th' for '1st', '2nd' type processing and 'd' & 'm' for non-padded days and months, eg : 'dddd dth mmmm yyyy' = 'Monday 20th July 1998'. And because not everybody speaks English, these are overridable by using either Environment variables or compile time environment settings.

Can also use :

    dddd mmmm th

in the using clause for full day names, month names and the (1)'st', (2)'nd', (3)'rd', (4)'th' of the day number

Go to top of page...


Local functions

functions can be defined as being local to a module (ie not callable outside that module).

    LOCAL FUNCTION func_name...
    END FUNCTION

This is the same as 'static' before a function in 'C'

Go to top of page...


get_info function

CALL getinfo...

Get information on most things from with 4GL code, includes :

        Windows

Height, Width etc., Comment/Message/Error line

        Forms

Delimiters, database, screen record count, names, field names/count, Current field, Width, Height of fields.

        Sessions

Database name, Most ODBC information.

        Statements

No. of columns, rows, Column names, types, length etc.

There is a wide range of information available to 4GL programs about the  current state of the application. This include details about Cursor, Windows,  Forms etc. This information is the mainstay of 'extreme 4GL' because it allows the  generation of generic code which can be used for a wide range of  applications.

Firstly then, the get_info function call :

CALL get_info(type, ID, info) RETURNING ...

In all cases ID is the ID being queried. Bear in mind that this will  normally point to a globbed value which should therefore include the  module name containing the definition of the ID and a colon. Eg.

module1.4gl

OPEN WINDOW w1 AT 2,2 ....
CALL get_info("window","module1:w1","BeginX") RETURNING a
# a would be set to 2

For appropriate values for info for each types, see "Internal statements" section of Aubit 4gl manual

Go to top of page...


Dynamic Screen Fields

This allows the user to type in more text than can be displayed in a field by allowing horizontal scrolling

            In the .per file :

            No Maximum Limit:
                f000=formonly.fld1,dynamic;

            Maximum of 80 characters:
                f000=formonly.fld1,dynamic size=80;

Go to top of page...


Remote Function Calls

This feature allow the programmer to write client/server applications using remote function calls. The functions can be based on the same host machine, or across a network. You can even use RFCs across platforms. This is useful for a variety of purposes especially :

Have you ever tried using rpcgen ? Ever tried to write the client and server side implementations ?

Heres the 4GL :

Server.4gl

FUNCTION disp(lv_who)
DEFINE lv_who CHAR(20)
DISPLAY "I've been called by ",lv_who CLIPPED
END FUNCTION

MAIN
START EXTERNAL FUNCTION[1] FOR disp
END MAIN

client.4gl
MAIN
CALL EXTERNAL dax:disp[1] ("Me")
END MAIN

The function calls can also be set to run asynchronously by using the without waiting clause

 Eg: 

Server Code:

DEFINE mv_inv_num INTEGER    

FUNCTION next_invoice_number()    
    LET mv_inv_num=mv_inv_num+1
    RETURN mv_inv_num
END FUNCTION

MAIN
    SELECT MAX(invoice_no) INTO mv_inv_num FROM invoices
    START EXTERNAL FUNCTION [1] FOR next_invoice_number
END MAIN

Client Code:

FUNCTION generate_invoice()
    DEFINE lv_inv_no INTEGER
    CALL EXTERNAL localhost : next_invoice_number[1] () RETURNING lv_inv_no
END FUNCTION

Example 2:

    CALL EXTERNAL host:function-name '[' servernumber ']' '(' parameters ')'

    START EXTERNAL FUNCTION '[' servernumber ']' FOR function-name-list

eg.2:

    server.4gl

    FUNCTION bibble()
        RETURN 1
    END FUNCTION

    FUNCTION myfunc(x,y)
        RETURN x+y
    END FUNCTION

    MAIN 
        START EXTERNAL FUNCTION [1] FOR bibble,myfunc
    END MAIN

client.4gl

    ...
    CALL EXTERNAL localhost:myfunc [1] (1,2)
    ...

Go to top of page...


SELECT/DELETE/UPDATE USING

 It is now possible to 'link' a record with a table and its primary key.

Eg.

    # stock (stock_code char(4), balance INTEGER);
    DEFINE lv_stock LINKED TO stock (stock_code)

After defining this relation, you can then use the new USING construct to access this data:

    LET lv_stock.stock_code="BAY1"
    SELECT USING lv_stock

This will fill the record with all the other details from the table via the value set in the primary key field (or fields) The above example would be written by hand as :

    SELECT * INTO lv_stock FROM stock WHERE stock_code=lv_stock.stock_code

You can also use the DELETE USING to delete the row referenced by the primary key.

In addition the UPDATE USING will update ALL non-key columns to the values in the structure for the given primary key. Columns given for the primary key are used to find the row to update. The values for the columns defined as primary keys are obviously not updated.

Eg.

LET lv_stock.stock_code="BAY1"

SELECT USING lv_stock
LET lv_stock.balance=lv_stock.balance-1

IF lv_stock > 0 THEN

UPDATE USING lv_stock
##################################################
# = UPDATE stock SET balance=lv_stock.balance 
# WHERE stock_code=lv_stock.stock_code
##################################################

ELSE # No stock left - remove the row

DELETE USING lv_stock
##################################################
# = DELETE FROM stock 
# WHERE stock_code=lv_stock.stock_code
##################################################

END IF

To define a composite primary key use the column names in the LINKED TO.

Eg. 

    DEFINE lv_tab1 LINKED TO mytable(pk1,pk2)

Go to top of page...


ON ANY KEY

Useful with fgl_set_arrline/fgl_set_scrline for searches and context sensitive handling.

Go to top of page...


Compile Time Environment Setting

The library uses some environment variable, default values are specified by the library - but you can set your own, this are still overridden by environment variables when required.

Go to top of page...


SET SESSION OPTION/SET CURSOR OPTION

Set ODBC specific options on sessions and cursors. For instance

            DECLARE c1 CURSOR
                    SELECT tabname FROM systables

            # only pull back up to three rows
            SET CURSOR c1 OPTION "MAX ROWS" TO "3"

            # not really need here - but dynamic cursors, no more stale data.
            SET CURSOR c1 OPTION "CURSOR TYPE" TO "DYNAMIC"

            OPEN c1

            FOREACH c1 INTO l_str
                DISPLAY l_str
            END FOREACH

Go to top of page...


Application Partitioning

The applications generated can access data on another machine, and a GUI viewer is in construction which communicates with the application via TCP/IP. The built-in viewer is currently only text based. Once construction of the Thin Client viewer is complete the application will be able to run on any of one to three machines, and with the onset of Unix ODBC clients, application logic can still run on a server.

    Possible Configurations

---            =        Network connection to different machines
 +             =        Running on the same machine
 

    Data     ---  Application Logic   ---   Viewer

    Data      +   Application Logic   ---   Viewer

    Data     ---  Application Logic    +    Viewer

    Data      +   Application Logic    +    Viewer

Go to top of page...


Y2K runtime translation

A4GL cannot ensure Y2K compliance of data sources but uses a AUBIT_Y2K variable for its own Y2K compliance. Setting this to a number will select which century will be chosen for a 2 digit year.  E.g.. setting this to 80 will mean 80+ will be treated as the 19xx whilst 0-79 will be treated as the 20xx.

Go to top of page...


Globbing

All IDs are shared between all modules in a program, in order to make them  unique within a module a mechanism called globbing is employed. This  entails using the module name to form part of the ID.

Upon compilation

DECLARE CURSOR c1 ...

has the Internal ID of "modulename:c1"
Eg. if the module was called test.4gl - "test:c1"

This is useful because it allows access to IDs defined in other modules  when required

module1.4gl

open form form1 from "sales"

module2.4gl

display form _variable("module1:form1")

When you use the '_variable' no extra globbing is done, hence:

module1.4gl

open form _variable("form1") from "sales"

could be referenced in module2.4gl as :

display form _variable("form1")

Warning: Using this form of declaration can lead to problems when you  require unique names across modules.

Go to top of page...


A4GL Wizard and program templates

the Wizard processor takes an input file, and generates the 4GL for you after answering a couple of questions.

eg.

TEMPLATE
database=PROMPT "Database Name" # prompts for a dbname
tabname=PROMPT "Table Name" # prompts for a table name
collist=COLUMNS tabname # gets a list of columns for tab
funcname=PREPEND "proc_" TO tabname # see note 1
pk=PROMPTMANY "Primary key" # note 2
nonpk=collist-(pk) # note 3
pkvar=PREPEND "l_record." TO pk
nonpkvar=PREPEND "l_record." TO nonpk
varpkeq=LIST (USE pkvar WITH " = " ON pk) WITH " AND "


ENDTEMPLATE

database %database

function %funcname
define l_record record like %tabname

declare c_id1 cursor for
select * from %tabname where %(%pk*=l_record.%pk*)
foreach c_id1 into l_record.*
end main

In order to use the file you need the template compiler. This takes a module.tpl and outputs a module.4gl.

It would be quite easy to fudge 4glpc to read a tpl, convert it into /tmp/...4gl and then compile that and move the .c & .h files back..

There are some enhancements still needed on this - like reading some data directory for the primary keys etc. But that hasnt been done yet...

Runtime

There usage is really bloody complicated, but I dont envisage normal users using them.

We can generate a few for simple 4gl modules etc.

They work on lists which you can perform actions on. eg. prepend which add some text to everything in the list

note 1:

tabname is a list with only one entry, so we'll end up with another list called funcname = "proc_",tabname

note 2:

This gets as many values for 'pk' as required.

note 3: 

list subtraction, removes elements from a list where they are in another list

When typing in 4GL there are some things that you have to keep on repeating because of the way 4GL works. This allows us to set up normal maintenance handlers in a couple of seconds!

Go to top of page...


ENABLE/DISABLE fields

ENABLE/DISABLE fields.. 100% complete

FIXME: This one is new, how do one use this?

Go to top of page...