intrinsics - mvx-lang/mvx GitHub Wiki

Intrinsic Functions

Strings

LEN(s) length
TRIM(s) strip leading/trailing blanks, squeeze runs
TRIM(s, c[, opt]) trim character c; opt = L/T/B/A/R (default R, both ends + squeeze)
STR(s, n) s repeated n times
SPACE(n) n spaces
CHAR(n) / SEQ(s) code to character and back
FIELD(s, delim, n {, cnt}) n-th delimited field (cnt fields with delimiters)
INDEX(s, sub, occ) position of the occ-th occurrence, 0 if none
s MATCHES pat pattern test (operator, 1/0); MATCH is a synonym
MATCHFIELD(s, pat, n) the substring matched by pattern component n
COUNT(s, sub) occurrences of sub
DCOUNT(s, delim) delimited-field count
NUM(x) 1 if x is numeric ("" counts)
s[start, len] substring (operator)
CHANGE(s, old, new) replace every occurrence of old with new
EREPLACE(s, old, new) / SWAP(s, old, new) same as CHANGE
CONVERT(from, to, s) translate each char: from[i] -> to[i], deleted when to is shorter
TRIMB(s) / TRIMF(s) strip trailing / leading blanks
ALPHA(s) 1 if s is non-empty and all letters
QUOTE(s) / DQUOTE(s) / SQUOTE(s) wrap in double / double / single quotes
FMT(x, mask) format: L/R justify, fill # space * star % zero, width β€” FMT(N, "R%8")

A pattern for MATCHES/MATCHFIELD is a run of tokens that must consume the whole string: nN/nA/nX (exactly n numeric / alphabetic / any characters), 0N/0A/0X (zero or more of that class), a quoted literal ('-' or "/"), or any other character taken literally. Value marks (@VM) in the pattern give alternatives β€” the string matches if it matches any one. So "01-234" MATCHES "2N'-'3N" is true, and "3N":@VM:"3A" matches three digits or three letters.

Dynamic arrays

EXTRACT(A, a{,v{,s}}) same as A<a,v,s>
REPLACE(A, a{,v{,s}}, x) copy with element replaced
INSERT(A, a{,v{,s}}, x) copy with element inserted
DELETE(A, a{,v{,s}}) copy with element removed
SUM(A) reduce the lowest delimiter level by addition (keeps higher structure)
MAXIMUM(A) / MINIMUM(A) largest / smallest numeric field at any level

System constants: @AM/@FM (attribute mark), @VM (value mark), @SM/@SVM (subvalue mark).

Numbers

INT(x) truncate toward zero
ABS(x), SQRT(x) as expected
MOD(a, b) modulo, sign of b
PWR(x, y) x to the power y (also x ^ y)
LN(x), EXP(x) natural log and its inverse
SIN(d), COS(d), TAN(d) trigonometry, argument in degrees
ATAN(x) arctangent, result in degrees
RND(n) random 0 .. n-1

Date and time

DATE() internal date (day 0 = 31 DEC 1967), local
TIME() seconds since midnight, local
SYSTEM(12) milliseconds since midnight
OCONV(x, code) internal β†’ external
ICONV(x, code) external β†’ internal

Conversion codes: D{y}{sep} dates (OCONV(0,"D") β†’ 31 DEC 1967, D2/ β†’ 12/31/67), MT{S} times (MTS β†’ 12:34:56), MD{n}[,][$] masked decimal (OCONV(1234567,"MD2,$") β†’ $12,345.67), MCU, MCL, MCT case. STATUS() after a conversion: 0 ok, 1 bad input, 2 bad code.

Environment and session

SENTENCE() the TCL command line that invoked this program
ENV(name) environment variable, "" if unset
STATUS() last conversion status
SYSTEM(2) / SYSTEM(3) terminal width / depth (live)
SYSTEM(11) 1 when a select list is active
LOGTO(acct) change account; 1 moved, 0 refused, reason in STATUS()
@LEVEL how many programs are above this one; 0 when none

LOGTO takes the account's directory, the same thing mvx -a and the LOGTO verb take. It leaves the old account before entering the new one β€” closing its files, dropping its locks and giving its connections back β€” and forgets everything resolved per account: the VOC chain, linked packages, and the subroutine libraries under LIB/.

The program continues either way, which is what UniData and UniVerse both do. What it held is gone, so re-opening is the program's own business:

IF LOGTO(DIVISION) THEN
   GOSUB OPEN.THE.FILES          ;* the handles from before are stale
END ELSE
   BEGIN CASE
      CASE STATUS() = 1 ; PRINT "no such account: ":DIVISION
      CASE STATUS() = 2 ; PRINT "commit or abort first"
   END CASE
END

A file variable held across a LOGTO says it is stale rather than reading a handle that has gone. EXECUTE "LOGTO ..." reaches the same place, so code ported from another MV system keeps working.

@LEVEL is 0 for a program run from the prompt or straight from Unix, and 1 for one an EXECUTE reached β€” the same numbering UniData and UniVerse use.

Use it when a routine must not talk to a terminal it does not own:

IF @LEVEL THEN
   RETURN                    ;* something is above me β€” ask nobody
END
CRT "Press RETURN " :
INPUT ANYTHING

This is a different question from @USER.TYPE, which says whether there is a terminal β€” a program three EXECUTEs deep still has one. An account's LOGIN wants both: it may be entered directly, or fired by a LOGTO from inside a running program whose screen and keyboard it would otherwise take over.

Terminal

KEYIN({ms}) one decoded keystroke; with ms, "" on timeout
MOUSE() last click as col : row : button : event β€” see the terminal chapter
@(col, row) cursor positioning string
@(-1..-18) screen codes β€” see the terminal chapter
COLOR(fg {, bg}) colour codes β€” see the terminal chapter

OS files

For moving records to and from the outside world (editors, git, diff).

OSREAD(path) whole OS file as a string; STATUS() 1 on failure
OSWRITE(data, path) write; returns 1/0
OSDELETE(path) remove; returns 1/0
TMPNAM() a fresh temp path
EDITFILE(path) run the external editor (unrestricted tier)

Export a record as attribute-per-line text and back:

LF = CHAR(10)
X = OSWRITE(CHANGE(REC, @AM, LF), PATH)     record -> text file
REC = CHANGE(OSREAD(PATH), LF, @AM)          text file -> record

Files, indexes, system

CREATEFILE(name {, "DIR"}) create a file (data + dictionary)
DELETEFILE(name) remove a file
FILELIST() the account's files, name @VM type per attribute
INDEXBUILD(fvar, item) (re)build an index; returns record count
INDEXDROP(fvar, item) drop an index
INDEXSELECT(fvar, item, value) form the select list from an index
MSGSEND(target, text {, class {, payload}}) send; returns ports reached, 0 none live, -1 refused, -2 no registry
MSGPENDING() messages waiting, -1 when no registry is running
MSGREAD() the next message as a record, "" when the inbox is empty
MSGMODE({m}) ON, OFF or DEFER; returns the previous mode
MSGDROPPED() messages lost to inbox overflow since logon
MSGWHO({scope}) the session roster: one session per attribute, port VM user VM account VM host VM line VM logged-on; empty when no registry is running
MSGSTATUS() state VM transport VM port VM prefix; state is up or unavailable

A message reads as an ordinary MV record β€” 1 version, 2 class + flags, 3–6 the sender's port, user, account and host, 7 sent, 8 text, 9 the payload. The payload is the attachment: file VM id VM program VM mode, with positions 5 and beyond free for an application. See Messaging. SEND and MESSAGE are statement spellings of MSGSEND and MSGMODE (Statements). | COMPILE(mode, src, out {, options}) | spawn the compiler (developer tier); mode "c", "exe", "shared"; options "NODEBUG", "STRIP", or both | | DOCTAG(tag) | inside an I-type dictionary item: extract "@tag value" from a record's comments |

Mapping and JSON

A mapping says how a record's attributes and associations become structured fields; it is an ordinary MV string (an @AM list of name<VM>attr<VM>conv<VM>type<VM>assoc fields) β€” hand-buildable, built field-by-field with MAPFIELD, or derived from a file's dictionary with MAPSPEC. JSONENCODE/JSONDECODE are built into the runtime β€” they were a package once, and are not one now, so they need no package and no configuration; MAPSPEC/MAPFIELD and the mapping engine live there too.

MAPFIELD(name, attr {, conv {, type {, assoc}}}) one mapping field; append with SPEC<-1> = MAPFIELD(...). Empty type is derived from conv (MD→NUMERIC, D→DATE, MT→TIME)
MAPSPEC(file) derive a mapping from file's dictionary (every D-item β†’ a field)
JSONENCODE(rec, mapping) the record as a JSON object: single attrs β†’ keys, each association β†’ an array of objects; NUMERIC unquoted, empty β†’ null/""
JSONDECODE(json, mapping) the inverse β€” map JSON back to a record (mapped attributes only)
SPEC = ""
SPEC<-1> = MAPFIELD("customer", 1)
SPEC<-1> = MAPFIELD("price", 7, "MD2", "", "items")   ;* type -> NUMERIC
REC = JSONDECODE(INBOUND, SPEC)                         ;* consume JSON on the fly
PRINT JSONENCODE(REC, MAPSPEC("ORDERS"))               ;* or map from a file's dict
⚠️ **GitHub.com Fallback** ⚠️