Standard Library
The Epitaph standard library provides built-in functions and importable modules for math, time, colors, formatting, JSON, byte manipulation, string utilities, and list operations.
Built-in functions are always available without any import. Modules require
access <name> at the top of your script.
Built-in Functions
These are available everywhere, no access needed.
concat(a, b) -> text
Concatenates two values as text. Non-text values are stringified.
concat("hp: ", 100) -- "hp: 100"
append(list, ...items) -> list
Appends one or more items to a list in place and returns the list.
let items = [1, 2]
append(items, 3, 4) -- items is now [1, 2, 3, 4]
Errors if the first argument is not a list.
len(collection) -> int
Returns the length of a list, text, map, bytes buffer, or range. Returns 0
for other types.
len([1, 2, 3]) -- 3
len("hello") -- 5
len(0..10) -- 10
int_to_text(n) -> text
Converts an integer to its decimal text representation.
int_to_text(42) -- "42"
math
access <math>
Host-backed module for common math operations.
Functions
| Function | Returns | Description |
|---|---|---|
math.floor(n) | int | Floor of n |
math.ceil(n) | int | Ceiling of n |
math.round(n) | int | Round n to nearest integer |
math.round(n, places) | float | Round n to places decimal digits |
math.abs(n) | int/float | Absolute value (preserves type if int) |
math.clamp(v, lo, hi) | int/float | Clamp v to [lo, hi] (int if all args are int) |
math.sqrt(n) | float | Square root |
math.pow(base, exp) | float | base raised to exp |
math.sin(n) | float | Sine (radians) |
math.cos(n) | float | Cosine (radians) |
math.atan2(y, x) | float | Two-argument arctangent |
math.lerp(a, b, t) | float | Linear interpolation: a + (b - a) * t |
math.rand(min, max) | int | Random integer in [min, max] inclusive |
math.rand_float() | float | Random float in [0, 1) |
Constants
| Name | Value |
|---|---|
math.PI | 3.14159265… |
math.TAU | 6.28318530… |
Examples
access <math>
let angle = math.atan2(dy, dx)
let dist = math.sqrt(dx * dx + dy * dy)
let clamped = math.clamp(hp, 0, max_hp)
let roll = math.rand(1, 6)
time
access <time>
Host-backed module for elapsed time queries.
| Function | Returns | Description |
|---|---|---|
time.now() | float | Seconds elapsed since engine start |
time.ms() | int | Milliseconds elapsed since engine start |
Example
access <time>
phase tick(dt) {
let elapsed = time.now()
when elapsed > 5.0 {
log.info("Five seconds have passed")
}
}
color
access <color>
Host-backed module for creating and using color records.
Functions
| Function | Returns | Description |
|---|---|---|
color.rgb(r, g, b) | record | Color with alpha 255. Args default to 0 if missing. |
color.rgba(r, g, b, a) | record | Color with explicit alpha. a defaults to 255. |
color.hex(s) | record | Parse a CSS-style hex string into the same Color record. |
color.hex accepts:
#RRGGBBorRRGGBB— alpha 255#RRGGBBAAorRRGGBBAA— includes alpha#RGBorRGB— each digit is expanded (e.g.#f0a→ red 255, green 0, blue 170)#RGBAorRGBA— short form with alpha
Leading # is optional; surrounding whitespace is trimmed. Invalid length or digits produce a runtime error.
All constructors return a Color record with fields r, g, b, a (all ints, 0–255).
Preset Colors
| Name | RGBA |
|---|---|
color.White | 255, 255, 255, 255 |
color.Black | 0, 0, 0, 255 |
color.Red | 255, 0, 0, 255 |
color.Green | 0, 255, 0, 255 |
color.Blue | 0, 0, 255, 255 |
color.Yellow | 255, 255, 0, 255 |
color.Cyan | 0, 255, 255, 255 |
color.Magenta | 255, 0, 255, 255 |
color.Transparent | 0, 0, 0, 0 |
Example
access <color>
access <gfx>
let sky = color.rgb(100, 149, 237)
gfx.fill_screen(sky)
let same_sky = color.hex("#6495ed")
gfx.fill_screen(same_sky)
let semi = color.rgba(255, 0, 0, 128)
gfx.fill_rect(10, 10, 50, 50, semi)
let semi_hex = color.hex("#ff000080")
gfx.fill_rect(60, 10, 50, 50, semi_hex)
fmt
access <fmt>
Host-backed module for text formatting.
Functions
| Function | Returns | Description |
|---|---|---|
fmt.str(v) | text | Convert any value to text. Text passes through; others are stringified. |
fmt.join(items, sep?) | text | Join a list into text with sep (default ""). Non-list → empty text. |
fmt.format(template, ...args) | text | Replace {} placeholders with args in order. |
Examples
access <fmt>
fmt.str(42) -- "42"
fmt.join(["a", "b", "c"], ", ") -- "a, b, c"
fmt.format("HP: {}/{}", hp, max_hp) -- "HP: 80/100"
Extra {} placeholders with no matching argument are left as literal {}.
json
access <json>
Host-backed module for JSON encoding and decoding.
Functions
| Function | Returns | Description |
|---|---|---|
json.decode(text) | value | Parse JSON text into an Epitaph value. Returns void on error. |
json.encode(value) | text | Compact JSON string. |
json.encode_pretty(value) | text | Pretty-printed JSON string. |
Type mapping
| Epitaph | JSON |
|---|---|
int, float | number |
bool | boolean |
text | string |
void | null |
list | array |
map, record | object |
symbol | string (:id format) |
bytes | array of ints |
range | [start, end] |
Example
access <json>
let data = json.decode("{\"name\": \"Kite\", \"level\": 5}")
let name = data["name"] -- "Kite"
let out = json.encode({"hp": 100, "items": [1, 2, 3]})
string
access <string>
Pure Epitaph module for string utilities.
| Function | Returns | Description |
|---|---|---|
string.pad_left(s, width, ch) | text | Left-pad s to width with character ch |
string.pad_right(s, width, ch) | text | Right-pad s to width with character ch |
string.repeat(s, count) | text | Repeat s count times |
Examples
access <string>
string.pad_left("42", 5, "0") -- "00042"
string.pad_right("hi", 10, ".") -- "hi........"
string.repeat("ab", 3) -- "ababab"
list
access <list>
Pure Epitaph module for list utilities.
| Function | Returns | Description |
|---|---|---|
list.contains(items, target) | bool | active if target is in items |
list.reverse(items) | list | New list with elements in reverse order |
Examples
access <list>
list.contains([1, 2, 3], 2) -- active
list.contains([1, 2, 3], 9) -- dormant
list.reverse([1, 2, 3]) -- [3, 2, 1]
vec
access <vec>
Pure Epitaph 2D / 3D vectors (fragment Vec2, fragment Vec3 with float
x, y, z). After importing, build values with the module helpers (not
Vec2(x, y) from another sector — that sugar only applies in the same file as
the fragment):
| Function | Returns | Description |
|---|---|---|
vec.vec2(x, y) | Vec2 | New vector |
vec.vec3(x, y, z) | Vec3 | New vector |
Instance / static methods (same naming as other fragments):
| Call | Returns | Description |
|---|---|---|
u.add(v) / Vec2.add(u, v) | Vec2 / Vec3 | Component-wise add |
u.sub(v) | Vec2 / Vec3 | Component-wise subtract |
u.scale(s) | Vec2 / Vec3 | Multiply each component by s |
u.dot(v) | num | Dot product |
u.len_sq() | num | Squared Euclidean length |
u.cross(v) (Vec3 only) | Vec3 | Cross product |
Examples
access <vec>
let u = vec.vec2(3.0, 4.0)
let v = vec.vec2(1.0, 0.0)
let w = Vec2.add(u, v) -- static call; same-sector code can use u.add(v)
let d = u.x + u.y -- 7.0
bytes
access <bytes>
Host-backed module for creating and manipulating binary data. Provides buffer constructors, slicing, encoding/decoding, and structured reader/writer APIs.
Buffer Creation
| Function | Returns | Description |
|---|---|---|
bytes.new(size) | bytes | Zero-filled buffer of size bytes. Default 0. |
bytes.from_list(list) | bytes | Buffer from a list of ints (each truncated to u8) |
bytes.to_list(buf) | list | List of ints, one per byte |
bytes.slice(buf, start?, end?) | bytes | Sub-buffer. Bounds are clamped. Defaults: start=0, end=len. |
bytes.from_text(text) | bytes | UTF-8 encoded bytes |
bytes.to_text(buf) | text | Decode UTF-8. Errors if bytes are not valid UTF-8. |
Examples
access <bytes>
let buf = bytes.new(4)
buf[0] = 72 -- 'H'
buf[1] = 105 -- 'i'
let greeting = bytes.from_text("Hello")
let back = bytes.to_text(greeting) -- "Hello"
let nums = bytes.from_list([10, 20, 30])
let mid = bytes.slice(nums, 1, 3) -- [20, 30]
ByteReader
A cursor-based reader for structured binary data. Reads advance the position automatically.
Creating a Reader
let reader = bytes.reader(buf)
Returns a record with fields buf (the byte buffer) and pos (current read
position, starts at 0).
Read Functions
All read functions take the reader as the first argument and advance pos.
Functions with a _le suffix read in little-endian byte order. Without a suffix,
the default is big-endian.
| Function | Returns | Description |
|---|---|---|
bytes.read_u8(r) | int | Read 1 byte (0–255) |
bytes.read_i16(r) | int | Read signed 16-bit integer (big-endian) |
bytes.read_i16_le(r) | int | Read signed 16-bit integer (little-endian) |
bytes.read_i32(r) | int | Read signed 32-bit integer (big-endian) |
bytes.read_i32_le(r) | int | Read signed 32-bit integer (little-endian) |
bytes.read_i64(r) | int | Read signed 64-bit integer (big-endian) |
bytes.read_i64_le(r) | int | Read signed 64-bit integer (little-endian) |
bytes.read_f32(r) | float | Read 32-bit float (big-endian) |
bytes.read_f32_le(r) | float | Read 32-bit float (little-endian) |
bytes.read_f64(r) | float | Read 64-bit float (big-endian) |
bytes.read_f64_le(r) | float | Read 64-bit float (little-endian) |
bytes.read_bytes(r, n) | bytes | Read n raw bytes |
bytes.read_text(r, n) | text | Read n bytes as UTF-8 text |
bytes.remaining(r) | int | Bytes left to read |
Reads error at runtime if there aren’t enough bytes remaining.
Example
access <bytes>
let data = bytes.from_list([0, 42, 0, 1, 0, 100])
let r = bytes.reader(data)
let header = bytes.read_u8(r) -- 0
let id = bytes.read_u8(r) -- 42
let value = bytes.read_i32(r) -- 65636 (big-endian)
let left = bytes.remaining(r) -- 0
ByteWriter
A growable buffer writer for building binary data.
Creating a Writer
let writer = bytes.writer()
Returns a record with a buf field (initially empty bytes buffer).
Write Functions
All write functions take the writer as the first argument and append bytes.
Functions with a _le suffix write in little-endian byte order. Without a
suffix, the default is big-endian.
| Function | Args | Description |
|---|---|---|
bytes.write_u8(w, val) | int | Write 1 byte (truncated to u8) |
bytes.write_i16(w, val) | int | Write signed 16-bit (big-endian) |
bytes.write_i16_le(w, val) | int | Write signed 16-bit (little-endian) |
bytes.write_i32(w, val) | int | Write signed 32-bit (big-endian) |
bytes.write_i32_le(w, val) | int | Write signed 32-bit (little-endian) |
bytes.write_i64(w, val) | int | Write signed 64-bit (big-endian) |
bytes.write_i64_le(w, val) | int | Write signed 64-bit (little-endian) |
bytes.write_f32(w, val) | float | Write 32-bit float (big-endian) |
bytes.write_f32_le(w, val) | float | Write 32-bit float (little-endian) |
bytes.write_f64(w, val) | float | Write 64-bit float (big-endian) |
bytes.write_f64_le(w, val) | float | Write 64-bit float (little-endian) |
bytes.write_bytes(w, src) | bytes | Append raw bytes |
bytes.write_text(w, text) | text | Append UTF-8 bytes |
All write functions return void.
Example
access <bytes>
let w = bytes.writer()
bytes.write_u8(w, 0x01)
bytes.write_i32(w, 1000)
bytes.write_text(w, "OK")
let result = w.buf -- the built buffer
let size = result.len -- 7 (1 + 4 + 2)
Roundtrip Example
access <bytes>
-- Write structured data
let w = bytes.writer()
bytes.write_u8(w, 1) -- version
bytes.write_i32(w, 320) -- width
bytes.write_i32(w, 240) -- height
bytes.write_text(w, "hello") -- payload
-- Read it back
let r = bytes.reader(w.buf)
let version = bytes.read_u8(r) -- 1
let width = bytes.read_i32(r) -- 320
let height = bytes.read_i32(r) -- 240
let payload = bytes.read_text(r, 5) -- "hello"