Standard Library (Std)
Std is the one library that never needs to be named — Std.pln(...) and pln(...) call the same function. Every other library needs its own prefix (Time.now(), Http.fetch(...)), but anything shown here works bare.
One distinction worth keeping straight: this page is about library functions, which never need a receiver. A document function always does — self.myFunc(), super.myFunc(), through a variable, whatever fits — because it belongs to a specific node in the graph, not to the runtime itself.
Output
pln(..) -> void
Prints all arguments to the standard output stream — this is what every other page's examples have been using the whole time.
#[main]
fn main() {
pln("hello, world");
}err(..) -> void
Same as pln, but to the error stream instead of standard output.
#[main]
fn main() {
err("hello, world");
}str(..) -> str
Formats all arguments the same way pln would, but returns the result as a string instead of printing it.
#[main]
fn main() {
pln(str("hello, world"));
}dbg(..) -> void
Prints all arguments as debug output. Depending on how the host environment wires up logging, this may not surface in every output stream pln does — worth checking your browser console too if it looks quiet.
#[main]
fn main() {
dbg("hello, world");
}Logging
The five log_* functions map onto the standard levels from Rust's log crate. Same caveat as dbg — whether these are visible depends on what the host has configured to receive them.
log_info(..) -> void
#[main]
fn main() {
log_info("we just did something cool");
}log_debug(..) -> void
#[main]
fn main() {
log_debug("this is what just happened, in case you need to debug me");
}log_warn(..) -> void
#[main]
fn main() {
log_warn("we encountered something, but are handling it");
}log_error(..) -> void
#[main]
fn main() {
log_error("we have a problem");
}log_trace(..) -> void
#[main]
fn main() {
log_trace("tracing through this code path");
}Debugging & Tracing
Three deeper introspection tools — expect dense, VM-level output that looks different every run. That's normal; these aren't meant to be tidy.
peek(..) -> void
Prints your arguments, then a trace of the current process and the next instructions about to execute. Pass a trailing integer to control how many.
#[main]
fn main() {
peek("getting here");
}trace(..) -> void
Like peek, but shows the instructions that already ran leading up to this point, instead of what's coming next.
#[main]
fn main() {
trace("getting here");
}dbg_tracestack() -> void
Prints a snapshot of the current stack, with no arguments needed.
#[main]
fn main() {
dbg_tracestack();
}Assertions
Every one of these throws when the check fails — wrap the failing case in try/catch if you want execution to continue past it, same as anywhere else in Stof.
assert(value: unknown = false) -> void
#[main]
fn main() {
assert(true);
pln('first assertion passed');
try { assert(false); }
catch { pln('assert(false) throws'); }
}assert_eq(first: unknown, second: unknown) -> void
#[main]
fn main() {
assert_eq('a', 'a');
pln('equal values pass');
try { assert_eq(43, 42); }
catch { pln('unequal values throw'); }
}assert_neq(first: unknown, second: unknown) -> void
#[main]
fn main() {
assert_neq('a', 'b');
pln('different values pass');
try { assert_neq(34, 34); }
catch { pln('equal values throw'); }
}assert_not(value: unknown = true) -> void
#[main]
fn main() {
assert_not(false);
pln('falsy value passes');
try { assert_not(true); }
catch { pln('truthy value throws'); }
}Errors & Process Control
throw(value: unknown = "Error") -> void
Throws any value, not just a string — the mechanism behind every try/catch example on the Error Handling page.
#[main]
fn main() {
try { throw("error message"); }
catch (msg: str) { pln('caught:', msg); }
}exit(..) -> void
Immediately halts the current process — nothing after it in the same process runs. Pass a promise to terminate that process's execution instead of the current one.
#[main]
fn main() {
pln('before exit');
exit();
pln('after exit — never runs');
}Parsing & Exporting
parse(source: str | blob, context: str | obj = self, format: str = "stof", profile: str = "prod") -> bool
Covered in depth on Import & Export — the runtime counterpart to the import statement, and it works fine sandboxed since it's just operating on a string.
#[main]
fn main() {
parse('fn hello() -> str { "hello" }');
pln(self.hello());
}stringify(format: str = "json", context: obj = null) -> str
Exports a string. Lossy for anything the target format can't represent — JSON has no concept of units, for instance, so they're silently dropped.
#[main]
fn main() {
const object = new { x: 3.14km, y: 42m };
pln(stringify("json", object));
}blobify(format: str = "json", context: obj = null) -> blob
Same idea as stringify, exporting raw bytes instead of a string.
#[main]
fn main() {
const object = new { x: 3.14km, y: 42m };
const bytes = blobify("json", object);
pln(bytes.len() > 0, bytes as str);
}Constructing Collections
Mostly useful for building an empty collection, or building one from values you already have in hand rather than writing out literal syntax.
list(..) -> list
#[main]
fn main() {
pln(list(1, 2, 3));
}map(..) -> map
Takes tuples of key/value pairs.
#[main]
fn main() {
pln(str(map(("a", 1), ("b", 2))));
}set(..) -> set
#[main]
fn main() {
pln(str(set(1, 2, 3)));
}Values & Memory
copy(val: unknown) -> unknown
A real deep copy — for an object, every field, function, and nested piece of data underneath it, recursively.
#[main]
fn main() {
const a = {1, 2, 3};
const b = copy(a);
b.clear();
pln(str(a), str(b));
}swap(first: unknown, second: unknown) -> void
Swaps two values in place — needs & on value types, the same reference operator from Variables & References, since a plain value type would just swap two disposable copies.
#[main]
fn main() {
let a = 42;
let b = -55;
swap(&a, &b);
pln(a, b);
}min(..) -> unknown
If an argument is a collection, only the values inside it are considered — not the collection as a whole.
#[main]
fn main() {
pln(str(min(1km, 2m, 3mm)));
}max(..) -> unknown
#[main]
fn main() {
pln(str(max(1km, 2m, 3mm)));
}drop(..) -> bool | list
Covered in depth on Document Memory Management — drops fields, functions, objects, or data from the graph, running #[dropped] on anything that has it. Given several things to drop at once, returns a list of one bool per item.
#[main]
fn main() {
const func = () => {};
const object = new {};
self.field = 42;
const results = drop("self.field", func, object);
pln(results);
}shallow_drop(..) -> bool | list
Same as drop, except if a dropped field pointed to an object, only the field is removed — the object itself stays in the graph. Useful when more than one field might point at the same object and you only want to disconnect this one.
#[main]
fn main() {
self.field = new {};
const object = self.field;
shallow_drop("self.field");
pln(self.contains("field"), object.exists());
}Introspection
Mostly useful for writing code that has to reason about the document generically — a validator, a debugging tool, anything that can't assume a fixed shape ahead of time.
funcs(attributes: str | list | set = null) -> list
Every function in the graph, optionally filtered to only those carrying a given attribute.
#[main]
#[main_or_test]
fn main() {
pln(funcs("main_or_test").len());
}callstack() -> list
A list of function pointers currently on the stack — last one is this, the function calling callstack() itself.
fn inner() {
for (const func in callstack()) {
pln(func.obj().path(), func.name());
}
}
#[main]
fn main() {
self.inner();
}graph_id() -> str
A unique ID string for this graph/document.
#[main]
fn main() {
pln(graph_id().len() > 10);
}format(format: str) -> bool
Is a given format loaded and available for parse/stringify/blobify?
#[main]
fn main() {
pln(format("json"), format("docx"));
}format_content_type(format: str) -> str
The HTTP content-type header value for a loaded format — useful if you're serving Stof-produced data over the wire.
#[main]
fn main() {
pln(format_content_type("json"));
}formats() -> set
Every format currently available to use.
#[main]
fn main() {
pln(formats().contains("json"));
}lib(lib: str, func?: str) -> bool
Is a library — and optionally a specific function on it — loaded?
#[main]
fn main() {
pln(lib("Std"), lib("Render"));
pln(lib("Num", "abs"));
}libs() -> set
Every library currently available.
#[main]
fn main() {
pln(libs().superset({"Std", "Fn", "Num", "Set"}));
}prof(name: str) -> bool
Was this graph parsed under a given profile name? Profiles are how parse's profile parameter tags a parse for later inspection.
#[main]
fn main() {
pln(prof('test'), prof('prod'));
}Environment
These four require the system feature flag, which reads and writes real process environment variables — not something a browser sandbox can meaningfully do, so these are reference-only rather than runnable here.
const host = env("HOST");
const vars: map = env_vars();
set_env("HOST", "localhost");
remove_env("HOST");
Timing
sleep(time: ms) -> void
Pauses the current process for at least the given duration — possibly longer, never less. Other processes keep making progress while this one sleeps, same cooperative model as Async.
#[main]
fn main() {
const start = Time.now();
sleep(50ms);
pln(Time.diff(start) >= 50);
}IDs & Text Helpers
nanoid(length: int = 21) -> str
A URL-safe random ID — collision probability is low and drops further as length increases.
#[main]
fn main() {
pln(nanoid() != nanoid(33));
}xml(text: str, tag: str) -> str
Wraps a string in an XML tag — a small formatting helper, nothing more.
#[main]
fn main() {
pln(xml("hello, world", "msg"));
}prompt(text: str = '', tag?: str) -> prompt
Builds a prompt value — a small tree of tagged text, meant for assembling structured prompts for AI contexts. += appends a nested prompt as a child.
#[main]
fn main() {
let p = prompt(tag = 'instruction');
p += prompt('do a thing', 'sub');
p += prompt('another thing', 'sub');
pln(p as str);
}