statements - mvx-lang/mvx GitHub Wiki
Reference, alphabetically. {...} marks optional parts.
var = expr
var<a{,v{,s}}> = expr dynamic-array replace
ARR(i{,j}) = expr dimensioned element
var += expr compound: also -= *= /= and := (append)
target OP= value is shorthand for target = target OP value; :=
appends (string concatenation). It works on any assignable target.
BEGIN CASE
CASE cond
statements
CASE 1 the catch-all
statements
END CASE
The first true CASE wins.
CALL NAME(args...)
CALL @VAR(args...) indirect: VAR holds the subroutine name
Arguments pass by reference. Resolution happens at call time: the
program itself, then cataloged LIB/ libraries, linked packages, and
the system account.
FUNCTION NAME(args...) first line of a function program
...
RETURN(value) return a result (RETURN alone returns "")
DEFFUN NAME(argcount) declare a function so NAME(...) is a call
X = NAME(a, b) call it in any expression
A FUNCTION is a program that yields a value through RETURN(expr); it
compiles and catalogs like a subroutine and shares the same resolution.
A caller DEFFUNs the function (before first use) so NAME(...) reads as
a call rather than an array reference, then uses it anywhere an
expression is allowed — including recursively. Arguments are passed by
value.
COMMON {/BLOCK/} item, item(dims), ...
DIM A(rows{, cols})
ECHO ON | ECHO OFF
Terminal echo for INPUT (password entry).
EQUATE name TO expr
EQUATE name LITERALLY expr (LIT is accepted for LITERALLY)
EQU is accepted as a short spelling of EQUATE.
A compile-time constant: every later use of name in expression context
expands to expr — so EQUATE AM TO CHAR(254) then X<1> = AM needs no
runtime lookup, and a numeric equate stays eligible for specialisation
(EQUATE SIZE TO 5 ; DIM V(SIZE)). The right-hand side is any expression
and may reference earlier equates. Names must be declared before use and
cannot be redefined or assigned to.
END ends the program text. STOP ends the program immediately —
even from inside a subroutine. RETURN returns from a GOSUB, or
from a subroutine when the GOSUB stack is empty. NULL is an explicit
no-op — useful as the body of an otherwise-empty THEN or CASE.
EXECUTE sentence {CAPTURING var} {RETURNING var} {ON ERROR ... END}
Runs a TCL sentence in this session. CAPTURING collects its output as a
dynamic array (line per attribute); RETURNING receives the exit status. A
select list formed by the sentence (e.g. an EXECUTEd SELECT) is consumed by
this program's next READNEXT. PERFORM is a synonym.
The program runs at a level of its own: its own unnamed COMMON, STATUS()
and SENTENCE(), sharing this session's open files, locks, select list and
transaction. So it sees what this program has already written — including
inside an open transaction — and a file opened here is open there.
STOP comes back; ABORT does not. A STOP, with or without a code,
returns here and RETURNING carries the code. An ABORT, or a runtime fault,
ends this program too — which is what should happen to work that has failed
half way through.
ON ERROR is how a program says otherwise:
EXECUTE "PAYMENTS" ON ERROR
PRINT "that option gave up" ;* and this program carries on
ENDThis is for a shell — a login and menu written in BASIC, replacing TCL. An option that gives up should return the operator to the menu, not log them out. Ordinary code should leave the clause off and let the abort through.
A non-zero STOP is a status, not an error: it does not run the ON ERROR body. Only an ABORT or a fault does.
See Replacing TCL.
FOR I = first TO last {STEP n}
statements
NEXT I
Inside any FOR or LOOP, CONTINUE skips to the next iteration and
EXIT leaves the innermost loop.
FORMLIST dynarray
Installs the attributes of dynarray as the active select list.
GOTO label GO TO label GOSUB label ... RETURN
ON expr GOTO l1, l2, ... computed jump to the expr-th label
ON expr GOSUB l1, l2, ... computed GOSUB to the expr-th label
A label is either a number at the start of a line (100 …) or a name
followed by a colon (RETRY:) — the same as the modern MV dialects. A named
label may stand alone or precede a statement on the same line (DONE: PRINT X),
and GOTO/GOSUB/ON accept either form. Label names and variable names are
separate, so SAVE: (a label) and SAVE (a variable) can coexist. GOSUBs nest
to depth 1024. For ON, expr selects a target 1-based; a value outside
1..n falls through to the next statement.
IF cond THEN statement {ELSE statement}
IF cond THEN
statements
END ELSE
statements
END
Single-line and block forms mix freely; ELSE without THEN is
allowed.
INPUT var
Reads one line from standard input. At end of input the program ends.
LOOP
statements pre-condition body
WHILE cond DO or UNTIL cond DO
statements post-condition body
REPEAT
The test may sit on the same line as LOOP — LOOP WHILE cond DO … REPEAT
(and LOOP UNTIL cond DO …) — the classic Pick/R83 compact form, equivalent
to putting WHILE/UNTIL on the next line with no pre-condition body. DO is
optional.
LOCATE(item, dynarray{, attr#{, value#}}; setting {; order}) THEN/ELSE
Finds item at the given level. setting receives the position — on
ELSE, the insertion point (per order: AL, AR, DL, DR) or
count+1.
MAT A = expr fill every element
MAT A = MAT B copy (dimensions must match)
MATPARSE arr FROM strexpr {USING delim}
MATBUILD strvar FROM arr {USING delim}
The in-memory pair to MATREAD/MATWRITE, with no file. MATPARSE
splits a string across a DIM'd array — the last element absorbs any
overflow, short input leaves trailing elements empty. MATBUILD joins
the elements back into a string, dropping trailing empties. The delimiter
defaults to @FM; USING gives any string (MATBUILD S FROM P USING ",").
PRINT expr {: expr ...} {,} {:}
: between items concatenates; , advances to the next 18-column
zone; a trailing : suppresses the newline. Cursor addressing and
colours are just strings — see the terminal chapter.
SEND expr TO port {THEN ... {ELSE ...}}
MESSAGE ON | OFF | DEFER
Message another port, and choose what this session does with arriving
messages. SEND takes THEN when the message reached somebody, and ELSE
when that port is not logged on, when the permit is missing, or when there is
no message service running — the program keeps going either way.
TO takes a port number, as D3 spelled it. A user, an account or every
port at once goes through MSGSEND (see Intrinsics), which
is also where a message carrying a record is built.
Neither word is reserved. MESSAGE is exactly the name a program would give a
variable, so each is a statement only in a shape that cannot be anything else:
SEND = "still a variable"
MESSAGE = "so is this"TRANSACTION START THEN ... {ELSE ...}
TRANSACTION COMMIT THEN ... {ELSE ...}
TRANSACTION ABORT
Bracket several writes so they commit or roll back as one unit. Without a
transaction each WRITE is already atomic on its own — with a mapping, the
record, its parent columns and its child rows land together or not at all —
and this widens that guarantee across statements.
START and COMMIT take THEN/ELSE, as they do on UniData and UniVerse.
ABORT takes neither: discarding is best effort by definition, so there is no
failure to branch on.
OPEN "ORD" TO F ELSE STOP
TRANSACTION START ELSE PRINT "no transaction" ; STOP
WRITE HEADER ON F, ID
WRITE DETAIL ON F, ID:".D"
TRANSACTION COMMIT ELSE PRINT "commit failed"The transaction need not be one block. It can be started in one place, written to by subroutines called later, and committed somewhere else again — which is how screen handlers on UniData have always used it:
TRANSACTION START ELSE STOP
GOSUB WRITE.THE.ORDER
GOSUB WRITE.THE.LINES
TRANSACTION COMMIT ELSE PRINT "commit failed"@TRANSACTION is 0 outside one and non-zero inside, so a subroutine can
tell whether its caller already opened one:
IF @TRANSACTION THEN
GOSUB JUST.WRITE
END ELSE
TRANSACTION START ELSE STOP
GOSUB JUST.WRITE
TRANSACTION COMMIT ELSE PRINT "commit failed"
ENDThree rules are worth knowing before you rely on it:
- One transaction, one connection. The first write inside a transaction binds it to that file's backend and location; a write to a file on a different connection is refused and says so. Two backends committing separately would be atomic in each and in neither overall.
-
A refused write means the transaction cannot commit. Once any write
inside one has been refused,
COMMITfails and rolls the rest back, so a program that handles the refused write by logging it cannot leave half its work committed. UseABORTto give up on purpose. -
A backend without transactions refuses the write.
sqlite,postgresandmysqlcan hold one;dir,lmdbandmongocannot, so a write to one of those inside a transaction fails rather than being quietly committed on its own — soft underON ERROR, fatal without. See Files for which backend an account is using. -
Ending without committing rolls back.
STOP, aSTOPwith a code, a fault or a kill all discard an open transaction, so the abort path needs no explicitABORT— which is how the pattern above survives a program that dies half way through a screen.
A second START while one is open is refused rather than nested. UniVerse
does allow nesting; MVX does not, because unwinding one level of a nest needs
savepoints in the backend, and refusing is better than appearing to nest and
then discarding everything on the inner ABORT.
OPEN {"DICT",} "file" TO fvar THEN/ELSE
CLOSE fvar release the file (and its connection)
READ var FROM fvar, id THEN/ELSE
READU var FROM fvar, id THEN/ELSE read with record lock
WRITE expr ON fvar, id releases the lock
WRITEU expr ON fvar, id keeps the lock
READV var FROM fvar, id, n THEN/ELSE read attribute n of a record
READVU var FROM fvar, id, n THEN/ELSE same, with a record lock
READU var FROM fvar, id LOCKED ... END THEN/ELSE (LOCKED: held elsewhere)
WRITE expr ON fvar, id ON ERROR ... END (ON ERROR: backend fault)
WRITEV expr ON fvar, id, n replace attribute n, keep the rest
WRITEVU expr ON fvar, id, n same, keeps the lock
MATREAD arr FROM fvar, id THEN/ELSE fields (@FM) -> array elements
MATREADU arr FROM fvar, id THEN/ELSE same, with a record lock
MATWRITE arr ON fvar, id elements -> record; releases the lock
MATWRITEU arr ON fvar, id keeps the lock
DELETE fvar, id
RELEASE {fvar, id} bare form releases all
SELECT fvar form the active select list
READNEXT var THEN/ELSE next id from the active list
A select list left unconsumed when a program ends passes to the next command in the TCL session.
CLOSE releases a file. Every file in one database shares one connection,
and the connection goes back when the last file on it closes — so a program
that walks a list of companies, or an account that is logged away from, does
not accumulate them. A file variable stops being one after CLOSE, and using
it says so.
Files are closed for you when the session ends, and when a LOGTO leaves the
account they belong to. A handle held across a LOGTO — in COMMON /FILES/,
say — is no longer open, and reports that rather than reading a handle that
has gone. (This is where MVX differs from UniData and UniVerse, whose handles
name a file rather than a place in an account's store, and survive. An MVX
handle is resolved through the account's bindings, so one that survived would
follow you into the new account rather than keep pointing where it was
opened.)
A LOGTO is refused while a transaction is open: it belongs to a
connection in the account being left, so the program commits it or ABORTs
first.
A source preprocessor runs before parsing, in the UniVerse/UniData style, so the same source can target several MultiValue systems:
$DEFINE name {value} define a symbol (optional replacement value)
$UNDEFINE name remove a definition
$IFDEF name keep the block if name is defined
$IFNDEF name keep the block if name is NOT defined
$ELSE the other branch
$ENDIF end the conditional
MVX is predefined by the mvx-basic compiler; define your own on the
command line with -D NAME or -D NAME=value. A $DEFINE with a value
also substitutes that value wherever the name appears (outside string
literals). Inactive lines are blanked, not removed, so error messages
and the source-level debugger still line up with the original file.
$IFDEF MVX
OPEN "ORDERS" TO F ELSE STOP
$ELSE
OPEN "", "ORDERS" TO F ELSE STOP ;* UniVerse two-argument form
$ENDIFThis is the portability seam: commit an account to git on one platform,
mvx-git clone it on MVX, and — with the $IFDEFs set correctly — it
compiles and runs.
$INCLUDE item splice a sibling source file
$INSERT item synonym for $INCLUDE
$INCLUDE file item splice file/item (the directory-file layout)
$INCLUDE and $INSERT pull another source file in where the directive
stands — the classic way to share EQUATEs, COMMON blocks, and
DEFFUN declarations across programs. The target resolves relative to
the including file's directory (a .b suffix is tried as a fallback),
includes may nest, and $DEFINE/$IFDEF state carries across the
boundary. A compiler tracks each spliced line back to its origin, so a
compile error in an included file reports that file and line, and the
source-level debugger still steps the right code.