Pyro

A scripting language for people who enjoy the simpler things in life.

Version 0.5.24

Builtin Types


Strings

A string, str, is an immutable array of bytes.

$str(arg: any) -> str

Stringifies the argument, i.e. returns its default string representation. If the argument has a :$str() method, the output of this method will be returned.

Pyro strings have methods that let you manipulate them as ASCII or as UTF-8 but the string type itself is agnostic about its encoding — a string can contain any sequence of byte values including null bytes or invalid UTF-8.

String literals come in two flavours — regular and raw. Both can contain arbitrary byte sequences, including literal newlines.

Regular string literals use double quotes:

var foo = "a string";

var bar = "a string
with multiple
lines";

Regular string literals process all the usual backslashed escapes:

\\ backslash
\0 null byte
\" double quote
\' single quote
\b backspace
\e escape
\f form feed
\n newline
\r carriage return
\t tab
\v vertical tab
\x## 8-bit hex-encoded byte value
\u#### 16-bit hex-encoded unicode code point (output as UTF-8)
\U######## 32-bit hex-encoded unicode code point (output as UTF-8)

Raw string literals use backticks:

var foo = `a raw string`;

var bar = `a raw string
with multiple
lines`;

Raw string literals ignore backslashed escapes. The only character a raw string literal can't contain is a backtick as this would end the string.

You can concatenate two strings using the + operator:

assert "abc" + "def" == "abcdef";

You can multiply a string by an integer n to produce a new string containing n copies of the original:

assert "foo" * 3 == "foofoofoo"

Strings have the following methods:

:byte(index: i64) -> i64

Returns the byte value at index as an integer in the range [0, 255]. Will panic if index isn't an integer or is out of range. Indices are zero-based.

:byte_count() -> i64

Returns the number of bytes in the string.

:bytes() -> iter[i64]

Returns an iterator over the string's bytes as i64 values.

:char(index: i64) -> char

Returns the char at index, where index is a zero-based offset into the sequence of UTF-8 encoded Unicode code points in the string. Will panic if index isn't an integer or is out of range.

(This is a potentially expensive method as it needs to seek forward from the beginning of the string. Note that it will panic if it encounters invalid UTF-8 along the way.)

:char_count() -> i64

Returns the number of UTF-8 encoded Unicode code points in the string.

(This is a potentially expensive method as it needs to traverse the string. Note that it will panic if it encounters invalid UTF-8.)

:chars() -> iter[char]

Returns an iterator over the string's char values, i.e. UTF-8 encoded Unicode code points.

:contains(target: str) -> bool

Returns true if the string contains the substring target.

(Note that every string contains the empty string "" as the empty string is a valid substring of every string.)

:count() -> i64

Returns the number of bytes in the string. This method is an alias for :byte_count().

:ends_with(suffix: str) -> bool

Returns true if the string ends with the string suffix, otherwise false.

:index_of(target: str, start_index: i64) -> i64|err

Returns the byte index of the next matching instance of the string target. Starts searching at start_index. Returns an err if target is not found.

:is_ascii() -> bool

Returns true if the string is empty or contains only byte values in the range [0, 127]. (This is a potentially expensive method as it needs to traverse the string.)

:is_empty() -> bool

Returns true if the string is empty, i.e. if its length is zero.

:is_utf8() -> bool

Returns true if the string is empty or contains only valid UTF-8. (This is a potentially expensive method as it needs to traverse the string.)

:join(items: iterable) -> str

Creates a new string by joining the stringified elements of the iterable argument using the receiver string as the separator. Elements are automatically stringified — this is equivalent to calling $str() on each element.

:lines() -> iter[str]

Returns an iterator over the string's lines. Recognised line breaks are \r\n, \r, and \n. Strips the line breaks from the returned strings.

:match(target: str, index: i64) -> bool

Returns true if the string target matches at byte index index.

:replace(old: str, new: str) -> str

Returns a new string with all instances of the string old replaced by the string new.

:slice(start_index: i64) -> str
:slice(start_index: i64, length: i64) -> str

Copies a slice of the source string and returns it as a new string. The source string is left unchanged. The slice starts at byte index start_index and contains length bytes.

If start_index is negative, counts backwards from the end of the string — i.e. a start_index of -1 refers to to the last byte in the string.

If length is omitted, copies to the end of the source string.

Panics if either argument is out of range.

:split(sep: str) -> vec[str]

Splits the string on instances of the delimiter string sep. Returns a vector of strings.

:split_lines() -> vec[str]

Splits the string into lines. Recognised line breaks are \r\n, \r, and \n. Strips the line breaks from the returned strings. Returns a vector of strings.

:split_on_ascii_ws() -> vec[str]

This method splits the string on contiguous sequences of ASCII whitespace characters. Leading and trailing whitespace is ignored. Returns a vector of strings.

:starts_with(prefix: str) -> bool

Returns true if the string starts with the string prefix, otherwise false.

:strip() -> str
:strip(bytes: str) -> str

When called with no arguments, this method returns the new string formed by stripping leading and trailing ASCII whitespace characters from the string. (In this case it functions as an alias for :strip_ascii_ws().)

When called with a single string argument, this method returns the new string formed by stripping any leading or trailing bytes that occur in bytes. (In this case it functions as an alias for :strip_bytes().)

:strip_ascii_ws() -> str

Returns the new string formed by stripping leading and trailing ASCII whitespace characters.

:strip_bytes(bytes: str) -> str

Returns the new string formed by stripping any leading or trailing bytes that occur in bytes.

:strip_chars(chars: str) -> str

Returns the new string formed by stripping any leading or trailing UTF-8 encoded codepoints that occur in chars.

:strip_prefix(prefix: str) -> str

Returns a new string with the leading string prefix stripped if present. (Only a single instance of prefix will be stripped.)

:strip_prefix_bytes(bytes: str) -> str

Returns the new string formed by stripping any leading bytes that occur in bytes.

:strip_prefix_chars(chars: str) -> str

Returns the new string formed by stripping any leading UTF-8 encoded codepoints that occur in chars.

:strip_suffix(suffix: str) -> str

Returns a new string with the trailing string suffix stripped if present. (Only a single instance of suffix will be stripped.)

:strip_suffix_bytes(bytes: str) -> str

Returns the new string formed by stripping any trailing bytes that occur in bytes.

:strip_suffix_chars(chars: str) -> str

Returns the new string formed by stripping any trailing UTF-8 encoded codepoints that occur in chars.

:strip_utf8_ws() -> str

Returns the new string formed by stripping leading and trailing UTF-8 encoded whitespace characters, as defined by the Unicode standard.

:to_ascii_lower() -> str

Returns a new string with all ASCII uppercase characters converted to lowercase.

:to_ascii_upper() -> str

Returns a new string with all ASCII lowercase characters converted to uppercase.

:to_hex() -> str

Returns a new string containing the hex-escaped byte values from the orginal string — e.g. `foo` becomes `\x66\x6F\x6F`. Useful for inspecting and debugging Unicode.

Characters

A character, char, is an unsigned 32-bit integer representing a Unicode code point.

$char(arg: i64) -> char

Converts arg to a char. Panics if the argument is out of range.

Character literals use single quotes:

var c1 = 'a';
var c2 = '€';
var c3 = '🔥';

A character literal should contain either a single UTF-8 encoded code point or a backslashed escape sequence representing a code point.

assert '\x61' == 'a';
assert '\u20AC' == '€';

Character literals support all the same backslashed escape sequences as strings.

You can convert a character to a string using the $str() function — this returns a string containing the UTF-8 encoded representation of the codepoint:

assert $str('a') == "a";
assert $str('🔥') == "🔥";

You can convert a character to an integer using the $i64() function or an integer to a character using the $char() function:

assert $i64('a') == 97;
assert $char(97) == 'a';

If you add two characters together using the + operator, the result will be a UTF-8 encoded string:

assert 'x' + 'y' == "xy";

Similarly, you can prepend or append a character to a string using the + operator:

assert 'x' + "yz" == "xyz";
assert "xy" + 'z' == "xyz";

You can multiply a character by an integer n to produce a string with n UTF-8 encoded copies of the character:

assert 'x' * 3 == "xxx";
assert '🔥' * 3 == "🔥🔥🔥";

Vectors

A vector, vec, is a dynamic array of values. Vectors use zero-based indices.

$vec() -> vec
$vec(arg: iterable) -> vec
$vec(size: i64, fill_value: any) -> vec

Creates a new vec object. If called with zero arguments, creates an empty vector. If called with a single iterable argument, fills the new vector by iterating over the argument. If called with two arguments, creates a vector with the specified initial size and fill value.

Alternatively, you can create a vector using literal syntax:

var vec = ["foo", "bar", "baz"];

You can index into a vector to get or set entries:

var value = vec[index];
var value = vec:get(index);

vec[index] = value;
vec:set(index, value);

Indexing is equivalent to using the :get() and :set() methods as shown.

Vectors are iterable:

for item in [123, 456, 789] {
    echo item;
}

Vectors have the following methods:

:append(value: any)

Appends a value to the vector.

:contains(value: any) -> bool

Returns true if the vector contains an entry equal to value, otherwise false.

:copy() -> vec

Returns a copy of the vector.

:count() -> i64

Returns the number of entries in the vector.

:filter(callback: func(any) -> bool) -> vec

Returns a new vector created by mapping the function callback to each element of the vector in turn. callback should be a callable that takes a single argument (the vector element) and returns true or false; if it returns true the corresponding element will be added to the new vector.

:first() -> any

Returns the first item from the vector without removing it. Panics if the vector is empty.

:get(index: i64) -> any

Returns the value at index. Will panic if index is out of range or not an integer.

:index_of(value: any) -> i64|err

Returns the index of the first occurrence of value in the vector. Returns an err if the vector does not contain value.

:insert_at(index: i64, value: any)

Inserts value at the specified index. index must be less than or equal to the vector's item count. Panics if index is out of range.

:is_empty() -> bool

Returns true if the vector is empty.

:is_sorted() -> bool
:is_sorted(callback: func(any, any) -> bool) -> bool

Returns true if the vector is sorted in ascending order.

If a callback function is supplied it will be used to compare pairs of values. It should accept two arguments a and b and return true if a < b.

:last() -> any

Returns the last item from the vector without removing it. Panics if the vector is empty.

:map(callback: func(any) -> any) -> vec

Returns a new vector created by mapping the function callback to each element of the vector in turn. callback should be a callable that takes a single argument (the vector element); its return values will form the elements of the new vector.

:mergesort() -> vec
:mergesort(callback: func(any, any) -> bool) -> vec

Sorts the vector in-place using a stable implementation of the mergesort algorithm. Returns the vector to allow chaining.

If a callback function is supplied it will be used to compare pairs of values. It should accept two arguments a and b and return true if a < b.

:quicksort() -> vec
:quicksort(callback: func(any, any) -> bool) -> vec

Sorts the vector in-place using the quicksort algorithm. Returns the vector to allow chaining.

If a callback function is supplied it will be used to compare pairs of values. It should accept two arguments a and b and return true if a < b.

:remove_at(index: i64) -> any

Removes and returns the value at the specified index. Panics if the index is out of range.

:remove_first() -> any

Removes and returns the first item from the vector. Panics if the vector is empty.

:remove_last() -> any

Removes and returns the last item from the vector. Panics if the vector is empty.

:reverse() -> vec

Reverses the vector in-place. Returns the vector to enable chaining.

:set(index: i64, value: any)

Sets the value at index. Will panic if index is out of range or not an integer.

:shuffle() -> vec

Shuffles the vector in-place. Uses Fisher-Yates/Durstenfeld shuffling and random numbers from the $std::prng module. Returns the vector to enable chaining.

:slice(start_index: i64) -> vec
:slice(start_index: i64, length: i64) -> vec

Copies a slice of the source vector and returns it as a new vector. The source vector is left unchanged.

If start_index is negative, counts backwards from the end of the vector — i.e. a start_index of -1 refers to to the last item in the vector.

If length is omitted, copies to the end of the source vector.

Panics if either argument is out of range.

:sort() -> vec
:sort(callback: func(any, any) -> bool) -> vec

Sorts the vector in-place using the default stable sorting algorithm (currently mergesort). Returns the vector to allow chaining.

If a callback function is supplied it will be used to compare pairs of values. It should accept two arguments a and b and return true if a < b.

Hash Maps

A hash map, map, is a collection of key-value pairs. Pyro's map type preserves insertion order so when you iterate over a map you get its entries back in the same order you inserted them.

$map() -> map

Creates a new map object.

Alternatively, you can create a map using literal syntax:

var map = {
    "foo" = 123,
    "bar" = 456,
};

You can index into a map to get or set entries:

var value = map[key];
var value = map:get(key);

map[key] = value;
map:set(key, value);

Indexing is equivalent to using the :get() and :set() methods as shown.

Maps are iterable:

for (key, value) in map:entries() {
    echo key;
    echo value;
}

The :entries() method returns an iterator over key-value tuples which you can unpack in a for-loop as shown. Maps also have a :keys() method which returns an iterator over the map's keys and a :values() method which returns an iterator over the map's values.

Maps have the following methods:

:contains(key: any) -> bool

Returns true if the map contains key, otherwise false.

:copy() -> map

Returns a copy of the map.

:count() -> i64

Returns the number of entries in the map.

:entries() -> iter[tup]

Returns an iterator over (key, value) tuples.

:get(key: any) -> any

Returns the value associated with key or err if key was not found.

:is_empty() -> bool

Returns true if the map is empty.

:keys() -> iter

Returns an iterator over the map's keys.

:remove(key: any)

Deletes the entry associated with key if it exists.

:set(key: any, value: any)

Adds a new entry to the map or updates an existing entry.

:values() -> iter

Returns an iterator over the map's values.

Tuples

A tuple, tup, is an immutable array of values. Tuples use zero-based indices

$tup() -> tup
$tup(arg1: any, arg2: any, ...) -> tup

Creates a new tup object. The arguments provide the tuple's values.

You can index into a tuple to get (but not set) entries:

var value = tup[index];
var value = tup:get(index);

Indexing is equivalent to using the :get() method as shown.

Tuples are iterable:

for item in $tup(123, 456, 789) {
    echo item;
}

Tuples have the following methods:

:count() -> i64

Returns the number of entries in the tuple.

:get(index: i64) -> any

Returns the value at index. Will panic if index is out of range or not an integer.

Errors

The error type, err, can be returned by functions to indicate failure.

$err() -> err
$err(arg1: any, arg2: any, ...) -> i64

Creates a new err object. The arguments provide the error's values.

Errors are a specialized variant of tuples and support all the same methods and operations.

You can use the $is_err(arg) function to check if a value is an err. Alternatively, you can use the error-coalescing operator !! to supply a default value for an operation that might fail:

var foo = might_fail() !! "default";

Iterator Wrappers

The iter type can wrap any iterator to automatically add support for a set of chainable, lazily-evaluated utility methods.

$iter(arg: iterator|iterable) -> iter

Wraps an iterator in an iter wrapper. arg can be either an iterator instance or an instance of an iterable type, e.g. a vector.

Iterators returned by Pyro builtins come pre-wrapped in iter wrappers, e.g. the character iterator returned by a string's :chars() method:

for char in "abcd":chars():enum() {
    echo char;
}

The :enum() method is provided by the iter wrapper. It adds an index to each element in the input, giving us the following output:

(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')

Iterator wrappers have the following methods:

:enum() -> iter[tup[i64, any]]
:enum(start_index: i64) -> iter[tup[i64, any]]

Adds an enumeration wrapper to an iterator. Output values are two-item tuples containing an integer index and the original input value. start_index defaults to zero if not specified.

:filter(callback: func(any) -> bool) -> iter

Returns a new iter instance that filters the output of the source iterator using the callback function. callback should be a callable that takes a single argument and returns a bool; input values will be passed through the filter if callback returns true.

:map(callback: func(any) -> any) -> iter

Returns a new iter instance mapping the function callback to the output of the source iterator. callback should be a callable that takes a single argument; its return values will form the output of the new iterator.

:skip_first(n: i64) -> iter

Skips the first n items generated by the iterator. Panics if the iterator generates less than n items.

:skip_last(n: i64) -> iter

Skips the last n items generated by the iterator. Panics if the iterator generates less than n items.

Note that this method needs to buffer the iterator's full output to determine the end point.

:to_set() -> set

Drains the iterator into a new set.

:to_vec() -> vec

Drains the iterator into a new vector.

Byte Buffers

A byte buffer, buf, is a dynamic array of bytes. Byte buffers use zero-based indices.

$buf() -> buf
$buf(size: i64, fill_value: i64|char) -> buf

Creates a new buf object. If called with zero arguments, creates a new empty buffer. If called with two arguments, creates a new buffer with the specified initial size and fill value, where size is a positive integer and value is an integer value in the range [0, 255].

You can index into a buffer to get or set byte values:

var value = buf[index];
var value = buf:get(index);

buf[index] = value;
buf:set(index, value);

Indexing is equivalent to using the :get() and :set() methods as shown.

Buffers have the following methods:

:count() -> i64

Returns the number of bytes in the buffer.

:get(index: i64) -> i64

Returns the byte value at index as an integer in the range [0, 255]. Will panic if index isn't an integer or is out of range.

:is_empty() -> bool

Returns true if the buffer is emtpy.

:set(index: i64, byte: i64)

Sets the byte value at index to byte where byte is an integer in the range [0, 255]. Will panic if index isn't an integer or is out of range. Will panic if byte isn't an integer or is out of range.

:to_str() -> str

Converts the content of the buffer into a string, leaving a valid but empty buffer object behind. Returns the new string.

Writing to a buffer and then converting it to a string is an efficient way of assembling a long string from multiple parts as it avoids the cost of creating multiple temporary strings along the way.

Note that calling $str(buf) on a buffer does something different — it creates a string with a copy of the buffer's content, leaving the buffer itself unchanged.

:write(arg: any)
:write(format_string: str, arg1: any, arg2: any, ...)

Writes a string to the buffer. Calling this method with a single argument is equivalent to calling $str() on that argument first and writing the resulting string. Calling this method with more than one argument is equivalent to calling $fmt() on those arguments first and writing the resulting string.

This method can panic if an error occurs while formatting the output string.

:write_byte(byte: i64)

Appends byte to the buffer where byte is an integer in the range [0, 255]. Will panic if byte is not an integer or is out of range.

Files

A file object, file, is a wrapper around a readable or writable byte stream.

$file(path: str, mode: str) -> file

Creates a new file object. Opens the underlying file stream using the C function fopen(). Panics on failure.

Mode options:

"r" Opens a file for reading. The file must exist.
"w" Creates an empty file for writing. If the file already exists, its content will be erased.
"a" Opens a file for appending. The file will be created if it does not exist.
"r+" Opens a file for both reading and writing. The file must exist.
"w+" Creates an empty file for both reading and writing. If the file already exists, its content will be erased.
"a+" Opens a file for both reading and appending. The file will be created if it does not exist.

Files have the following methods:

:close()

Closes the file stream. You can safely call close() on a file multiple times. If the file hasn't been explicitly closed it will be automatically closed by the garbage collector before the file object is destroyed.

:flush()

Flushes the file stream.

:read() -> buf

Reads the content of the file into a new byte buffer, buf.

Returns the new buffer. Returns an empty buffer if the file was empty or if the end of the file had already been reached before the method was called.

Panics if an I/O read error occurs or if sufficient memory cannot be allocated for the buffer.

:read_byte() -> i64?

Reads the next byte value from the file. Returns the byte value as an integer in the range [0, 255], or null if the end of the file had already been reached before the method was called. Panics if an I/O read error occurs.

:read_bytes(n: i64) -> buf?

Attempts to read n bytes from the file into a new byte buffer. May read less than n bytes if the end of the file is reached first.

Returns the new byte buffer or null if the end of the file had already been reached before the method was called.

Panics if an I/O read error occurs, if the argument is invalid, or if sufficient memory cannot be allocated for the buffer.

:read_line() -> str?

Reads the next line of input from the file and returns it as a string. Strips the terminating \n or \r\n if present.

Returns the string or null if the end of the file had already been reached before the method was called.

Panics if an I/O read error occurs or if sufficient memory cannot be allocated for the string.

:read_string() -> str

Reads the content of the file into a string, str.

Returns the new string. Returns an empty string if the file was empty or if the end of the file had already been reached before the method was called.

Panics if an I/O read error occurs or if sufficient memory cannot be allocated for the string.

:write(arg: any) -> i64
:write(format_string: str, arg1: any, arg2: any, ...) -> i64

Writes a string or buffer to the file.

If arg is a buf, its content will be written to the file directly. Otherwise, calling this method with a single argument is equivalent to calling $str() on that argument first and writing the resulting string. Calling this method with more than one argument is equivalent to calling $fmt() on those arguments first and writing the resulting string.

Returns the number of bytes written.

Panics if an I/O write error occurs or if an error occurs while formatting the output string.

:write_byte(byte: i64)

Writes a byte value to the file, where byte is an integer in the range [0, 255]. Panics if an I/O write error occurs or if the argument is an invalid type or is out of range.

Stacks

A stack is a LIFO container type that supports only two operations — pushing and popping.

$stack() -> stack

Creates a new stack object.

Stacks have the following methods:

:count() -> i64

Returns the number of items in the stack.

:is_empty() -> bool

Returns true if the stack is empty.

:pop() -> any

Pops and returns the value on top of the stack. Panics if the stack is empty.

:push(item: any)

Pushes an item onto the stack.

Queues

A queue is a FIFO container type that supports only two operations — enqueueing and dequeueing.

$queue() -> queue

Creates a new queue object.

Queues have the following methods:

:count() -> i64

Returns the number of items in the queue.

:dequeue() -> any

Removes and returns the first item from the queue. Panics if the queue is empty.

:enqueue(item: any)

Adds a new item to the end of the queue.

:is_empty() -> bool

Returns true if the queue is empty.

Sets

A set is an unordered collection of distinct objects.

$set() -> set
$set(arg: iterable) -> set

Creates a new set object. If arg is iterable, initializes the new set by iterating over its values.

Alternatively, you can create a set by draining an iterator, e.g.

var chars = "supercalifragilistic":chars():to_set();
assert chars:count() == 12;

Sets have the following methods:

:add(item: any)

Adds an item to the set. This is a null operation if the set already contains a member equal to item.

:contains(item: any) -> bool

Returns true if the set contains a member equal to item.

:count() -> i64

Returns the number of items in the set.

:is_empty() -> bool

Returns true if the set is empty.

:remove(item: any)

Removes item from the set.