# Statements Reference, alphabetically. `{...}` marks optional parts. ### Assignment ``` var = expr var = 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 ``` BEGIN CASE CASE cond statements CASE 1 the catch-all statements END CASE ``` The first true `CASE` wins. ### CALL ``` 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 / DEFFUN ``` 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 `DEFFUN`s 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 ``` COMMON {/BLOCK/} item, item(dims), ... ``` ### DIM ``` DIM A(rows{, cols}) ``` ### ECHO ``` ECHO ON | ECHO OFF ``` Terminal echo for `INPUT` (password entry). ### EQUATE ``` 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 / STOP / RETURN / NULL `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 ``` 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: ```basic EXECUTE "PAYMENTS" ON ERROR PRINT "that option gave up" ;* and this program carries on END ``` This 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](replacing-tcl). ### FOR / NEXT ``` 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 ``` FORMLIST dynarray ``` Installs the attributes of `dynarray` as the active select list. ### GOTO / GOSUB ``` 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 ``` 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 ``` INPUT var ``` Reads one line from standard input. At end of input the program ends. ### LOOP ``` 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 ``` 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 ``` MAT A = expr fill every element MAT A = MAT B copy (dimensions must match) ``` ### MATPARSE / MATBUILD ``` 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 / CRT ``` 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 / MESSAGE ``` 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](intrinsics.md)), 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: ```basic SEND = "still a variable" MESSAGE = "so is this" ``` ### TRANSACTION ``` 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. ```basic 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: ```basic 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: ```basic IF @TRANSACTION THEN GOSUB JUST.WRITE END ELSE TRANSACTION START ELSE STOP GOSUB JUST.WRITE TRANSACTION COMMIT ELSE PRINT "commit failed" END ``` Three 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, `COMMIT` fails and rolls the rest back, so a program that handles the refused write by logging it cannot leave half its work committed. Use `ABORT` to give up on purpose. - **A backend without transactions refuses the write.** `sqlite`, `postgres` and `mysql` can hold one; `dir`, `lmdb` and `mongo` cannot, so a write to one of those inside a transaction fails rather than being quietly committed on its own — soft under `ON ERROR`, fatal without. See [Files](files.md) for which backend an account is using. - **Ending without committing rolls back.** `STOP`, a `STOP` with a code, a fault or a kill all discard an open transaction, so the abort path needs no explicit `ABORT` — 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`. ### File statements ``` 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 `ABORT`s first. ## Conditional compilation 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. ```basic $IFDEF MVX OPEN "ORDERS" TO F ELSE STOP $ELSE OPEN "", "ORDERS" TO F ELSE STOP ;* UniVerse two-argument form $ENDIF ``` This is the portability seam: commit an account to git on one platform, `mvx-git clone` it on MVX, and — with the `$IFDEF`s set correctly — it compiles and runs. ### Source inclusion ``` $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 `EQUATE`s, `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.