Standard Library
ZayneScript ships seven built-in modules accessible via the
core: import prefix. Each module provides functions or a
class that is loaded on demand — only import what you need.
core:io — Input / Output
Import with:
import { print, println, scan, parseNum, format,
clearScreen, setColor, decompile } 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 with each {} placeholder in
template replaced by the next argument in order. Extra
arguments are ignored; extra placeholders are left as-is.
const msg = format("Hello, {}! You are {} years old.", "Alice", 30);
println(msg); // Hello, Alice! You are 30 years old.
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));
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:os — Operating System Utilities
Import with:
import { getCwd, getPid, getUser, getType, system } 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);
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(index, element) 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(i, e) { return e * 2; });
println(doubled); // [2, 4, 6, 8, 10]
// Using a vararg function (index first, then element)
nums.each(println);
// 0 1
// 1 2
// 2 3 ...
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(i, e) { return e % 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: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: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: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:mongoose — HTTP Server
Import with:
import { Server } from "core:mongoose";
core:mongoose wraps the
Mongoose
embedded networking library to give ZayneScript an Express.js-style
HTTP server. Create a Server, register routes, and call
listen() — the event loop runs until
close() is called.
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 });
});
listen / close
Binds to 0.0.0.0:port and enters the event loop. This
call blocks until app.close() is
called. The optional callback is invoked once the
server is ready, receiving a ready message string.
Stops the event loop and frees the server resources.
import { println } from "core:io";
app.listen(3000, fn(msg) {
println(msg); // Server listening on port 3000
});
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); });