Arrays & Objects
OcaltQL has three structured data types: Array — a flat ordered list with numeric indices. Multiarray — an array of arrays for nested data. Object — a key-value structure with string keys. All three share consistent verbs for access, search, mutation, and conversion.
Arrays
An array is a flat ordered list. Indices are zero-based numbers. Items are appended sequentially and repack automatically when removed.
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER EMIT ?arr(0)
AFTER EMIT ?arr(3)
(* Output: alphadelta *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER COUNT ?arr SET ?num
AFTER EMIT ?num
(* Output: 4 *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER REMOVE KEY 0 FROM ?arr SET ?arr
(* ?arr is now: beta(0), gamma(1), delta(2) — repacked automatically *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER REVERSE ARRAY ?arr SET ?rev
AFTER EMIT ?rev(0)
(* Output: delta *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER SEARCH ARRAY ?arr FOR "beta" SET ?found
AFTER EMIT ?found(0)
(* Output: 1 — index of "beta" in the array *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER SEARCH ARRAY ?arr FOR "zulu" SET ?notfound
AFTER EMIT ?notfound
(* Output: null *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER JOIN ?arr WITH ", " SET ?csv
AFTER EMIT ?csv
(* Output: alpha, beta, gamma, delta *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER JOIN ?arr SET ?flat
AFTER EMIT ?flat
(* Output: alphabetagammadelta — no separator *)
JOIN ARRAY ?arr ... is an accepted alias for JOIN ?arr ... — both forms are equivalent.
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER SLICE ?arr FROM 1 TO 2 SET ?sub
AFTER EMIT ?sub(0)
(* Output: beta *)
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER APPEND "gamma" TO ?arr
AFTER APPEND "delta" TO ?arr
AFTER SORT ?arr SET ?sorted
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER NEW ARRAY SET ?more
AFTER APPEND "echo" TO ?more
AFTER MERGE ARRAY ?arr WITH ?more SET ?combined
NEW ARRAY SET ?dupes
AFTER APPEND "a" TO ?dupes
AFTER APPEND "b" TO ?dupes
AFTER APPEND "a" TO ?dupes
AFTER APPEND "c" TO ?dupes
AFTER APPEND "b" TO ?dupes
AFTER DEDUPLICATE ?dupes SET ?unique
AFTER EMIT ?unique(0)
AFTER EMIT ?unique(1)
AFTER EMIT ?unique(2)
AFTER COUNT ?unique SET ?count
AFTER EMIT ?count
(* Output: abc3 *)
Multiarrays
A multiarray is an array of arrays. It is used for nested or grid-like data structures. Rows are arrays appended to the multiarray. Both () notation and OF chain notation are valid for access.
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND 6 TO ?row2
AFTER APPEND ?row2 TO ?matrix
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND 6 TO ?row2
AFTER APPEND ?row2 TO ?matrix
AFTER EMIT ?matrix(0)(0)
AFTER EMIT ?matrix(1)(2)
(* Output: 16 *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND 6 TO ?row2
AFTER APPEND ?row2 TO ?matrix
AFTER EMIT 0 OF 0 OF ?matrix
AFTER EMIT 2 OF 1 OF ?matrix
(* Output: 16 — identical result to () notation *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND 6 TO ?row2
AFTER APPEND ?row2 TO ?matrix
AFTER COUNT ?matrix SET ?rows
AFTER EMIT ?rows
(* Output: 2 *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER COUNT ?matrix(0) SET ?cols
AFTER EMIT ?cols
(* Output: 3 *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER REMOVE KEY 1 FROM ?matrix(0) SET ?matrix
(* Row 0 is now 1, 3 — repacked to indices 0, 1 *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER REMOVE KEY 1 FROM 0 OF ?matrix SET ?matrix
(* Row 0 is now 1, 3 — identical result to () notation *)
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND ?row2 TO ?matrix
AFTER REVERSE ARRAY ?matrix SET ?revrows
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER REVERSE ARRAY ?matrix(0) SET ?matrix
NEW MULTIARRAY SET ?matrix
AFTER NEW ARRAY SET ?row1
AFTER APPEND 1 TO ?row1
AFTER APPEND 2 TO ?row1
AFTER APPEND 3 TO ?row1
AFTER APPEND ?row1 TO ?matrix
AFTER NEW ARRAY SET ?row2
AFTER APPEND 4 TO ?row2
AFTER APPEND 5 TO ?row2
AFTER APPEND 6 TO ?row2
AFTER APPEND ?row2 TO ?matrix
AFTER FLATTEN ?matrix SET ?flat
AFTER EMIT ?flat(0)
AFTER EMIT ?flat(3)
(* Output: 14 — first element of row 0, first element of row 1 *)
Objects
An object holds key-value pairs with string keys. Properties are set with SET ?obj("key") AS value. Both () notation and OF chain notation are valid throughout.
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("age") AS 30
AFTER SET ?user("active") AS true
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER EMIT ?user("name")
(* Output: Alice *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER EMIT "name" OF ?user
(* Output: Alice — identical result to () notation *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("age") AS 30
AFTER SET ?user("active") AS true
AFTER COUNT ?user SET ?propcount
AFTER EMIT ?propcount
(* Output: 3 *)
NEW OBJECT SET ?user
AFTER SET ?user("age") AS 30
AFTER SET ?user("age") AS 31
NEW OBJECT SET ?user
AFTER SET ?user("age") AS 30
AFTER SET "age" OF ?user AS 32
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("active") AS true
AFTER REMOVE KEY "active" FROM ?user SET ?user
(* "active" is gone entirely — no null, no gap *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("age") AS 32
AFTER KEYS ?user SET ?props
AFTER EMIT ?props(0)
AFTER EMIT ?props(1)
(* Output: nameage *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("age") AS 32
AFTER VALUES ?user SET ?vals
AFTER EMIT ?vals(0)
AFTER EMIT ?vals(1)
(* Output: Alice32 *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER APPEND "role" AS "admin" TO ?user
(* ?user now has "role" = "admin" *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SEARCH OBJECT ?user FOR "Alice" SET ?foundkey
AFTER EMIT ?foundkey(0)
(* Output: "name" *)
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SEARCH OBJECT ?user FOR "zulu" SET ?nokey
AFTER EMIT ?nokey
(* Output: null *)
NEW OBJECT SET ?defaults
AFTER SET ?defaults("theme") AS "dark"
AFTER SET ?defaults("lang") AS "en"
AFTER NEW OBJECT SET ?prefs
AFTER SET ?prefs("lang") AS "af"
AFTER MERGE OBJECT ?defaults WITH ?prefs SET ?config
(* ?config("theme") = "dark" *)
(* ?config("lang") = "af" — right side wins *)
Nested Objects
Object properties can hold other objects. Nested access, mutation, and removal all follow the same two-notation pattern.
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER SET ?addr("country") AS "South Africa"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER EMIT ?user("address")("city")
(* Output: Johannesburg *)
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER EMIT "city" OF "address" OF ?user
(* Output: Johannesburg — identical result to () notation *)
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER SET ?user("address")("city") AS "Cape Town"
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER SET "city" OF "address" OF ?user AS "Cape Town"
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER SET ?addr("country") AS "South Africa"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER REMOVE KEY "country" FROM ?user("address") SET ?user
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER SET ?addr("country") AS "South Africa"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER KEYS ?user("address") SET ?addrprops
Copying via Variable-to-Variable Assignment
The SET ... AS and VARIABLE ... SET forms documented on Variables SET | UNSET work identically on arrays and objects, not just plain values.
NEW ARRAY SET ?arr1
AFTER APPEND "alpha" TO ?arr1
AFTER SET ?arr2 AS ?arr1
AFTER EMIT ?arr2(0)
NEW ARRAY SET ?arr1
AFTER APPEND "alpha" TO ?arr1
AFTER VARIABLE ?arr1 SET ?arr2
AFTER EMIT ?arr2(0)
NEW OBJECT SET ?user1
AFTER SET ?user1("name") AS "Alice"
AFTER SET ?user2 AS ?user1
AFTER EMIT ?user2("name")
NEW OBJECT SET ?user1
AFTER SET ?user1("name") AS "Alice"
AFTER VARIABLE ?user1 SET ?user2
AFTER EMIT ?user2("name")
Object Literals — Inline Creation
An object can be built in one statement instead of a bare NEW OBJECT followed by separate SET calls. Square brackets directly after NEW OBJECT hold the key/value pairs, joined with AND.
NEW OBJECT ["name" AS "Alice" AND "age" AS 30] SET ?user
AFTER EMIT ?user("name")
AFTER EMIT " / "
AFTER EMIT ?user("age")
(* Output: Alice / 30 *)
A nested object as a value keeps the NEW OBJECT keyword and its own brackets — written directly after AS, with no extra wrapping needed.
NEW OBJECT ["name" AS "Alice" AND "address" AS NEW OBJECT ["city" AS "Johannesburg"]] SET ?user
AFTER EMIT ?user("address")("city")
(* Output: Johannesburg *)
A bare NEW OBJECT with no brackets still creates an empty object, exactly as before — keys can be added afterward with SET.
NEW OBJECT SET ?empty
AFTER SET ?empty("x") AS "y"
AFTER EMIT ?empty("x")
(* Output: y *)
Native Object Storage
Internally, an object’s canonical stored form is valid OcaltQL source text — not a JSON-style format. An object written to storage is written as the literal NEW OBJECT [...] body wrapped in one more set of brackets — [NEW OBJECT [...]] — with no SET, since the value is only captured when something actually consumes it. This means reading a stored object back is not a separate deserialization step — the stored text is simply re-evaluated as an inline expression through the same parser that runs any other script.
This applies uniformly regardless of an object’s origin. An object parsed from JSON, YAML, XML, or TOML via PARSE becomes the exact same underlying object type as one built with NEW OBJECT — there is no lingering distinction based on source format. Format only matters again at the moment of serialization, using CAST ?obj AS OQLOBJECT or CAST ?obj AS JSON to choose the output form explicitly. See Convert & Cast for the full CAST and PARSE reference.
FOREACH — Iteration
FOREACH iterates over arrays and objects. It exposes the value alone, or both the key and value together.
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER FOREACH ?arr SET ?value
OPEN
EMIT ?value
CLOSE
NEW ARRAY SET ?arr
AFTER APPEND "alpha" TO ?arr
AFTER APPEND "beta" TO ?arr
AFTER FOREACH ?arr SET ?key AS ?value
OPEN
EMIT ?key & ": " & ?value
CLOSE
NEW OBJECT SET ?user
AFTER SET ?user("name") AS "Alice"
AFTER SET ?user("age") AS 30
AFTER FOREACH ?user SET ?key AS ?value
OPEN
EMIT ?key & " = " & ?value
CLOSE
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER SET ?addr("country") AS "South Africa"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER FOREACH ?user("address") SET ?key AS ?value
OPEN
EMIT ?key & ": " & ?value
CLOSE
NEW OBJECT SET ?addr
AFTER SET ?addr("city") AS "Johannesburg"
AFTER SET ?addr("country") AS "South Africa"
AFTER NEW OBJECT SET ?user
AFTER SET ?user("address") AS ?addr
AFTER FOREACH "address" OF ?user SET ?key AS ?value
OPEN
EMIT ?key & ": " & ?value
CLOSE