Standard Library
ZayneScript registers built-in modules under the
core: import prefix (names are case-sensitive:
core:date, core:array,
core:regex, core:string, …). They load on
demand — import only what you need. The repo
also includes lib/http.zs, a thin wrapper re-exporting the
HTTP client and common constants for use via lib:http.
core:array — Array class
Import with:
import { Array } from "core:array";
Array methods are available directly on every array literal via the
prototype. You do not need to explicitly import
core:array to use the built-in methods — they are always
present. Import it only when you need the Array class
itself (e.g. to add static helpers at runtime).
push
Appends value to the end of the array. Returns
null.
const arr = [1, 2];
arr.push(3);
println(arr); // [1, 2, 3]
pop
Removes and returns the last element. Throws an error if the array is empty.
const last = arr.pop();
println(last); // 3
println(arr); // [1, 2]
length
Returns the number of elements in the array.
println([10, 20, 30].length()); // 3
each
Calls callback(value, index) for every element and
returns a new array containing each return value. Analogous to
Array.prototype.map in JavaScript.
const nums = [1, 2, 3, 4, 5];
const doubled = nums.each(fn(v, i) { return v * 2; });
println(doubled); // [2, 4, 6, 8, 10]
// println prints each argument: element, then index
nums.each(println);
// 1 0
// 2 1
// 3 2 ...
keep
Returns a new array containing only the elements for which
callback returns a truthy value. Analogous to
Array.prototype.filter.
const evens = nums.keep(fn(v, i) { return v % 2 == 0; });
println(evens); // [2, 4]
Adding static helpers at runtime
Because Array is a first-class class value you can attach
arbitrary static functions to it:
import { Array } from "core:array";
Array.sum = fn(arr) {
local total = 0;
arr.each(fn(i, e) { total += e; });
return total;
};
println(Array.sum([1, 2, 3, 4, 5])); // 15
core:crypto — detailed API reference
Import with:
import { Crypto } from "core:crypto";
Native implementation lives in src/core/crypto.c. The
module mirrors a CryptoJS-style aggregate API and returns
WordArray objects for all binary outputs.
Overview
- Hashing: MD5, SHA-1, SHA-2 family, SHA-3 family, RIPEMD-160.
- HMAC: MD5/SHA1/SHA256/SHA384/SHA512/RIPEMD160.
- KDF: PBKDF2 and EVP-style MD5 KDF (
EvpKDF). - Ciphers: AES-128 ECB/CBC (PKCS#7) and RC4.
- Encoders: Hex, Utf8, Base64, Base64Url.
Accepted input types
Most crypto functions accept message/key inputs as:
string, byte Array (0..255),
WordArray, object with bytes field, or
null (treated as empty input). Output is usually
WordArray.
AES sample (real-world flow)
Typical secure-message flow: derive key/IV from passphrase + salt, encrypt with AES-CBC, transmit as Base64, then decode/decrypt.
import { println } from "core:io";
import { Crypto } from "core:crypto";
const passphrase = "correct horse battery staple";
const salt = "NaCl-1234"; // demo only; use per-message random salt in real apps
const msg = "confidential payload";
// 1) Derive 16-byte key + 16-byte IV
const kivHex = Crypto.EvpKDF(passphrase, salt, 1000, 16, 16).toString(null);
const key = Crypto.enc.Hex.parse(kivHex.substring(0, 32));
const iv = Crypto.enc.Hex.parse(kivHex.substring(32, 64));
// 2) Encrypt
const ciphertextWa = Crypto.AesCbcEncrypt(key, iv, msg);
// 3) Transport-friendly encoding
const transport = ciphertextWa.toString(Crypto.enc.Base64);
println(transport);
// 4) Decode + decrypt (receiver side)
const decodedWa = Crypto.enc.Base64.parse(transport);
const plaintext = Crypto.AesCbcDecrypt(key, iv, decodedWa).toString(Crypto.enc.Utf8);
println(plaintext); // confidential payload
Cipher helpers
AES-128 ECB with PKCS#7 padding. Key must be 16 bytes.
AES-128 CBC with PKCS#7 padding. Key and IV must both be 16 bytes.
RC4 stream cipher. Use same key and dropN to decrypt.
const key = Crypto.enc.Utf8.parse("0123456789abcdef");
const iv = Crypto.enc.Hex.parse("000102030405060708090a0b0c0d0e0f");
const pt = "hello-aes-cbc";
const ct = Crypto.AesCbcEncrypt(key, iv, pt);
const dec = Crypto.AesCbcDecrypt(key, iv, ct).toString(Crypto.enc.Utf8);
println(dec); // hello-aes-cbc
const rc4Ct = Crypto.RC4("secret-key", "hello", 768);
const rc4Pt = Crypto.RC4("secret-key", rc4Ct, 768).toString(Crypto.enc.Utf8);
println(rc4Pt); // hello
Common errors and constraints
- PBKDF2 requires 5 args; invalid
prfis rejected. - AES helpers require 16-byte key (and 16-byte IV for CBC).
- Invalid encoded input for
enc.*.parsereturns error. - UTF-8 stringify fails for invalid byte sequences.
Complete example
import { println } from "core:io";
import { Crypto } from "core:crypto";
const msg = "payload";
// digest + hmac
println(Crypto.SHA256(msg).toString(null));
println(Crypto.HmacSHA256(msg, "k").toString(null));
// derive key+iv then encrypt
const kiv = Crypto.EvpKDF("password", "12345678", 1000, 16, 16).toString(null);
const key = Crypto.enc.Hex.parse(kiv.substring(0, 32));
const iv = Crypto.enc.Hex.parse(kiv.substring(32, 64));
const c = Crypto.AesCbcEncrypt(key, iv, msg);
println(Crypto.AesCbcDecrypt(key, iv, c).toString(Crypto.enc.Utf8));
Encoders
Hex encode/decode. Invalid hex parse returns error.
UTF-8 conversion. Invalid UTF-8 decode returns error.
RFC 4648 Base64 with padding.
RFC 4648 base64url (URL-safe alphabet).
const hello = Crypto.enc.Utf8.parse("Hello");
println(Crypto.enc.Hex.stringify(hello)); // 48656c6c6f
println(Crypto.enc.Base64.stringify(hello)); // SGVsbG8=
println(Crypto.enc.Base64Url.stringify(hello)); // SGVsbG8
println(Crypto.enc.Utf8.stringify(hello)); // Hello
Hash functions
All hash functions take one argument and return WordArray.
println(Crypto.SHA512("abc").toString(null));
println(Crypto.SHA3_256("").toString(null));
println(Crypto.RIPEMD160("").toString(null));
HMAC functions
All HMAC functions take (message, key) and return WordArray.
const mac = Crypto.HmacSHA256(
"The quick brown fox jumps over the lazy dog",
"key"
);
println(mac.toString(null));
Key derivation functions
iterations must be positive. dkLen range is
1..65536. prf values:
0 = HMAC-SHA256, 1 = HMAC-SHA1,
2 = HMAC-MD5.
OpenSSL-style EVP_BytesToKey MD5 chain. Returns one WordArray
containing key || iv. keyLen/ivLen
must be within implementation limits.
// PBKDF2 example
const dk = Crypto.PBKDF2("password", "salt", 10000, 32, 0);
println(dk.toString(null));
// EvpKDF example: first 16 bytes key, next 16 bytes iv
const kiv = Crypto.EvpKDF("password", "12345678", 1000, 16, 16);
const key = Crypto.enc.Hex.parse(kiv.toString(null).substring(0, 32));
const iv = Crypto.enc.Hex.parse(kiv.toString(null).substring(32, 64));
Use-case scenarios
- API request signing: hash body +
HmacSHA256for tamper detection. - Password-derived encryption:
PBKDF2/EvpKDFthen AES-CBC for data-at-rest. - Token fingerprinting: store SHA-256 digests instead of raw secret tokens.
- Binary transport conversion: convert payloads via
enc.Base64/enc.Base64Url.
Validation
./dist/zscript.exe --run tests/test_crypto.zs
WordArray and conversion
Without encoder (or with null) returns lowercase
hex. Encoder options are Crypto.enc.Hex,
Crypto.enc.Utf8, Crypto.enc.Base64, and
Crypto.enc.Base64Url.
const wa = Crypto.SHA256("abc");
println(wa.toString(null)); // hex
println(wa.toString(Crypto.enc.Base64)); // base64
core:date — Date and Time
Import with:
import { Date } from "core:date";
// Or wildcard:
import "core:date";
Constructor
Creates a new Date instance.
• No argument — current date and time.
• Number — Unix timestamp in milliseconds.
• String — parsed date. Supported formats:
YYYY-MM-DD, YYYY-MM-DD HH:mm:ss,
YYYY-MM-DDTHH:mm:ss,
YYYY/MM/DD HH:mm:ss.
• Components — year (full), month (0-based Jan=0),
day (default 1), hours, minutes, seconds, milliseconds.
const now = new Date();
println(now.toString()); // e.g. Fri Apr 04 2026 14:30:00
const d1 = new Date("2023-12-25");
println(d1.getFullYear()); // 2023
println(d1.getMonth()); // 11 (0-based)
println(d1.getDate()); // 25
const d2 = new Date(2024, 0, 1, 12, 30, 45);
// year=2024, month=January(0), day=1, 12:30:45
const d3 = new Date(1705449600000); // from timestamp ms
Instance Getters
| Method | Returns | Notes |
|---|---|---|
getFullYear() |
int |
Full 4-digit year (e.g. 2026) |
getMonth() |
int |
Month 0–11 (0 = January) |
getDate() |
int |
Day of month 1–31 |
getDay() |
int |
Day of week 0–6 (0 = Sunday) |
getHours() |
int |
Hours 0–23 |
getMinutes() |
int |
Minutes 0–59 |
getSeconds() |
int |
Seconds 0–59 |
const d = new Date("2026-04-04T14:30:00");
println(d.getFullYear()); // 2026
println(d.getMonth()); // 3 (April = index 3)
println(d.getDate()); // 4
println(d.getDay()); // 6 (Saturday)
println(d.getHours()); // 14
println(d.getMinutes()); // 30
println(d.getSeconds()); // 0
getTime
Returns the internal Unix timestamp in milliseconds.
println(new Date().getTime()); // e.g. 1743778200000
toString
Returns a human-readable string representation of the date, e.g.
"Sat Apr 04 2026 14:30:00".
println(new Date().toString());
core:io — Input / Output
Import with:
import { print, println, scan, parseNum, format,
clearScreen, setColor, decompile, File } from "core:io";
Writes each argument (space-separated) to stdout without a trailing newline. Accepts any number of arguments of any type.
print("x =", 42); // x = 42 (no newline)
println
Like print but appends a newline after all arguments.
Calling with no arguments prints a blank line.
println("Hello", "World"); // Hello World
println(); // (blank line)
scan
Reads one line from stdin and returns it as a string (newline
stripped). If prompt is provided it is printed first
without a trailing newline. Returns null on EOF.
const name = scan("Enter your name: ");
println("Hello,", name);
parseNum
Parses str into a number. Returns an integer when the
parsed value has no fractional part, otherwise a float.
const n = parseNum(scan("Enter a number: "));
println(n * 2);
format
Returns a new string by replacing each exact {} token in template with the next argument in order. Only empty-brace placeholders are recognized.
const msg = format("Hello, {}! You are {} years old.", "Alice", 30);
println(msg); // Hello, Alice! You are 30 years old.
// Fewer args than placeholders: remaining {} are kept
println(format("{} + {} = {}", 1, 2)); // 1 + 2 = {}
// Extra args are ignored
println(format("x={}", 42, 99)); // x=42
// Specifiers like {0}, %s, etc. are not supported
println(format("name={0}, value=%s, raw={}", "Alice")); // name={0}, value=%s, raw=Alice
format only replaces literal
{} pairs. It does not implement positional or typed
format specifiers.
clearScreen
Clears the terminal screen using ANSI escape codes.
clearScreen();
setColor
Sets the terminal foreground (and optionally background) colour
using ANSI colour codes. Calling with no arguments resets to default
colours. Typical foreground codes: 31 red,
32 green, 33 yellow, 34 blue,
0 reset.
setColor(32); // green text
println("Success!");
setColor(); // reset
decompile
Decompiles a compiled user function back into a human-readable source representation. Useful for debugging or inspecting generated bytecode.
fn add(a, b) { return a + b; }
println(decompile(add));
File
Text-oriented file I/O similar to Python. Construct with
new File(path, mode?) (the class constructor is
init). path and mode are strings;
mode defaults to "r" and follows the same
conventions as C fopen (for example "w",
"a", "r+", "rb").
import { File } from "core:io";
const out = new File("notes.txt", "w");
out.writelines(["first line\n", "second\n"]);
out.close();
const inp = new File("notes.txt", "r");
println(inp.read()); // full remainder of file
inp.close();
Reading text
read() with no argument (or a negative size) reads from the
current position until EOF. read(0) returns an empty string
without advancing. read(n) with n > 0 reads at
most that many bytes and returns a string (still text mode).
import { File } from "core:io";
const f = new File("data.txt", "r");
const whole = f.read(); // string: rest of file
f.close();
const g = new File("data.txt", "r");
const head = g.read(4); // first four bytes as text
const line = g.readline(); // next line, including "\n" if present
g.close();
const h = new File("data.txt", "r");
const lines = h.readlines(); // Array of strings, one per line
h.close();
Writing text
In text mode, write accepts any value: non-strings are turned
into UTF-8 text before writing. writelines takes an array and
writes each element in order with no extra separators between elements.
import { File } from "core:io";
const w = new File("out.txt", "w");
w.write("Hello\n");
w.writelines(["line2\n", "line3\n"]);
w.flush();
w.close();
Binary mode and Blob
Open with a fopen-style mode that includes
"b" (for example "rb", "wb",
"ab"). Then read() / read(n) return
a Blob instead of a string. readline and
readlines are not available on binary files. Use
import { Blob } from "core:blob" to build byte payloads.
import { File } from "core:io";
import { Blob } from "core:blob";
const bin = new File("app.bin", "wb");
bin.write(new Blob([0x48, 0x69])); // raw bytes, not "Hi" as text
bin.close();
const r = new File("app.bin", "rb");
const b = r.read(); // Blob
r.close();
// Optional: reopen as text to decode the same bytes as UTF-8
const t = new File("app.bin", "r");
println(t.read()); // "Hi"
t.close();
In binary mode, write and writelines only accept
string or Blob (each array element must be one of
those in writelines).
import { File } from "core:io";
import { Blob } from "core:blob";
const w = new File("parts.bin", "wb");
w.writelines([new Blob([0x01, 0x02]), new Blob([0x03])]);
w.close();
Position: tell and seek
Use a read/write mode such as "r+" when you need both
read and write on the same handle.
seek(offset, whence) uses whence
0 = start of file, 1 = current position,
2 = end; omit whence to seek from the start.
import { File } from "core:io";
const f = new File("notes.txt", "r+");
println(f.tell()); // 0
f.seek(6, 0); // six bytes from start
println(f.read(1)); // one character at that offset
f.seek(0, 2); // end of file
println(f.tell()); // file length in bytes
f.close();
Mode helpers
After new File(...), readable() and
writable() reflect whether the mode allows reading or writing
(for example "w" is writable but not readable;
"r+" is both). isClosed() becomes true after
close(). If fopen fails (missing path, permission
denied, and so on), the constructor raises an error.
import { File } from "core:io";
const w = new File("tmp.txt", "w");
println(w.writable(), w.readable()); // true, false
w.close();
println(w.isClosed()); // true
| Constructor / methods | Description |
|---|---|
Constructor: init(path, mode?) (invoked by
new File(path, mode?))
|
Opens the file. On failure raises an error (for example
fopen: … from the host C library).
|
read(size?) |
Without size, or with a negative size, reads until
EOF. With size === 0, returns an empty string. With
size > 0, reads up to that many bytes. In text
mode returns a string; in binary mode ("b" in
mode) returns a Blob.
|
readline() |
Next line including the newline when present; text modes only
(not "rb").
|
readlines() |
Returns an Array of lines; text modes only.
|
write(value) |
A Blob is always written as its raw byte payload
(fwrite). Any other value is converted to a UTF-8
string first. In binary mode ("b" in
mode), only string or
Blob may be passed. Requires a writable mode.
|
writelines(lines) |
Writes each array element in order with no extra separators
(Python-style). A Blob element writes its raw
payload; other values use string conversion. In binary mode,
every element must be a string or
Blob. Requires a writable mode.
|
flush() / close() |
fflush / fclose. After
close(), other operations report a closed file.
|
tell() |
Current byte offset as a number. |
seek(offset, whence?) |
whence: 0 = start of file,
1 = current position, 2 = end (same as
C SEEK_SET / SEEK_CUR /
SEEK_END). Defaults to 0.
|
readable() / writable() /
isClosed()
|
Boolean helpers derived from the open mode and handle state. |
Regression coverage lives in
tests/test_fileio.zs.
core:math — Mathematics
Import with:
// Named imports
import { sqrt, pow, sin, cos, pi, e } from "core:math";
// Or wildcard — module bound as `math`
import "core:math";
println(math.sqrt(16)); // 4
Single-argument functions
Each accepts one numeric argument and returns a num:
| Function | Description |
|---|---|
abs(x) |
Absolute value |
acos(x) |
Arc cosine (radians) |
asin(x) |
Arc sine (radians) |
atan(x) |
Arc tangent (radians) |
ceil(x) |
Round up to nearest integer |
cos(x) |
Cosine (radians) |
cosh(x) |
Hyperbolic cosine |
exp(x) |
ex |
floor(x) |
Round down to nearest integer |
log(x) |
Natural logarithm |
log10(x) |
Base-10 logarithm |
round(x) |
Round to nearest integer |
sin(x) |
Sine (radians) |
sinh(x) |
Hyperbolic sine |
sqrt(x) |
Square root |
tan(x) |
Tangent (radians) |
tanh(x) |
Hyperbolic tangent |
Two-argument functions
| Function | Description |
|---|---|
atan2(y, x) |
Arc tangent of y/x (full quadrant) |
hypot(x, y) |
√(x² + y²) |
max(a, b) |
Larger of two values |
min(a, b) |
Smaller of two values |
pow(base, exp) |
baseexp |
Constants
| Name | Value |
|---|---|
pi |
3.14159265358979323846 |
e |
2.71828182845904523536 |
import { sin, cos, sqrt, pow, pi, e } from "core:math";
println(sqrt(2)); // 1.4142135623730951
println(pow(2, 10)); // 1024
println(sin(pi / 2)); // 1
println(e); // 2.718281828459045
core:mongoose — HTTP Server
Import with:
import { Server, request } from "core:mongoose";
core:mongoose wraps the
Mongoose
embedded networking library to give ZayneScript an Express.js-style
HTTP server and a small async HTTP client. Create a
Server, register routes, and call
listen() — the event loop runs until
close() is called. Outbound requests use
request() and return a Promise.
Overview
Import symbols for both the server and client:
import { Server, request } from "core:mongoose";
request — HTTP client
Performs a non-blocking HTTP request using the interpreter's shared
Mongoose manager. Resolves with an object
{ status, statusText, headers, body } — on failure the
promise is rejected with an error string.
Optional options fields:
-
method— verb string (default"GET"); upper-cased internally. -
headers— plain object of header names to values (merged into the request). -
body— string for raw text; an object value is stringified and sent as JSON withContent-Type: application/jsonif not already set.
If the response has Content-Type: application/json,
body is parsed into objects/arrays/values; otherwise it
remains a string.
import { println } from "core:io";
import { request } from "core:mongoose";
request("http://127.0.0.1:8080/api/ping", {
method: "GET"
}).then(fn(res) {
println(res.status, res.body);
}).error(fn(e) {
println("failed:", e);
});
For named constants and a re-export of request, see
lib:http below. For parsing
args() into flags and positionals, see
lib:argparser.
Server class
constructor
Creates a new HTTP server instance. No arguments required.
import { Server } from "core:mongoose";
const app = new Server();
Routing
Route handlers receive (req, res) and are matched in
registration order. The path is matched with Mongoose's glob patterns
— use * as a wildcard segment.
Matches any HTTP method.
app.get("/hello", fn(req, res) {
res.send("Hello World!");
});
app.post("/users", fn(req, res) {
res.status(201).json({ created: true });
});
Wildcard captures
Wildcards in the path are captured and exposed on
req.params as numeric keys ("0",
"1", …).
app.get("/users/*", fn(req, res) {
const id = req.params["0"]; // e.g. "42" for /users/42
res.json({ id: id });
});
Multiple wildcards
If your route contains more than one wildcard, each is captured in order:
app.get("/users/*/posts/*", fn(req, res) {
const userId = req.params["0"]; // e.g. "42" for /users/42/posts/99
const postId = req.params["1"]; // e.g. "99" for /users/42/posts/99
res.json({ user: userId, post: postId });
});
Query parameters
The req.query property contains the raw query string (e.g. "name=Alice&age=30" for /users?name=Alice&age=30). To access query parameters as an object, you can parse it manually:
// Example: GET /search?term=cat&page=2
app.get("/search", fn(req, res) {
// Parse query string into an object
const params = {};
req.query.split("&").each(fn(pair) {
const kv = pair.split("=");
if (kv.length == 2) {
params[kv[0]] = kv[1];
}
});
res.json(params); // { term: "cat", page: "2" }
});
// For more robust parsing (decoding, missing values), see core:string helpers.
req.params contains wildcard route captures (from * in the path), while req.query is the raw query string after ? in the URL. They are separate.
Request object
Available inside every route handler as the first argument.
| Property | Type | Description |
|---|---|---|
req.method |
string |
HTTP verb in upper-case — "GET",
"POST", etc.
|
req.path |
string | Request URI path (without query string). |
req.url |
string | Same as req.path (full URI). |
req.query |
string |
Raw query string, e.g. "name=Alice&age=30".
|
req.body |
string | Raw request body as a string. |
req.headers |
object | All request headers as a plain object with lower-cased keys. |
req.params |
object |
Wildcard captures from the route path, keyed "0" …
"3".
|
Response object
Available inside every route handler as the second argument.
res.send(body)
Sends a plain-text response with the current status code (default
200) and any headers set via
res.setHeader().
res.json(value)
Serialises value to a string and sends it with
Content-Type: application/json.
res.status(code)
Sets the response status code. Returns res for
chaining: res.status(404).send("Not found").
res.redirect(url)
Sends a 302 Found redirect to url.
res.setHeader(name, value)
Appends a response header. Can be chained before
send() or json().
Middleware
Registers a function that runs before every route handler. Useful
for logging, auth checks, or attaching properties to
req. Up to 32 middleware functions are supported.
import { println } from "core:io";
app.use(fn(req, res) {
println(req.method, req.path);
});
Common Patterns
Simple REST-like API
import { Server } from "core:mongoose";
import { println } from "core:io";
const app = new Server();
app.get("/", fn(req, res) {
res.send("Welcome!");
});
app.get("/greet/*", fn(req, res) {
const name = req.params["0"];
res.json({ message: "Hello, " + name + "!" });
});
app.post("/echo", fn(req, res) {
res.setHeader("X-Echo", "true").send(req.body);
});
app.listen(8080, fn(msg) { println(msg); });
Request logging middleware
import { Server } from "core:mongoose";
import { println } from "core:io";
const app = new Server();
app.use(fn(req, res) {
println("[" + req.method + "]", req.path);
});
app.get("/ping", fn(req, res) {
res.send("pong");
});
app.listen(3000, fn(msg) { println(msg); });
JSON API with status codes
import { Server } from "core:mongoose";
import { Database } from "core:sqlite";
const app = new Server();
const db = new Database("app.sqlite");
db.exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)");
app.get("/notes", fn(req, res) {
const rows = db.prepare("SELECT * FROM notes").all();
res.json(rows);
});
app.post("/notes", fn(req, res) {
const info = db.prepare("INSERT INTO notes (text) VALUES (?)").run(req.body);
res.status(201).json({ id: info.lastInsertRowid });
});
app.listen(4000, fn(msg) { println(msg); });
core:mysql — MySQL / MariaDB Database
Import with:
import { Database } from "core:mysql";
Overview
core:mysql provides a lightweight synchronous SQL API for
MySQL-compatible servers (MySQL and MariaDB). It dynamically loads
libmariadb / libmysqlclient at runtime and
exposes one class: Database.
-
query(sql) returns an array of row objects for
SELECT-style statements. -
exec(sql) executes write/DDL statements and returns
metadata
{ affectedRows, insertId }. - close() closes the connection; further use throws a runtime error.
Database
Constructor
Opens a new connection. Preferred usage is an options object.
Default port is 3306.
const db = new Database({
host: "127.0.0.1",
user: "root",
password: "secret",
database: "zscript_test",
port: 3306
});
// Positional form is also supported:
const db2 = new Database("127.0.0.1", "root", "secret", "zscript_test", 3306);
query
Executes a query and returns rows. Each row is currently keyed by
column index strings ("0", "1", ...),
matching the current runtime implementation.
const rows = db.query("SELECT id, name FROM users ORDER BY id LIMIT 3");
rows.each(fn(row, i) {
println("id=", row["0"], "name=", row["1"]);
});
exec
Executes statements such as INSERT,
UPDATE, DELETE, CREATE, and
DROP. Returns an object with:
affectedRows and insertId.
db.exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64))");
const ins = db.exec("INSERT INTO users (name) VALUES ('alice')");
println("affected:", ins.affectedRows, "insertId:", ins.insertId);
const upd = db.exec("UPDATE users SET name = 'alice_updated' WHERE id = " + ins.insertId);
println("updated:", upd.affectedRows);
const del = db.exec("DELETE FROM users WHERE id = " + ins.insertId);
println("deleted:", del.affectedRows);
close
Closes the active database handle. It is safe to call during cleanup before process exit.
db.close();
Common Patterns
CLI-driven connection options
import { Database } from "core:mysql";
import { args } from "core:os";
const argv = args();
if (argv.length() < 4) {
raise "Usage: [port]";
}
const db = new Database({
host: argv[0],
user: argv[1],
password: argv[2],
database: argv[3],
port: argv.length() > 4 ? argv[4] : 3306
});
Ephemeral test table lifecycle (CRUD)
import { Database } from "core:mysql";
import { getPid } from "core:os";
import { format } from "core:io";
const db = new Database({ host: "127.0.0.1", user: "root", password: "secret", database: "zscript_test" });
const suffix = format("{}", getPid());
const table = "zscript_mysql_crud_" + suffix;
db.exec("CREATE TABLE IF NOT EXISTS " + table + " (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(128) NOT NULL)");
const ins = db.exec("INSERT INTO " + table + " (name) VALUES ('alice_" + suffix + "')");
const rows = db.query("SELECT id, name FROM " + table + " WHERE id = " + ins.insertId);
assert rows.length() == 1;
assert rows[0]["1"] != null;
db.exec("DROP TABLE IF EXISTS " + table);
db.close();
Safe cleanup with try/catch
import { Database } from "core:mysql";
import { println } from "core:io";
const db = new Database({
host: "127.0.0.1",
user: "root",
password: "secret",
database: "zscript_test"
});
try {
db.exec("CREATE TABLE IF NOT EXISTS logs (id INT AUTO_INCREMENT PRIMARY KEY, msg TEXT)");
db.exec("INSERT INTO logs (msg) VALUES ('hello')");
const rows = db.query("SELECT id, msg FROM logs ORDER BY id DESC LIMIT 1");
println(rows[0]["0"], rows[0]["1"]);
} catch (e) {
println("MySQL error:", e);
}
db.close();
core:object — Object utilities
Import with:
import { Object } from "core:object";
Exposes the Object class used for plain maps. Static
helpers mirror common JavaScript-style utilities. The constructor
new Object() produces a new empty object instance.
Object.keys
Returns an array of the object's enumerable string keys.
const o = { a: 1, b: 2 };
println(Object.keys(o)); // [a, b]
Object.values
Returns an array of the object's values in key iteration order.
Object.freeze
Returns a new object with a shallow copy of
obj's map, marked frozen in the runtime (mutations on
the copy are rejected). Use for immutable snapshots or constant
lookup tables (see lib/http.zs for
StatusCode / Method objects).
core:os — Operating System Utilities
Import with:
import { getCwd, getPid, getUser, getType, system, args } from "core:os";
getCwd
Returns the current working directory as a string.
println(getCwd()); // /home/alice/projects
getPid
Returns the process ID of the running interpreter.
println(getPid()); // 12345
getUser
Returns the current OS username. Falls back to the
USER environment variable on headless Linux
environments.
println(getUser()); // alice
getType
Returns a string identifying the host platform:
"win32", "mac", "linux", or
"unknown".
if (getType() == "linux") {
println("Running on Linux");
}
system
Executes cmd in the system shell (equivalent to C
system()). Returns the exit status code.
system(). Validate and sanitise all inputs first to avoid
command injection.
const code = system("ls -la");
println("exit:", code);
args
When the interpreter is started with
zscript --run path/to/script.zs …, returns the tokens
passed after the script path as an array of strings. The host joins
argv[3…] with single spaces and the runtime splits on
spaces, so each entry is one shell word after the script name (no
embedded spaces inside a token unless the shell already split
differently).
Use these tokens as a conventional argv for flags and
positionals. For structured parsing (long options, short clusters,
--, defaults), import
lib:argparser.
import { args } from "core:os";
import { println } from "core:io";
// zscript --run ./app.zs input.txt --verbose --out=out.txt
println(args()); // e.g. [input.txt, --verbose, --out=out.txt]
core:path — File paths
Import with:
import {
normalize, basename, dirname,
absolutePath, absolutePathFromBase, join,
exists, isDirectory, isFile, isAbsolute, separator
} from "core:path";
Overview
core:path helps you work with file and directory paths in
a way that is consistent on both POSIX and Windows: both
/ and \ are accepted as separators in
input, and results use the host's native separator. Functions that
return a boolean (exists, isDirectory,
isFile, isAbsolute) use the interpreter's
canonical true and false values, not new
boolean allocations.
normalize
Returns a normalized path: redundant separators are removed,
. and .. components are resolved, and
separators are converted to the native form.
println(normalize("foo//bar/../baz")); // foo/baz (POSIX) or foo\baz (Windows)
basename
Returns the last path segment (file name). If the name contains a
. and the dot is not the first character, the suffix
after the last dot is stripped (extension removed).
println(basename("/home/user/readme.txt")); // readme
dirname
Returns the directory portion of p, or
"." when there is no directory separator.
println(dirname("/tmp/data.json")); // /tmp
absolutePath
If p is already absolute, returns a normalized absolute
path. Otherwise resolves it against the current working directory.
Returns an error if the path cannot be resolved
(for example when the working directory is unavailable).
import { getCwd } from "core:os";
import { absolutePath, join } from "core:path";
println(absolutePath(join(getCwd(), "README.md")));
absolutePathFromBase
If p is absolute, returns it normalized. Otherwise
combines p with base (intended to be an
absolute directory) and normalizes. Returns an error
if resolution fails.
join
Joins two segments with the native separator and normalizes the
result. Treats empty segments like "." after
normalization.
println(join("src", "main.zs")); // src/main.zs or src\main.zs
exists
true if p refers to an existing file or
directory.
isDirectory
true if p exists and is a directory.
isFile
true if p exists and is a regular file
(not a directory or special node).
isAbsolute
true if p is absolute for the host OS
(POSIX root path, or Windows drive letter / UNC).
separator
Returns a one-character string: backslash on Windows and slash on
POSIX and macOS (for example / on Linux).
println(separator()); // "/" on Linux and macOS
core:promise — Promises
Import with:
import { Promise } from "core:promise";
Promises are created implicitly by calling any
async function. You rarely construct them manually. The
two instance methods below form the chaining API.
then
Registers a callback to run when the promise resolves. The callback
receives the resolved value as its sole argument. The return value
of the callback becomes the resolved value of the new promise
returned by
then, enabling chaining.
fn getData() async { return 42; }
getData()
.then(fn(v) {
println("value:", v); // value: 42
return v * 2;
})
.then(fn(v) {
println("doubled:", v); // doubled: 84
});
then callback must accept
exactly 1 argument (or use a vararg signature). A
callback with the wrong arity will produce a runtime error.
error
Registers a callback to run if the promise (or any preceding promise in the chain) is rejected. The callback receives the error value. Returns a new promise for further chaining.
fn mayFail() async {
return somethingRisky();
}
mayFail()
.then(fn(v) { println("Got:", v); })
.error(fn(e) { println("Error:", e); });
Full async / Promise example
import { println } from "core:io";
fn step1() async { return "hello"; }
fn step2(s) async { return s + " world"; }
fn pipeline() async {
const s = await step1();
return await step2(s);
}
pipeline()
.then(fn(result) {
println(result); // hello world
})
.error(fn(e) {
println("Failed:", e);
});
core:regex — Python-style Regular Expressions
Import with:
import {
compile, search, match, fullmatch, findall,
flags, I, M, S, U
} from "core:regex";
core:regex is powered by the embedded
libregexp engine and exposes a Python-like surface:
module-level helpers plus compiled pattern objects.
Match-returning calls use this shape:
null when no match, otherwise an array where
index 0 is the full match and subsequent indexes are
capture groups.
Module API
| Function | Description |
|---|---|
compile(pattern, flags = 0) |
Compiles and returns a regex object (Pattern / RegExp). |
search(patternOrCompiled, text, flags = 0) |
Finds first match anywhere in text. |
match(patternOrCompiled, text, flags = 0) |
Requires match at start of text. |
fullmatch(patternOrCompiled, text, flags = 0) |
Requires the whole string to match. |
findall(patternOrCompiled, text, flags = 0) |
Returns all matches. Shape mirrors Python: full-match strings (no groups), one string per match (one group), or arrays of groups (multiple groups). |
import { search, match, fullmatch, findall } from "core:regex";
const s = search("cat", "xxcatyy");
println(s[0]); // cat
println(match("cat", "xxcatyy")); // null
println(match("cat", "catyy")[0]); // cat
println(fullmatch("cat", "cat")[0]); // cat
println(fullmatch("cat", "cat!")); // null
println(findall("a.", "abac")); // [ab, ac]
Compiled Pattern API
Compiled objects also keep compatibility aliases:
exec(text) (same as search) and
test(text) (boolean).
import { compile, I } from "core:regex";
const rx = compile("(ab)+", I);
println(rx.search("..ABab..")[0]); // ABab
println(rx.match("zzAB")); // null
println(rx.fullmatch("ABab")[0]); // ABab
Flags
Use numeric bitmasks from flags or short aliases:
I, M, S, U.
| Name | Meaning |
|---|---|
flags.IGNORECASE / I | Case-insensitive matching |
flags.MULTILINE / M | Multiline anchors |
flags.DOTALL / S | . matches newlines |
flags.UNICODE / U | Unicode behavior |
import { compile, I } from "core:regex";
const rx = compile("hello", I);
println(rx.search("HeLLo")); // [HeLLo]
core:sqlite — SQLite3 Database
Import with:
import { Database } from "core:sqlite";
// Or import both exported names:
import { Database, Statement } from "core:sqlite";
Overview
core:sqlite wraps the bundled
SQLite3
amalgamation and exposes two classes:
- Database — represents an open SQLite connection.
-
Statement — a compiled prepared statement returned
by
db.prepare(). You do not construct it directly.
Every new Database(...) call opens an independent
connection. Multiple databases can be open simultaneously.
db.close() when you
are finished. The garbage collector does not automatically
close SQLite handles — that is intentional so you have full control
over transaction lifetime.
Database
Constructor
Opens (or creates) an SQLite database at path.
Pass ":memory:" or omit the argument for a private
in-memory database that is discarded when closed.
const db = new Database("app.sqlite"); // file-based
const mem = new Database(":memory:"); // in-memory
const def = new Database(); // also in-memory
exec
Executes one or more semicolon-separated SQL statements with no
parameter binding. Ideal for DDL (CREATE,
DROP, ALTER), PRAGMAs, and
multi-statement migration scripts. Returns null on
success; an error on failure.
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
);
`);
// Transactions
db.exec("BEGIN");
// ... statements ...
db.exec("COMMIT");
prepare
Compiles sql into a reusable
Statement object. All queries and
DML should go through prepared statements: they are safer (parameter
binding prevents SQL injection), faster when reused, and ergonomic
with the run / get /
all methods. Call stmt.finalize() when you
are done with the statement.
// One-shot INSERT — chain prepare().run()
const info = db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 30);
println(info.changes); // 1
println(info.lastInsertRowid); // 1
// One-shot SELECT — chain prepare().all()
const rows = db.prepare("SELECT * FROM users WHERE age >= ?").all(18);
println(rows.length());
// Reusable statement
const stmt = db.prepare("SELECT * FROM users WHERE age >= ?");
const adults = stmt.all(18);
const first = stmt.get(18);
stmt.finalize();
close
Closes the database connection. Before closing, any prepared
statements that are still open (including one-shot statements
created via inline chains like
db.prepare(sql).run(x)) are automatically finalized via
sqlite3_next_stmt, ensuring the connection frees all of
its resources immediately. Calling close() on an
already-closed database is a no-op. Do not use any
Statement objects after the database has been closed.
db.close();
Statement
A Statement is obtained via
db.prepare(sql). Unlike the raw SQLite C API, you never call step /
reset manually — the run /
get / all methods handle the full lifecycle
internally (modelled after
better-sqlite3).
run
Executes the statement (intended for DML: INSERT,
UPDATE, DELETE) and returns an info
object. Parameters are bound for this call only, unless
bind() was already used
to lock them permanently.
const r1 = db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 30);
println(r1.changes); // 1
println(r1.lastInsertRowid); // 1
const r2 = db.prepare("UPDATE users SET age = ? WHERE name = ?").run(31, "Alice");
println(r2.changes); // 1
// Named parameters
const r3 = db.prepare("INSERT INTO users (name, age) VALUES (@name, @age)")
.run({ name: "Bob", age: 25 });
println(r3.lastInsertRowid); // 2
get
Executes the query and returns the first matching
row as an object whose keys are the column names, or
null if no rows are found. Column types are mapped as
shown in the type table below.
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(1);
println(user.name); // Alice
println(user.age); // 31
// Named parameter
const byName = db.prepare("SELECT * FROM users WHERE name = @name")
.get({ name: "Bob" });
println(byName); // { id: 2, name: "Bob", age: 25 }
// Returns null when nothing matches
const missing = db.prepare("SELECT * FROM users WHERE id = ?").get(999);
println(missing); // null
all
Executes the query and collects every matching row
into an array. Each element is a row object (same shape as
get()). Returns an empty array if no rows match.
const all = db.prepare("SELECT * FROM users").all();
all.each(fn(row, i) { println(row.id, row.name); });
// With a filter
const seniors = db.prepare("SELECT * FROM users WHERE age >= ?").all(30);
println(seniors.length());
// Reusable: call all() multiple times with different params
const stmt = db.prepare("SELECT * FROM users WHERE age >= ?");
println(stmt.all(18).length());
println(stmt.all(30).length());
stmt.finalize();
Type mapping
| SQLite type | ZayneScript type |
|---|---|
INTEGER |
int |
REAL |
num |
TEXT |
string |
NULL |
null |
BLOB |
null |
bind
Permanently binds parameters to the statement for
its entire lifetime. Bindings survive internal resets so the
statement can be executed many times without rebinding. After
calling bind() you must not pass parameters to
run(), get(), or all().
Supports the same positional and named-object conventions as those
methods. Returns this for chaining:
db.prepare(sql).bind(x).get().
// Positional permanent bind
const topUser = db.prepare("SELECT * FROM users WHERE id = ?").bind(1);
println(topUser.get()); // { id:1, name:"Alice", age:31 }
println(topUser.get()); // same result – statement is reused
topUser.finalize();
// Named permanent bind
const byName = db.prepare("SELECT * FROM users WHERE name = @name")
.bind({ name: "Bob" });
println(byName.get()); // { id:2, ... }
byName.finalize();
pluck
Toggles pluck mode. When on (the default when called with
no argument or true), get() and
all() return the value of the
first column only instead of the full row object.
Pass false
to turn pluck back off. Chainable.
const stmt = db.prepare("SELECT id FROM users");
// Returns array of id values instead of array of objects
const ids = stmt.pluck().all();
ids.each(fn(id, i) { println(id); }); // 1 2 3 ...
stmt.pluck(false); // back to full row objects
const rows = stmt.all();
stmt.finalize();
columns
Returns an array of column metadata objects for the result set, matching the better-sqlite3 shape:
| Field | Description |
|---|---|
name |
Column alias or name (always populated) |
column |
Source column name (null for expressions) |
table |
Source table name (null for expressions) |
database |
Source database name (null for expressions)
|
type |
Declared type string, e.g. "TEXT" (null
for expressions)
|
column, table, and
database are only populated when the bundled SQLite
amalgamation is compiled with
SQLITE_ENABLE_COLUMN_METADATA.
const stmt = db.prepare("SELECT id, name, age FROM users");
const cols = stmt.columns();
cols.each(fn(c, i) {
println(c.name, c.type); // id INTEGER, name TEXT, age INTEGER
});
stmt.finalize();
finalize
Destroys the prepared statement and frees its resources. After this
call the statement must not be used. Calling
finalize() on an already-finalized statement is a safe
no-op. Note: db.close() automatically finalizes any
not-yet-finalized statements, so explicit finalize()
calls are only strictly necessary if you want to release the
statement's resources before closing the database.
stmt.finalize();
Common Patterns
In-memory scratch database
import { Database } from "core:sqlite";
import { println } from "core:io";
const db = new Database(":memory:");
db.exec("CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)");
db.prepare("INSERT INTO kv VALUES (?, ?)").run("theme", "dark");
const row = db.prepare("SELECT value FROM kv WHERE key = ?").get("theme");
println(row.value); // dark
db.close();
Bulk insert wrapped in a transaction
import { Database } from "core:sqlite";
const db = new Database("data.sqlite");
const names = ["Alice", "Bob", "Carol", "Dave", "Eve"];
db.exec("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY, name TEXT)");
db.exec("BEGIN");
const stmt = db.prepare("INSERT INTO people (name) VALUES (?)");
names.each(fn(name, i) {
stmt.run(name); // run() resets and re-executes automatically
});
stmt.finalize();
db.exec("COMMIT");
db.close();
Named parameters
// SQLite supports @name, :name, and $name placeholders.
// Pass a plain object to run / get / all to bind by name.
db.exec("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, sku TEXT, price NUM)");
db.prepare("INSERT INTO products (sku, price) VALUES (@sku, @price)")
.run({ sku: "WIDGET-42", price: 9.99 });
const p = db.prepare("SELECT * FROM products WHERE sku = @sku")
.get({ sku: "WIDGET-42" });
println(p.price); // 9.99
Pluck — collect a single column
const stmt = db.prepare("SELECT name FROM users ORDER BY name");
const names = stmt.pluck().all();
names.each(fn(n, i) { println(n); }); // Alice Bob Carol ...
stmt.finalize();
Multiple independent connections
const users = new Database("users.sqlite");
const orders = new Database("orders.sqlite");
const cache = new Database(":memory:");
// All three connections are fully independent.
users.close();
orders.close();
cache.close();
Error handling
import { Database } from "core:sqlite";
import { println } from "core:io";
const db = new Database(":memory:");
db.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT UNIQUE)");
try {
db.prepare("INSERT INTO t VALUES (1, 'hello')").run();
db.prepare("INSERT INTO t VALUES (1, 'hello')").run(); // duplicate PK
} catch (e) {
println("Caught:", e);
}
db.close();
core:string — text helpers
Import with:
import {
toUpper, toLower, capitalize,
isIdentifier, isAlpha, isDigit, isAlnum,
split, ord, chr, bytes, codepoints,
strip, join, repeat, reverse,
contains, startsWith, startswith, endsWith, endswith,
replace, byteLength, encode, decode
} from "core:string";
core:string complements
core:utf8: higher-level helpers on script strings (Unicode scalar / rune
strings at runtime). Case mapping and classification use
utf8proc. Normalization and case folding stay in
core:utf8 (nfc, casefold, …).
Overview
-
Case —
toUpper,toLower,capitalize(first codepoint title-case, rest lower). -
Predicates —
isIdentifier,isAlpha,isDigit(UnicodeNdonly, whole string),isAlnum(letters and number categories for every codepoint; empty string is false). -
split— one argument: split on Unicode whitespace (Zs,Zl,Zp); two arguments: split on a non-empty UTF-8 delimiter substring (may yield empty segments, like many languages). -
Encoding —
ord/chrfor a single codepoint;bytes(UTF-8 byte values as an array of ints),codepoints(rune values as ints),byteLength(UTF-8 byte count). -
Base64 (RFC 4648) —
encode(s)from UTF-8 text, orencode(byteArray)from integers0..255;decode(s)ignores ASCII whitespace ins, decodes standard Base64 with=padding, and returns a string only when the decoded bytes are valid UTF-8 (otherwise an error value). -
Other —
strip,join(array of strings + separator),repeat,reverse(by codepoint),contains/startsWith/startswith/endsWith/endswith,replace(all occurrences; second argument must be non-empty).
Quick example
import { println } from "core:io";
import {
toUpper, toLower, capitalize,
isIdentifier, isAlpha, isDigit, isAlnum,
split, join, strip, replace,
ord, chr, byteLength, bytes, encode, decode
} from "core:string";
println(toUpper("ab")); // AB
println(toLower("AB")); // ab
println(capitalize("hello WORLD")); // Hello world
println(isIdentifier("_x9")); // true
println(isAlpha("abc")); // true
println(isDigit("42")); // true
println(isAlnum("a1")); // true
println(join(split("a,b,c", ","), "-")); // a-b-c
println(strip(" x ")); // x
println(replace("one one", "one", "two")); // two two
println(ord("X")); // 88
println(ord(chr(65))); // 65
println(byteLength("hello")); // 5
println(split(" a b ").length()); // 2 (whitespace split)
println(bytes("AB").length()); // 2 (UTF-8 byte count)
println(decode(encode("hello"))); // hello (UTF-8 roundtrip)
println(encode(bytes("AB"))); // QUI=
Functions (reference)
Native bindings are implemented in
src/core/string.c and declared for the host from
src/core/string.h (LoadCoreString). Errors
use the usual Error value pattern (wrong arity or type).
Per-codepoint case mapping (capitalize: title + lower rest).
Whole-string predicates; empty string is false for the is* checks.
One-arg whitespace split; two-arg delimiter split (delim
non-empty UTF-8 substring).
Exactly one codepoint required.
One Unicode scalar; invalid codepoint is an error.
UTF-8 byte values vs UTF-32 code units as integers.
strlen of the UTF-8 encoding.
Trim leading/trailing Unicode whitespace (same categories as one-arg split).
parts must be an array of strings.
n non-negative integer.
Reverses codepoint order (not full grapheme clusters).
UTF-8 substring / prefix / suffix tests; startswith/endswith are lowercase aliases.
Non-overlapping replacements; from must be non-empty.
Standard Base64 (no line breaks). s is UTF-8 text;
byteArray is an array of byte values 0..255.
Decodes Base64; skips spaces, tabs, CR, LF in s. Result
must be well-formed UTF-8 or callers receive an error value (binary
payloads are not returned as raw strings).
core:utf8 — Unicode and UTF-8
Import with:
import {
nfc, nfd, nfkc, nfkd, casefold,
toLower, toUpper, toTitle, len,
charWidth, categoryString, validCodepoint,
graphemeBreak, version
} from "core:utf8";
String values in ZayneScript are stored internally as
UTF-32 rune arrays (Unicode scalar values). The
core:utf8 module wraps
utf8proc
for normalization, case folding, character metadata, and grapheme
boundary checks. Functions that take or return string use
the same runtime representation as the rest of the language.
Overview
-
Normalization —
nfc,nfd,nfkc,nfkdeach take one string and return the normalized string (or an error value if normalization fails). -
Case —
toLower,toUpper, andtoTitlemap each scalar independently.casefoldapplies full Unicode case folding (for case-insensitive comparison). -
Length —
len(s)returns the number of Unicode codepoints (runes) ins, not the byte length of the original UTF-8 source.
Normalization
Canonical composition (NFC). Equivalent to Unicode normalization form NFC.
Canonical decomposition (NFD).
Compatibility composition (NFKC). Often used for matching and search.
Compatibility decomposition (NFKD).
import { nfc, len } from "core:utf8";
assert len(nfc("")) == 0;
Case mapping and folding
Simple lowercase mapping per codepoint (not locale-aware title rules for whole strings).
Uppercase mapping per codepoint.
Titlecase mapping per codepoint.
Full Unicode case fold, suitable for case-insensitive equality of strings.
import { toLower, casefold } from "core:utf8";
println(toLower("AbC")); // abc
Codepoint properties and graphemes
Rune count of s (excluding any internal terminator).
Display width for numeric codepoint cp (similar to
wcwidth, with non-printable widths defined by utf8proc).
Two-letter Unicode general category for integer codepoint
cp (for example "Lu" for an uppercase
letter).
Whether cp is a valid Unicode scalar value (in range,
not a surrogate).
Whether a grapheme cluster boundary is allowed between codepoints
prev and curr (UAX #29, legacy mode without
full state machine).
import { categoryString, validCodepoint } from "core:utf8";
println(categoryString(65)); // Lu (Latin uppercase)
println(validCodepoint(65)); // true
version
Returns the utf8proc library version string (for example
"2.1.0").
import { version } from "core:utf8";
println(version());
lib:argparser — command-line argument parser
The repository includes lib/argparser.zs, copied next to
the executable at build time (see the
dist/ layout on the home page). It parses the string array
returned by args() from
core:os into positionals, boolean flags, and value flags.
How args() relates to the shell
Everything after the script path on the host command line becomes
args(). Tokens are split on ASCII spaces only; use
lib:argparser on that flat list the same way you would use
a classic argv in C.
ArgParser class
Import symbols:
import { ArgParser, createArgParser, parseWithDefs } from "lib:argparser";
-
createArgParser()— same asnew ArgParser(); returns a parser instance withstrictdefaulting tofalse. -
p.option(def)— registers an option. Objectdefmay include:long(string),short(single-character string),takesValue(boolean),multiple(boolean; values accumulate in an array),defaultVal(applied if the flag never appears), andhelp(forhelpText). At least one oflongorshortis required. Duplicate names or invalid identifiers raise at registration time. -
p.strict = true— unknown--longor-xoptions are recorded inerrorsinstead of only inunknown. -
p.parse(argv)— returns an object:ok(boolean),errors(array of strings),positional(array),flags(object; keys uselongwhen present, otherwiseshort), andunknown(object of unrecognized flags when not strict). -
p.helpText(programName)— multi-line usage string from registered options.
Supported syntax (high level):
-
--— end of options; remaining tokens are positional. -
Long options:
--name,--name=value, or--namewith the next token as the value whentakesValueis true and=is omitted. -
Short options:
-x; clustered booleans-xyz; value either glued (-fvalue) or as the next token (-f value). -
A lone
-is kept as a positional (common "stdin" placeholder). -
Tokens that look like signed decimal numbers (for example
-3or-1.5) are treated as positionals so numeric arguments are not mistaken for flags.
import { args } from "core:os";
import { createArgParser } from "lib:argparser";
import { println } from "core:io";
const p = createArgParser();
p.option({ long: "output", short: "o", takesValue: true, help: "output file" });
p.option({ long: "verbose", short: "v", help: "more logging" });
const r = p.parse(args());
if (!r.ok) {
println(r.errors);
} else {
println("flags:", r.flags, "positionals:", r.positional);
}
parseWithDefs
parseWithDefs(defs, argv) builds a temporary parser,
registers every object in the defs array with
option, then returns parse(argv). Useful for
small tools without keeping a parser instance.
import { parseWithDefs } from "lib:argparser";
const r = parseWithDefs([
{ long: "out", short: "o", takesValue: true },
{ long: "quiet", short: "q" }
], argv);
See tests/test_args.zs for a runnable example with
args() and lib:argparser.
lib:http — bundled HTTP helpers
The repository includes lib/http.zs, copied next to the
interpreter at build time. It imports request from
core:mongoose and builds two frozen lookup tables with
Object.freeze from core:object:
StatusCode and Method.
import { request, Method, StatusCode } from "lib:http";
request("http://localhost:3000/missing", {
method: Method.GET
}).then(fn(res) {
if (res.status == StatusCode.NotFound) {
println("not found");
} else {
println(res.status, res.body);
}
});
StatusCode
A frozen object mapping symbolic names to their numeric HTTP status
codes. Access any code with StatusCode.Ok,
StatusCode.NotFound, etc.
| Name | Code | Category |
|---|---|---|
Continue | 100 | 1xx Informational |
SwitchingProtocols | 101 | 1xx Informational |
Processing | 102 | 1xx Informational |
EarlyHints | 103 | 1xx Informational |
Ok | 200 | 2xx Success |
Created | 201 | 2xx Success |
Accepted | 202 | 2xx Success |
NonAuthoritativeInformation | 203 | 2xx Success |
NoContent | 204 | 2xx Success |
ResetContent | 205 | 2xx Success |
PartialContent | 206 | 2xx Success |
MultipleChoices | 300 | 3xx Redirection |
MovedPermanently | 301 | 3xx Redirection |
Found | 302 | 3xx Redirection |
SeeOther | 303 | 3xx Redirection |
NotModified | 304 | 3xx Redirection |
TemporaryRedirect | 307 | 3xx Redirection |
PermanentRedirect | 308 | 3xx Redirection |
BadRequest | 400 | 4xx Client Error |
Unauthorized | 401 | 4xx Client Error |
PaymentRequired | 402 | 4xx Client Error |
Forbidden | 403 | 4xx Client Error |
NotFound | 404 | 4xx Client Error |
MethodNotAllowed | 405 | 4xx Client Error |
NotAcceptable | 406 | 4xx Client Error |
ProxyAuthRequired | 407 | 4xx Client Error |
RequestTimeout | 408 | 4xx Client Error |
Conflict | 409 | 4xx Client Error |
Gone | 410 | 4xx Client Error |
LengthRequired | 411 | 4xx Client Error |
PreconditionFailed | 412 | 4xx Client Error |
PayloadTooLarge | 413 | 4xx Client Error |
UriTooLong | 414 | 4xx Client Error |
UnsupportedMediaType | 415 | 4xx Client Error |
RangeNotSatisfiable | 416 | 4xx Client Error |
ExpectationFailed | 417 | 4xx Client Error |
ImATeapot | 418 | 4xx Client Error |
MisdirectedRequest | 421 | 4xx Client Error |
UnprocessableEntity | 422 | 4xx Client Error |
Locked | 423 | 4xx Client Error |
FailedDependency | 424 | 4xx Client Error |
TooEarly | 425 | 4xx Client Error |
UpgradeRequired | 426 | 4xx Client Error |
PreconditionRequired | 428 | 4xx Client Error |
TooManyRequests | 429 | 4xx Client Error |
RequestHeaderFieldsTooLarge | 431 | 4xx Client Error |
UnavailableForLegalReasons | 451 | 4xx Client Error |
InternalServerError | 500 | 5xx Server Error |
NotImplemented | 501 | 5xx Server Error |
BadGateway | 502 | 5xx Server Error |
ServiceUnavailable | 503 | 5xx Server Error |
GatewayTimeout | 504 | 5xx Server Error |
HttpVersionNotSupported | 505 | 5xx Server Error |
InsufficientStorage | 507 | 5xx Server Error |
LoopDetected | 508 | 5xx Server Error |
NetworkAuthRequired | 511 | 5xx Server Error |
Method
A frozen object mapping HTTP verb names to their string values.
Use Method.GET, Method.POST, etc. to avoid
typos in request code:
| Name | Value |
|---|---|
Method.GET | "GET" |
Method.POST | "POST" |
Method.PUT | "PUT" |
Method.PATCH | "PATCH" |
Method.DELETE | "DELETE" |
Method.HEAD | "HEAD" |
Method.OPTIONS | "OPTIONS" |
Method.CONNECT | "CONNECT" |
Method.TRACE | "TRACE" |
import { request, Method, StatusCode } from "lib:http";
import { println } from "core:io";
// POST with JSON body using Method and StatusCode constants
request("http://api.example.com/users", {
method: Method.POST,
headers: { "Content-Type": "application/json" },
body: { name: "Alice", age: 30 }
}).then(fn(res) {
if (res.status == StatusCode.Created) {
println("User created:", res.body);
} else if (res.status == StatusCode.BadRequest) {
println("Bad request:", res.body);
} else {
println("Unexpected status:", res.status);
}
}).error(fn(e) {
println("Request failed:", e);
});
See tests/test_request.zs for a minimal client example
using lib:http.