tcl - mvx-lang/mvx GitHub Wiki

TCL and Verbs

mvx is the classic shell: a prompt, a builtin table, VOC dispatch, and nothing else. Verbs are compiled BASIC programs.

mvx -a /path/to/account          # log on to an account
mvx -a acct -c "LIST PARTS"      # one command (ssh/cron style)

Interactive sessions have arrow-key history (persisted in ~/.mvx_history) and the prompt names the account.

Resolution order for a typed command: builtins โ†’ the account VOC โ†’ linked packages (in order) โ†’ the system account. Local entries override everything; the system account is the source of truth for standard verbs.

Builtins

OFF / QUIT / BYE end the session
LOGTO dir switch accounts
! command raw Unix (unrestricted tier)
SH interactive shell (unrestricted tier)

Paragraphs

A VOC record whose attribute 1 is PA is a paragraph: the attributes after it are sentences, run in order. It is typed like a verb, reached by EXECUTE, and may be an account's LOGIN.

DAILY
001 PA
002 * the overnight run
003 SELECT ORDERS WITH DESPATCHED = "N"
004 REPORT.PICKLIST
005 COUNT ORDERS
  • Comments begin with *; blank lines are skipped.

  • Sentences are not echoed โ€” a paragraph of two commands prints what those two commands print, and nothing else.

  • <<text>> asks the operator text and puts the answer where it stood. The same prompt twice is one question, reused:

    002 COUNT <<Which file>> ; LIST <<Which file>>
    
  • A sentence that fails does not stop the paragraph. The message names the paragraph โ€” In paragraph DAILY: ... โ€” because "not a verb" on its own leaves you hunting for what ran it.

  • Paragraphs nest: one may invoke another.

A cataloged program still wins: a V record is looked for first, and a paragraph only when there is none.

These rules are UniData's, measured rather than assumed. Two are not what you might expect: sentences are not echoed, and <<%1>> is not parameter substitution โ€” paragraphs have no %n parameters, and that spelling simply prompts with the text %1.

PROCs

A VOC record whose attribute 1 is PQ (or PQN) is a PROC. Where a paragraph is a list of sentences, a PROC builds a command and then runs it โ€” the older Pick mechanism, carried by accounts ported from UniData or OpenQM.

DPQ
001 PQ
002 S2          <- put the input pointer on word 2 of the command line
003 HCOUNT      <- build "COUNT " into the output buffer
004 A           <- copy the word under the pointer in after it
005 P           <- execute what was built, and carry on
opcode
Otext write text to the terminal
Htext append text to the output buffer; several H lines concatenate
A / An copy the word under the input pointer (or word n) into the buffer
Sn put the input pointer on word n
P execute the buffer โ€” and carry on, so a PROC may run several commands
X stop
RI / RO reset the input pointer / empty the output buffer
GO label jump to a numeric label (10 Omatched)
IF An = value command run command on a match; # for "not equal"
IF # An command run command when word n is absent

Word 1 is the verb, so a PROC's first argument is word 2 โ€” which is why S2 is the usual opening line.

An opcode MVX does not know is named, not skipped. The secondary input buffer, T terminal control, STON/STOFF stacking and the [] call are not implemented: they exist because PROC had no other way to do things MVX does directly. A PROC needing one fails loudly rather than doing half its job.

These rules are UniData 8.3's and ScarletDME 2.6-6's, which agree on every opcode above.

LOGIN โ€” the account's own setup

If the account's VOC holds a record named LOGIN, it runs when a session enters the account: at mvx -a, and on every LOGTO. It is an ordinary cataloged program, so making one is:

CATALOG BP LOGIN
  • The account's own. A LOGIN in a linked package or the system account is not inherited โ€” it would otherwise run in every account on the machine.
  • On entering, not per program. A cataloged program run straight from Unix does not trigger it.
  • A failure is reported, not fatal. The session is already in the account by then, so an abort in LOGIN is caught and the caller carries on.
  • No loops. A LOGIN that itself does a LOGTO does not start another.
  • It may not own the screen. A LOGIN entered directly sees @LEVEL = 0; one fired by a LOGTO from inside a running program sees 1, and should not prompt โ€” the keystroke it reads is the one that program was waiting for.

LOGIN may equally be a paragraph or a PROC (above), which is what it is on UniData โ€” a cataloged program cannot be a LOGIN there at all. The hook does not care: it runs the sentence LOGIN, whatever the record turns out to be.

UniVerse additionally honours a record named after the account, and prefers it. LOGIN is the one spelling those systems share, which is why it is the one MVX uses.

Standard verbs

verb
CREATE-FILE name {DIR|USING driver {conn}} create a file โ€” local, directory, or on another backend
DELETE-FILE name delete a file
CLEAR-FILE name delete every record
LISTF the account's files
CONVERT-FILE file newtype {conn} move one file to another backend โ€” records, dictionary and all
CONVERT-ACCOUNT newtype {FROM oldtype} {CONNECTION conn} {LISTONLY} move the whole account; skips directory files unless FROM dir, never converts CATALOG, does VOC last
FIX-POINTERS {LISTONLY} bring an account's VOC file pointers up to date โ€” cleanup for an account created before the pointer carried the location; only the pre-stage-2 form is touched, never an edit of your own
COUNT {DICT} file {WITH item op value} record count; pushes count(*) (optionally filtered) into a SQL backend, or uses an active select list
SUM {DICT} file field {WITH item op value} total a numeric field; pushes sum(col) into a SQL backend when the field is a mapped NUMERIC column, else scans and OCONVs
LIST {DESCRIBE} {DICT} file {items} {WITH item op value {AND โ€ฆ}} {BY item} query; multiple WITH/AND conditions are ANDed (pushed to one SQL WHERE); DESCRIBE prints the plan instead of running it
SORT {DESCRIBE} {DICT} file {items} {WITH item op value} {BY item} {FIRST n} like LIST, but sorted by id (or the BY key); FIRST n keeps the top n, pushed to ORDER BY โ€ฆ LIMIT on a SQL backend when the BY field is a mapped column
SELECT {DESCRIBE} {DICT} file {WITH item op value} form a select list for the next command
SSELECT {DESCRIBE} {DICT} file {WITH item op value} {BY item} like SELECT, but the list is sorted
MAP file {items...|ALL} {DATA} the relational schema the named items imply (SQL mapping preview)
CREATE-MAP file fieldโ€ฆ declare the mapping (%MAP%) + build it; writes are then mirrored live
BUILD-MAP file fieldโ€ฆ {PROGRESS} materialise the mapping in the backend and backfill (SQL columns); PROGRESS shows a live indicator
LIST-MAPS the account's mapped files: mode, state (current/stale vs the dictionary), and fields
MAP-MODE file {native|mirror} show or set the write policy (mirror: NULL on mismatch; native: reject the write)
DELETE-MAP file drop a mapping โ€” tear down its columns/child tables and stop mirroring
CT {DICT} file id show a record, numbered attributes
COPY file id TO {file2} id2 copy a record
DELETE file id {id...} delete records
ED file id the built-in line editor (any tier)
VI file id edit a record in an external editor (unrestricted)
BASIC file item {NODEBUG} {STRIP} compile to an object (developer)
CATALOG file item {NODEBUG} {STRIP} compile, link and publish (developer)
PORT-SOURCE file item {TO file2} {item2} rewrite C-style comments as classic
CREATE-INDEX file item / DELETE-INDEX / LIST-INDEXES secondary indexes
LINK-PKG path / UNLINK-PKG path / LIST-PKGS packages
WHO TIME DATE session information
LISTU who is logged on: port, user, account, line
MSG{n} target text / MESSAGE message a port, a range, an account, a user, or * for every port
EXPORT {DICT} file {dir} copy records (or a dictionary) to a directory file
BUILD provision an account from its git-tracked config (post-clone)
IMPORT file {dir} mirror a directory file back (full sync)
GIT add|commit|branch|checkout|merge|cherry-pick|... git for records: full porcelain incl. branching
SET-CREDENTIAL driver target key field=value ... store a backend secret in .mvx-private (git-ignored)
LIST-CREDENTIALS list stored credentials, values masked
SET-CONNECTION name field=value ... define a named connection profile (@name in BINDINGS)
LIST-CONNECTIONS list connection profiles, secret fields masked

Select lists between commands

SELECT leaves its list for the next command:

> SELECT PARTS WITH COLOR = blue
2 record(s) selected
> LIST PARTS NAME PRICE          only the selected records

The list is consumed exactly once. Inside a program, EXECUTE "SELECT ..." followed by READNEXT uses the same mechanism.

DESCRIBE โ€” how a query would run

Add DESCRIBE (or EXPLAIN) to LIST, SORT, SELECT, or SSELECT and the verb prints the plan the backend would use without running the query. The word can go right after the verb (LIST DESCRIBE โ€ฆ) or at the end of the sentence โ€” either way it does the same thing. On a SQL backend the plan is the SQL text the push-down would issue, so you can see exactly what work moves server-side:

> LIST DESCRIBE CUST STATE WITH STATE = "VIC" AND PRICE > "500"
postgres: SELECT id FROM "acct"."CUST" WHERE "STATE" = 'VIC'
  AND NULLIF("acct".mvx_attr(rec,3),'')::numeric > '500'::numeric

> SORT ORDERS BY TOTAL FIRST 10 EXPLAIN
postgres: SELECT id FROM "acct"."ORDERS" ORDER BY "TOTAL" LIMIT 10

The plan follows the same push-down rules the query itself would: a mapped identity column is compared as a column, an unmapped or converted field through the record blob (mvx_attr), and a numeric range as a numeric comparison. When a condition can't be pushed (an @ID filter, a TRANS() join to another backend, a text range) the plan says so and notes that the verb applies it while scanning:

> LIST CUST WITH @ID = "C1000" DESCRIBE
postgres: SELECT id FROM "acct"."CUST"; 1 condition(s) applied in the verb

The description is backend-specific: a store with no server-side query planner (the local LMDB and directory backends) reports that the driver returns the id list โ€” an index lookup when one exists, otherwise a sequential scan โ€” and the verb applies the conditions and ordering. A future document backend would render its own native query the same way.

ED

The classic line editor: ED FILE ID.

Enter next line
n go to line n
L {n} list n lines
T / B top / bottom
I insert after current line โ€” . alone ends input
DE {n} delete n lines
R/old/new replace in the current line
FI file (save) and exit
EX exit without saving

The privilege gate

Every spawn โ€” !, SH, EXECUTE, and the compiler โ€” passes through one gate in the runtime, controlled by $MVXPRIV:

tier may
restricted (default) run verbs, EXECUTE sentences
developer + compile (BASIC, CATALOG, COMPILE())
unrestricted + raw Unix (!, SH)

The check lives in the runtime because anyone who can compile could call the primitives directly โ€” a shell-only check would be decorative.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ