Data Types

Data Types. #

In Cyber, there are primitive types and object types. Primitives are copied around by value and don’t need additional heap memory or reference counts. Primitives include Booleans, Floats, Integers, Enums, Symbols, Errors, Static Strings, and the none value. Object types include Lists, Tuples, Maps, Strings, Custom Objects, Lambdas, Fibers, Errors with payloads, Pointers, and several internal object types.

The none value represents an empty value. This is similar to null in other languages.

Booleans. #

Booleans can be true or false.

var a = true
if a:
    print 'a is true'

When other value types are coerced to the boolean type, the truthy value is determined as follows.

  • The none value is false.
  • Other objects and values are always true.

Numbers. #

Integers. #

int is the default integer type. It has 48-bits and can represent integers in the range -(247) to 247-1.

When a numeric literal is used and the type can not be inferred, it will default to the int type:

var a = 123

Integer notations always produce a int value:

var a = 0xFF     -- hex.
a = 0o17         -- octal.
a = 0b1010       -- binary.
a = 0u'🐶'       -- UTF-8 rune.

Arbitrary values can be converted to a int using the type as a function.

var a = '123'
var b = int(a) 

In addition to arithmetic operations, integers can also perform bitwise operations.

Floats. #

float is the default floating point type. It has a (IEEE 754) 64-bit floating point format.

Although a float represents a decimal number, it can also represent integers between -(253-1) and (253-1). Any integers beyond the safe integer range is not guaranteed to have a unique representation.

A numeric literal can be used to create a float if the inferred type is a float:

a float = 123

Decimal and scientific notations always produce a float value:

var a = 2.34567
var b = 123.0e4

Arbitrary values can be converted to a float using the type as a function.

var a = '12.3'
var b = float(a) 

Big Numbers. #

Planned Feature

Strings. #

The string type represents a sequence of UTF-8 codepoints, also known as runes. Each rune is stored internally as 1-4 bytes and can be represented as an int. Under the hood, Cyber implements 6 different internal string types to optimize string operations, but the user just sees them as one type and doesn’t need to care about this detail under normal usage.

Strings are immutable, so operations that do string manipulation return a new string. By default, small strings are interned to reduce memory footprint.

To mutate an existing string, use the StringBuffer.

Planned Feature

A string is always UTF-8 validated. rawstrings outperform strings but you’ll have to validate them and take care of indexing yourself.

A single line string literal is surrounded in single quotes.

var apple = 'a fruit'

You can escape the single quote inside the literal or use double quotes.

var apple = 'Bob\'s fruit'
apple = "Bob's fruit"

Strings are UTF-8 encoded.

var str = 'abc🦊xyz🐶'

Use double quotes to surround a multi-line string.

var str = "line a
line b
line c"

You can escape double quotes inside the literal or use triple quotes.

var str = "line a
line \"b\"
line c"

-- Using triple quotes.
str = '''line a
line "b"
line c
'''

The following escape sequences are supported:

Escape SequenceCodeDescription
\a0x07Terminal bell.
\b0x08Backspace.
\e0x1bEscape character.
\n0x0aLine feed character.
\r0x0dCarriage return character.
\t0x09Horizontal tab character.

The boundary of each line can be set with a vertical line character. This makes it easier to see the whitespace.

var poem = "line a
       |  two spaces from the left
       |     indented further"

Using the index operator will return the UTF-8 rune at the given index as a slice. This is equivalent to calling the method sliceAt().

var str = 'abcd'
print str[1]     -- "b"
print str[-1]    -- "d"

Using the slice index operator will return a view of the string at the given start and end (exclusive) indexes. The start index defaults to 0 and the end index defaults to the string’s length.

var str = 'abcxyz'
var sub = str[0..3]
print sub        -- "abc"
print str[..5]   -- "abcxy"
print str[1..]   -- "bcxyz"

-- One way to use slices is to continue a string operation.
str = 'abcabcabc'
var i = str.findRune(0u'c')
print(i)                            -- "2"
i += 1
print(i + str[i..].findRune(0u'c'))  -- "5"

type string #

func concat(self, str string) string
-- Returns a new string that concats this string and `str`.

func endsWith(self, suffix string) bool
-- Returns whether the string ends with `suffix`.

func find(self, needle string) int?
-- Returns the first index of substring `needle` in the string or `none` if not found.

func findAnyRune(self, set string) int?
-- Returns the first index of any UTF-8 rune in `set` or `none` if not found.

func findRune(self, needle int) int?
-- Returns the first index of UTF-8 rune `needle` in the string or `none` if not found.

func insert(self, idx int, str string) string
-- Returns a new string with `str` inserted at index `idx`.

func isAscii(self) bool
-- Returns whether the string contains all ASCII runes.

func len(self) int
-- Returns the number of UTF-8 runes in the string.

func less(self, str string) bool
-- Returns whether this string is lexicographically before `str`.

func lower(self) string
-- Returns this string in lowercase.

func replace(self, needle string, replacement string) string
-- Returns a new string with all occurrences of `needle` replaced with `replacement`. | 

func repeat(self, n int) string
-- Returns a new string with this string repeated `n` times.

func runeAt(self, idx int) int
-- Returns the UTF-8 rune at index `idx`.

func slice(self, start int, end int) string
-- Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`.

func sliceAt(self, idx int) string
-- Returns the UTF-8 rune at index `idx` as a single rune string.

func split(self, delim string) List
-- Returns a list of UTF-8 strings split at occurrences of `delim`.

func startsWith(self, prefix string) bool
-- Returns whether the string starts with `prefix`.

func trim(self, mode symbol, trimRunes any) string
-- Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be #left, #right, or #ends.

func upper(self) string
-- Returns this string in uppercase.

String Interpolation. #

You can embed expressions into string templates using braces.

var name = 'Bob'
var points = 123
var str = 'Scoreboard: {name} {points}'

Escape braces with a backslash.

var points = 123
var str = 'Scoreboard: \{ Bob \} {points}'

String templates can not contain nested string templates.

rawstring. #

A rawstring does not automatically validate the string and is indexed by bytes and not UTF-8 runes.

Using the index operator will return the UTF-8 rune starting at the given byte index as a slice. If the index does not begin a valid UTF-8 rune, error.InvalidRune is returned. This is equivalent to calling the method sliceAt().

var str = rawstring('abcd').insertByte(1, 255)
print str[0]     -- "a"
print str[1]     -- error.InvalidRune
print str[-1]    -- "d"

type rawstring #

func byteAt(self, idx int) int
-- Returns the byte value (0-255) at the given index `idx`.

func concat(self, str string) string
-- Returns a new string that concats this string and `str`.

func endsWith(self, suffix string) bool
-- Returns whether the string ends with `suffix`.

func find(self, needle string) int?
-- Returns the first index of substring `needle` in the string or `none` if not found.

func findAnyRune(self, set string) int?
-- Returns the first index of any UTF-8 rune in `set` or `none` if not found.

func findRune(self, needle int) int?
-- Returns the first index of UTF-8 rune `needle` in the string or `none` if not found.

func insert(self, idx int, str string) string
-- Returns a new string with `str` inserted at index `idx`.

func insertByte(self, idx int, byte int) string
-- Returns a new string with `byte` inserted at index `idx`.

func isAscii(self) bool
-- Returns whether the string contains all ASCII runes.

func len(self) int
-- Returns the number of bytes in the string.

func less(self, str rawstring) bool
-- Returns whether this rawstring is lexicographically before `str`.

func lower(self) string
-- Returns this string in lowercase.

func repeat(self, n int) rawstring
-- Returns a new rawstring with this rawstring repeated `n` times.

func replace(self, needle string, replacement string) string
-- Returns a new string with all occurrences of `needle` replaced with `replacement`.

func runeAt(self, idx int) int
-- Returns the UTF-8 rune at index `idx`. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned.

func slice(self, start int, end int) rawstring
-- Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`.

func sliceAt(self, idx int) string
-- Returns the UTF-8 rune at index `idx` as a single rune string. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned.

func split(self, delim string) List
-- Returns a list of rawstrings split at occurrences of `delim`.

func startsWith(self, prefix string) bool
-- Returns whether the string starts with `prefix`.

func upper(self) string
-- Returns this string in uppercase.

func trim(self, mode symbol, trimRunes any) rawstring
-- Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be #left, #right, or #ends.

func utf8(self) string
-- Returns a valid UTF-8 string or returns `error.InvalidRune`.

Lists. #

Lists are a builtin type that holds an ordered collection of elements. Lists grow or shrink as you insert or remove elements.

-- Construct a new list.
var list = [1, 2, 3]

-- The first element of the list starts at index 0.
print list[0]    -- Prints '1'

-- Using a negative index starts at the back of the list.
print list[-1]   -- Prints '3'

Lists can be sliced with the range .. clause. The sliced list becomes a new list that you can modify without affecting the original list. The end index is non-inclusive. Negative start or end values count from the end of the list.

var list = [ 1, 2, 3, 4, 5 ]
list[0..0]  -- []          Empty list.
list[0..3]  -- [ 1, 2, 3 ] From start to end index.
list[3..]   -- [ 4, 5 ]    From start index to end of list. 
list[..3]   -- [ 1, 2, 3 ] From start of list to end index.
list[2..+2] -- [ 3, 4 ]    From start index to start index + amount.

List operations.

var list = [234]
-- Append a value.
list.append 123
print list[-1]     -- Prints '123'

-- Inserting a value at an index.
list.insert(1, 345)

-- Get the length.
print list.len()  -- Prints '2'

-- Sort the list in place.
list.sort((a, b) => a < b)

-- Iterating a list.
for list each it:
    print it

-- Remove an element at a specific index.
list.remove(1)

type List #

MethodSummary
append(val any) noneAppends a value to the end of the list.
concat(val any) noneConcats the elements of another list to the end of this list.
insert(idx int, val any) noneInserts a value at index idx.
iterator() Iterator<any>Returns a new iterator over the list elements.
joinString(separator any) stringReturns a new string that joins the elements with separator.
len() intReturns the number of elements in the list.
seqIterator() SequenceIterator<int, any>Returns a new sequence iterator over the list elements.
remove(idx int) noneRemoves an element at index idx.
resize(len int) noneResizes the list to len elements. If the new size is bigger, none values are appended to the list. If the new size is smaller, elements at the end of the list are removed.
sort(less func (a, b) bool) noneSorts the list with the given less function. If element a should be ordered before b, the function should return true otherwise false.

Tuples. #

Incomplete: Tuples can only be created from @host funcs at the moment.

Maps. #

Maps are a builtin type that store key value pairs in dictionaries.

var map = { a: 123, b: () => 5 }
print map['a']

-- You can also access the map using an access expression.
print map.a

-- Map entries can be separated by the new line.
map = {
    foo: 1
    bar: 2
}

Entries can also follow a {}: block. This gives structure to the entries and has the added benefit of allowing multi-line lambdas.

Planned Feature

var colors = {}:
    red: 0xFF0000
    green: 0x00FF00
    blue: 0x0000FF
    dump func (c):
        print c.red
        print c.green
        print c.blue

    -- Nested map.
    darker {}: 
        red: 0xAA0000
        green: 0x00AA00
        blue: 0x0000AA

Map operations.

var map = {}
-- Set a key value pair.
map[123] = 234

-- Get the size of the map.
print map.size()

-- Remove an entry by key.
map.remove 123

-- Iterating a list.
for map each [val, key]:
    print '{key} -> {value}'

type Map #

MethodSummary
iterator() Iterator<any>Returns a new iterator over the map elements.
seqIterator() SequenceIterator<any, any>Returns a new sequence iterator over the map elements.
remove(key any) noneRemoves the element with the given key key.
size() intReturns the number of key-value pairs in the map.

Objects. #

Any value that isn’t a primitive is an object. You can declare your own object types using the type object declaration. Object types are similar to structs and classes in other languages. You can declare members and methods. Unlike classes, there is no concept of inheritance at the language level.

type Node object:
    value
    next

var node = Node{ value: 123, next: none }
print node.value          -- '123'

New instances of an object template are created using the type name and braces that surround the initial member values.

Methods. #

The first parameter of a method must be self. Otherwise, it declares a static function that can only be invoked from the type’s namespace.

type Node object:
    value
    next

    -- A static function.
    func create():
        return Node{ value: 123, next: none }

    -- A method.
    func dump(self):
        print self.value

var n = Node.create()
n.dump()

Although self is required in a method’s signature, it’s optional when referencing the type’s members.

type Node object:
    value

    func double(self):
        return value * 2

Enums. #

A new enum type can be declared with the type enum declaration. An enum value can only be one of the unique symbols declared in the enum type. By default, the symbols generate unique ids starting from 0.

type Fruit enum:
    apple
    orange
    banana
    kiwi

var fruit = Fruit.kiwi
print fruit       -- 'Fruit.kiwi'
print int(fruit)  -- '3'

When the type of the value is known to be an enum, it can be assigned using a symbol literal.

var fruit = Fruit.kiwi
fruit = .orange
print(fruit == Fruit.orange)   -- 'true'

Symbols. #

Symbol literals begin with ., followed by an identifier. They have their own global unique id.

var currency = .usd
print(currency == .usd)   -- 'true'
print int(currency)       -- '123' or some arbitrary id.