Skip to content

Built-in functions

These functions and methods are part of the language. They need no import, and they are available in a .cdlb artifact running under candela-vm with no library directory installed.

A function your program declares or imports takes precedence over a built-in function of the same name; see functions. The built-in methods work the other way round and keep precedence over an impl block, as methods describes.

Everything here raises errors the same way the language does. A raised error carries a short code, and that code is what a catch binds and what catch "code" filters on; the longer message is what an uncaught error prints. The codes appear against each function below. See error handling and the error catalogue.

Output and input

print

print(value, ...)

Writes each argument to standard output, one per line. Takes any number of arguments of any type and returns nothing. A list prints as [1,2,3], a map as {a:1,b:2}, a struct as Name {field:value}, and an enum value as its variant name with any payload in brackets. A float keeps its decimal point wherever it appears, so print(3.0) writes 3.0 and print([3.0]) writes [3.0], the same text str gives. An infinity prints as inf and a not-a-number as NaN. A function prints as <fn>, wherever it is held.

A stream that refuses the write costs the line, not the run. program | head -1 leaves standard output with no reader, and the prints that follow go nowhere while the program runs to the end as it otherwise would.

input

input()
input(prompt)

Writes prompt to standard output without a trailing newline, reads one line from standard input, and returns it as a string with the trailing newline (and a carriage return, if present) removed. prompt is a string; with no argument the prompt is empty.

Conversion

int

int(value)

Converts a string or a float to an int. A float truncates towards zero, so int(3.9) is 3 and int(-3.9) is -3. A string that does not parse as an integer raises invalid_int.

float

float(value)

Converts a string or an int to a float. A string that does not parse as a floating-point number raises invalid_float.

str

str(value)

Renders any value as a string. Ints and floats use their shortest exact decimal form, bools become true or false, a string is returned unchanged, and collections, structs, and enum values use their literal form. A struct reads as Name {field:value}, the spelling print writes, wherever it appears: on its own, inside a list or a map, and as an enum payload. A function reads <fn>: there is no signature at run time to tell one function from another.

bool

bool(value)

Converts the string "true" or "false" to a bool. Any other string raises invalid_bool. The argument has to be a string; there is no truthiness conversion from other types.

type

type(value)

Returns the static type of the expression as a string, resolved at compile time. Every answer is spelled the way the same type is written in a program: int, float, bool, string, null, any for a dynamic value, int[] for a list of ints, {string: int} for a map, a|b for a union, the declared name for a struct or an enum, and fn(int) -> int for a function. A struct or an enum that takes type arguments carries them, as in Cell<int>. A list or a map names what it holds, so a map of your own enum reads {string: Value}.

A function reads as its declaration does. A parameter with no annotation is any, as is a return type the declaration leaves to the body, and a function that hands nothing back is written without the arrow.

struct Point { x: int, y: int }

fn id(x: int) -> int { return x; }
fn greet(name: string) { print(name); }
fn add(a, b) { return a + b; }

fn main() {
    print(type(Point { x: 1, y: 2 }));
    print(type(id), type(greet), type(add));
}
Point
fn(int) -> int
fn(string)
fn(any, any) -> any

The answer is the type the compiler inferred, not the tag the value carries when the program runs, so a value the compiler could not pin reads any whatever it turns out to hold. The is_* predicates below answer at run time.

Sequences

range

range(end)
range(start, end)

Returns a list of ints from start (0 when omitted) up to but not including end. Both arguments are ints. The result is empty when start is not less than end.

Dynamic values

A value typed any (a json parse result, an Option payload, a host function return) carries its type at run time. These functions test and unwrap it.

is_int, is_float, is_str, is_bool, is_list, is_map, is_null

is_int(value)
is_float(value)
is_str(value)
is_bool(value)
is_list(value)
is_map(value)
is_null(value)

Each returns a bool: true when the value's run-time type is the one named. They never raise.

as_int, as_float, as_str, as_bool, as_list, as_map

as_int(value)
as_float(value)
as_str(value)
as_bool(value)
as_list(value)
as_map(value)

Checked downcasts. Each returns the value typed concretely, so the result takes part in ordinary typed expressions. A value whose run-time type differs raises bad_downcast, with a message naming both the requested and the found type. There is no as_null; use is_null.

as_list gives a list of any, and as_map a map with any keys and values, because the entries of a dynamic collection are dynamic too. Such a collection takes a push or an insert of any type, in any order, and an entry read back out is an any that needs its own downcast.

fn main() {
    let v = json_parse("{\"n\": 7}");
    let m = as_map(v);
    print(as_int(m.get("n")) + 1);
    m.insert("name", "ada");
    print(as_str(m.get("name")));
}

json

json_parse

json_parse(text)

Parses a json string into candela values and returns the result typed any. An object becomes a map keyed by strings, an array becomes a list, and a scalar becomes an int, float, string, bool, or null. text has to be a string. Malformed input raises json_parse_error, with a message carrying the reason.

json_stringify

json_stringify(value)

Serialises any value to a json string.

Both are wrapped by the json module, which gives them the shorter names json::parse and json::stringify.

Errors and process control

throw

throw(message)

Raises a catchable error carrying message, which has to be a string. Nothing after the throw in the enclosing block runs.

exit

exit()
exit(code)

Stops the program. With no argument it ends the run normally; with an int code the process exits with that status.

At the REPL a line calling exit leaves the prompt with the status the call names, and the line is not kept in the session: the prompt compiles the session in a process of its own, and a kept line ending that process would stop every later run before it printed anything. The argument is worked out by the session the line is typed into, so exit(code) leaves with what code holds, and the status is the one the same call leaves with in a file.

argv

argv()

Returns the command-line arguments that follow the script path, as a list of strings. Takes no arguments.

the_answer

the_answer()

Prints the answer to the Ultimate Question of Life, the Universe, and Everything, and returns 42.

File system

The fs namespace is built in; it needs no import. Every function takes the path as a string and raises a catchable error on failure. The code names the cause: fs_not_found, fs_permission_denied, fs_is_a_directory, fs_storage_full, and the rest.

fs::read

fs::read(path)

Returns the whole file at path as a string.

fs::exists

fs::exists(path)

Returns true when something exists at path.

fs::write

fs::write(path, contents)

Writes contents to path, replacing what was there. Creates the file when it does not exist. Returns nothing.

fs::append

fs::append(path, contents)

Appends contents to the end of the file at path. Creates the file when it does not exist. Returns nothing.

fs::delete

fs::delete(path)

Deletes the file at path. Raises when the path does not exist or names a directory. Returns nothing.

fs::delete_dir

fs::delete_dir(path)

Deletes the empty directory at path. Raises when the directory is missing or still has entries. Returns nothing.

String methods

Called on a string receiver.

Method Returns Behaviour
s.len() int The number of characters
s.uppercase() string s with every character upper-cased
s.lowercase() string s with every character lower-cased
s.starts_with(prefix) bool True when s begins with the string prefix
s.ends_with(suffix) bool True when s ends with the string suffix
s.contains(needle) bool True when the string needle occurs in s
s.find(needle) int The position of the first occurrence of needle, or -1
s.replace(from, to) string s with every occurrence of from replaced by to
s.split(separator) string[] s cut at each occurrence of the string separator; an empty separator answers the characters
s.trim() string s without leading or trailing whitespace
s.trim_left() string s without leading whitespace
s.trim_right() string s without trailing whitespace
s.trim_sequence(chars) string s with any of the characters in chars stripped from both ends
s.trim_sequence_left(chars) string The same, from the start only
s.trim_sequence_right(chars) string The same, from the end only
s.is_int() bool True when s parses as an integer
s.is_float() bool True when s parses as a float but not as an integer
s.repeat(n) string s joined to itself n times
s.reverse() string A new string with the characters in reverse order

len, find, indexing, slicing, and split with an empty separator all count characters; the empty separator answers the characters one at a time. A character is a Unicode scalar value, so an accented letter or an emoji is one position and arrives whole. The chars method in the string module is that empty-separator split under a name that says so.

List methods

Called on an array receiver. push, remove, reverse, and sort change the list in place and return nothing; the rest return a new value.

Method Returns Behaviour
arr.len() int The number of elements
arr.push(x) nothing Appends x
arr.remove(i) nothing Removes the element at index i; raises index_out_of_bounds when i is outside the list
arr.contains(x) bool True when some element equals x
arr.find(x) int The index of the first element equal to x, or -1
arr.repeat(n) list The elements of arr repeated n times
arr.reverse() nothing Reverses arr in place
arr.sort() nothing Sorts arr in place, ascending
arr.join() string The elements concatenated; the receiver has to be a list of strings
arr.join(separator) string The same, with separator between elements
arr.partition(x) list[] arr cut into sub-lists at each element equal to x

sort picks its ordering from the first element: ints, floats, and strings sort ascending, and a list of any other element type is left unchanged. Strings sort by their bytes, the order < gives them; see operators.

arr.find(x) is the index search. The same spelling with a function argument, arr.find(predicate), is the list module helper that returns the matching element. The other higher-order methods (map, filter, reduce, each, any, all, sort_by) and the reductions (first, last, sum, min, max, and the rest) also come from that module through the automatic prelude.

Map methods

Called on a map receiver. insert and remove change the map in place and return nothing.

Method Returns Behaviour
m.len() int The number of entries
m.get(k) value The value stored under k; raises unknown_map_key when the key is absent
m.insert(k, v) nothing Stores v under k, replacing any existing value
m.remove(k) nothing Drops the entry under k; a key the map does not hold changes nothing
m.contains(k) bool True when k is a key in m
m.keys() list The keys
m.values() list The values

A map keeps its entries in the order they went in, so keys, values, for k in m, printing, and json all walk it first-inserted first. Three rules settle every case:

  • Inserting a key the map does not hold puts it at the end.
  • Inserting a key the map already holds replaces the value and leaves the entry where it is.
  • Removing a key and inserting it again puts it at the end; the entries after the removed one keep their order.

Two maps holding the same entries are equal whatever order they were built in. keys and values walk the map the same way, so their results line up entry for entry.

An empty map literal {} takes its key and value types from the first insert, the way an empty list takes its element type from the first push. Only a literal written empty works that way. A map whose entries are typed any, which is what as_map hands back, keeps taking entries of any type.

Number methods

Method Receiver Returns Behaviour
x.abs() int or float same as receiver The magnitude of x
x.sqrt() float float The square root of x
x.round() float float x rounded to the nearest whole number, halves away from zero
x.floor() float float The largest whole number not greater than x

The math module covers the rest of the numeric surface, including ceil, trunc, the trigonometric functions, and the logarithms.