Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Builtins

Generated from the language spec, which is normative.

Builtins are available without import. They are resolved only when the name is not bound by a variable in scope or a declared function; such a binding shadows the builtin entirely (a shadowed builtin is not callable through any other path).

print

print(v1, ..., vn)

Takes zero or more arguments of any single-value types, renders each canonically, joins them with single spaces, and writes the result followed by a line feed to standard output. Canonical rendering (shared by print, the %v verb, and str()):

TypeRendering
intdecimal
bytedecimal (byte(7) renders as 7)
floatshortest decimal that round-trips; integral values render without a fractional part (3.0 renders as 3); infinities as inf/-inf, NaN as NaN
booltrue / false
strthe string itself, unquoted
list[e1, e2, ...], elements rendered recursively
map{k1: v1, k2: v2} in insertion order
structName{f1: v1, f2: v2} in declaration order
nonenone
errorerror(<msg>)
pyPython str() of the object
fnfn

printf and sprintf

printf(format, a1, ..., an)          // writes to standard output, no implicit newline
sprintf(format, a1, ..., an) str     // returns the formatted string

The format string uses Go-style verbs:

VerbArgument typeOutput
%vanycanonical rendering (section 14.1); renders byte as decimal
%dintdecimal; byte is not accepted, widen with int(b)
%sstrthe string
%tbooltrue / false
%qstrdouble-quoted, backslash-escaped
%ffloatfixed-point, default 6 fractional digits
%%noneliteral %

A verb may carry a minimum width (%5s) and a precision (%.2f), both decimal digit sequences, in the form %[width][.precision]verb. Width pads on the left with spaces to the given count of characters (not bytes). Precision is honored by %f; on other verbs it is accepted and ignored. There is no left-align or zero-pad flag. A width or precision exceeding the implementation’s pad limit (2^20 in the reference implementation) is rejected: a compile-time diagnostic when the format string is a literal (section 14.3), a fault otherwise (chapter 12).

Static and dynamic format checking

When the format argument is a string literal, the format is checked at compile time: verb count must equal argument count, each argument’s type must match its verb, verbs must be from the table, and the format must not end inside a verb. Violations are compile-time errors.

When the format is not a literal, the same checks happen at runtime and a violation is a fault (chapter 12).

len

len(x) int

For str, the number of characters; for list (including []byte), the element count; for map, the entry count. Any other argument type is a compile-time error.

charcode

charcode(c str) int

The Unicode code point (the character’s number) of c, which must be exactly one character; any other argument value is a runtime fault.

char

char(n int) str

The one-character string for the Unicode code point n. A value that is not a valid Unicode scalar (negative, greater than 0x10FFFF, or a surrogate) is a runtime fault. char(charcode(c)) == c for every one-character c.

Program arguments and standard input live in the os module (section 15.9); nevla has no args or input builtin.

append

append(xs []T, v1 T, ..., vn T) []T

A fresh list: xs with the values appended, as in Go’s idiom xs = append(xs, v). The first argument must be a list; every following value must be assignable to its element type. Zero values yield a plain copy. The original list is never modified; other names bound to it see growth only through rebinding (chapter 11).

append on []byte observes the same contract: every call returns a fresh buffer, and xs is never modified in place from the caller’s perspective. An earlier reference-count-based in-place growth optimization was removed (design 2026-07-14) after it broke exactly this contract: no refcount threshold can distinguish xs = append(xs, v) (safe to reuse storage, since xs immediately rebinds to the result) from ys := append(xs, v) (xs must stay unchanged) without move semantics or escape analysis, which the reference implementation has neither of. []byte append now always copies, exactly like every other []T.

clone

clone(x []T) []T
clone(x map[K]V) map[K]V

A one-level copy of a list or map (chapter 11): the container is new, its elements copy by their kinds, exactly Go’s slices.Clone/maps.Clone. Applying clone to a value type is a compile-time error; value types already copy. clone on []byte copies the underlying buffer.

Methods on builtin types

All receivers are unchanged; results are new values.

String methods

Receiver str. Positions and counts are in characters.

  • split(sep str) []str — split on the separator.
  • trim() str — strip leading and trailing white space.
  • upper() str, lower() str — case conversion.
  • contains(sub str) bool, starts_with(prefix str) bool, ends_with(suffix str) bool — substring, prefix, and suffix tests.
  • replace(from str, to str) str — replace all occurrences.
  • find(sub str) int? — character index of the first occurrence, none if absent.
  • fields() []str — split on runs of white space; no empty fields.
  • lines() []str — split on line feeds; a trailing line feed adds no empty line.
  • trim_prefix(p str) str, trim_suffix(p str) str — remove a leading or trailing p if present, else unchanged.
  • chars() []str — the characters as one-character strings.
  • repeat(n int) str — the string tiled n times; negative n faults, as does a result exceeding the implementation’s size limit (2^30 bytes in the reference implementation).
fn main() {
    s := "  the nevla book  "
    t := s.trim()
    print(t.to_upper())                  // THE NEVLA BOOK
    print(t.split(" ").join("-"))     // the-nevla-book
    print(t.replace("book", "spec"))  // the nevla spec
    i := t.index("nevla")
    if i != none {
        print(i)                      // 4
    }
    print("na".repeat(2) + " batman") // nana batman
    print(len("héllo"))               // 5: characters, not bytes
}

▸ run it in the playground

List methods

Receiver []T.

  • map(f fn(T) U) []U — apply f to each element.
  • filter(f fn(T) bool) []T — keep elements where f is true.
  • each(f fn(T)) — call f on each element; no result.
  • sum() TT must be int or float; the sum of the elements. Integer overflow faults (chapter 12). Summing an empty []int yields 0; the result of summing an empty []float is unspecified in v1 (the reference implementation yields a value that faults on later float use).
  • sorted() []TT must be int, byte, float, or str; a fresh ascending list.
  • sorted_by(before fn(T, T) bool) []T — a sorted copy per the comparator; the sort is stable.
  • contains(v T) bool — structural membership (section 11.2).
  • join(sep str) strT must be str; concatenation with the separator.

[]byte (T = byte) has every method above except sum and join, which byte does not satisfy (byte is not int/float, and is not str); sorted, sorted_by, contains, filter, each, and map all apply, per the general rules above.

fn main() {
    xs := [3, 1, 4, 1, 5]
    print(xs.sorted())                          // [1, 1, 3, 4, 5]
    print(xs.map(fn(x) { x * 10 }).sum())       // 140
    print(xs.filter(fn(x) { x > 2 }))           // [3, 4, 5]
    print(xs.contains(4))                       // true
    print(xs.sorted_by(fn(a, b) { a > b }))     // [5, 4, 3, 1, 1]
}

▸ run it in the playground

Map methods

Receiver map[K]V. Iteration order is insertion order (section 5.3).

  • keys() []K — the keys, in insertion order.
  • values() []V — the values, in insertion order.
  • has(k K) bool — key presence.
  • delete(k K) — removes k in place, Go’s delete; the remaining order is preserved.

keys() on a map[byte]V and values() on a map[K]byte are compile-time errors: the result would need to be a compact []byte, but the two methods build their result generically across every K/V, independently of byteness, and have no compact []byte repack. Iterate with a for k, v := range m instead (section 8.7), which binds the byte key or value directly with no repack in the way.

fn main() {
    m := map[str]int{"b": 2, "a": 1}
    print(m.keys())      // [b, a]: insertion order, not sorted
    print(m.has("a"))    // true
    m.delete("b")
    print(m.values())    // [1]
}

▸ run it in the playground