# 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` | | `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: ```basic 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: ```basic 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 `EXECUTE`s 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](deployment.md#sending-somebody-a-record). `SEND` and `MESSAGE` are statement spellings of `MSGSEND` and `MSGMODE` ([Statements](statements.md#send--message)). | `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 `nameattrconvtypeassoc` 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 ```