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, notFLOAT, for money.FLOATrounds 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). UseCASEfor multi-way branches, or nestIF/ELSE.CASEis 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.