The Koto Programming Language
Language Overview
Koto is an expression-based scripting language with the goals of keeping things simple, and visually clean. Effort has been taken to only introduce visual elements to the syntax when absolutely necessary.
Assignments
Assignment to an identifier is performed using the = operator.
x = 1
a, b, c = 1, 2, 3
Identifiers
Identifier names use the Unicode xid_start and xid_continue definitions as
outlined here.
Examples of valid identifiers include:
my_id = 123
foo42 = 99
héllø = -1
やあ = 100
Comments
Single-line comments are using a # symbol.
Multi-line comments are started with #- and finished with -#.
Mulii-line comments can be nested, and can be used as inline comments.
# This is a single-line comment
#- This is a
multi-line comment
#- Multi-line comments can be nested -#
-#
a = 1 + #- This is an inline comment -# 2
Indentation
Whitespace is used to define indented blocks of code, and for continuing long expressions.
Spaces and tabs are both counted as whitespace characters.
Any amount of indentation can be used for a block, but the level must remain consistent throughout a block for the parser to understand that the expressions belong together.
x = if foo # An if expression, starting on column 1
result = do_something() # An indented 'then' block for the if expression
result # A continuation of the 'then' block
else # The else condition matching the 'if' indentation
do_something_else() # The 'else' body.
# No 'end if' is necessary here to start a new expression,
# the return of indentation to column 1
print x
Text format
Koto scripts are expected to contain valid UTF-8 data.
Value Types
Control Flow
Functions
Iterators
Error Handling
Importing Code
Testing
Core Library
The core library for Koto contains utilities that are a standard part of the Koto runtime.
The library is grouped into modules, e.g. test contains functions related to testing Koto code. Many of the modules are related to value types
io
A collection of utilities for working with the local filesystem.
Reference
- create
- current_dir
- exists
- open
- read_to_string
- remove_file
- stderr
- stdin
- stdout
- temp_dir
- File
- File.flush
- File.path
- File.read_line
- File.read_to_string
- File.seek
- File.write
- File.write_line
create
|String| -> File
Returns an empty File at the provided path.
If the file already exists it will be truncated.
Errors
A runtime error will be thrown if the file can't be created.
Example
f = io.create "foo.temp"
f.write_line "Hello"
f.read_to_string()
# Hello
current_dir
|| -> String
Returns the current working directory as a String, or Empty if the current directory can't be retrieved.
exists
|String| -> Bool
Returns true if a file exists at the provided path.
Example
path = "foo.temp"
io.exists path
# false
io.create path
io.exists path
# true
open
|String| -> File
Opens the file at the given path, and returns a corresponding File.
Errors
An error is thrown if a file can't be opened at the given path.
Example
f = io.open "path/to/existing.file"
f.exists()
# true
|Value| -> ()
|String, Value...| -> ()
Prints a formatted string to the active logger, by default this is the standard output.
Note
See string.format for the formatting syntax.
read_to_string
|String| -> String
Returns a string containing the contents of the file at the given path.
Errors
Errors are thrown:
- if the file doesn't contain valid UTF-8 data.
- if a file can't be opened at the given path.
Example
f = io.create "foo.temp"
f.write_line "Hello!"
io.read_to_string "foo.temp"
# Hello!
remove_file
|String| -> ()
Removes the file at the given path.
Errors
- An error is thrown if a file can't be removed at the given path.
Example
path "foo.temp"
io.create path
io.exists path
# true
io.remove_file path
io.exists path
# false
stderr
|| -> File
Returns the standard error output of the current process as a file.
Example
io.stderr().write_line "An error occurred!"
See Also
stdin
|| -> File
Returns the standard input of the current process as a file.
Example
io.stdin().read_to_string()
# "..."
See Also
stdout
|| -> File
Returns the standard output of the current process as a file.
Example
io.stdout().write_line "Hello, World!"
See Also
temp_dir
|| -> String
Returns the path to a temporary directory.
Note
This defers to Rust's std::env::temp_dir, for details see
its documentation.
File
A map that wraps a file handle, returned from functions in io.
File.flush
|File| -> ()
Ensures that any buffered changes to the file have been written.
See Also
File.path
|File| -> String
Returns the file's path.
File.read_line
|File| -> String or Empty
Reads a line of output from the file as a string, not including the newline.
When the end of the file is reached, Empty will be returned.
Errors
An error is thrown if the line doesn't contain valid UTF-8 data.
File.read_to_string
|File| -> String
Reads the file's contents to a string.
Errors
An error is thrown if the file doesn't contain valid UTF-8 data.
File.seek
|File, Number| -> ()
Seeks within the file to the specified position in bytes.
File.write
|File, Value| -> ()
Writes the formatted value as a string to the file.
File.write_line
|File, Value| -> ()
Writes the formatted value as a string, with a newline, to the file.
iterator
Iterators in Koto provide access to sequences of data, yielding values via
.next(), until the end of the sequence is reached and the empty value ()
is returned.
Iterable Values
Values that can produce iterable sequences are referred to as Iterable.
Iterables include:
- Iterators (naturally!)
- Lists
- Maps
- The map's key/value pairs are provided as a Tuple.
- Ranges
- Strings
- Tuples
The contents of the iterator module are made available to all Iterables.
Example
# Starting with a List
[1, 2, 3]
# Calling iterator.each with the List as the implicit first argument
.each |x| x * 2
# Calling iterator.to_list with the Iterator resulting from iterator.each
.to_list()
# [2, 4, 6]
Loops
for, while, and until loops take any Iterable and then provide its
output values for each iteration of the loop.
for x in (2, 3, 4).each |n| n * 2
io.print "-> {}", x
# -> 4
# -> 6
# -> 8
Reference
- all
- any
- chain
- consume
- count
- each
- enumerate
- fold
- intersperse
- keep
- last
- max
- min
- min_max
- next
- position
- product
- skip
- sum
- take
- to_list
- to_map
- to_string
- to_tuple
- zip
all
|Iterable, Function(|Value| -> Bool)| -> Bool
Checks the Iterable's values against a test Function.
The Function should return true or false, and then all returns true
if all values pass the test.
all stops running as soon as it finds a failing test, and then false is
returned.
Example
(1..9).all |x| x > 0
# true
("", "", "foo").all string.is_empty
# false
[10, 20, 30]
.each |x| x / 10
.all |x| x < 10
# true
any
|Iterable, |Value| -> Bool| -> Bool
Checks the Iterable's values against a test Function.
The Function should return true or false, and then any returns true
if any of the values pass the test.
any stops running as soon as it finds a passing test.
Example
(1..9).any |x| x == 5
# true
("", "", "foo").any string.is_empty
# true
[10, 20, 30]
.each |x| x / 10
.any |x| x == 2
# true
chain
|Iterable, Iterable| -> Iterator
chain returns an iterator that iterates over the output of the first iterator,
followed by the output of the second iterator.
Example
[1, 2].chain([3, 4, 5]).to_tuple()
# (1, 2, 3, 4, 5)
consume
|Iterable| -> ()
Consumes the output of the iterator. This is useful when the side-effects of the iterator chain are important, but not so much the output value.
count
|Iterable| -> Number
Counts the number of items yielded from the iterator.
Example
(5..=15).count()
# 10
(0..100)
.keep |x| x % 2 == 0
.count()
# 50
each
|Iterable, |Value| -> Value| -> Iterator
Takes an Iterable and a Function, and returns a new iterator that provides the result of calling the function with each value in the iterable.
Example
(2, 3, 4)
.each |x| x * 2
.to_list()
# [4, 6, 8]
enumerate
|Iterable| -> Iterator
Returns an iterator that provides each value along with an associated index.
Example
("a", "b", "c").enumerate().to_list()
# [(0, "a"), (1, "b"), (2, "c")]
fold
|Iterable, Value, |Value, Value| -> Value| -> Value
Returns the result of 'folding' the iterator's values into an accumulator function.
The function takes the accumulated value and the next iterator value, and then returns the result of folding the value into the accumulator.
The first argument is an initial accumulated value that gets passed to the function along with the first value from the iterator.
The result is then the final accumulated value.
This operation is also known in other languages as reduce, accumulate,
inject, fold left, along with other names.
Example
("a", "b", "c").fold "", |result, x| result += x + "-"
# a-b-c-
See Also
intersperse
|Iterable, Value| -> Iterator
Returns an iterator that yields a copy of the provided value between each adjacent pair of output values.
|Iterable, || -> Value| -> Iterator
Returns an iterator that yields the result of calling the provided function between each adjacent pair of output values.
Example
("a", "b", "c").intersperse("-").to_string()
# "a-b-c"
separators = (1, 2, 3).iter()
("a", "b", "c")
.intersperse || separators.next()
.to_tuple(),
# ("a", 1, "b", 2, "c")
keep
|Iterable, |Value| -> Bool| -> Iterator
Returns an iterator that keeps only the values that pass a test function.
The function is called for each value in the iterator, and returns either true
if the value should be kept in the iterator output, or false if it should be
discarded.
Example
(0..10).keep(|x| x % 2 == 0).to_tuple()
# (0, 2, 4, 6, 8)
last
|Iterable| -> Value
Consumes the iterator, returning the last yielded value.
Example
(1..100).take(5).last()
# 5
(0..0).last()
# ()
max
|Iterable| -> Value
Returns the maximum value found in the iterable.
A < 'less than' comparison is performed between each value and the maximum
found so far, until all values in the iterator have been compared.
Example
(8, -3, 99, -1).max()
# 99
See Also
min
|Iterable| -> Value
Returns the minimum value found in the iterable.
A < 'less than' comparison is performed between each value and the minimum
found so far, until all values in the iterator have been compared.
Example
(8, -3, 99, -1).min()
# -3
See Also
min_max
|Iterable| -> (Value, Value)
Returns the minimum and maximum values found in the iterable.
A < 'less than' comparison is performed between each value and both the
minimum and maximum found so far, until all values in the iterator have been
compared.
Example
(8, -3, 99, -1).min_max()
# (-3, 99)
See Also
next
|Iterator| -> Value
Returns the next value from the iterator.
Example
x = (1, 2).iter()
x.next()
# 1
x.next()
# 2
x.next()
# ()
position
|Iterable, |Value| -> Bool| -> Value
Returns the position of the first value in the iterable that passes the test function.
The function is called for each value in the iterator, and returns either true
if the value is a match, or false if it's not.
The first matching value will cause iteration to stop, and the number of steps taken to reach the matched value is returned as the result.
If no match is found then () is returned.
Example
(10..20).position |x| x == 15
# 5
(10..20).position |x| x == 99
# ()
product
|Iterable| -> Value
Returns the result of multiplying each value in the iterable together.
Example
(2, 3, 4).product()
# 24
See also
skip
|Iterable, Number| -> Iterator
Skips over a number of steps in the iterator.
Example
(100..200).skip(50).next()
# 150
See also
sum
|Iterable| -> Value
Returns the result of adding each value in the iterable together.
Example
(2, 3, 4).sum()
# 9
See also
take
|Iterable, Number| -> Iterator
Provides an iterator that consumes a number of values from the input before finishing.
Example
(100..200).take(3).to_tuple()
# (100, 101, 102)
See also
to_list
|Iterable| -> List
Consumes all values coming from the iterator and places them in a list.
Example
("a", 42, (-1, -2)).to_list()
# ["a", 42, (-1, -2)]
See also
to_map
|Iterable| -> Map
Consumes all values coming from the iterator and places them in a map.
If a value is a tuple, then the first element in the tuple will be inserted as the key for the map entry, and the second element will be inserted as the value.
If the value is anything other than a tuple, then it will be inserted as the map
key, with () as the entry's value.
Example
("a", "b", "c").to_map()
# {"a": (), "b": (), "c": ()}
("a", "bbb", "cc")
.each |x| x, x.size()
.to_map()
# {"a": 1, "bbb": 3, "cc": 2}
See also
to_string
|Iterable| -> String
Consumes all values coming from the iterator and produces a string containing the formatted values.
Example
("x", "y", "z").to_string()
# "xyz"
(1, 2, 3).intersperse("-").to_string()
# "1-2-3"
See also
to_tuple
|Iterable| -> Tuple
Consumes all values coming from the iterator and places them in a tuple.
Example
("a", 42, (-1, -2)).to_list()
# ["a", 42, (-1, -2)]
See also
zip
|Iterable, Iterable| -> Iterator
Combines the values in two iterables into an iterator that provides corresponding pairs of values, one at a time from each input iterable.
Example
(1, 2, 3).zip(("a", "b", "c")).to_list()
# [(1, "a"), (2, "b"), (3, "c")]
koto
A collection of utilities for working with the Koto runtime.
Reference
args
Tuple
Provides access to the arguments that were passed into the script when running
the koto CLI application.
If no arguments were provided then the list is empty.
Example
# Assuming that the script was run with `koto script.koto -- 1 2 "hello"`
koto.args.size()
# 3
koto.args.first()
# 1
koto.args.last()
# hello
exports
|| -> Map
Returns the current module's export map.
Although typically module items are exported with export expressions,
it can be useful to export items programatically.
script_dir
String or Empty
If a script is being executed then script_dir provides the directory that the
current script is contained in as a String, otherwise script_dir is Empty.
script_path
String or Empty
If a script is being executed then script_path provides the path of the
current script as a String, otherwise script_path is Empty.
type
|Value| -> String
Returns the type of the input Value as a String.
Note that a map value can override the value returned from type by defining
the @type meta value, for more information see
the reference for map.
Example
koto.type true
# Bool
x = 42
koto.type x
# Int
foo =
@type: "Foo"
koto.type foo
# Foo
list
Lists in Koto are dynamically sized contiguous arrays of values.
Like other containers in Koto, a list's data is shared between all instances of the list.
Example
x = [1, 2, "hello"]
x[1] = 99
x
# [1, 99, "hello"]
y = x
y[0] = "abc" # x and y share the same internal list data
x
# ["abc", 99, "hello"]
z = x.copy()
z[1] = -1 # z is a copy of x, so has unique internal data
x # x remains unchanged after the modificaton of z
# ["abc", 99, "hello"]
Reference
- clear
- contains
- copy
- deep_copy
- fill
- first
- get
- insert
- is_empty
- iter
- last
- pop
- push
- remove
- resize
- retain
- reverse
- size
- sort
- sort_copy
- swap
- to_tuple
- transform
- with_size
clear
|List| -> ()
Clears the list by removing all of its elements.
Example
x = [1, 2, 3]
x.clear()
x
# []
contains
|List, Value| -> Bool
Returns true if the list contains a value that matches the input value.
Matching is performed with the == equality operator.
Example
[1, "hello", (99. -1)].contains "hello"
# true
copy
|List| -> List
Makes a unique copy of the list data.
Note that this only copies the first level of data, so nested containers
will share their data with their counterparts in the copy. To make a copy where
any nested containers are also unique, use list.deep_copy.
Example
x = [1, 2, "hello"]
y = x
y[0] = "abc" # x and y share the same internal list data
x
# ["abc", 99, "hello"]
z = x.copy()
z[1] = -1 # z is a copy of x, so has unique internal data
x # x remains unchanged after the modificaton of z
# ["abc", 99, "hello"]
See also
deep_copy
|List| -> List
Makes a unique deep copy of the list data.
This makes a unique copy of the list data, and then recursively makes deep copies of any nested containers in the list.
If only the first level of data needs to be made unique, then use
list.copy.
Example
x = [[1, 2], [3, [4, 5]]]
y = x.deep_copy()
y[1][1] = 99
x # a deep copy has been made, so x is unaffected by the assignment to y
# [[1, 2], [3, [4, 5]]]
See also
fill
|List, Value| -> ()
Fills the list with copies of the provided value.
Example
x = [1, 2, 3]
x.fill 99
x
# [99, 99, 99]
first
|List| -> Value
Returns the first value in the list, or () if the list is empty.
Example
[99, -1, 42].first()
# 99
[].first()
# ()
See also
get
|List, Number| -> Value
Gets the Nth value in the list, or () if the list doesn't contain a value at
that position.
Example
[99, -1, 42].get 1
# -1
[99, -1, 42].get 5
# ()
See also
insert
|List, Number, Value| -> ()
Inserts the value into the Nth position in the list.
An error is thrown if the position is negative or greater than the size of the list.
Example
[99, -1, 42].insert 2, "hello"
# [99, -1, "hello", 42]
See also
is_empty
|List| -> Bool
Returns true if the list has a size of zero, and false otherwise.
Example
[].is_empty()
# true
[1, 2, 3].is_empty()
# false
iter
|List| -> Iterator
Returns an iterator that iterates over the list's values.
Lists are iterable, so it's not necessary to call .iter() to get access to
iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
x = [2, 3, 4].iter()
x.skip(1)
x.next()
# 3
last
|List| -> Value
Returns the last value in the list, or () if the list is empty.
Example
[99, -1, 42].first()
# 42
[].first()
# ()
See also
pop
|List| -> Value
Removes the last value from the list and returns it.
If the list is empty then () is returned.
Example
x = [99, -1, 42]
x.pop()
# 42
x
# [99, -1]
[].pop()
# ()
See also
push
|List, Value| -> ()
Adds the value to the end of the list.
Example
[99, -1].push "hello"
# [99, -1, "hello"]
See also
remove
|List, Number| -> Value
Removes the value at the given position from the list and returns it.
Throws an error if the position isn't a valid index in the list.
Example
[99, -1, 42].remove 1
# [99, 42]
See also
resize
|List, Number, Value| -> ()
Grows or shrinks the list to the specified size. If the new size is larger, then copies of the provided value are used to fill the new space.
Example
x = [1, 2]
x.resize 4, "x"
x
# [1, 2, "x", "x"]
x.resize 3, ()
x
# [1, 2, "x"]
retain
|List, Value| -> ()
Retains matching values in the list, discarding values that don't match.
If the test value is a function, then the function will be called with each of
the list's values, and if the function returns true then the value will be
retained, otherwise if the function returns false then the value will be
discarded.
If the test value is not a function, then the list's values will be compared
using the == equality operator, and then retained if they match.
Example
x = [1..10]
x.retain |n| n < 5
x
# [1, 2, 3, 4]
x = [1, 3, 8, 3, 9, -1]
x.retain 3
x
# [3, 3]
reverse
|List| -> ()
Reverses the order of the list's contents.
Example
x = ["hello", -1, 99, "world"]
x.reverse()
x
# ["world", 99, -1, "hello"]
size
|List| -> Number
Returns the number of values contained in the list.
Example
x = [1..=100]
x.size()
# 100
[].size()
# 0
sort
|List| -> ()
Sorts the list in place.
|List, |Value| -> Value| -> ()
Sorts the list in place, based on the output of calling a 'key' function for each value. The function result is cached, so it's only called once per value.
Example
x = [1, -1, 99, 42]
x.sort()
x
# [-1, 1, 42, 99]
x = ["bb", "ccc", "a"]
x.sort string.size
x
# ["a", "bb", "ccc"]
x = [2, 1, 3]
x.sort |n| -n
x
# [3, 2, 1]
sort_copy
|List| -> List
Returns a sorted copy of the list. The input is left untouched.
Example
x = [1, -1, 99, 42]
y = x.sort_copy()
y
# [-1, 1, 42, 99]
x # x remains untouched
# [1, -1, 99, 42]
swap
|List, List| -> ()
Swaps the contents of the two input lists.
Example
x = [1, 2, 3]
y = [7, 8, 9]
x.swap y
x
# [7, 8, 9]
y
# [1, 2, 3]
to_tuple
|List| -> Tuple
Returns a copy of the list data as a tuple.
Example
[1, 2, 3].to_tuple()
# (1, 2, 3)
transform
|List, |Value| -> Value| -> ()
Transforms the list data by calling a function with each value and then replacing the value with the function result.
Example
x = ["aaa", "bb", "c"]
x.transform string.size
x
# [3, 2, 1]
x.transform |n| "{}".format n
x
# ["3", "2", "1"]
with_size
|Number, Value| -> List
Returns a list containing N copies of a value.
Example
import list
list.with_size 5, "$"
# ["$", "$", "$", "$", "$"]
map
Maps in Koto are associative containers of keys mapped to values.
The order in which items are added to the map will be preserved.
Creating a map
There are two ways to directly create a map in Koto: map blocks, and inline maps.
Block map syntax
Maps can be created with indented blocks, where each line contains an entry of
the form Key: Value.
x =
hello: -1
goodbye: 99
x.hello
# -1
x.goodbye
# 99
Nested Maps can be defined with additional indentation:
x =
hello:
world: 99
everybody: 123
to:
you: -1
x.hello.world
# 99
x.hello.to.you
# 123
Inline map syntax
Maps can also be created with curly braces, with comma-separated entries.
x = {hello: -1, goodbye: "abc"}
x.hello
# -1
x.goodbye
# abc
If only the key is provided for an entry, then a value matching the name of the key is looked for and is then copied into the entry.
hello = "abc"
goodbye = 99
x = {hello, goodbye, tschüss: 123}
x.goodbye
# 99
Keys
When creating a Map directly, the keys are defined as strings.
To use non-string values as keys, map.insert can be used.
x = {}
x.insert 0, "Hello"
x.insert true, "World"
"{}, {}!".format x.get(0), x.get(true)
# Hello, World!
Instance functions
When a Function is used as a value in a Map, and if it uses the keyword self
as its first argument, then the runtime will pass the instance of the map that
contains the function as the self argument.
x =
# Initialize an empty list
data: []
# Takes a value and adds it to the list
add_to_data: |self, n| self.data.push n
# Returns the sum of the list
sum: |self| self.data.sum()
x.add_to_data 2
x.add_to_data 20
x.sum()
# 22
Operators
The + operator can be used to merge two maps together.
x = {hello: 123}
y = {goodbye: 99}
x + y
# {hello, goodbye}
Meta Maps and overloaded operations
Maps can be used to create value types with custom behaviour.
Keys with @ prefixes go into the map's 'meta map',
which is checked when the map is encountered in operations.
make_x = |n|
data: n
# Overloading the addition operator
@+: |self, other|
# a new instance is made with the result of adding the two values together
make_x self.data + other.data
# Overloading the subtraction operator
@-: |self, other|
make_x self.data - other.data
x1 = make_x 10
x2 = make_x 20
(x1 + x2).data
# 30
(x1 - x2).data
# -10
All binary operators can be overloaded following this pattern.
Additionally, the following meta functions can customize object behaviour:
@negate- Overloads the unary negation operator:
@negate: |self| make_x -self.data
- Overloads the unary negation operator:
@index- Overloads
[]indexing:@index: |self, index| self.data + index
- Overloads
@display- Customizes how the map will be displayed when formatted as a string:
@display: |self| "X: {}".format self.data
- Customizes how the map will be displayed when formatted as a string:
@type- Provides a String that's used when checking the map's type:
@type: "X"
- Provides a String that's used when checking the map's type:
Meta entries
@meta can be used as a prefix on a map entry to add it to the meta map.
The entry will be accessible on value lookups but won't show up in the regular
map data:
make_x = |n|
data: n
# Overloading the addition operator
@meta get_data: |self| self.data
x = make_x 42
x.keys().to_list()
# ["data"]
x.get_data()
# 42
Tests
Tests are also stored in the meta map, see test.md for info.
Reference
- clear
- contains_key
- copy
- deep_copy
- get
- get_index
- insert
- is_empty
- iter
- keys
- remove
- size
- sort
- update
- values
clear
|Map| -> ()
Clears the map by removing all of its elements.
Example
x = {x: -1, y: 42}
x.clear()
x
# {}
contains_key
|Map, Key| -> Bool
Returns true if the map contains a value with the given key,
and false otherwise.
copy
|Map| -> Map
Makes a unique copy of the map data.
Note that this only copies the first level of data, so nested containers
will share their data with their counterparts in the copy. To make a copy where
any nested containers are also unique, use map.deep_copy.
Example
x = {foo: -1, bar: 99}
y = x
y.foo = 42
x.foo
# 42
z = x.copy()
z.bar = -1
x.bar # x.bar remains unmodified due to the
# 99
See also
deep_copy
|Map| -> Map
Makes a unique deep copy of the map data.
This makes a unique copy of the map data, and then recursively makes deep copies of any nested containers in the map.
If only the first level of data needs to be made unique, then use
map.copy.
Example
x = {foo: 42, bar: {baz: 99}}
y = m.deep_copy()
y.bar.baz = 123
x.bar.baz # a deep copy has been made, so x is unaffected by the change to y
# 99
See also
get
|Map, Key| -> Value
Returns the value corresponding to the given key, or () if the map doesn't
contain the key.
Example
x = {hello: -1}
x.get "hello"
# -1
x.get "goodbye"
# ()
x.insert 99, "xyz"
x.get 99
# xyz
See also
get_index
|Map, Number| -> Tuple
Returns the entry at the given index as a key/value tuple, or () if the map
doesn't contain an entry at that index.
An error will be thrown if a negative index is provided.
Example
x = {foo: -1, bar: -2}
x.get_index 1
# (bar, -2)
x.get_index 99
# ()
See also
insert
|Map, Key| -> Value
Inserts () into the map with the given key.
|Map, Key, Value| -> Value
Inserts a value into the map with the given key.
If the key already existed in the map, then the old value is returned.
If the key didn't already exist, then () is returned.
Example
x = {hello: -1}
x.insert "hello", 99 # -1 already exists at `hello`, so it's returned here
# -1
x.hello # hello is now 99
# 99
x.insert "goodbye", 123 # No existing value at `goodbye`, so () is returned
# ()
x.goodbye
# 123
See also
is_empty
|Map| -> Bool
Returns true if the map contains no entries, otherwise false.
Example
{}.is_empty()
# true
{hello: -1}.is_empty()
# false
See also
iter
|Map| -> Iterator
Returns an iterator that iterates over the map's entries.
Each key/value pair is provided in order as a tuple.
Maps are iterable, so it's not necessary to call .iter() to get access to
iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
m =
hello: -1
goodbye: 99
x = m.iter();
x.next()
# ("hello", -1)
x.next()
# ("goodbye", 99)
x.next()
# ()
See also
keys
|Map| -> Iterator
Returns an iterator that iterates in order over the map's keys.
Example
m =
hello: -1
goodbye: 99
x = m.keys()
x.next()
# "hello"
x.next()
# "goodbye"
x.next()
# ()
See also
remove
|Map, Key| -> Value
Removes the entry that matches the given key.
If the entry existed then its value is returned, otherwise () is returned.
Example
x =
hello: -1
goodbye: 99
x.remove "hello"
# -1
x.remove "xyz"
# ()
x.remove "goodbye"
# 99
x.is_empty()
# true
See also
size
|Map| -> Number
Returns the number of entries contained in the map.
Example
{}.size()
# 0
{"a": 0, "b": 1}.size()
# 2
See also
sort
|Map| -> ()
Sorts the map's entries by key.
|Map, |Value, Value| -> Value| -> ()
Sorts the map's entries, based on the output of calling a 'key' function for each entry. The entry's key and value are passed into the function as separate arguments.
The function result is cached, so it's only called once per entry.
Example
x =
hello: 123
bye: -1
tschüss: 99
x.sort() # Sorts the map by key
x
# {bye, hello, tschüss}
x.sort |_, value| value # Sort the map by value
x
# {bye, tschüss, hello}
x.sort |key, _| -key.size() # Sort the map by reversed key length
x
# {tschüss, hello, bye}
update
|Map, Key, |Value| -> Value| -> Value
Updates the value associated with a given key by calling a function with either
the existing value, or () if it doesn't there isn't a matching entry.
The result of the function will either replace an existing value, or if no value existed then an entry with the given key will be inserted into the map with the function's result.
The function result is then returned from update.
|Map, Key, Value, |Value| -> Value| -> Value
This variant of update takes a default value that is provided to the
function if a matching entry doesn't exist.
Example
x =
hello: -1
goodbye: 99
x.update "hello", |n| n * 2
# -2
x.hello
# -2
x.update "tschüss", 10, |n| n * 10
# 100
x.tschüss
# 100
See also
values
|Map| -> Iterator
Returns an iterator that iterates in order over the map's values.
Example
m =
hello: -1
goodbye: 99
x = m.values()
x.next()
# -1
x.next()
# 99
x.next()
# ()
See also
num2
A Num2 in Koto is a packed pair of 64bit floating-point numbers,
which can be useful when dealing with operations that require pairs of numbers,
like 2D coordinates.
Element-wise arithmetic operations between Num2s are available, while operations with Numbers apply the number to each element.
Example
x = num2 1, 2
y = num2 3, 4
x + y
# num2(4, 6)
x[0] + y[0]
# 4
x + 10
# num2(11, 12)
x[0] = -1
x
# num2(-1, 2)
Reference
iter
|Num2| -> Iterator
Returns an iterator that iterates over the list's values.
Num2 values are iterable, so it's not necessary to call .iter() to get access
to iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
x = (num2 3, 4).iter()
x.skip(1)
x.next()
# 4
length
|Num2| -> Float
Returns the length of the vector represented by the Num2's elements.
Example
x = num2(3, 4)
x.length()
# 5
max
|Num2| -> Float
Returns the value of the largest element in the Num2.
Example
x = num2(10, 20)
x.max()
# 20
min
|Num2| -> Float
Returns the value of the smallest element in the Num2.
Example
x = num2(10, 20)
x.min()
# 10
normalize
|Num2| -> Num2
Returns a Num2 with the same direction as the input, with its length normalized to 1.
Example
x = num2(3, 4)
x.normalize()
# num2(0.6, 0.8)
product
|Num2| -> Float
Returns the result of multiplying the Num2's elements together.
Example
x = num2(10, 20)
x.product()
# 300
sum
|Num2| -> Float
Returns the result of adding the Num2's elements together.
Example
x = num2(10, 20)
x.sum()
# 30
num4
A Num4 in Koto is a packed group of 32bit floating-point numbers, which can be useful when working with operations that require 3D coordinates, or RGBA colour values.
Element-wise arithmetic operations between Num4s are available, while operations with Numbers apply the number to each element.
Example
x = num4 1, 2, 3, 4
y = num4 5, 6, 7, 8
x + y
# num4(6, 8, 10, 12)
x[2]
# 10
x * 0.5
# num4(0.5, 1, 1.5, 2)
x[0..2] = -1
x
# num4(-1, -1, 10, 12)
Reference
iter
|Num4| -> Iterator
Returns an iterator that iterates over the list's values.
Num4 values are iterable, so it's not necessary to call .iter() to get access
to iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
x = (num4 3, 4, 5, 6).iter()
x.skip(2)
x.next()
# 5
length
|Num4| -> Float
Returns the length of the vector represented by the Num4's elements.
Example
x = num4(2, -2, 2, -2)
x.length()
# 4
max
|Num4| -> Float
Returns the value of the largest element in the Num4.
Example
x = num4(10, 20, -50, -10)
x.max()
# 20
min
|Num4| -> Float
Returns the value of the smallest element in the Num4.
Example
x = num4(10, 20, -50, -10)
x.min()
# -50
normalize
|Num4| -> Num4
Returns a Num4 with the same direction as the input, with its length normalized to 1.
Example
x = num4(2, -2, 2, -2)
x.normalize()
# num4(0.5, -0.5, 0.5, 0.5)
product
|Num4| -> Float
Returns the result of multiplying the Num4's elements together.
Example
x = num4(10, 20, -50, -10)
x.product()
# 100000
sum
|Num4| -> Float
Returns the result of adding the Num4's elements together.
Example
x = num4(10, 20, 30, 40)
x.sum()
# 100
number
Numbers in Koto are represented internally as either a signed 64 bit integer or float, switching between the two representations automatically depending on usage.
Example
x = 1 # x is assigned as an integer
y = x + 0.5 # y is assigned as a float
x += 0.99 # x is now a float
Reference
- abs
- acos
- and
- asin
- atan
- ceil
- clamp
- cos
- cosh
- degrees
- e
- exp
- exp2
- flip_bits
- floor
- infinity
- is_nan
- ln
- log2
- log10
- max
- min
- nan
- negative_infinity
- or
- pi
- pow
- radians
- recip
- shift_left
- shift_right
- sin
- sinh
- sqrt
- tan
- tanh
- tau
- to_float
- to_int
- xor
abs
|Number| -> Number
Returns the absolute value of the number.
Example
-1.abs()
# 1
1.abs()
# 1
acos
|Number| -> Float
Returns the arc cosine of the number. acos is the inverse function of cos.
Example
0.acos()
# π / 2
1.acos()
# 1
and
|Integer, Integer| -> Integer
Returns the bitwise combination of two integers, where a 1 in both input
positions produces a 1 in corresponding output positions.
Example
0b1010.and 0b1100
# 0b1000
asin
|Number| -> Float
Returns the arc sine of the number. asin is the inverse function of sin.
Example
0.asin()
# 0
1.asin()
# π / 2
atan
|Number| -> Float
Returns the arc tangent of the number. atan is the inverse function of tan.
Example
0.atan()
# 0
1.atan()
# π / 4
ceil
|Number| -> Integer
Returns the smallest integer that's greater than or equal to the input.
Example
0.5.ceil()
# 1
2.ceil()
# 2
-0.5.ceil()
# 0
clamp
|Number, Number, Number| -> Number
Returns the first number restricted to the range defined by the second and third numbers.
Example
0.clamp 1, 2
# 1
1.5.clamp 1, 2
# 1.5
3.0.clamp 1, 2
# 2
cos
|Number| -> Float
Returns the cosine of the number.
Example
0.cos()
# 1.0
import number.pi
pi.cos()
# -1.0
cosh
|Number| -> Float
Returns the hyperbolic cosine of the number.
Example
3.cosh()
# (e.pow(3) + e.pow(-3)) / 2
degrees
|Number| -> Float
Converts radians into degrees.
Example
from number import pi, tau
pi.degrees()
# 180.0
tau.degrees()
# 360.0
e
Float
Provides the e constant.
exp
|Number| -> Float
Returns the result of applying the exponential function,
equivalent to calling e.pow x.
Example
0.exp()
# 1.0
1.exp()
# number.e
exp2
|Number| -> Float
Returns the result of applying the base-2 exponential function,
equivalent to calling 2.pow x.
Example
1.exp2()
# 2.0
3.exp2()
# 8.0
flip_bits
|Integer| -> Integer
Returns the input with its bits 'flipped', i.e. 1 => 0, and 0 => 1.
Example
1.flip_bits()
# -2
floor
|Number| -> Integer
Returns the smallest integer that's less than or equal to the input.
Example
0.5.floor()
# 0
2.floor()
# 2
-0.5.floor()
# -1
infinity
Float
Provides the ∞ constant.
is_nan
|Number| -> Bool
Returns true if the number is NaN.
Example
1.is_nan()
# false
(0 / 0).is_nan()
# true
ln
|Number| -> Float
Returns the natural logarithm of the number.
Example
1.ln()
# 0.0
from number import e
e.ln()
# 1.0
log2
|Number| -> Float
Returns the base-2 logarithm of the number.
Example
2.log2()
# 1.0
4.log2()
# 2.0
log10
|Number| -> Float
Returns the base-10 logarithm of the number.
Example
10.log10()
# 1.0
100.log10()
# 2.0
max
|Number, Number| -> Number
Returns the larger of the two numbers.
Example
1.max 2
# 2
4.5.max 3
# 4.5
min
|Number, Number| -> Number
Returns the smaller of the two numbers.
Example
1.min 2
# 1
4.5.min 3
# 3
nan
Float
Provides the NaN (Not a Number) constant.
negative_infinity
Float
Provides the -∞ constant.
or
|Integer, Integer| -> Integer
Returns the bitwise combination of two integers, where a 1 in either input
positions produces a 1 in corresponding output positions.
Example
0b1010.or 0b1100
# 0b1110
pi
Float
Provides the π constant.
pow
|Number, Number| -> Number
Returns the result of raising the first number to the power of the second.
Example
2.pow 3
# 8
radians
|Number| -> Float
Converts degrees into radians.
Example
90.radians()
# π / 2
360.radians()
# π * 2
recip
|Number| -> Float
Returns the reciprocal of the number, i.e. 1 / x.
Example
2.recip()
# 0.5
shift_left
|Integer, Integer| -> Integer
Returns the result of shifting the bits of the first number to the left by the amount specified by the second number.
Note
The shift amount must be greater than or equal to 0.
Example
0b1010.shift_left 2
# 0b101000
shift_right
|Integer, Integer| -> Integer
Returns the result of shifting the bits of the first number to the right by the amount specified by the second number.
Note
The shift amount must be greater than or equal to 0.
Example
0b1010.shift_left 2
# 0b101000
sin
|Number| -> Float
Returns the sine of the number.
Example
import number.pi
(pi * 0.5).sin()
# 1.0
(pi * 1.5).sin()
# -1.0
sinh
|Number| -> Float
Returns the hyperbolic sine of the number.
Example
3.sinh()
# (e.pow(3) - e.pow(-3)) / 2
sqrt
|Number| -> Float
Returns the square root of the number.
Example
64.sqrt()
# 8.0
tan
|Number| -> Float
Returns the tangent of the number.
Example
1.tan()
# 1.sin() / 1.cos()
tanh
|Number| -> Float
Returns the hyperbolic tangent of the number.
Example
1.tanh()
# 1.sinh() / 1.cosh()
tau
Float
Provides the τ constant, equivalent to 2π.
to_float
|Number| -> Float
Returns the number as a Float.
Example
1.to_float()
# 1.0
to_int
|Number| -> Integer
Returns the number as an Integer. This is equivalent to calling ceil.
Example
1.5.to_int()
# 2
-0.5.to_int()
# 0
xor
|Integer, Integer| -> Integer
Returns the bitwise combination of two integers,
where a 1 in one (and only one) of the input positions
produces a 1 in corresponding output positions.
Example
0b1010.xor 0b1100
# 0b0110
os
A collection of utilities for working with the operating system.
Reference
cpu_count
|| -> Int
Provides the number of logical CPU cores that are available in the system.
Note that this may differ from the number of physical CPU cores in the system, which is provided by physical_cpu_count.
physical_cpu_count
|| -> Int
Provides the number of physical CPU cores that are available in the system.
Note that this may differ from the number of logical CPU cores in the system, which is provided by cpu_count.
range
Ranges are represented in Koto by start and end integers.
Creating a range
Ranges are created using the syntax start..end for a non-inclusive range.
start..=end is used for an inclusive range, although this is currently just
syntax sugar for creating a non-inclusive range that includes the end value.
This simplifies the internal implementation but could be confusing for users,
so this may change in the future.
Descending ranges are allowed, so the start value can be smaller than end.
Example
# Non-inclusive range
x = 10..20
# 10..20
x.start()
# 10
x.end()
# 20
# Inclusive range
x2 = 100..=200
# 100..201
x2.contains 200
# true
# Descending non-inclusive range
x3 = 3..0
# 3..0
x3.start()
# 3
x3.to_tuple()
# (3, 2, 1)
# Descending inclusive range
x4 = 3..=0
# 3..-1
x4.to_list()
# [3, 2, 1, 0]
Reference
contains
|Range, Number| -> Bool
Returns true if the provided number is within the range, and false otherwise.
Example
(10..20).contains 15
# true
(200..=100).contains 100
# true
x = 1..10
x.contains -1
# false
end
|Range| -> Int
Returns the end value of the range.
Example
(50..100).end()
# 100
(10..0).end()
# 0
See also
expanded
|Range, Number| -> Range
Returns a copy of the input range which has been 'expanded' in both directions
by the provided amount. For an ascending range this will mean that start will
decrease by the provided amount, while end will increase.
Negative amounts will cause the range to shrink rather than grow.
Example
(10..20).expanded 5
# 5..25
(10..20).expanded -2
# 12..18
(5..-5).expanded 5
# 10..-10
(5..-5).expanded -5
# 0..0
(5..-5).expanded -10
# -5..5
iter
|Range| -> Iterator
Returns an iterator that iterates over the values contained in the range.
Ranges are iterable, so it's not necessary to call .iter() to get access to
iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
x = 10..20
i = x.iter()
i.next()
# 10
i.next()
# 11
size
|Range| -> Int
Returns the size of the range.
This is equivalent to range.end() - range.start().
Note that for descending ranges, a negative value will be returned.
Example
(10..20).size()
# 10
(100..=200).size()
# 101
(20..0).size()
# -20
start
|Range| -> Int
Returns the start value of the range.
Example
(50..100).start()
# 50
(10..0).start()
# 10
See also
union
|Range, Number| -> Range
Returns the union of the range and a provided number.
If the number falls outside of the range then the resulting range will be expanded to include the number.
|Range, Range| -> Range
Returns the union of two ranges.
The resulting range will encompass all values that are contained in the two ranges, and any values that lie between them.
Example
(0..10).union 5
# 0..10
(0..10).union 99
# 0..100
a = 10..20
b = 40..50
a.union b
# 10..50
string
Koto's strings are immutable sequences of characters with UTF-8 encoding.
String literals can be created with either double or single quotation marks.
Both styles are offered as a convenience to reduce the need for escaping,
e.g. 'a "b" c' is equivalent to "a \"b\" c",
and "a 'b' c" is equivalent to 'a \'b\' c'
Functions that produce sub-strings (e.g. string.trim) share the string
data between the original string and the sub-string.
Strings support indexing operations, with string indices referring to grapheme clusters.
Escape codes
Strings can contain the following escape codes to define special characters,
all of which start with a \. To avoid having an escape sequence acting as an
escape code, then it can be escaped with an additional \.
\n: Newline\r: Carriage Return\t: Tab\u{NNNNNN}: Unicode character- Up to 6 hexadecimal digits can be included within the
{}braces. The maximum value is\u{10ffff}.
- Up to 6 hexadecimal digits can be included within the
\xNN: ASCII character- Exactly 2 hexadecimal digits follow the
\x.
- Exactly 2 hexadecimal digits follow the
\': Single quote\": Double quote\\: Backslash
Example
a = "Hello"
b = 'World!'
x = "{}, {}!".format a, b
# Hello, World!
'👋🥳😆'[1]
# 🥳
Reference
- bytes
- chars
- contains
- ends_with
- escape
- format
- is_empty
- lines
- size
- slice
- split
- starts_with
- to_lowercase
- to_number
- to_uppercase
- trim
bytes
|String| -> Iterator
Returns an iterator that yields a series of Numbers representing the bytes contained in the string data.
Example
"Hëy".bytes().to_tuple()
# (72, 195, 171, 121)
chars
|String| -> Iterator
Returns an iterator that yields the string's characters as strings.
Note
A 'character' in Koto is defined as a grapheme, so .chars() iterates over the
string's grapheme clusters.
Example
"Héllø! 👋".chars().to_tuple()
# ("H", "é", "l", "l", "ø", "!", " ", "👋")
contains
|String, String| -> Bool
Returns true if the second provided string is a sub-string of the first.
Example
"xyz".contains "abc"
# false
"xyz".contains "yz"
# true
"xyz".contains "xyz"
# true
"xyz".contains ""
# true
ends_with
|String, String| -> Bool
Returns true if the first string ends with the second string.
Example
"abcdef".ends_with "def"
# true
"xyz".ends_with "abc"
# false
escape
|String| -> String
Returns the string with characters replaced with escape codes.
For example, newlines get replaced with \n, tabs get replaced with \t.
Example
"
".escape()
# "\n"
format
|String, Value...| -> String
Returns a formatted string, with the arguments being assigned to
{} placeholders in the format string.
Formatting Syntax
The syntax for format strings in Koto is similar to Rust's formatting syntax.
Placeholders
{}- Takes the next value from the list of arguments, starting with the first.
- Subsequent
{}placeholders will take following values.
{0}, {1}, {2}, ...- Takes the value at the specified index.
{x}, {name}, {id}- Takes values by name from a Map.
- The Map is expected to be the first argument after the format string.
- Takes values by name from a Map.
{ characters can be included in the output string by escaping them with
another {, e.g. "{{}}".format() will output "{}".
Formatting modifiers
Modifiers can be provided after a : separator in the format string.
Minimum width, fill, and alignment
A minimum width can be specified, ensuring that the formatted value takes up at
least that many characters, e.g. "x{:4}x".format "ab" will output xab x.
The minimum width can be prefixed with an alignment modifier:
<- left-aligned^- centered>- right-aligned
e.g. "x{:>4}x".format "ab" will output x abx.
Values are left-aligned by default, except for numbers which are right-aligned
by default, e.g. "x{:4}x".format 1.2 will output x 1.2x.
The alignment modifier can be prefixed with a character which will be used to
fill any empty space in the formatted string (the default character being ).
e.g. "{:x^8}".format 1234 will output xx1234xx.
Maximum width / Precision
A maximum width can be specified following a . character,
e.g. "{:.2}".format abcd" will output ab.
For numbers this will define the number of decimal places that should be displayed.
Combining a maximum width with a minimum width is allowed, with the minimum
coming before the maximum in the format string,
e.g. "x{:4.2}x".format "abcd" will output xab x.
Example
"{}, {}!".format "Hello", "World"
# "Hello, World!"
"{0}-{1}-{0}".format 99, "xxx"
# "99-xxx-99
"{foo} {bar}".format {foo: 42, bar: true}
# "42 true"
"{:.2}".format 1/3
# 0.33
"{:-^8.2}".format 2/3
# --0.67--
"foo = {foo:8.3}".format {foo: 42}
# foo = 42.000
is_empty
|String| -> Bool
Returns true if the string contains no characters.
Example
"abcdef".is_empty()
# false
"".is_empty()
# true
lines
|String| -> Iterator
Returns an iterator that yields the lines contained in the input string.
Note
Lines end with either \r\n or \n.
Example
"foo\nbar\nbaz".lines().to_tuple()
# ("foo", "bar", "baz")
"\n\n\n".lines().to_tuple()
# ("", "", "")
size
|String| -> Number
Returns the number of graphemes in the string.
Note
Equivalent to calling .chars().count().
Example
"".size()
# 0
"abcdef".size()
# 6
"🥳👋😁".size()
# 3
slice
|String, Number| -> String
Returns a string with the contents of the input string starting from the provided character index.
|String, Number, Number| -> String
Returns the sub-string of the input string, starting at the first index and ending at the second number.
Note
Invalid start indices return Empty.
Example
"abcdef".slice 3
# "def"
"abcdef".slice 2, 4
# "cd"
"abcdef".slice 100, 110
# ()
split
|String, String| -> Iterator
Returns an iterator that yields strings resulting from splitting the first string wherever the second string is encountered.
|String, |String| -> Bool| -> Iterator
Returns an iterator that yields strings resulting from splitting the input string based on the result of calling a function. The function will be called for each grapheme in the input string, and splits will occur when the function returns true.
Example
"a,b,c".split(",").to_tuple()
# ("a", "b", "c")
"O_O".split("O").to_tuple()
# ("", "_", "")
"x!y?z".split(|c| c == "!" or c == "?").to_tuple()
# ("x", "y", "z")
starts_with
|String, String| -> Bool
Returns true if the first string starts with the second string.
Example
"abcdef".starts_with "abc"
# true
"xyz".starts_with "abc"
# false
to_lowercase
|String| -> String
Returns a lowercase version of the input string.
Example
"HÉLLÖ".to_lowercase()
# "héllö"
"O_o".to_lowercase()
# o_o
to_number
|String| -> Number
Returns the string parsed as a number.
Example
"123".to_number()
# 123
"-8.9".to_number()
# -8.9
to_uppercase
|String| -> String
Returns an uppercase version of the input string.
Example
"héllö".to_uppercase()
# "HÉLLÖ"
"O_o".to_uppercase()
# O_O
trim
|String| -> String
Returns the string with whitespace at the start and end of the string trimmed.
Example
" x ".trim()
# "x"
"> ".trim()
# >
test
A collection of utilities for writing tests.
Writing tests
To add tests to a Koto script, export a Map named @tests, and then any
functions in the Map tagged with @test will be run as tests.
If a function named @pre_test is in the @tests Map, then it will be run
before each test. Similarly, if a function named @post_test is present then it
will be run after each test.
These functions are useful if some setup work is needed before each test, and then maybe there's some cleanup work to do after the test has finished.
To access the result of the setup work, if the test function takes self as its
first argument, then the @tests Map itself will be passed in as self.
Example
# Tests are exported from a module as a map named `@tests`
export @tests =
# '@pre_test' will be run before each test
@pre_test: |self|
self.test_data = 1, 2, 3
# '@post_test' will be run after each test
@post_test: |self|
self.test_data = ()
# Functions that are tagged with @test are automatically run as tests
@test basic_assertions: ||
# assert checks that its argument is true
assert 1 > 0
# assert_near checks that its arguments are equal, within a specied margin
allowed_error = 0.1
assert_near 1.3, 1.301, allowed_error
# Instance test functions receive the tests map as `self`
@test data_size: |self|
# assert_eq checks that its two arguments are equal
assert_eq self.test_data.size(), 3
# assert_ne checks that its two arguments are not equal
assert_ne self.test_data.size(), 1
Running tests
Enabling tests in the runtime
When the Koto runtime has the run_tests setting enabled, then after a module
is compiled and initialized then tests will be run before calling the main
function.
Enabling tests in the CLI
The run_tests setting can be enabled when using the koto CLI with
the --tests flag.
Running tests from a Koto script
Tests can be run from a Koto script by calling test.run_tests.
Reference
assert
|Bool| -> ()
Throws a runtime error if the argument if false.
Example
# This assertion will pass, and no error will be thrown
assert 1 < 2
# This assertion will fail and throw an error
assert 1 > 2
# error: Assertion failed
assert_eq
|Value, Value| -> ()
Checks the two input values for equality and throws an error if they're not equal.
Example
# This assertion will pass, and no error will be thrown
assert_eq 1 + 1, 2
# This assertion will fail and throw an error
assert_eq 2 + 2, 5
# error: Assertion failed, '4' is not equal to '5'
assert_ne
|Value, Value| -> ()
Checks the two input values for inequality and throws an error if they're equal.
Example
# This assertion will pass, and no error will be thrown
assert_ne 1 + 1, 3
# This assertion will fail and throw an error
assert_ne 2 + 2, 4
# error: Assertion failed, '4' should not be equal to '4'
assert_near
|Number, Number, Number| -> ()
|Num2, Num2, Number| -> ()
|Num4, Num4, Number| -> ()
Checks that the two input numbers are equal, within an allowed margin of error.
This is useful when testing floating-point operations, where the result can be close to a target with some acceptable imprecision.
Example
allowed_error = 0.01
# This assertion will pass, and no error will be thrown
assert_near 1.3, 1.301, allowed_error
# This assertion will fail and throw an error
assert_near 1.3, 1.32, allowed_error
# error: Assertion failed, '1.3' and '1.32' are not within 0.01 of each other
run_tests
|Map| -> ()
Runs the tests contained in the map.
Example
my_tests =
@pre_test: |self| self.test_data = 1, 2, 3
@post_test: |self| self.test_data = ()
@test data_size: |self| assert_eq self.test_data.size(), 3
@test failure: |self| assert not self.test_data.is_empty()
try
run_tests my_tests
catch error
print "An error occurred while running my_tests: {}", error
thread
Utilities for working with threads.
Reference
create
|Function| -> Thread
Creates a new thread and executes the provided function.
See also
Example
threads = 0..4
.each |i| thread.create(|| "thread {}".format i)
.to_tuple()
threads
.each |t| t.join()
.to_tuple()
# ("thread 0", "thread 1", "thread 2", thread 3")
assert_eq data, [10..18]
sleep
|Thread, Number| -> ()
Suspends the current thread for a specified number of seconds.
The duration must be positive and finite.
Thread
A thread, created with thread.create.
Thread.join
|Thread| -> Value
Waits for the thread to finish, and then returns the result of the thread's function.
If the thread finished due to an error being thrown, then the error is propagated to the joining thread.
Example
t = thread.create || "hello"
t.join()
# hello
tuple
Tuples in Koto are fixed contiguous arrays of values.
In contrast to Lists (which contains data that can modified), once a tuple is created its data can't be modified.
Nested Lists and Maps in the Tuple can themselves be modified, but the Tuple itself can be thought of as 'read-only'.
Creating a Tuple
Tuples are created with comma-separated values:
x = "hello", -1, 99, [1, 2, 3]
# ("hello", -1, 99, [1, 2, 3])
x[2]
# 99
x[3]
# [1, 2, 3]
Parentheses are used when necessary for disambiguation:
x, y = (1, 2, 3), (4, 5, 6)
# ((1, 2, 3), (4, 5, 6))
x[1], y[2]
# (2, 6)
Reference
contains
|Tuple, Value| -> Bool
Returns true if the tuple contains a value that matches the input value.
Matching is performed with the == equality operator.
Example
(1, "hello", [99. -1]).contains "hello"
# true
("goodbye", 123).contains "hello"
# false
deep_copy
first
|Tuple| -> Value
Returns the first value in the tuple, or () if the tuple is empty.
Example
x = 99, -1, 42
x.first()
# 99
[].to_tuple().first()
# ()
get
|Tuple, Number| -> Value
Gets the Nth value in the tuple, or () if the tuple doesn't contain a value at
that position.
Example
(99, -1, 42).get 1
# -1
(99, -1, 42).get 5
# ()
iter
|Tuple| -> Iterator
Returns an iterator that iterates over the tuple's values.
Tuples are iterable, so it's not necessary to call .iter() to get access to
iterator operations, but it can be useful sometimes to make a standalone
iterator for manual iteration.
Example
x = (2, 3, 4).iter()
x.skip(1)
x.next()
# 3
last
|Tuple| -> Value
Returns the last value in the tuple, or () if the tuple is empty.
Example
x = 99, -1, 42
x.last()
# 42
[].to_tuple().last()
# ()
size
|Tuple| -> Number
Returns the number of values contained in the tuple.
Example
x = (10, 20, 30, 40, 50)
x.size()
# 5
sort_copy
|Tuple| -> Tuple
Returns a sorted copy of the tuple.
Example
x = (1, -1, 99, 42)
y = x.sort_copy()
y
# (-1, 1, 42, 99)
x # x remains untouched
# (1, -1, 99, 42)
to_list
|Tuple| -> List
Returns a copy of the tuple's data as a list.
Example
(1, 2, 3).to_list()
# [1, 2, 3]