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";

print

print(...args)

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

println(...args)

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

scan(prompt?: string) → string

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

parseNum(str: string) → int | num

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

format(template: string, ...args) → string

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

clearScreen()

Clears the terminal screen using ANSI escape codes.

clearScreen();

setColor

setColor(fg?: int, bg?: int)

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

decompile(fn: function) → string

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

getCwd() → string

Returns the current working directory as a string.

println(getCwd()); // /home/alice/projects

getPid

getPid() → int

Returns the process ID of the running interpreter.

println(getPid()); // 12345

getUser

getUser() → string

Returns the current OS username. Falls back to the USER environment variable on headless Linux environments.

println(getUser()); // alice

getType

getType() → string

Returns a string identifying the host platform: "win32", "mac", "linux", or "unknown".

if (getType() == "linux") {
    println("Running on Linux");
}

system

system(cmd: string) → int

Executes cmd in the system shell (equivalent to C system()). Returns the exit status code.

Warning: Never pass untrusted user input directly to 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

array.push(value)

Appends value to the end of the array. Returns null.

const arr = [1, 2];
arr.push(3);
println(arr); // [1, 2, 3]

pop

array.pop() → value

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

array.length() → int

Returns the number of elements in the array.

println([10, 20, 30].length()); // 3

each

array.each(callback(index, element)) → array

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

array.keep(callback(index, element)) → array

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

new Date()
new Date(timestamp: number)
new Date(dateString: string)
new Date(year, month, [day, hours, minutes, seconds, ms])

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

date.getTime() → num

Returns the internal Unix timestamp in milliseconds.

println(new Date().getTime()); // e.g. 1743778200000

toString

date.toString() → string

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

promise.then(callback(value)) → Promise

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
    });
Note: The then callback must accept exactly 1 argument (or use a vararg signature). A callback with the wrong arity will produce a runtime error.

error

promise.error(callback(err)) → Promise

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:

Every new Database(...) call opens an independent connection. Multiple databases can be open simultaneously.

Note: Always call 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

new Database(path?: string)

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

db.exec(sql: string)

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

db.prepare(sql: string) → Statement

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

db.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

stmt.run([...params]) → { changes: int, lastInsertRowid: num }

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

stmt.get([...params]) → object | null

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

stmt.all([...params]) → array

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

stmt.bind([...params]) → this

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

stmt.pluck([bool]) → this

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

stmt.columns() → array of { name, column, table, database, type }

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)
Note: 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

stmt.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

new Server()

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.

app.get(path, handler)
app.post(path, handler)
app.put(path, handler)
app.delete(path, handler)
app.patch(path, handler)
app.all(path, handler)

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

app.listen(port: int, callback?: fn)

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.

app.close()

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)

res.send(body: string)

Sends a plain-text response with the current status code (default 200) and any headers set via res.setHeader().

res.json(value)

res.json(value)

Serialises value to a string and sends it with Content-Type: application/json.

res.status(code)

res.status(code: int) → res

Sets the response status code. Returns res for chaining: res.status(404).send("Not found").

res.redirect(url)

res.redirect(url: string)

Sends a 302 Found redirect to url.

res.setHeader(name, value)

res.setHeader(name: string, value: string) → res

Appends a response header. Can be chained before send() or json().

Middleware

app.use(fn(req, res))

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); });