Standard library
Pluvial’s built-ins are implemented as native (OBJ_NATIVE) functions and are visible from any module. The set is intentionally small — the dedicated standard library at std/ is reserved but not yet shipped.
Output
Section titled “Output”print(x)
Section titled “print(x)”Writes x to standard output. Does not append a newline.
println(x)
Section titled “println(x)”Writes x to standard output and appends a newline.
Both accept any value type: int, float, bool, string, array<T>, Result<T>, or T?. The formatting matches the internal value_print used everywhere else:
| Type | Formatted as |
|---|---|
int | decimal |
float | %g with a trailing .0 for integral values |
bool | true / false |
string | unquoted |
array<T> | [e1, e2, e3] |
Result<T> | ok(x) / err(msg) |
T? | the value, or null |
print / println return no value: int x = println(42) is a compile error.
Variadic forms (print(a, b, c)), format strings, and writing to stderr or files are not currently provided.
Conversion
Section titled “Conversion”All conversions are explicit. There is no automatic promotion between numeric types or between bool and numeric types.
| Built-in | Result | Notes |
|---|---|---|
to_string(int | float | bool) | string | One built-in dispatches on the argument type. |
to_int(float) | int | Truncates toward zero. Loss of precision is intentional. |
to_float(int) | float | Lossless. |
to_int(string) | Result<int> | Whole-string parse; rejects trailing whitespace and partial matches. Overflow returns err(...). |
to_float(string) | Result<float> | Whole-string parse. |
to_bool(string) | Result<bool> | Accepts "true" and "false" only; case-sensitive. |
Forbidden conversions (compile errors): to_int(bool), to_float(bool), to_bool(int), to_bool(float). bool and numeric types are not interconvertible — this preserves the “no implicit conversions” rule on the explicit-conversion side.
Result<int> r = to_int("42")if (r is ok) { println(r.value) } # 42
Result<int> bad = to_int("hello")if (bad is err) { println(bad.error) } # parse error messageStrings
Section titled “Strings”The receiver s must be string (non-nullable). Calling a string method on a string? without narrowing is a compile error.
Property
Section titled “Property”s.length→int— byte length. For ASCII, this equals the character count.
Always-succeeds predicates
Section titled “Always-succeeds predicates”| Method | Returns | Notes |
|---|---|---|
s.is_empty() | bool | true if s.length == 0 |
s.is_digit() | bool | true if every byte is a digit and s is non-empty |
s.is_alpha() | bool | true if every byte is an ASCII letter and s is non-empty |
s.contains(sub) | bool | substring search |
s.starts_with(pre) | bool | |
s.ends_with(suf) | bool |
Always-succeeds transformations
Section titled “Always-succeeds transformations”| Method | Returns | Notes |
|---|---|---|
s.to_upper() | string | ASCII range only |
s.to_lower() | string | ASCII range only |
s.trim() | string | strips ASCII whitespace (' ', \t, \r, \n) from both ends |
s.lstrip() | string | left side only |
s.rstrip() | string | right side only |
s.replace(old, new) | string | replaces all non-overlapping occurrences; if old == "" returns s unchanged |
s.repeat(n) | string | n must be int; n == 0 returns ""; n < 0 is a runtime error ("repeat count must be non-negative") |
s.count(sub) | int | non-overlapping occurrences; empty sub returns 0 |
Methods that can fail
Section titled “Methods that can fail”s.index(sub) -> Result<int>— returnsok(byte_offset)if found,err("substring not found")otherwise. Emptysubreturnsok(0).s.substring(start, end) -> Result<string>— half-open slice[start, end). Out-of-range indices returnerr("substring index out of range").
Splitting
Section titled “Splitting”s.split(sep) -> array<string>— splits onsep. Ifsep == "", splits into single bytes ("hello".split("") → ["h","e","l","l","o"]). On an empty receiver the result is always[""].
Argument-type rules
Section titled “Argument-type rules”sub,pre,suf,old,new,sepmust bestring.start,end,nmust beint.
These restrictions are not user-visible overloads; they are part of the same multi-type built-in mechanism used elsewhere (only the built-ins use it — user-defined functions still follow “one name = one definition”).
Arrays
Section titled “Arrays”The receiver arr has type array<T>.
Property
Section titled “Property”arr.length→int— current number of elements.
Mutating
Section titled “Mutating”arr.push(x)— appendsxto the end.xmust have typeTexactly. Returns no value.arr.pop() -> T— removes and returns the last element. Runtime error on an empty array (exit 70).arr.reverse()— reverses the array in place. Returns no value.arr.sort()— sorts in place, ascending. Supported only for non-nullablearray<int>,array<float>,array<bool>,array<string>. Returns no value.
Non-mutating
Section titled “Non-mutating”arr.contains(x) -> bool— linear search;xmust match the element type exactly.arr.slice(start, end) -> array<T>— half-open slice. Out-of-range indices are a runtime error.arr.join(sep) -> string— only valid onarray<string>. Other element types are a compile error.
Higher-order
Section titled “Higher-order”arr.map(fn) -> array<T2>— applies the lambda to every element, returning a new array.T2is the lambda’s return type.arr.filter(pred) -> array<T>— keeps the elements wherepredreturnstrue.arr.reduce(init, fn) -> R— folds the array withfn(acc, element)starting frominit.
array<int> a = [3, 1, 4, 1, 5, 9]a.sort() # [1, 1, 3, 4, 5, 9]a.reverse() # [9, 5, 4, 3, 1, 1]println(a.contains(5)) # trueint last = a.pop() # last == 1, a == [9, 5, 4, 3, 1]array<int> head = a.slice(0, 3) # [9, 5, 4]
array<string> words = ["one", "two", "three"]println(words.join(" / ")) # "one / two / three"
println([1,2,3,4,5].map((x) => x * 2)) # [2, 4, 6, 8, 10]println([1,2,3,4,5].filter((x) => x > 2)) # [3, 4, 5]println([1,2,3,4,5].reduce(0, (acc, x) => acc + x)) # 15The receiver m has type map<K, V>.
| Method | Returns | Notes |
|---|---|---|
m.length | int | live entry count (property — no parentheses) |
m.has(key) | bool | key must match K exactly |
m.delete(key) | bool | returns whether the key was present; uses tombstones internally |
m.keys() | array<K> | hash-table order, not insertion order |
m.values() | array<V?> | element type is always V? to match m[key] |
m.clear() | (no value) | removes all entries; the underlying buffer is reused, no reallocation |
Indexing: m[key] returns V? — missing keys yield null, never a runtime error. m[key] = value strictly type-checks both key and value.
map<string, int> counts = {}for w in "a b c a b a".split(" ") { int? n = counts[w] if (n != null) { counts[w] = n + 1 } else { counts[w] = 1 }}for (k, v) in counts { if (v != null) { println(k + "=" + to_string(v)) }}# e.g. a=3, b=2, c=1 — hash-table orderStruct methods
Section titled “Struct methods”Methods defined inside a struct are part of that type and are called with the same .method(args) syntax. See Types — struct for the definition syntax. Struct methods are not part of any global namespace — they exist only through a receiver.