Reading UniBasic
A primer for anyone opening AFTEC source for the first time. Six ideas cover almost everything you will meet. Companion to Reading AFTEC Source, which covers where the code is and which copy runs.
AFTEC is written in UniBasic — the PICK/UniData dialect. It looks like BASIC and is not: the data model underneath is a nested record, not a table, and most of the syntax exists to walk that nesting.
1. A record is a whole tree, not a row
Every record has attributes; each attribute can hold multiple values; each value can hold multiple sub-values. Three invisible delimiters do the work, and virtually every program declares them on line one:
AM = CHAR(254) ; attribute mark — the fields of the record
VM = CHAR(253) ; value mark — repeats within one field
SVM = CHAR(252) ; sub-value mark — repeats within one value
So a single WIPLEDGER.MAT record holds an entire cost matrix: attribute 1 is the list of fiscal periods,
attribute 5 is actual material cost value-aligned to it, sub-divided by cost class. One key, one read, the
whole history.
This is why AFTEC has no “cost transactions” table. If you come from SQL expecting one, you will look for it for a long time. The equivalent of a fact table is a single record whose shape encodes the dimensions.
It is a deliberate model, not a quirk
Worth knowing before you dismiss it as an old flat-file system. The vendor manual (§1.2.1–1.2.2) describes PRO-III’s database as “the first commercial implementation of the ‘nested’ relational model or NF2 model” — Non-First-Normal-Form.
The argument they make is the one you would make today about document databases:
“All relational database management systems to date are based on the First Normal Form (1NF) model. This model requires that each relation be depicted in a two dimensional table, where each attribute is atomic. Problems arise when trying to ‘map’ complex business applications onto this simple table structure… [NF2 allows] multiple relations to reside in a single file, thereby collapsing the three tables into one… dramatically reduces the number of tables and indexes in a database, and eliminates the redundancies of the table containing the relationship-relation.”
So WIPLEDGER.MAT holding a whole period × class matrix in one record is the model working as intended:
the header-detail-subdetail join has been collapsed into the record itself. The trade is the one you would
expect — no join cost, no referential integrity either.
2. Positional access — and why it is brittle
EXTRACT(REC, 5, VAL, SUBVAL) ; read attr 5, value VAL, sub-value SUBVAL
REC = REPLACE(REC, 5, VAL, 0, X) ; write it back
REC = INSERT(REC, 1, FPOS, 0, FP) ; splice a new value in at FPOS
LOCATE(CLASSNO, REC, 2, FPOS; CPOS) ELSE ; find it, or tell me where to insert
Fields are addressed by number. Nothing enforces that attribute 5 is actual material cost — only the dictionary and convention. Insert an attribute in the wrong position and every program reading that file is silently wrong.
This is the single biggest hazard in the codebase, and it is why the DICT is authoritative: see the AFTEC DICT endpoint for reading field positions from the live dictionary rather than hardcoding them.
3. Money is a scaled integer
OCONV(X, "MD4") ; stored 57764948 → displayed 5776.4948
ICONV(X, "MD4") ; and back again
Ledger files are MD4 — four implied decimals. JOB.IP.1287 is MD2. Reading a raw attribute without
converting gives a number 10,000× too large; mixing MD2 and MD4 in one sum is a real and easy bug. MD3 is
used for quantities.
4. Locking is manual
READU REC FROM FILE, ID ELSE REC = "" ; read AND lock
WRITE REC ON FILE, ID ; writes and releases
RELEASE FILE, ID ; release without writing
A READU that never reaches a WRITE or RELEASE leaves the record locked until the process ends. Nothing
enforces the pairing — 108 running programs take a READU with no RELEASE anywhere in the source
(see the change audit; most are fine because they always write).
5. Programs talk through the select list
EXECUTE 'SSELECT WIPLEDGER.SUM WITH JOBNO # "" BY CO BY JOBNO'
IF @SYSTEM.RETURN.CODE = 0 THEN CHAIN SEL ; nothing selected — bail out
EXECUTE 'CA1778' ; inherits the list
One program selects a set of keys; the next consumes it with READNEXT. The list is an invisible
argument — you cannot tell what a program operates on by reading only that program; you have to find its
driver PROC (in the matching *PR directory).
Skip the @SYSTEM.RETURN.CODE check and an empty selection becomes a run that quietly does nothing. 461
running programs never check.
6. The dictionary computes
; I-descriptors on JOB.IP.1287
COST.DIFF = JOBCOST.TTL - JOBWIP.TTL
JOBWIP.TTL = ICONV(OCONV(SUM(JOB.WIP),"MD2") + OCONV(SUM(JOB.INV),"MD4"), "MD2")
PARTNO = TRANS(OPENWO, @ID, 'F2', 'X') ; a virtual foreign key
Dictionary entries are not just labels. D-type entries name a stored attribute; I-type entries are
expressions evaluated at read time, and TRANS() is a join. Real business logic lives in the dictionary —
the entire JOB.IP.1287 reconciliation is dictionary arithmetic, not program code. Reading only the
programs will miss it.
Two local conventions
@FOPEN — PSI’s file-open wrapper, used in newer code in place of the vendor’s form:
CALL @FOPEN("JOBLEDGER.SUM", JOBLEDGER.SUM, ERR) ; PSI
OPEN "", "JOBLEDGER.SUM" TO JOBLEDGER.SUM ELSE ... ; vendor
FPLOCATE — PSI’s fiscal-period locate helper:
CALL FPLOCATE(FP, LEDGREC<1>, "D", FPOS, FPFOUND, "JOBLEDGER.MAT!01!":DATE())
Retrofitted across the ledger programs in 2000 and the most common single PSI modification in the tree.
It replaces the vendor’s raw LOCATE … BY "DR" on the fiscal-period attribute.
Fossils: the platform tags
You will constantly see the same statement written several times, all but one commented out:
* LOCATE CLASSNO IN JMATREC<2,FPOS> SETTING CPOS ELSE ;*ULT*;*MCD*
LOCATE(CLASSNO, JMATREC, 2, FPOS; CPOS) ELSE ;*ADDS*;*UDT*
* LOCATE CLASSNO IN EXTRACT(JMATREC,2,FPOS,0)<1,1,1> SETTING CPOS ELSE ;*PRIME*;*UNIX*
These are the same logic for different Pick implementations — Ultimate, McDonnell Douglas, ADDS, UniData, Prime. Only the uncommented one is live. They are fossils of the ports this code has survived, and a useful dating signal: a program carrying all four variants predates PSI’s UniData move.
Related: AFTEC Module Reference · Change Audit · Reading AFTEC Source · AFTEC Job Costing & WIP