replacing tcl - mvx-lang/mvx GitHub Wiki

Replacing TCL

Most MV sites do not put operators at a command prompt. They put them in a login program and a menu — CueBic, SB+, or something the site wrote — and TCL is either hidden or reached only by people who are trusted with it. Sites that want a command language of their own write one.

MVX is built to be removed from that picture. mvx is a shell like any other program: a prompt, a builtin table, and dispatch. Everything underneath it — the session, the files, the locks, the transactions, the ability to run a program — belongs to the runtime, and your program can have it instead.

This page is how.

The usual way: a BASIC program

A cataloged main program is an executable that owns a session. So the whole of "replacing TCL" is: run your program instead of mvx.

* MENU -- catalog this and run it instead of mvx
COMMON /FILES/ F.ORD, F.CUST
OPEN "ORD"  TO F.ORD  ELSE PRINT "cannot open ORD"  ; STOP
OPEN "CUST" TO F.CUST ELSE PRINT "cannot open CUST" ; STOP

LOOP
   PRINT
   PRINT "  1) Orders   2) Customers   Q) Log off"
   PRINT "  choice: ":
   INPUT CH
UNTIL OCONV(CH, "MCU") = "Q" DO
   BEGIN CASE
      CASE CH = 1 ; EXECUTE "ORDER.ENTRY"
      CASE CH = 2 ; EXECUTE "CUSTOMER.ENQUIRY"
      CASE 1      ; PRINT "  no such option"
   END CASE
REPEAT
mvx -a /srv/acct -c 'CATALOG BP MENU'     # once
cd /srv/acct && MVXACCOUNT=. ./CATALOG/MENU

Set that as the operator's login command and they never see a prompt.

What the session gives you

A verb reached by EXECUTE runs in this process, at a level on your session. It gets its own unnamed COMMON, its own STATUS and its own SENTENCE(), and shares everything the session owns. Three consequences, and they are the reason this is worth doing:

Files opened once stay open. The menu above opens ORD and CUST at login and puts them in named COMMON. Every option that declares the same block gets the handles — no OPEN anywhere else, and no reopening per screen.

* ORDER.ENTRY -- no OPEN in it at all
COMMON /FILES/ F.ORD, F.CUST
READ ORDER FROM F.ORD, ID THEN ... 

A transaction can span screens. Start one in a pre-save, let the operator work, commit it at the next screen. The transaction belongs to the session, not to whichever program happens to be running.

TRANSACTION START ELSE PRINT "cannot start" ; STOP
EXECUTE "TAKE.PAYMENT"          ;* writes inside the transaction
EXECUTE "POST.LEDGER"           ;* and so does this
TRANSACTION COMMIT ELSE PRINT "commit failed"

A program reached by EXECUTE reports @TRANSACTION as 1 and sees what the caller has already written. This is not what other MV systems do — on jBASE and UniData an EXECUTE'd program sits outside the caller's transaction and reads a stale view, with nothing reported. MVX diverges deliberately; see DECISIONS.md.

A select list simply persists. EXECUTE a SELECT, then READNEXT in the caller — no file is handed sideways between processes, because there is only one process.

EXECUTE "SELECT ORD"
LOOP
   READNEXT ID ELSE EXIT
   ...
REPEAT

What you give up

the prompt libedit history, the command stack (.L, .R, .X), .C macros
crash isolation a verb you run in-process can take the session down — there is no forked child between you and it

The first is usually the point.

Surviving an abort

An ABORT, or a runtime fault, takes the calling program with it. That is UniData's behaviour and MVX matches it: a program that EXECUTEs one that aborts does not resume either. It is right for ordinary code — work that has failed half way through should not carry on as though it had not.

A shell is the exception, and says so:

EXECUTE "PAYMENTS" ON ERROR
   PRINT "that option gave up"      ;* back in the menu loop
END

Without the clause the abort passes through, exactly as before. With it, the menu survives the option.

On UniData there is no equivalent: the abort is caught by TCL, so a site that removes TCL loses the only thing that was catching one. This is one of the few places MVX adds rather than matches, and the reason is that MVX means removing TCL to be a supported thing to do.

A non-zero STOP is a status and not an error — RETURNING carries it, and the ON ERROR body does not run.

A shell in C

For a host that is not BASIC — an application server, a terminal concentrator, something embedding MVX — the surface is five calls.

#include "mvx_runtime.h"
#include <dlfcn.h>

mvx_ctx *ctx = mvx_ctx_create();               /* the session */

for (;;) {
    char *line = your_menu_reads_a_choice();
    char verb[128], path[2048];
    /* your own idea of what an option means, or VOC's: */
    if (mvx_voc_lookup(ctx, verb, path, sizeof path) != 1) continue;

    /* the library suffix first, then the plain path */
    char lp[4200];
    snprintf(lp, sizeof lp, "%s%s", path, LIB_SUFFIX);   /* .so / .dylib */
    void *h = dlopen(lp, RTLD_NOW | RTLD_LOCAL);
    if (!h) h = dlopen(path, RTLD_NOW | RTLD_LOCAL);
    mvx_program_fn fn = (mvx_program_fn)dlsym(h, "mvx_main");

    int aborted = 0;
    int64_t rc = mvx_level_run_at_prompt(ctx, fn, line, &aborted);
    if (aborted) show("that option gave up");   /* you are still here */
}

mvx_ctx_destroy(ctx);                          /* log off */

mvx_level_run_at_prompt is what makes you the abort boundary: it runs a program at a level and stops an abort there instead of passing it on. That is the only difference between it and mvx_level_run, which is what EXECUTE uses.

mvx_voc_lookup is optional. It gives you VOC resolution — the account, then linked packages, then the system account — but nothing stops a menu from mapping option 3 to a path of its own.

Loading a program: try the platform's library suffix first, then the plain path. On Linux CATALOG/<name> is a small loader and CATALOG/<name>.so is the program; on macOS the one file is both. See Packages.

The account

A program takes its account from the working directory, so a login does:

cd /srv/acct
MVXACCOUNT=. exec ./CATALOG/MENU

mvx -a <dir> does the same thing before showing its prompt.

The account's own setup

Entering an account runs its VOC LOGIN, if it has one — a V record naming a program, the same shape as any verb, so CATALOG BP LOGIN is all it takes. It runs on mvx -a, and on every LOGTO, which is where it earns its keep: a menu that moves to SALES gets SALES's setup without knowing what that is.

It is the account's own — never inherited from a linked package or the system account, because a LOGIN that came from behind would run in every account on the machine with nothing naming where it came from.

It runs on entering an account, not per program: a cataloged program run straight from Unix pays nothing, so the login still is your program when you start one that way.

A LOGIN that gives up is reported and does not take the caller with it — by the time it runs, the session is already in the new account, so killing the menu that moved there would be the worst of both. And a LOGIN that itself does a LOGTO does not start another one.

Changing account

A menu that picks a company or a division needs to move, and LOGTO() is how:

IF LOGTO(DIVISION) THEN
   GOSUB OPEN.THE.FILES
END ELSE
   PRINT "cannot log to ":DIVISION:", status ":STATUS()
END

It returns 1 when the session moved and 0 when it did not — STATUS() is 1 for an account that cannot be entered and 2 for a transaction still open, which is the one refusal a shell should expect to handle rather than report. The program keeps running either way.

Everything the old account held is gone: its files are closed, its locks dropped, its connections given back, and the VOC, package and LIB/ chains forgotten. A handle kept in COMMON from before the move says it is stale rather than reading somewhere the operator no longer is — so re-opening is part of the move, and belongs in the GOSUB above.

A transaction is not settled for you. Commit it or ABORT it first; moving account with one open would commit into somewhere the program no longer is.

EXECUTE "LOGTO <dir>" does the same thing, because that is the spelling UniData and UniVerse use and ported code will already have it.

See also

  • TCL and Verbs — what mvx does, if you want to reproduce part of it
  • Statements — EXECUTE, TRANSACTION, COMMON
  • Packages — how a cataloged program is published
⚠️ **GitHub.com Fallback** ⚠️