data-exchange
Data Exchange
json — JSON Parsing and Generation
json stores a JSON tree in a heap-allocated node pool (json-pool), supporting nested arrays/objects, string/number/bool/null scalars, escape sequences and exponent-format numbers. The pool is built from dynamic []vec buffers, so there is no fixed capacity cap — total node count and children-per-node are bounded only by available memory. The high-level json struct wraps the pool plus a root index; its pool field is annotated #{inline=false} (a pointer field), which lets json be safely returned and copied by value.
; Parse: returns ?json (ok = value, err = failure with a specific reason)
j = json.parse('{"name":"Alice","age":30,"items":[1,2,3],"active":true}')
j: {
ok -> {
name, found = it.get-str('name') ; scalars: get-str / get-num / get-bool / get-i64
age, ok2 = it.get-i64('age')
items = it.get('items') ; child node, returns ?json
s = it.stringify() ; serialize back to string
}
err(e) -> print(e)
}
; Build: start from an empty (null) json, then set / arr-push
j2 = json.new()
j2.set-str('key', 'value')
j2.set-i64('count', 3)
j2.arr-push-str('a')
print(j2.stringify())
High-level API (methods on json):
json.new()— create an empty (null)jsonjson.parse(s)— parse, returns?json; theerrbranch carries the reason (empty input/parse error/trailing characters)j.stringify()— serialize to a string- Read by key:
j.get-str(key)/j.get-num(key)/j.get-bool(key)/j.get-i64(key)(each returns(val, ok));j.get(key)returns a child?json - Direct root scalar access (when root is a scalar):
j.str()/j.num()/j.bool()/j.i64()/j.str-val() - Write:
j.set-str(key,val)/j.set-num/j.set-i64/j.set-bool/j.set-null/j.set-key(key, val json) - Array:
j.arr-get(i)/j.arr-len()/j.arr-push(val json)/j.arr-push-str/j.arr-push-num/j.arr-push-bool - Object enumeration:
j.obj-len()/j.obj-key(i)/j.obj-keys-str()/j.delete-key(key) - Type checks:
j.kind()andj.is-null()/j.is-obj()/j.is-arr()/j.is-str()/j.is-num()/j.is-bool()
Pool capacity (no fixed cap). The pool grows automatically on heap []vec:
- Total nodes and children-per-node have no hard limit (only memory-bound). The old
JSON-MAX-NODES = 128/JSON-MAX-CHILDREN = 32caps are gone. json.parsereturnserronly on a genuine syntax error — there is no longer a "pool exhausted" branch.j.set*/j.arr-push*returnfalseonly on a kind mismatch.j.overflowed()is kept for backward compatibility and now always returnsfalse.
jsoncopy semantics —#{inline=false}. Becausepoolis a pointer field, a child handle returned byj.get/j.arr-getdeep-copies the whole pool: the handle is an independent snapshot of the parent, and writes through it do not propagate back. Also, matching a?jsonoption consumes its value — matching the same option a second time falls into thenilbranch. To reuse one parsed result, finish the work inside a singlematch(useit) or bind it once into a plainjsonlocal.
Low-level API (json-pool / json-value) — operate by node index; the pool is a single-level struct so it is safe to return/copy by value:
p = json.new-pool(), orp = json-pool {}thenp.init()(setsnodes/strs/ec/ek/entowith-len(0))idx = p.alloc()— allocate a node (heap vec auto-grows);p.add-child(node-idx, child-idx, key)— append a child via theec/ek/enedge linked-listnode-idx, next-pos, ok = p.parse(s, pos)— parse one value fromposp.get-key(obj-idx, key);p.arr-get(arr-idx, i);p.arr-len(arr-idx)p.get-kind(idx)/p.get-str(idx)/p.get-num(idx)/p.get-bool(idx)p.set-key(obj-idx, key, val-idx);p.copy-tree(src, idx)(recursive deep copy)p.stringify(node-idx)— serialize a specific node
toml — TOML 1.0 Parsing and Generation
toml stores a document in toml-doc and supports comments, bare/quoted/dotted keys, tables, array-of-tables, basic/literal/multiline strings, decimal/hex/octal/binary integers, floats, booleans, date-time literals, arrays, and inline tables. Values without a dedicated Nolang type remain available as their original TOML literals.
cfg = toml.parse('title = "Nolang"\n[server]\nport = 8080\n[[server.backends]]\nname = "local"')
doc = toml.new()
doc.put('name', '"nolang"')
doc.put('server.port', '8080')
doc.put('enabled', 'true')
raw = toml.get(doc, 'server.port')
text = toml.stringify(doc)
Common functions:
toml.new()— create an empty documenttoml.parse(text)— parse and return?toml-docdoc.put(key, raw-value)— validate and insert/update a TOML valuetoml.get(doc, key)— retrieve the raw valuetoml.get-str,toml.get-i64,toml.get-f64,toml.get-bool— retrieve scalar valuestoml.stringify(doc)— serialize the document
The document limits are 4 keys, 2 ordinary tables, and 2 array-of-tables; keys are limited to 32 bytes and values to 128 bytes.
yaml — YAML 1.2 Parsing and Generation
yaml keeps a YAML 1.2 core-schema document tree in a node pool (yaml-pool). It supports block mappings, block sequences, flow collections ([...] / {...}), single-quoted / double-quoted / plain scalars, block scalars (| / > with - / + chomping), comments, document markers (--- / ...) and multi-document streams, anchors (&) and aliases (*), merge keys <<, explicit keys (? k), core-schema type inference (null / bool / int / float / .inf / .nan), dotted-path lookup, and JSON / YAML serialization.
doc ?yaml = yaml.parse('name: Alice\nserver:\n port: 8080\ntags:\n - a\n - b\n')
doc: {
nil -> print('nil')
err -> print(it)
-> {
name, ok = it.find-str('name')
port, ok2 = it.find-i64('server.port')
print(name)
print(port.to-str())
print(it.to-json())
print(it.to-yaml())
}
}
Common functions and methods:
yaml.parse(text)/yaml.parse-all(text)— parse, returning?yaml(theerrbranch carries aline L, column Cposition)yaml.valid-of(text)/yaml.error-of(text)— validate only, without building a documentyaml.strip-bom(text)— strip a UTF-8 BOMdoc-count()/doc(i)— multi-document accesskind(),is-null(),is-bool(),is-int(),is-float(),is-nan(),is-str(),is-seq(),is-map(),len(),text(),tag(),anchor(),line-of()— node informationstr(),i64(),f64(),bool(),scalar-text()— value access; all return(value, ok)at(i),value(i),key(i),key-node(i),get(key),find(path)— child nodes and keysget-str/get-i64/get-f64/get-bool,find-str/find-i64/find-f64/find-bool— typed shortcutsto-json(),to-json-all(),to-yaml(),stringify(),source-of()— serialization
Kind constants: YAML-KIND-NULL / BOOL / INT / FLOAT / STR / SEQ / MAP (0–6). Scalar-style constants: YAML-STYLE-PLAIN / SINGLE / DOUBLE / LITERAL / FOLDED / ALIAS (0–5), plus YAML-STYLE-BLOCK / FLOW (6–7).
Caveat 1: child views are returned as
(out yaml, ok bool), not?yaml. This is an API-style choice, not an ownership limitation — a child sharing the parent's node pool is perfectly safe. Prefer the path helpers (find-str/find-i64/ ...) for lookups.Caveat 2: inside nested matches
itis restored per level. An inner match's arm rebindsitto its own subject (the arm body must see the inner value), but as soon as that arm endsitreverts to the enclosing arm's subject — so this is safe:d ?yaml = yaml.parse(src)d: {nil -> print('nil')err -> print(it)-> {print(it.find-str('a')) ; outer it = the documentv ?yaml = it.at(0)v: {nil -> print('none')err -> print('err')-> print(it.find-str('b')) ; inner it = the child}print(it.find-str('c')) ; ✅ it is the document again}}The one exception is the outermost match: after it ends
itstill holds the last arm's value (it is not restored), so never rely onitoutside a match. To use the value across levels, still copy it into a named local (doc = it) or use a destructuring bindingok(v) -> ....Caveat 3:
.nanparses to a real NaN —f64()returns NaN andx != xis true. JSON has no NaN, soto-json()emitsnullfor it;to-yaml()emits.nan.