tcl - mvx-lang/mvx GitHub Wiki
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.
OFF / QUIT / BYE
|
end the session |
LOGTO dir |
switch accounts |
! command |
raw Unix (unrestricted tier) |
SH |
interactive shell (unrestricted tier) |
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 operatortextand 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.
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.
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
LOGINin 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
LOGINis caught and the caller carries on. -
No loops. A
LOGINthat itself does aLOGTOdoes not start another. -
It may not own the screen. A
LOGINentered directly sees@LEVEL= 0; one fired by aLOGTOfrom 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.
| 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 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.
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.
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 |
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.