Skip to content

Standard Library

Frank edited this page Jan 26, 2026 · 19 revisions

Standard Library

Table of Contents

These functions are registered by cs_register_stdlib(vm).

IO / Introspection

print(...values)

Prints values separated by spaces and ends with newline.

typeof(value) -> string

Returns one of: "nil" "bool" "int" "float" "string" "list" "map" "strbuf" "range" "function" "native" "promise"

Type Predicates

Boolean type-checking functions:

  • is_nil(value) -> bool
  • is_bool(value) -> bool
  • is_int(value) -> bool
  • is_float(value) -> bool
  • is_string(value) -> bool
  • is_list(value) -> bool
  • is_map(value) -> bool
  • is_function(value) -> bool
  • is_promise(value) -> bool

Example:

if (is_int(x) || is_float(x)) {
  print("x is a number");
}

getenv(key: string) -> string | nil

Returns environment variable value or nil if missing.

assert(cond, message?)

  • If cond is falsy → runtime error
  • Optional second argument string overrides error message

Loading / Modules

load(path: string) -> bool

Runs a file. Returns true on success, nil/error on failure.

require(path: string) -> value

Runs a file once per resolved path.

  • Subsequent calls with the same resolved path return the cached exports without re-running.
  • Returns the module's exports map.

require_optional(path: string) -> value | nil

Like require(), but returns nil if the file doesn't exist instead of throwing an error.

If the file exists, it behaves like require() and returns the module exports.

Error Handling

error(msg: string) -> map

error(msg: string, code: string) -> map

Creates a standardized error object with:

  • msg - Error message
  • code - Error code (defaults to "ERROR" if omitted)
  • stack - List of stack frame strings

is_error(value) -> bool

Returns true if value is an error object (has msg and code fields).

Example:

fn validate(x) {
  if (x < 0) {
    throw error("Invalid value: must be non-negative", "INVALID_ARG");
  }
}

try {
  validate(-5);
} catch (e) {
  if (is_error(e)) {
    print("Error:", e.msg);
    print("Code:", e.code);
  }
}

format_error(err) -> string

Formats an error object (as produced by error()) into a human-readable string.

Current format is a single line: [CODE] message. Stack traces are available on err.stack.

ERR -> map

Global map of common error codes (strings), intended to be used with error(msg, code).

Constructors

list() -> list

map() -> map

strbuf() -> strbuf

Length

len(x) -> int

Works for:

  • string: number of bytes
  • list: element count
  • map: entry count
  • strbuf: byte length Else returns 0.

Iteration

range(end) / range(start, end) / range(start, end, step) -> range

Returns a lazy range object (iterable) without allocating a list:

  • range(5) → yields 0, 1, 2, 3, 4
  • range(2, 7) → yields 2, 3, 4, 5, 6
  • range(0, 10, 2) → yields 0, 2, 4, 6, 8
  • range(10, 0, -2) → yields 10, 8, 6, 4, 2

Default step is 1. Step cannot be 0.

Example:

for i in range(10) {
  print(i);
}

List Ops

push(list, value)

pop(list) -> value | nil

extend(list, other_list)

Appends all elements from other_list onto list (in-place).

index_of(list, value) -> int

Returns the first index of value, or -1 if not found.

sort(list, [cmp], [algo])

Sorts the list in-place.

  • Default sort supports numbers, strings, bools, and nil (matching types only).
  • Optional cmp(a, b) should return negative/zero/positive like C's comparator.
  • Optional algo selects the algorithm: "insertion", "quick", "merge".

Examples:

sort(xs); // insertion sort (default)
sort(xs, "quick");
sort(xs, "merge");

fn desc(a, b) { return b - a; }
sort(xs, desc, "quick");

enumerate(list) -> list

Returns a list of [index, value] pairs.

let xs = ["a", "b"];
print(enumerate(xs)); // [[0, "a"], [1, "b"]]

zip(listA, listB) -> list

Pairs elements by index up to the shorter list length.

print(zip([1, 2], ["x", "y", "z"])); // [[1, "x"], [2, "y"]]

any(list, [pred]) -> bool

Returns true if any element is truthy (or satisfies pred).

any([nil, 0, 3]) // true
any([1, 3, 5], fn(x) => x % 2 == 0) // false

all(list, [pred]) -> bool

Returns true if all elements are truthy (or satisfy pred).

all([1, 2, 3]) // true
all([1, 2, 3], fn(x) => x < 3) // false

filter(list, pred) -> list

Returns a new list with elements that satisfy pred.

filter([1, 2, 3, 4], fn(x) => x % 2 == 0) // [2, 4]

map(list, mapper) -> list

Returns a new list with mapper applied to each element.

map([1, 2, 3], fn(x) => x * 2) // [2, 4, 6]

reduce(list, reducer, [init]) -> value

Reduces a list left-to-right. If init is omitted, uses the first element.

reduce([1, 2, 3], fn(a, b) => a + b) // 6
reduce([1, 2, 3], fn(a, b) => a + b, 10) // 16

insert(list, index: int, value)

Inserts value at position index, shifting existing elements.

remove(list, index: int) -> value | nil

Removes and returns the element at index.

slice(list, start: int, length: int) -> list

Returns a new list containing length elements starting at start.

Map Ops

mget(map, key: value) -> value | nil

mset(map, key: value, value)

mhas(map, key: value) -> bool

mdel(map, key: value)

Deletes a key from the map.

keys(map) -> list[value]

values(map) -> list[value]

Returns a list of all values in the map.

items(map) -> list[list]

Returns a list of [key, value] pairs.

map_values(map) -> list[value]

Alias for values(). Returns all values as a list for convenient iteration.

Data Quality-of-Life

copy(x) -> value

Returns a shallow copy:

  • Lists: creates new list with same elements
  • Maps: creates new map with same key-value pairs
  • Other types: returns the value as-is

deepcopy(x) -> value

Returns a deep copy with cycle detection:

  • Lists and maps are recursively copied
  • Handles self-referential structures
  • Other types: returns the value as-is

reverse(list)

Reverses a list in-place.

reversed(list) -> list

Returns a new reversed list without modifying the original.

contains(container, item) -> bool

Checks if item exists:

  • Lists: checks for element
  • Maps: checks for key
  • Strings: checks for substring

Example:

let original = [1, [2, 3], 4];
let shallow = copy(original);
let deep = deepcopy(original);

shallow[1][0] = 99;  // Modifies original[1][0]
deep[1][1] = 88;     // Does not affect original

reverse([1, 2, 3]);  // Returns [3, 2, 1], in-place
let rev = reversed([1, 2, 3]);  // original unchanged

print(contains([1, 2, 3], 2));        // true
print(contains({"a": 1}, "a"));       // true
print(contains("hello", "ell"));      // true

String Ops

str_find(s: string, sub: string) -> int

Returns first index or -1.

str_replace(s, old, rep) -> string

Replaces all occurrences of old with rep.

  • If old is empty, returns original string.

str_split(s, sep) -> list[string]

Splits on a string separator.

  • If sep is empty, returns [s].

str_contains(s: string, sub: string) -> bool

Returns true if sub appears anywhere in s.

str_count(s: string, sub: string) -> int

Counts non-overlapping occurrences of sub. If sub is empty, returns 0.

str_pad_start(s: string, width: int, pad: string = " ") -> string

Pads on the left until width is reached.

str_pad_end(s: string, width: int, pad: string = " ") -> string

Pads on the right until width is reached.

str_reverse(s: string) -> string

Returns the reversed string (byte-wise).

Regex

Regex uses POSIX Extended Regular Expressions (ERE). Invalid patterns raise a runtime error. On Windows builds, regex functions may be unavailable.

regex_is_match(pattern: string, text: string) -> bool

Returns true if any match is found.

regex_match(pattern: string, text: string) -> bool

Returns true only if the entire string matches.

regex_find(pattern: string, text: string) -> map | nil

Returns a match object or nil if no match:

  • start (int) - byte index of match start
  • end (int) - byte index of match end
  • match (string) - matched text
  • groups (list) - capture groups (nil for unmatched groups)

regex_find_all(pattern: string, text: string) -> list[map]

Returns a list of match objects with the same shape as regex_find.

regex_replace(pattern: string, text: string, replacement: string) -> string

Replaces all matches with a literal replacement string. Capture expansion (like $1) is not supported.

Example:

let text = "foo-123-bar";
print(regex_is_match("[0-9]+", text));

let m = regex_find("([0-9]+)", text);
print(m.start, m.end, m["match"], m.groups[0]);

let all = regex_find_all("[0-9]+", "a1 b22 c333");
print(len(all));

print(regex_replace("[0-9]+", "a1b22c", "#"));

substr(s: string, start: int, length: int) -> string

Returns substring starting at start with length characters.

join(list, separator: string) -> string

Joins list elements into a string with the given separator.

List Utilities

list_unique(list) -> list

Returns a new list with duplicates removed (preserves order).

list_flatten(list) -> list

Flattens one level of nested lists.

list_chunk(list, size: int) -> list[list]

Splits into sublists of at most size elements.

list_compact(list) -> list

Removes nil values.

list_sum(list) -> int | float | nil

Sums numeric elements (ignores nil). Returns nil if any non-number is present.

String Ergonomics

str_trim(s: string) -> string / trim(s: string) -> string

Removes leading and trailing whitespace.

str_ltrim(s: string) -> string / ltrim(s: string) -> string

Removes leading whitespace only.

str_rtrim(s: string) -> string / rtrim(s: string) -> string

Removes trailing whitespace only.

str_lower(s: string) -> string / lower(s: string) -> string

Converts string to lowercase (ASCII only).

str_upper(s: string) -> string / upper(s: string) -> string

Converts string to uppercase (ASCII only).

str_startswith(s: string, prefix: string) -> bool / starts_with(s, prefix) -> bool

Returns true if string starts with the given prefix.

str_endswith(s: string, suffix: string) -> bool / ends_with(s, suffix) -> bool

Returns true if string ends with the given suffix.

str_repeat(s: string, count: int) -> string

Returns a new string with s repeated count times.

split_lines(s: string) -> list[string]

Splits the string on \n or \r\n. An empty trailing line is preserved.

Example:

let text = "  Hello World  ";
print(str_trim(text));           // "Hello World"
print(str_lower("HELLO"));       // "hello"
print(str_upper("hello"));       // "HELLO"
print(str_startswith("test", "te"));  // true
print(str_endswith("test", "st"));    // true
print(str_repeat("ab", 3));      // "ababab"
print(split_lines("a\n b\r\n")); // ["a", " b", ""]

Path Ops

path_join(a: string, b: string) -> string

Joins with / when needed.

path_dirname(path: string) -> string

Returns directory portion:

  • no slash → .

Date / Time

unix_ms() -> int

Returns the current Unix time in milliseconds.

unix_s() -> int

Returns the current Unix time in seconds.

datetime_now() -> map

Returns local time as a map with fields:

  • year, month, day
  • hour, minute, second, ms
  • wday (0=Sunday), yday (1-366)
  • is_dst (bool), is_utc (bool)

datetime_utc() -> map

Same as datetime_now() but in UTC (is_utc = true).

datetime_from_unix_ms(ms: int|float) -> map

Converts a Unix milliseconds timestamp to local time.

datetime_from_unix_ms_utc(ms: int|float) -> map

Converts a Unix milliseconds timestamp to UTC.

Example:

let ms = unix_ms();
let local = datetime_now();
let utc = datetime_utc();
let epoch = datetime_from_unix_ms_utc(0);
print(ms, local, utc, epoch);
  • /x → /
  • a/b → a

path_basename(path: string) -> string

Returns last path component.

path_ext(path: string) -> string

Returns extension without dot, or "" if none.

Filesystem

read_file(path: string) -> string | nil

Reads a file and returns its contents as a string. Returns nil if the file can't be read.

write_file(path: string, data: string) -> bool

Writes data to path (overwrites). Returns true on success.

exists(path: string) -> bool

Returns true if the path exists.

is_dir(path: string) -> bool

Returns true if the path exists and is a directory.

is_file(path: string) -> bool

Returns true if the path exists and is a regular file.

list_dir(path: string) -> list | nil

Returns a list of entry names (not full paths). Returns nil if the directory can't be opened.

mkdir(path: string) -> bool

Creates a directory. Returns true on success (or if it already exists).

rm(path: string) -> bool

Removes a file or an empty directory. Returns true on success.

rename(from: string, to: string) -> bool

Renames or moves a file or directory. Returns true on success.

cwd() -> string

Returns the VM's current working directory (used for resolving relative paths).

chdir(path: string) -> bool

Sets the VM's current working directory for future relative paths. Returns true on success.

Formatting

fmt(format_string, ...args) -> string

Supported specifiers:

  • %d expects int
  • %b expects bool
  • %s expects string
  • %v accepts any value (uses a generic repr)

Literal percent: %%

Example:

print(fmt("x=%d ok=%b name=%s", 10, true, "frank"));

JSON

json_parse(text: string) -> value

Parses JSON into CupidScript values:

  • null → nil
  • true/false → bool
  • numbers → int or float
  • strings → string
  • arrays → list
  • objects → map

json_stringify(value) -> string

Serializes a value to JSON. Supports nil, bool, int, float, string, list, map.

Example:

let obj = {"name": "frank", "age": 30, "tags": ["a", "b"]};
let s = json_stringify(obj);
let back = json_parse(s);
print(back.name);

Time

now_ms() -> int

Milliseconds timestamp:

  • Uses gettimeofday on POSIX
  • Uses clock() fallback on Windows build

sleep(ms: int)

Returns a promise that resolves after ms milliseconds.

await sleep(10);

If ms <= 0, the promise resolves immediately.

Promises

promise() -> promise

Creates a new pending promise.

resolve(promise, value = nil) -> bool

Resolves a promise. Returns true if it was pending.

reject(promise, value = nil) -> bool

Rejects a promise. Returns true if it was pending.

is_promise(value) -> bool

Checks if a value is a promise.

Math Functions

abs(x) -> int | float

Returns absolute value. Preserves type (int → int, float → float).

min(...values) -> number

Returns minimum of one or more numbers. Accepts int and/or float.

max(...values) -> number

Returns maximum of one or more numbers. Accepts int and/or float.

floor(x) -> int

Rounds down to nearest integer. Accepts int or float, always returns int.

ceil(x) -> int

Rounds up to nearest integer. Accepts int or float, always returns int.

round(x) -> int

Rounds to nearest integer (0.5 rounds away from zero). Returns int.

sqrt(x) -> float

Returns square root. Accepts int or float, always returns float.

pow(base, exp) -> float

Returns base raised to exp power. Accepts int or float, returns float.

sin(x) -> float

Returns sine of x (radians). Accepts int or float.

cos(x) -> float

Returns cosine of x (radians). Accepts int or float.

tan(x) -> float

Returns tangent of x (radians). Accepts int or float.

log(x) -> float

Returns natural logarithm of x (base $e$). Accepts int or float.

exp(x) -> float

Returns $e^x$. Accepts int or float.

random() -> float

Returns a pseudo-random float in the range $[0, 1)$.

Math Constants

PI

The constant $\pi$.

E

The constant $e$.

Example:

let x = sqrt(16);     // 4.0
let y = pow(2, 8);    // 256.0
let z = abs(-42);     // 42
let m = min(3, 7, 2); // 2
let s = sin(PI / 2);  // ~1.0
let c = cos(0);       // 1.0
let t = tan(0);       // 0.0
let l = log(E);       // ~1.0
let ex = exp(1);      // ~2.71828
let r = random();     // [0, 1)

Conversion

to_int(value) -> int | nil

Converts a value to integer:

  • int → returns as-is
  • float → truncates toward zero (3.9 → 3, -3.9 → -3)
  • bool → 0 or 1
  • string → parses decimal integer, returns nil if invalid
  • Otherwise → nil

to_str(value) -> string

Converts any value to its string representation.

Memory Management

gc() -> int

Manually triggers garbage collection cycle detection. Returns the number of cycles collected.

gc_stats() -> map

Returns GC statistics as a map:

  • tracked - Number of tracked objects (lists/maps)
  • collections - Total collections performed
  • collected - Total objects collected
  • allocations - Total allocations since VM start

gc_config() -> map

Returns current GC configuration:

  • threshold - Collection threshold (0 = disabled)
  • alloc_trigger - Allocation trigger interval (0 = disabled)

gc_config(map) -> bool

Sets GC configuration from a map with optional fields:

  • threshold (int)
  • alloc_trigger (int)

Returns true.

gc_config(threshold, alloc_trigger) -> bool

Sets both values directly. Returns true.

Auto-GC Policy:

  • GC can run when tracked objects >= threshold
  • GC can run every alloc_trigger allocations
  • Both policies can be enabled simultaneously
  • Setting either value to 0 disables that policy

See Implementation Notes for details.

Safety Controls

Scripts can configure their own execution limits:

set_timeout(milliseconds)

Sets maximum execution time in milliseconds. Script aborts if exceeded.

set_timeout(30000);  // Allow 30 seconds for batch processing

set_instruction_limit(count)

Sets maximum instruction count. Script aborts if exceeded.

set_instruction_limit(100000000);  // Allow 100M instructions

get_timeout() -> int

Returns current timeout in milliseconds (0 = unlimited).

get_instruction_limit() -> int

Returns current instruction limit (0 = unlimited).

get_instruction_count() -> int

Returns number of instructions executed in current script run.

Example:

// Check if we need more time for heavy processing
if (get_instruction_count() > get_instruction_limit() / 2) {
    print("Warning: approaching instruction limit");
    set_instruction_limit(get_instruction_limit() * 2);
}

Network I/O

TCP Sockets

// Connect to a server
let sock = await tcp_connect("example.com", 80);

// Send data
await socket_send(sock, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");

// Receive data
let response = await socket_recv(sock, 4096);

// Close socket
socket_close(sock);

TCP Server

let server = tcp_listen("0.0.0.0", 8080);
print("Listening on port 8080...");

let client = await socket_accept(server);
let data = await socket_recv(client, 1024);
await socket_send(client, "HTTP/1.1 200 OK\r\n\r\nHello!");
socket_close(client);
socket_close(server);

TLS/SSL Connections

// Connect with TLS
let sock = tls_connect("example.com", 443);

// Check if secure
print(socket_is_secure(sock));  // true

// Get TLS info
let info = tls_info(sock);
print(info.version);   // "TLSv1.3"
print(info.cipher);    // "TLS_AES_256_GCM_SHA384"

// Upgrade existing connection (STARTTLS)
let smtp = tcp_connect("smtp.example.com", 587);
socket_send(smtp, "STARTTLS\r\n");
tls_upgrade(smtp);  // Now encrypted

socket_close(sock);

HTTP/HTTPS Client

// HTTP request
let resp = http_get("http://example.com");

// HTTPS request (automatic TLS)
let resp = http_get("https://api.example.com/data");
print(resp.status);  // 200
print(resp.body);

// POST request
let resp = http_post("https://api.example.com/data", json_stringify({key: "value"}));

// Full control
let resp = http_request({
  method: "PUT",
  url: "https://api.example.com/users/1",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123"
  },
  body: json_stringify({name: "bob"}),
  timeout: 10000,
  follow_redirects: true,
  verify_ssl: true  // default, set false to skip cert verification
});

URL Utilities

let parts = url_parse("http://user:pass@example.com:8080/path?q=1");
// {scheme: "http", user: "user", pass: "pass", host: "example.com", port: 8080, path: "/path", query: "q=1"}

let url = url_build({scheme: "https", host: "api.example.com", path: "/v1/users"});
// "https://api.example.com/v1/users"

Configuration

net_set_default_timeout(60000);  // 60 second default timeout

Clone this wiki locally