Skip to content

Built-in Methods

Built-in methods use dot notation on values. The compiler translates each method call directly into a dedicated VM opcode — there is no function call overhead at runtime.

Array

arr.len()

Returns the number of elements as i32.

var arr: array<number> = [1.0, 2.0, 3.0]
var n: i32 = arr.len()   # 3

arr.push(value)

Appends an element to the end. Returns void.

var arr: array<number> = [1.0]
arr.push(2.0)
arr.push(3.0)
assert(arr.len() == 3)

Typed arrays enforce element type at runtime:

var nums: array<i32> = [1, 2]
nums.push(3)       # OK
nums.push("hello") # runtime error: type mismatch

arr.pop()

Removes and returns the last element. Raises an error if the array is empty.

var arr: array<number> = [1.0, 2.0, 3.0]
var last: number = arr.pop()   # 3.0
assert(arr.len() == 2)

arr.contains(value)

Returns true if the array contains the value, false otherwise. Comparison is by value for primitives and strings, by reference for structs.

var arr: array<number> = [1.0, 2.0, 3.0]
assert(arr.contains(2.0) == true)
assert(arr.contains(99.0) == false)

arr.indexOf(value)

Returns the index of the first match as i32, or -1 if not found.

var arr: array<number> = [10.0, 20.0, 30.0]
assert(arr.indexOf(20.0) == 1)
assert(arr.indexOf(99.0) == -1)

arr.slice(start, end)

Returns a new array containing elements from start (inclusive) to end (exclusive). Out-of-bounds indices are clamped.

var arr: array<number> = [1.0, 2.0, 3.0, 4.0, 5.0]
var mid: array = arr.slice(1, 4)   # [2.0, 3.0, 4.0]
assert(mid.len() == 3)

arr.sort()

Sorts the array in place. Works with number, i32, and string arrays.

var nums: array<i32> = [3, 1, 4, 1, 5]
nums.sort()
assert(nums[0] == 1)
assert(nums[4] == 5)

var words: array<string> = ["banana", "apple", "cherry"]
words.sort()
assert(words[0] == "apple")

arr.reverse()

Reverses the array in place.

var arr: array<i32> = [1, 2, 3]
arr.reverse()
assert(arr[0] == 3)
assert(arr[2] == 1)

arr.join(separator)

Joins all elements into a string with the given separator.

var arr: array<string> = ["a", "b", "c"]
var result: string = arr.join(", ")
assert(result == "a, b, c")

var nums: array<i32> = [1, 2, 3]
var s: string = nums.join("-")
assert(s == "1-2-3")

String

str.len()

Returns the byte length as i32.

var s: string = "hello"
assert(s.len() == 5)

str.upper()

Returns a new string with all ASCII letters uppercased.

var s: string = "Hello World"
assert(s.upper() == "HELLO WORLD")

str.lower()

Returns a new string with all ASCII letters lowercased.

var s: string = "Hello World"
assert(s.lower() == "hello world")

str.trim()

Returns a new string with leading and trailing whitespace removed (space, tab, newline, carriage return).

var s: string = "  hello  "
assert(s.trim() == "hello")

str.contains(substring)

Returns true if the string contains the substring, false otherwise.

var s: string = "Hello World"
assert(s.contains("World") == true)
assert(s.contains("xyz") == false)

str.indexOf(substring)

Returns the byte index of the first occurrence as i32, or -1 if not found.

var s: string = "Hello World"
assert(s.indexOf("World") == 6)
assert(s.indexOf("xyz") == -1)

str.split(separator)

Splits the string by the separator and returns an array<string>.

var csv: string = "a,b,c"
var parts: array = csv.split(",")
assert(parts.len() == 3)
assert(parts[0] == "a")
assert(parts[1] == "b")
assert(parts[2] == "c")

str.substring(start, end)

Returns a new string from byte index start (inclusive) to end (exclusive).

var s: string = "Hello World"
assert(s.substring(0, 5) == "Hello")
assert(s.substring(6, 11) == "World")

Dict

dict.len()

Returns the number of key-value pairs as i32.

var d: dict = {"a": 1, "b": 2}
assert(d.len() == 2)

dict.has(key)

Returns true if the key exists, false otherwise.

var d: dict = {"host": "localhost", "port": 8080}
assert(d.has("host") == true)
assert(d.has("missing") == false)

dict.keys()

Returns all keys as a typed array. If the dict is dict<string, i32>, keys returns array<string>.

var d: dict<string, i32> = {"x": 10, "y": 20}
var k: array<string> = d.keys()
assert(k.len() == 2)

dict.values()

Returns all values as a typed array. If the dict is dict<string, i32>, values returns array<i32>.

var d: dict<string, i32> = {"x": 10, "y": 20}
var v: array<i32> = d.values()
assert(v.len() == 2)

dict.delete(key)

Removes a key-value pair. Returns void. No error if the key doesn't exist.

var d: dict = {"a": 1, "b": 2, "c": 3}
d.delete("b")
assert(d.len() == 2)
assert(d.has("b") == false)

Native Modules

Native modules provide additional functions implemented in C. They are imported like regular modules but dispatch via OP_NATIVE_CALL with zero overhead.

math

Requires require ../dev-libs/math v0.1 in star.mod.

import "math"

var x: number = math.sqrt(16.0)    # 4.0
var y: number = math.pow(2.0, 10.0) # 1024.0
Function Args Returns Description
sin(x) number number Sine
cos(x) number number Cosine
tan(x) number number Tangent
sqrt(x) number number Square root
abs(x) number number Absolute value
floor(x) number number Round down
ceil(x) number number Round up
round(x) number number Round to nearest
pow(base, exp) number, number number Exponentiation
log(x) number number Natural logarithm
log10(x) number number Base-10 logarithm
atan2(y, x) number, number number Two-argument arctangent
min(a, b) number, number number Minimum
max(a, b) number, number number Maximum

See Modules for how to create native modules.

Global Functions

console.log(value)

Prints a value to stdout followed by a newline. Accepts any type. In release mode (compile), console.log calls are stripped entirely — zero bytecode, zero overhead.

console.log("hello")
console.log(42)
console.log(true)

runtime.guard(opcodes, heap_bytes, max_event_chain)

Configures VM safety guards. Each parameter sets a hard limit — exceeding it traps the script with a runtime error. The VM itself stays alive; only the script is killed.

runtime.guard(100000, 65536, 10)
Parameter Type Description
opcodes number Max VM instructions before trap. Catches infinite loops.
heap_bytes number Max heap allocation in bytes. Catches memory exhaustion.
max_event_chain number Max consecutive same-type event emits during a handler. Catches event storms.

A value of 0 means unlimited — that specific guard is disabled. All three default to 0 (no limits). Set only the guards you need:

runtime.guard(50000, 0, 0)     # opcode budget only
runtime.guard(0, 32768, 0)     # heap limit only
runtime.guard(0, 0, 5)         # event storm only
runtime.guard(10000, 4096, 3)  # all three active

Guard errors are catchable with try:

runtime.guard(5000, 0, 0)
var r: result<void> = try dangerousWork()
if (r.failed) {
    console.log(r.error)    # "opcode budget exceeded"
}

When a guard trips:

Guard Error Behavior
Opcode budget opcode budget exceeded Script halts after N instructions without yield
Heap limit heap limit exceeded GC runs first — if still over limit, script halts
Event chain event storm detected Script halts when handler emits same event type N times

If no try frame is on the call stack, the guard kills the script entirely.

Host-side Guards (Floor)

From C (host/firmware side), use star_vm_configure_guards() to set immutable floor values:

star_vm_configure_guards(vm, 100000, 65536, 10);

Host guards act as a floor — scripts can tighten limits but never loosen them:

# host set: 100000 opcodes, 65536 heap, 10 event chain
runtime.guard(50000, 0, 0)     # OK — tighter than host (50k < 100k)
runtime.guard(200000, 0, 0)    # clamped to 100000 — cannot exceed host floor
runtime.guard(0, 0, 0)         # ignored — 0 means "don't change", host floor stays

Guards apply in both debug and release mode. On ESP32, the host sets floor values before loading any script — a runaway script must not kill the firmware.

gc.collect()

Triggers a manual garbage collection cycle. Normally the GC runs automatically, but this can be used for deterministic memory management in performance-critical sections.

gc.collect()

toStr(value)

Converts any value to its string representation.

var s: string = toStr(42)
assert(s == "42")

var b: string = toStr(true)
assert(b == "true")

toNum(value)

Converts a string to a number. Returns 0 for non-numeric strings. Bools convert to 1/0.

var n: number = toNum("3.14")
assert(n == 3.14)

var b: number = toNum(true)
assert(b == 1)

Summary

Type Method Args Returns Description
array len() i32 Element count
array push(val) 1 void Append element
array pop() element Remove and return last
array contains(val) 1 bool Value existence check
array indexOf(val) 1 i32 First index or -1
array slice(start, end) 2 array Sub-array (new copy)
array sort() void Sort in place
array reverse() void Reverse in place
array join(sep) 1 string Join elements with separator
string len() i32 Byte length
string upper() string ASCII uppercase
string lower() string ASCII lowercase
string trim() string Strip whitespace
string contains(sub) 1 bool Substring check
string indexOf(sub) 1 i32 First byte index or -1
string split(sep) 1 array<string> Split by separator
string substring(s, e) 2 string Sub-string by byte index
dict len() i32 Pair count
dict has(key) 1 bool Key existence check
dict keys() array<K> All keys (typed)
dict values() array<V> All values (typed)
dict delete(key) 1 void Remove key-value pair
toStr(val) 1 string Convert any value to string
toNum(val) 1 number Convert to number
console.log(val) 1 void Print value (debug only)
runtime.guard(ops, heap, chain) 3 void Set VM safety limits (0 = unlimited)
gc.collect() void Manual GC cycle