Skip to content

Language Basics

This chapter is a tour of the 4GL language itself: how to declare data, make decisions, loop, and organize code into functions. If you have written any procedural language before, most of this will feel familiar — the syntax is plain and English-like. Pay special attention to the gotchas at the end; they trip up almost everyone new to Aubit 4GL.

Program structure

A program is a MAIN block plus any number of functions:

MAIN
    CALL greet("world")
END MAIN

FUNCTION greet(who)
    DEFINE who CHAR(40)
    DISPLAY "Hello, ", who CLIPPED
END FUNCTION

Execution starts at MAIN. There is exactly one MAIN per program.

Comments

-- a single-line comment
# also a single-line comment
{
   a block comment
   over several lines
}

Variables and data types

You declare variables with DEFINE, giving each a type.

DEFINE customer_name CHAR(40)
DEFINE quantity      INTEGER
DEFINE price         DECIMAL(10,2)
DEFINE order_date    DATE

The everyday types:

Type Use it for Notes
CHAR(n) Fixed-length text Padded with spaces up to n
VARCHAR(n) Variable-length text Up to 32,767 characters
STRING Variable-length text (handy for messages) Up to 32,767 characters
INTEGER Whole numbers 32-bit
SMALLINT Small whole numbers 16-bit
DECIMAL(p,s) Exact decimals (money, quantities) p digits, s after the point
MONEY(p,s) Monetary amounts Like DECIMAL, formatted as currency
FLOAT Approximate decimals Avoid for money
DATE A calendar date Displayed per DBDATE
DATETIME Date and time

Use DECIMAL, not FLOAT, for money. FLOAT rounds in surprising ways.

You can declare several variables of the same type at once:

DEFINE first_name, last_name, city CHAR(30)

Assignment

LET assigns a value:

LET quantity = 10
LET price    = 19.95
LET order_date = TODAY               -- TODAY is the current date
LET customer_name = "Acme Ltd"

Records

A record groups related fields:

DEFINE customer RECORD
    id    INTEGER,
    name  CHAR(40),
    city  CHAR(30)
END RECORD

LET customer.id   = 101
LET customer.name = "Acme Ltd"

When a record mirrors a database table, declare it with LIKE so it always matches the schema:

DEFINE customer RECORD LIKE customers.*

Arrays

A fixed-size array:

DEFINE scores ARRAY[10] OF INTEGER
LET scores[1] = 95

A dynamic array grows as you need it, and is what you will use most:

DEFINE names DYNAMIC ARRAY OF CHAR(40)

CALL names.appendElement()
LET names[names.getLength()] = "Alice"

DISPLAY "I have ", names.getLength(), " names"

Useful dynamic-array methods:

Method What it does
.getLength() Number of elements
.appendElement() Add an empty element at the end
.deleteElement(i) Remove element i
.clear() Remove all elements

You can also have a dynamic array of records — the backbone of list screens (see chapter 5):

DEFINE order_lines DYNAMIC ARRAY OF RECORD
    product CHAR(20),
    qty     INTEGER,
    price   DECIMAL(10,2)
END RECORD

Operators

-- arithmetic
LET total = a + b - c * d / e
LET rest  = a MOD b

-- comparison
IF a = b  THEN ... END IF        -- equal (note: a single =)
IF a != b THEN ... END IF        -- not equal
IF a < b  THEN ... END IF        -- also <=, >, >=

-- logic
IF a > 0 AND b > 0 THEN ... END IF
IF NOT done THEN ... END IF

-- NULL tests
IF value IS NULL     THEN ... END IF
IF value IS NOT NULL THEN ... END IF

-- string concatenation (two ways)
LET full = first CLIPPED, " ", last CLIPPED     -- in a LET, commas join
LET full = first CLIPPED || " " || last CLIPPED -- || also concatenates

Making decisions

IF

IF age >= 18 THEN
    DISPLAY "Adult"
ELSE
    DISPLAY "Minor"
END IF

CASE — the clean way to handle many branches

For more than two outcomes, prefer CASE. It comes in two forms.

Match a value:

CASE grade
    WHEN "A"  DISPLAY "Excellent"
    WHEN "B"  DISPLAY "Good"
    WHEN "C"  DISPLAY "Pass"
    OTHERWISE DISPLAY "See me"
END CASE

Test conditions (like a chain of IFs):

CASE
    WHEN score >= 90  LET grade = "A"
    WHEN score >= 80  LET grade = "B"
    WHEN score >= 70  LET grade = "C"
    OTHERWISE         LET grade = "F"
END CASE

Avoid ELSE IF (two words). Use CASE for multi-way branches, or nest IF/ELSE. CASE is clearer and avoids a common compile pitfall.

Looping

WHILE

DEFINE i INTEGER
LET i = 1
WHILE i <= 5
    DISPLAY "step ", i
    LET i = i + 1
END WHILE

FOR

FOR i = 1 TO 10
    DISPLAY i
END FOR

FOR i = 10 TO 1 STEP -1     -- count down
    DISPLAY i
END FOR

FOREACH — loop over query results

FOREACH runs a query and gives you one row per iteration. This is how you read many rows from the database (more in chapter 6):

DECLARE c_cust CURSOR FOR
    SELECT id, name FROM customers ORDER BY name

FOREACH c_cust INTO customer.id, customer.name
    DISPLAY customer.id, " ", customer.name CLIPPED
END FOREACH

Leaving a loop early

FOR i = 1 TO 100
    IF i = 50 THEN EXIT FOR END IF      -- jump out
    IF i MOD 2 = 0 THEN CONTINUE FOR END IF  -- skip to next iteration
    DISPLAY i
END FOR

Functions

A function takes parameters, may return one or more values, and is called with CALL.

FUNCTION line_total(qty, price)
    DEFINE qty   INTEGER
    DEFINE price DECIMAL(10,2)
    DEFINE total DECIMAL(12,2)

    LET total = qty * price
    RETURN total
END FUNCTION

Call it and capture the result with RETURNING:

DEFINE amount DECIMAL(12,2)
CALL line_total(3, 19.95) RETURNING amount

A function can return several values at once:

FUNCTION split_name(full)
    DEFINE full  CHAR(60)
    DEFINE first CHAR(30)
    DEFINE last  CHAR(30)
    -- ... fill first and last ...
    RETURN first, last
END FUNCTION

CALL split_name("Ada Lovelace") RETURNING first_name, last_name

If a function returns nothing, call it on its own:

CALL greet("world")

Readability tip. Keep your top-level functions short and let them read like a table of contents — each line a clear step that calls into more detail. A reader should understand what happens by scanning the top function, and dive into a called function only when they need the how.

The gotchas (read this twice)

Aubit 4GL has a few rules that surprise newcomers. Learning them now saves hours.

1. DEFINE goes at the top of a function

All DEFINE statements must come before the first executable line of the function (or MAIN). You cannot declare a variable in the middle.

FUNCTION demo()
    DEFINE x INTEGER     -- all DEFINEs first
    DEFINE y INTEGER
    LET x = 1            -- then the code
    LET y = 2
END FUNCTION

2. Test "empty" text with LENGTH(... CLIPPED)

A CHAR(n) field is padded with spaces, so it is never literally equal to "". To check whether it is empty:

-- reliable:
IF LENGTH(customer_name CLIPPED) > 0 THEN
    DISPLAY "has a name"
END IF

For numbers, the normal comparison is fine: IF quantity != 0 THEN ....

3. STRING has no methods — use built-in functions

There are no str.trim() / str.toUpperCase() style methods. Use functions:

You might expect Use instead
str.indexOf(sub) instr(str, sub)
str.substring(a,b) str[a,b] (or the substr function)
str.toUpperCase() UPSHIFT(str)
str.toLowerCase() DOWNSHIFT(str)
str.trim() str CLIPPED (trailing spaces)
str.length LENGTH(str)
LET shout = UPSHIFT(customer_name)
LET pos   = instr(email, "@")
LET first3 = code[1,3]

4. You cannot pass an array as a parameter

This is the big one. A function call like CALL process(my_array) is not allowed. Two ways around it:

Read-only: pass a copy with COPYOF. The function gets the data but cannot send changes back.

CALL print_all(COPYOF names)     -- print_all can read names, not modify the caller's copy

Read-write: declare the array as a global (in a shared g_*.4gl file) and let functions read and write it directly, without passing it.

-- in g_app.4gl
GLOBALS
    DEFINE g_order_lines DYNAMIC ARRAY OF RECORD
        product CHAR(20),
        qty     INTEGER
    END RECORD
END GLOBALS

Any module that includes this with GLOBALS "g_app.4gl" can use g_order_lines directly.

5. There is no TRY/CATCH

Check status after operations instead:

SELECT name INTO customer_name FROM customers WHERE id = 101
IF SQLCA.SQLCODE != 0 THEN
    DISPLAY "Customer not found"
END IF

We cover error handling fully in chapter 6.


You now know enough of the language to be dangerous. Next, learn how to design the screens your users will see: Forms and Widgets.