This page is an English fallback. I have not published a Français translation of this generated reference yet.

My Standard Library Reference#

I provide these built-in functions. They are my core capabilities, available to you without external modules.

This file and my builtin registry in src/builtins_registry.c are kept in step mechanically: tests/check_stdlib_docs.sh (run by make check-stdlib-docs, make test-quick, and make test) fails when a builtin has no ### entry here, when an entry names a builtin I no longer have, or when a ## Section (N) count disagrees with the entries beneath it.


Core I/O (3)#

I print a value without a trailing newline.

(print "Hello")
(print 42)
(print 3.14)

println(value: any) -> void#

I print a value with a trailing newline. I am polymorphic — I work with int, float, string, and bool.

(println "Hello, World!")
(println 42)
(println true)

range(start: int, end: int) -> iterator#

I provide this special function for use only in for loops. I create an iterator from start (inclusive) to end (exclusive).

for i in (range 0 10) {
    (println i)  # Prints 0, 1, 2, ..., 9
}
for i in (range 5 8) {
    (println i)  # Prints 5, 6, 7
}

I only allow range to be used in for-loop contexts.


Math (20)#

abs(x: int) -> int#

I return the absolute value of an integer.

(abs -5)    # Returns 5
(abs 5)     # Returns 5
(abs 0)     # Returns 0

min(a: int, b: int) -> int#

I return the minimum of two integers.

(min 5 10)   # Returns 5
(min -3 0)   # Returns -3
(min 7 7)    # Returns 7

max(a: int, b: int) -> int#

I return the maximum of two integers.

(max 5 10)   # Returns 10
(max -3 0)   # Returns 0
(max 7 7)    # Returns 7

sqrt(x: float) -> float#

I return the square root of x.

(sqrt 16.0)   # Returns 4.0
(sqrt 2.0)    # Returns 1.41421...
(sqrt 9.0)    # Returns 3.0

pow(base: float, exponent: float) -> float#

I return base raised to the power of exponent.

(pow 2.0 3.0)    # Returns 8.0
(pow 5.0 2.0)    # Returns 25.0
(pow 2.0 -1.0)   # Returns 0.5

floor(x: float) -> float#

I return the largest integer ≤ x as a float.

(floor 3.7)    # Returns 3.0
(floor 3.2)    # Returns 3.0
(floor -2.3)   # Returns -3.0

ceil(x: float) -> float#

I return the smallest integer ≥ x as a float.

(ceil 3.2)    # Returns 4.0
(ceil 3.7)    # Returns 4.0
(ceil -2.7)   # Returns -2.0

round(x: float) -> float#

I round to the nearest integer as a float.

(round 3.4)   # Returns 3.0
(round 3.6)   # Returns 4.0
(round 3.5)   # Returns 4.0

sin(x: float) -> float#

I return the sine of x in radians.

(sin 0.0)      # Returns 0.0
(sin 1.5708)   # Returns ≈1.0 (π/2)
(sin 3.14159)  # Returns ≈0.0 (π)

cos(x: float) -> float#

I return the cosine of x in radians.

(cos 0.0)      # Returns 1.0
(cos 3.14159)  # Returns ≈-1.0 (π)
(cos 1.5708)   # Returns ≈0.0 (π/2)

tan(x: float) -> float#

I return the tangent of x in radians.

(tan 0.0)     # Returns 0.0
(tan 0.7854)  # Returns ≈1.0 (π/4)
(tan 1.0)     # Returns ≈1.5574

atan2(y: float, x: float) -> float#

I return the angle in radians between the positive x-axis and the point (x, y). I handle the quadrant correctly, unlike atan.

(atan2 1.0 1.0)    # Returns ≈0.7854 (π/4, first quadrant)
(atan2 1.0 -1.0)   # Returns ≈2.3562 (3π/4, second quadrant)
(atan2 0.0 1.0)    # Returns 0.0

asin(x: float) -> float#

I return the arcsine of x (inverse sine) in radians. My domain is -1.0 to 1.0 and I return values in -π/2 to π/2.

(asin 1.0)    # Returns ≈1.5708 (π/2)
(asin 0.5)    # Returns ≈0.5236 (π/6)
(asin 0.0)    # Returns 0.0

acos(x: float) -> float#

I return the arccosine of x (inverse cosine) in radians. My domain is -1.0 to 1.0 and I return values in 0 to π.

(acos 1.0)    # Returns 0.0
(acos 0.0)    # Returns ≈1.5708 (π/2)
(acos -1.0)   # Returns ≈3.14159 (π)

atan(x: float) -> float#

I return the arctangent of x (inverse tangent) in radians. I return values in -π/2 to π/2.

(atan 1.0)    # Returns ≈0.7854 (π/4)
(atan 0.0)    # Returns 0.0
(atan -1.0)   # Returns ≈-0.7854 (-π/4)

log(x: float) -> float#

I return the natural logarithm (base e) of x.

(log 1.0)    # Returns 0.0
(log 2.718)  # Returns ≈1.0
(log 10.0)   # Returns ≈2.303

log2(x: float) -> float#

I return the base-2 logarithm of x.

(log2 1.0)   # Returns 0.0
(log2 2.0)   # Returns 1.0
(log2 8.0)   # Returns 3.0

log10(x: float) -> float#

I return the base-10 logarithm of x.

(log10 1.0)    # Returns 0.0
(log10 10.0)   # Returns 1.0
(log10 100.0)  # Returns 2.0

exp(x: float) -> float#

I return e raised to the power of x.

(exp 0.0)   # Returns 1.0
(exp 1.0)   # Returns ≈2.71828
(exp 2.0)   # Returns ≈7.389

fmod(x: float, y: float) -> float#

I return the floating-point remainder of x divided by y.

(fmod 5.5 2.0)    # Returns 1.5
(fmod 10.0 3.0)   # Returns 1.0
(fmod -5.5 2.0)   # Returns -1.5

Type Casting and Conversion (13)#

cast_int(value: any) -> int#

I cast any value to an integer. I truncate floats and parse strings.

(cast_int 3.14)   # Returns 3
(cast_int "42")   # Returns 42
(cast_int true)   # Returns 1

cast_float(value: any) -> float#

I cast any value to a float. I parse strings and convert integers.

(cast_float 42)      # Returns 42.0
(cast_float "3.14")  # Returns 3.14
(cast_float false)   # Returns 0.0

float_from_bits(bits: int) -> float#

I copy the 64 bits of my signed integer operand into a binary64 value.

float_to_bits(value: float) -> int#

I copy the 64 bits of my binary64 operand into a signed integer. The following representation and name-resolution rules apply to both operations.

I copy an exact binary64 representation. My signed integer carries the same 64 bits in two's-complement form; I do not perform a numeric cast. I preserve both zeros and every quiet/signaling NaN payload and sign, and evaluate my operand once. I require the exact declared input type.

assert (== (float_to_bits (float_from_bits 1)) 1)

I reserve these names against ordinary function redeclarations. My current source profile refuses calls through same-named local/global bindings instead of substituting intrinsic semantics. See my transport contract for backend and reconstruction boundaries.

cast_bool(value: any) -> bool#

I cast any value to a boolean. I treat 0, empty string, and null as false; everything else becomes true.

(cast_bool 1)       # Returns true
(cast_bool 0)       # Returns false
(cast_bool "hello") # Returns true
(cast_bool "")      # Returns false

cast_string(value: any) -> string#

I cast any value to its string representation.

(cast_string 42)    # Returns "42"
(cast_string 3.14)  # Returns "3.14"
(cast_string true)  # Returns "true"

to_string(value: any) -> string#

I convert any value to its string representation. I am an alias for cast_string.

(to_string 99)      # Returns "99"
(to_string false)   # Returns "false"
(to_string 1.5)     # Returns "1.5"

int_to_string(n: int) -> string#

I convert an integer to its string representation.

(int_to_string 42)    # Returns "42"
(int_to_string 0)     # Returns "0"
(int_to_string -100)  # Returns "-100"

float_to_string(f: float) -> string#

I convert a float to its string representation.

(float_to_string 3.14)   # Returns "3.14"
(float_to_string 0.0)    # Returns "0.0"
(float_to_string -1.5)   # Returns "-1.5"

bool_to_string(b: bool) -> string#

I convert a boolean to its string representation.

(bool_to_string true)   # Returns "true"
(bool_to_string false)  # Returns "false"

string_to_int(s: string) -> int#

I parse a string to an integer. I return 0 if the string cannot be parsed.

(string_to_int "42")     # Returns 42
(string_to_int "-100")   # Returns -100
(string_to_int "abc")    # Returns 0

string_to_float(s: string) -> float#

I parse a string to a float. I return 0.0 if the string cannot be parsed.

(string_to_float "3.14")  # Returns 3.14
(string_to_float "1e-3")  # Returns 0.001
(string_to_float "bad")   # Returns 0.0

null_opaque() -> opaque#

I return a null opaque handle. I am useful as a sentinel value when working with foreign function interfaces.

let handle: opaque = (null_opaque)

String Operations (20)#

str_length(s: string) -> int#

I return the length of a string in bytes.

(str_length "Hello")   # Returns 5
(str_length "")        # Returns 0
(str_length "abc")     # Returns 3

str_concat(s1: string, s2: string) -> string#

I concatenate two strings and return a new string.

(str_concat "Hello" " World")   # Returns "Hello World"
(str_concat "foo" "bar")        # Returns "foobar"
(str_concat "" "test")          # Returns "test"

str_substring(s: string, start: int, length: int) -> string#

I extract a substring starting at start with the given length. The index is 0-based. I return until the end of the string if start + length exceeds the string length. I return an empty string if start is out of bounds.

(str_substring "Hello, World!" 0 5)   # Returns "Hello"
(str_substring "Hello, World!" 7 5)   # Returns "World"
(str_substring "Hello" 2 100)         # Returns "llo"

str_contains(s: string, substr: string) -> bool#

I return true if string s contains substring substr.

(str_contains "The quick brown fox" "quick")   # Returns true
(str_contains "The quick brown fox" "slow")    # Returns false
(str_contains "hello" "")                      # Returns true

str_equals(s1: string, s2: string) -> bool#

I return true if both strings are exactly equal.

(str_equals "Hello" "Hello")   # Returns true
(str_equals "Hello" "World")   # Returns false
(str_equals "" "")             # Returns true

char_at(s: string, index: int) -> int#

I return the ASCII value of the character at the specified 0-based index. I terminate with an error if the index is out of bounds.

(char_at "Hello" 0)   # Returns 72 ('H')
(char_at "Hello" 1)   # Returns 101 ('e')
(char_at "Hello" 4)   # Returns 111 ('o')

string_from_char(c: int) -> string#

I create a single-character string from an ASCII value.

(string_from_char 65)   # Returns "A"
(string_from_char 90)   # Returns "Z"
(string_from_char 48)   # Returns "0"

str_starts_with(s: string, prefix: string) -> bool#

I return true if s begins with prefix. Every string starts with the empty prefix.

(str_starts_with "hello world" "hello")   # Returns true
(str_starts_with "hello" "world")         # Returns false
(str_starts_with "hello" "")              # Returns true

str_ends_with(s: string, suffix: string) -> bool#

I return true if s ends with suffix. Every string ends with the empty suffix.

(str_ends_with "hello.nano" ".nano")   # Returns true
(str_ends_with "hello.nano" ".c")      # Returns false
(str_ends_with "abc" "xabc")           # Returns false (suffix is longer)

str_index_of(haystack: string, needle: string) -> int#

I return the byte index of the first occurrence of needle in haystack, or -1 if it does not occur. I return 0 for an empty needle.

(str_index_of "hello" "e")           # Returns 1
(str_index_of "hello world" "world") # Returns 6
(str_index_of "hello" "x")           # Returns -1

str_last_index_of(haystack: string, needle: string) -> int#

I return the byte index of the last occurrence, including overlapping matches, or -1 when no match exists. An empty needle matches at the byte length. I search NUL-terminated strings, not Unicode character positions.

(str_last_index_of "ababa" "aba") # Returns 2
(str_last_index_of "abc" "")      # Returns 3
(str_last_index_of "abc" "x")     # Returns -1

str_trim(s: string) -> string#

I return a copy of s with leading and trailing whitespace (space, tab, newline, carriage return) removed.

(str_trim "  hello  ")     # Returns "hello"
(str_trim "\n line \t")    # Returns "line"

str_trim_left(s: string) -> string#

I return a copy of s with leading whitespace removed.

(str_trim_left "  hello  ")   # Returns "hello  "

str_trim_right(s: string) -> string#

I return a copy of s with trailing whitespace removed.

(str_trim_right "  hello  ")   # Returns "  hello"

str_to_lower(s: string) -> string#

I return a copy of s with every ASCII A–Z character lowercased. I leave all other bytes, including non-ASCII UTF-8 sequences, untouched.

(str_to_lower "HELLO")         # Returns "hello"
(str_to_lower "Hello World")   # Returns "hello world"

str_to_upper(s: string) -> string#

I return a copy of s with every ASCII a–z character uppercased. I leave all other bytes, including non-ASCII UTF-8 sequences, untouched.

(str_to_upper "hello")         # Returns "HELLO"
(str_to_upper "Hello World")   # Returns "HELLO WORLD"

str_replace(s: string, old: string, new: string) -> string#

I replace every occurrence of old in s with new. I return s unchanged when old is empty or does not occur.

(str_replace "hello world" "world" "nano")   # Returns "hello nano"
(str_replace "aaa" "a" "b")                  # Returns "bbb"
(str_replace "hello" "xyz" "abc")            # Returns "hello"

str_split(s: string, delimiter: string) -> array<string>#

I split s on every occurrence of delimiter and return the pieces. A string with no delimiter yields a one-element array. An empty delimiter splits into single characters.

let parts: array<string> = (str_split "a,b,c" ",")
(array_length parts)   # Returns 3
(at parts 0)           # Returns "a"

(array_length (str_split "hello" ","))   # Returns 1

str_join(parts: array<string>, delimiter: string) -> string#

I concatenate the strings in parts, placing delimiter between adjacent elements. I return the empty string for an empty array.

let mut parts: array<string> = []
set parts (array_push parts "a")
set parts (array_push parts "b")
set parts (array_push parts "c")
(str_join parts "-")   # Returns "a-b-c"

format(template: string, args: any...) -> string#

I am variadic. I substitute each %s, %d, %f, or %g placeholder in template with the next argument, converted to its string form. I copy any placeholder left over after the arguments run out verbatim, and I require at least the template argument.

My C-seed and NanoVirt frontends reject a non-string template during typechecking. My VM converts arguments to strings and calls a runtime scanner through a fixed string-array ABI. This is interpolation, not printf: %f does not request fixed decimal precision, and %% has no special escape rule. I evaluate extra arguments but do not substitute them after the template ends. Cross-backend conversion parity remains unfinished: whole floats have different decimal suffixes, and aggregate substitutions lack a consistent contract.

(format "Hello, %s!" "world")            # Returns "Hello, world!"
(format "kind=%s seq=%d" "spawn" 7)      # Returns "kind=spawn seq=7"
(println (format "%s scored %d" name score))

Character Classification (10)#

is_digit(c: int) -> bool#

I return true if the character code represents a decimal digit ('0'–'9').

(is_digit 48)   # Returns true  ('0')
(is_digit 53)   # Returns true  ('5')
(is_digit 65)   # Returns false ('A')

is_alpha(c: int) -> bool#

I return true if the character code represents a letter (a–z, A–Z).

(is_alpha 65)    # Returns true  ('A')
(is_alpha 97)    # Returns true  ('a')
(is_alpha 48)    # Returns false ('0')

is_alnum(c: int) -> bool#

I return true if the character code represents an alphanumeric character (digit or letter).

(is_alnum 48)   # Returns true  ('0')
(is_alnum 65)   # Returns true  ('A')
(is_alnum 32)   # Returns false (' ')

is_space(c: int) -> bool#

I return true if the character code is a space character (ASCII 32).

(is_space 32)   # Returns true  (' ')
(is_space 65)   # Returns false ('A')
(is_space 9)    # Returns false ('\t')

is_whitespace(c: int) -> bool#

I return true if the character code represents any whitespace: space, tab, newline, or carriage return.

(is_whitespace 32)   # Returns true  (' ')
(is_whitespace 9)    # Returns true  ('\t')
(is_whitespace 10)   # Returns true  ('\n')
(is_whitespace 65)   # Returns false ('A')

is_upper(c: int) -> bool#

I return true if the character code represents an uppercase letter (A–Z).

(is_upper 65)   # Returns true  ('A')
(is_upper 90)   # Returns true  ('Z')
(is_upper 97)   # Returns false ('a')

is_lower(c: int) -> bool#

I return true if the character code represents a lowercase letter (a–z).

(is_lower 97)    # Returns true  ('a')
(is_lower 122)   # Returns true  ('z')
(is_lower 65)    # Returns false ('A')

digit_value(c: int) -> int#

I convert a digit character code to its numeric value. I return -1 if it is not a digit.

(digit_value 48)   # Returns 0  ('0' -> 0)
(digit_value 53)   # Returns 5  ('5' -> 5)
(digit_value 57)   # Returns 9  ('9' -> 9)
(digit_value 65)   # Returns -1 ('A' is not a digit)

char_to_lower(c: int) -> int#

I convert an uppercase letter code to lowercase. I leave non-letters unchanged.

(char_to_lower 65)   # Returns 97  ('A' -> 'a')
(char_to_lower 90)   # Returns 122 ('Z' -> 'z')
(char_to_lower 48)   # Returns 48  ('0' -> '0', unchanged)

char_to_upper(c: int) -> int#

I convert a lowercase letter code to uppercase. I leave non-letters unchanged.

(char_to_upper 97)    # Returns 65  ('a' -> 'A')
(char_to_upper 122)   # Returns 90  ('z' -> 'Z')
(char_to_upper 48)    # Returns 48  ('0' -> '0', unchanged)

Array Operations (17)#

at(arr: array<T>, index: int) -> T#

I return the element at the specified 0-based index. I perform bounds-checking and terminate with an error if the index is out of bounds.

My C-seed and NanoVirt frontends require exactly two arguments: an array and an integer index (int or u8). I reject strings, floats and booleans as indices during typechecking, before publishing native or bytecode output. The same rule applies to array_get.

let nums: array<int> = [1, 2, 3, 4, 5]
(at nums 0)   # Returns 1
(at nums 4)   # Returns 5

array_get(arr: array<T>, index: int) -> T#

I return the element at the specified 0-based index. I am an alias for at.

let nums: array<int> = [10, 20, 30]
(array_get nums 0)   # Returns 10
(array_get nums 2)   # Returns 30

array_length(arr: array<T>) -> int#

I return the number of elements in an array.

let nums: array<int> = [10, 20, 30]
(array_length nums)    # Returns 3
(array_length [])      # Returns 0

array_new(size: int, default: T) -> array<T>#

I create a new array of the specified size, filled with the default value.

let zeros: array<int> = (array_new 5 0)
# [0, 0, 0, 0, 0]
let strs: array<string> = (array_new 3 "")
# ["", "", ""]

array_set(arr: array<T>, index: int, value: T) -> void#

I set the element at the specified 0-based index. I perform bounds-checking and terminate with an error if the index is out of bounds. I require a mutable array.

let mut nums: array<int> = [1, 2, 3]
(array_set nums 1 42)
# nums is now [1, 42, 3]

array_push(arr: array<T>, value: T) -> array<T>#

I append an element to the end of an array and return the updated array.

let mut numbers: array<int> = [1, 2, 3]
(array_push numbers 4)
# numbers is now [1, 2, 3, 4]

array_pop(arr: array<T>) -> T#

I remove and return the last element of the array.

let mut stack: array<int> = [1, 2, 3]
let last: int = (array_pop stack)   # Returns 3
# stack is now [1, 2]

array_remove_at(arr: array<T>, index: int) -> array<T>#

I remove the element at the specified index, shifting remaining elements left, and return the updated array.

let mut items: array<int> = [10, 20, 30, 40]
(array_remove_at items 1)
# items is now [10, 30, 40]

array_slice(arr: array<T>, start: int, length: int) -> array<T>#

I create a new array from a portion of the original, starting at start with the given length.

let numbers: array<int> = [1, 2, 3, 4, 5]
let subset: array<int> = (array_slice numbers 1 3)
# subset is [2, 3, 4]

array_concat(arr1: array<T>, arr2: array<T>) -> array<T>#

I concatenate two arrays and return a new array containing all elements.

let a: array<int> = [1, 2, 3]
let b: array<int> = [4, 5, 6]
let c: array<int> = (array_concat a b)
# c is [1, 2, 3, 4, 5, 6]

array_map(arr: array<T>, f: fn(T) -> U) -> array<U>#

I apply a function to each element of an array and return a new array of the results.

fn square(x: int) -> int { return (* x x) }

let nums: array<int> = [1, 2, 3, 4]
let squares: array<int> = (array_map nums square)
# squares is [1, 4, 9, 16]

array_filter(arr: array<T>, pred: fn(T) -> bool) -> array<T>#

I return a new array containing only the elements for which the predicate returns true.

fn is_even(n: int) -> bool { return (== (% n 2) 0) }

let nums: array<int> = [1, 2, 3, 4, 5, 6]
let evens: array<int> = (array_filter nums is_even)
# evens is [2, 4, 6]

array_fold(arr: array<T>, init: U, f: fn(U, T) -> U) -> U#

I reduce an array to a single value by applying a function cumulatively, starting with init.

fn add(acc: int, x: int) -> int { return (+ acc x) }

let nums: array<int> = [1, 2, 3, 4]
let sum: int = (array_fold nums 0 add)
# sum is 10

array_sort(arr: array<T>) -> array<T>#

I return a new array, leaving the source unchanged. My C-seed interpreter, native emitter and VM share scalar ordering: int, u8 and float ascend, false precedes true, and strings compare bytewise. Float NaNs sort last; equal elements have no stable-order guarantee. My runtime rejects unsupported element layouts instead of returning an unsorted copy. Complete compile-time diagnostics and self-hosted-driver parity remain separate acceptance work.

let nums: array<int> = [3, 1, 2]
let sorted: array<int> = (array_sort nums)
(at sorted 0)   # Returns 1
# nums is unchanged

array_reverse(arr: array<T>) -> array<T>#

I return a new array with the elements in reverse order. I leave the input untouched.

let nums: array<int> = [1, 2, 3]
let flipped: array<int> = (array_reverse nums)
(at flipped 0)   # Returns 3

array_contains(arr: array<int>, value: int) -> bool#

I return true if value appears in the array. I compare integer elements.

let nums: array<int> = [10, 20, 30]
(array_contains nums 20)   # Returns true
(array_contains nums 99)   # Returns false

array_index_of(arr: array<int>, value: int) -> int#

I return the index of the first occurrence of value, or -1 if it is absent. I compare integer elements.

let nums: array<int> = [10, 20, 30]
(array_index_of nums 30)   # Returns 2
(array_index_of nums 99)   # Returns -1

Higher-Order Functions (3)#

filter(arr: array<T>, predicate: fn(T) -> bool) -> array<T>#

I create a new array with elements that satisfy the predicate. I am equivalent to array_filter.

fn is_even(n: int) -> bool { return (== (% n 2) 0) }

let numbers: array<int> = [1, 2, 3, 4, 5, 6]
let evens: array<int> = (filter numbers is_even)
# evens is [2, 4, 6]

map(arr: array<T>, f: fn(T) -> U) -> array<U>#

I transform each element using the provided function. I am equivalent to array_map.

fn square(x: int) -> int { return (* x x) }

let numbers: array<int> = [1, 2, 3, 4]
let squares: array<int> = (map numbers square)
# squares is [1, 4, 9, 16]

reduce(arr: array<T>, init: U, f: fn(U, T) -> U) -> U#

I reduce an array to a single value. I am equivalent to array_fold.

fn add(acc: int, x: int) -> int { return (+ acc x) }

let numbers: array<int> = [1, 2, 3, 4]
let sum: int = (reduce numbers 0 add)
# sum is 10

HashMap Operations (16)#

I provide HashMap as a key-value collection with O(1) average lookup.

hashmap_new() -> hashmap#

I create a new empty hashmap.

let hm: hashmap = (hashmap_new)

hashmap_get(hm: hashmap, key: string) -> any#

I return the value associated with key, or null if the key does not exist.

(hashmap_set hm "name" "Alice")
let val: string = (hashmap_get hm "name")   # Returns "Alice"

hashmap_set(hm: hashmap, key: string, value: any) -> void#

I insert or update the value for key.

let hm: hashmap = (hashmap_new)
(hashmap_set hm "score" 100)
(hashmap_set hm "name" "Bob")

hashmap_has(hm: hashmap, key: string) -> bool#

I return true if key exists in the hashmap.

(hashmap_set hm "x" 42)
(hashmap_has hm "x")       # Returns true
(hashmap_has hm "missing")  # Returns false

hashmap_delete(hm: hashmap, key: string) -> void#

I remove the key-value pair for key. I do nothing if the key does not exist.

(hashmap_set hm "temp" 99)
(hashmap_delete hm "temp")
(hashmap_has hm "temp")   # Returns false

hashmap_keys(hm: hashmap) -> array<string>#

I return all keys in the hashmap as an array of strings.

(hashmap_set hm "a" 1)
(hashmap_set hm "b" 2)
let keys: array<string> = (hashmap_keys hm)
# keys contains ["a", "b"] (order may vary)

hashmap_values(hm: hashmap) -> array<any>#

I return all values in the hashmap as an array.

(hashmap_set hm "x" 10)
(hashmap_set hm "y" 20)
let vals: array<int> = (hashmap_values hm)
# vals contains [10, 20] (order may vary)

hashmap_length(hm: hashmap) -> int#

I return the number of key-value pairs in the hashmap.

let hm: hashmap = (hashmap_new)
(hashmap_set hm "a" 1)
(hashmap_set hm "b" 2)
(hashmap_length hm)   # Returns 2

map_new() -> hashmap#

I create a new empty hashmap. I am an alias for hashmap_new.

let m: hashmap = (map_new)

map_get(hm: hashmap, key: string) -> any#

I return the value for key. I am an alias for hashmap_get.

let score: int = (map_get hm "alice")

map_set(hm: hashmap, key: string, value: any) -> void#

I insert or update a key-value pair. I am an alias for hashmap_set.

(map_set hm "alice" 10)
(map_set hm "bob" 20)

map_has(hm: hashmap, key: string) -> bool#

I check if a key exists. I am an alias for hashmap_has.

if (map_has hm "alice") { (println "found") }

map_delete(hm: hashmap, key: string) -> void#

I remove a key-value pair. I am an alias for hashmap_delete.

(map_delete hm "temp")

map_keys(hm: hashmap) -> array<string>#

I return all keys as an array. I am an alias for hashmap_keys.

let keys: array<string> = (map_keys hm)

map_values(hm: hashmap) -> array<any>#

I return all values as an array. I am an alias for hashmap_values.

let vals: array<int> = (map_values hm)

map_length(hm: hashmap) -> int#

I return the number of entries. I am an alias for hashmap_length.

let count: int = (map_length hm)

Result Type Operations (7)#

My Result<T, E> type represents either success (Ok) or failure (Err).

result_is_ok(r: Result<T, E>) -> bool#

I return true if the result is an Ok value.

let r: Result<int, string> = (divide 10 2)
if (result_is_ok r) { (println "Success!") }

result_is_err(r: Result<T, E>) -> bool#

I return true if the result is an Err value.

let r: Result<int, string> = (divide 10 0)
if (result_is_err r) { (println "Error occurred") }

result_unwrap(r: Result<T, E>) -> T#

I extract the Ok value. I panic if the result is Err — use result_is_ok to check first.

let r: Result<int, string> = (divide 10 2)
let value: int = (result_unwrap r)   # Returns 5

result_unwrap_err(r: Result<T, E>) -> E#

I extract the Err value. I panic if the result is Ok.

let r: Result<int, string> = (divide 10 0)
if (result_is_err r) {
    let msg: string = (result_unwrap_err r)
    (println msg)
}

result_unwrap_or(r: Result<T, E>, default: T) -> T#

I extract the Ok value, or return default if the result is Err.

let r: Result<int, string> = (divide 10 0)
let value: int = (result_unwrap_or r 0)   # Returns 0 (the default)

result_map(r: Result<T, E>, f: fn(T) -> U) -> Result<U, E>#

I apply a function to the Ok value and return a new result. I pass Err values through unchanged.

fn double(x: int) -> int { return (* x 2) }

let r: Result<int, string> = (divide 10 2)
let r2: Result<int, string> = (result_map r double)
# r2 is Ok(10)

result_and_then(r: Result<T, E>, f: fn(T) -> Result<U, E>) -> Result<U, E>#

I apply a function that itself returns a Result, and flatten the result. I pass Err values through unchanged. I use this to chain fallible operations.

fn safe_sqrt(x: int) -> Result<float, string> {
    if (< x 0) { return (Err "negative input") }
    return (Ok (sqrt (cast_float x)))
}

let r: Result<int, string> = (divide 16 1)
let r2: Result<float, string> = (result_and_then r safe_sqrt)
# r2 is Ok(4.0)

File I/O (8)#

file_read(path: string) -> string#

I read text without seeking. My C-seed interpreter, native helper, VM bridge and std/fs.read share the reader. Open, read and close failures return empty text, as does embedded NUL data that my current string API cannot represent without truncation. Use file_read_bytes for binary input. Empty files and these failures are not distinguishable through this API. I do not impose a size limit or read deadline, or validate UTF-8. Foreign-string ownership remains a separate runtime boundary.

module "modules/std/fs.nano" as fs
let content: string = (fs.read "data.txt")
(println content)

I expose the public wrapper as fs.read; file_read is its foreign boundary.

file_read_bytes(path: string) -> array<u8>#

I read binary contents into byte-typed storage, including zero bytes. My C-seed interpreter, native emitter and VM bridge share a streaming reader that does not seek. An open, read or close failure returns an empty array; a read or close failure discards partial contents. Empty files and failures are therefore not distinguishable through this API. I do not impose a size limit or read deadline.

let data: array<u8> = (file_read_bytes "image.png")
let size: int = (array_length data)

file_write(path: string, content: string) -> int#

I write string content to a file, overwriting it if it already exists. I return 0 on success and 1 on failure.

let status: int = (file_write "output.txt" "Hello, World!")
if (== status 0) { (println "Write successful") }

file_append(path: string, content: string) -> int#

I append string content to the end of a file, creating it if it does not exist. I return 0 on success and 1 on failure.

let status: int = (file_append "log.txt" "New log entry\n")

file_remove(path: string) -> int#

I delete a file permanently. I return 0 on success and 1 on failure.

let status: int = (file_remove "temp.txt")

file_rename(old_path: string, new_path: string) -> int#

I rename or move a file. I return 0 on success and 1 on failure.

let status: int = (file_rename "old.txt" "new.txt")

file_exists(path: string) -> bool#

I return true if the file exists and is accessible.

if (file_exists "config.json") {
    let config: string = (file_read "config.json")
} else {
    (println "Config file not found")
}

file_size(path: string) -> int#

I return the file size in bytes, or -1 on error.

let size: int = (file_size "data.bin")
(println (str_concat "File size: " (int_to_string size)))

Directory and Navigation (10)#

dir_exists(path: string) -> bool#

I return true if the path exists and is a directory.

if (not (dir_exists "output")) {
    (dir_create "output")
}

dir_create(path: string) -> int#

I create a directory. Parent directories must already exist. I return 0 on success and 1 on failure.

let status: int = (dir_create "build/output")

dir_remove(path: string) -> int#

I remove an empty directory. I return 0 on success and 1 on failure.

let status: int = (dir_remove "temp")

dir_list(path: string) -> array<string>#

I list all entries in a directory and return an array of filenames (not full paths). I return an empty array on error.

let entries: array<string> = (dir_list ".")
for entry in entries {
    (println entry)
}

getcwd() -> string#

I return the current working directory as an absolute path.

let cwd: string = (getcwd)
(println cwd)   # Prints e.g. "/home/user/project"

chdir(path: string) -> int#

I change the current working directory. I return 0 on success and 1 on failure.

let status: int = (chdir "/tmp")
let cwd: string = (getcwd)
(println cwd)   # Prints "/tmp"

fs_walkdir(path: string) -> array<string>#

I recursively walk a directory tree and return an array of all file paths found.

let files: array<string> = (fs_walkdir ".")
for f in files {
    (println f)
}

tmp_dir() -> string#

I return the system's temporary directory path.

let tmp: string = (tmp_dir)
(println tmp)   # Prints e.g. "/tmp"

mktemp(prefix: string) -> string#

I create a new temporary file with the given prefix and return its path. The file is created and left open for writing.

let path: string = (mktemp "nano_work_")
(file_write path "some data")

mktemp_dir(prefix: string) -> string#

I create a new temporary directory with the given prefix and return its path.

let dir: string = (mktemp_dir "nano_build_")
let out: string = (path_join dir "output.txt")

Path Operations (6)#

path_isfile(path: string) -> bool#

I return true if the path exists and is a regular file.

if (path_isfile "config.json") {
    (println "Found config file")
}

path_isdir(path: string) -> bool#

I return true if the path exists and is a directory.

if (path_isdir "src") {
    (println "src directory exists")
}

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

I join two path components with the appropriate separator. I use / on Unix.

let full: string = (path_join "/home/user" "documents")
# Returns "/home/user/documents"
let nested: string = (path_join "src" "main.nano")
# Returns "src/main.nano"

path_basename(path: string) -> string#

I extract the filename component from a path.

(path_basename "/path/to/file.txt")   # Returns "file.txt"
(path_basename "src/main.nano")       # Returns "main.nano"

path_dirname(path: string) -> string#

I extract the directory component from a path.

(path_dirname "/path/to/file.txt")   # Returns "/path/to"
(path_dirname "src/main.nano")       # Returns "src"

path_normalize(path: string) -> string#

I normalize a path by resolving ., .., and redundant separators.

(path_normalize "./foo/../bar/./baz")   # Returns "bar/baz"
(path_normalize "/a//b/./c")           # Returns "/a/b/c"

Process and Environment (5)#

system(command: string) -> int#

I execute a shell command and wait for it to complete. I return the exit code.

let status: int = (system "ls -la")
if (!= status 0) { (println "Command failed") }

exit(code: int) -> void#

I terminate the program immediately with the given exit code.

if (not (file_exists "required.txt")) {
    (println "Error: required.txt not found")
    (exit 1)
}

getenv(name: string) -> string#

I return the value of an environment variable. I return an empty string if it is not set.

let home: string = (getenv "HOME")
let path: string = (getenv "PATH")

setenv(name: string, value: string) -> int#

I set an environment variable for the current process and its children. I return 0 on success and 1 on failure.

let status: int = (setenv "MY_VAR" "my_value")

process_run(command: string) -> array<string>#

I execute shell code through /bin/sh -c and return exactly three strings: [exit_code, stdout, stderr]. I preserve complete text streams, including newlines. My module, interpreter, native and VM paths share file-backed capture so one full output pipe cannot block draining the other. Commands are passed without a fixed-size command buffer; the host's argument limit still applies. Shell launch failure returns 127; capture failures and signal termination return -1. I reject embedded NUL output rather than silently truncating it. I do not impose a command deadline or output-storage quota here. This API executes shell syntax; callers must quote untrusted arguments as data.

let result: array<string> = (process_run "echo hello")
let code: string = (at result 0)     # "0" (exit code)
let output: string = (at result 1)   # "hello\n"
let errors: string = (at result 2)   # ""

Binary String and UTF-8 (5)#

bytes_from_string(s: string) -> array<int>#

I convert a string to an array of byte values (0–255), one per character.

let bytes: array<int> = (bytes_from_string "Hello")
(at bytes 0)   # Returns 72 ('H')

string_from_bytes(bytes: array<int>) -> string#

I construct a string from an array of byte values.

let bytes: array<int> = [72, 101, 108, 108, 111]
let s: string = (string_from_bytes bytes)   # Returns "Hello"

bstr_utf8_length(s: string) -> int#

I return the number of Unicode code points in a UTF-8 encoded string, which may differ from its byte length for multi-byte characters.

(bstr_utf8_length "Hello")   # Returns 5
(bstr_utf8_length "café")    # Returns 4 (4 code points, 5 bytes)

bstr_utf8_char_at(s: string, index: int) -> int#

I return the Unicode code point at the given character index (not byte index) in a UTF-8 string.

(bstr_utf8_char_at "Hello" 0)   # Returns 72 ('H')
(bstr_utf8_char_at "café" 3)    # Returns the code point for 'é'

bstr_validate_utf8(s: string) -> bool#

I return true if the string contains valid UTF-8 encoded text.

if (bstr_validate_utf8 content) {
    (println "Valid UTF-8")
} else {
    (println "Invalid encoding")
}

GPU Kernel Builtins (15)#

I make these available only inside gpu fn bodies. The PTX backend (--target ptx) lowers each of them to a GPU special register or instruction; they have no meaning in host code. See docs/AI_ML_GUIDE.md for the surrounding kernel-launch model.

gpu fn vec_add(a: int, b: int) -> int {
    let tid = (thread_id_x)
    let bid = (block_id_x)
    let bsz = (block_dim_x)
    let gid = (+ (* bid bsz) tid)
    return (+ (+ a b) gid)
}

thread_id_x() -> int#

I return the calling thread's x index within its block (PTX %tid.x).

thread_id_y() -> int#

I return the calling thread's y index within its block (PTX %tid.y).

thread_id_z() -> int#

I return the calling thread's z index within its block (PTX %tid.z).

block_id_x() -> int#

I return the block's x index within the grid (PTX %ctaid.x).

block_id_y() -> int#

I return the block's y index within the grid (PTX %ctaid.y).

block_id_z() -> int#

I return the block's z index within the grid (PTX %ctaid.z).

block_dim_x() -> int#

I return the number of threads per block along x (PTX %ntid.x).

block_dim_y() -> int#

I return the number of threads per block along y (PTX %ntid.y).

block_dim_z() -> int#

I return the number of threads per block along z (PTX %ntid.z).

grid_dim_x() -> int#

I return the number of blocks in the grid along x (PTX %nctaid.x).

grid_dim_y() -> int#

I return the number of blocks in the grid along y (PTX %nctaid.y).

grid_dim_z() -> int#

I return the number of blocks in the grid along z (PTX %nctaid.z).

global_id_x() -> int#

I return the global x index of the calling thread. I compute (block_id_x * block_dim_x) + thread_id_x for you.

gpu fn scale_by_index(x: int) -> int {
    return (* x (global_id_x))
}

global_id_y() -> int#

I return the global y index of the calling thread, computed as (block_id_y * block_dim_y) + thread_id_y.

gpu_barrier() -> void#

I synchronize every thread in the block before any of them continues (PTX bar.sync 0).

gpu fn staged(x: int) -> int {
    (gpu_barrier)
    return x
}