diff --git a/index.html b/index.html index 518d94ef1..42079ce4f 100644 --- a/index.html +++ b/index.html @@ -949,6 +949,11 @@ padding: 0px; } +p { + margin-block-start: 1.3em; + margin-block-end: 1.3em; +} + blockquote em { color: #fb6767; } @@ -985,7 +990,7 @@
use math
-var worlds = ['World', '世界', 'दुनिया', 'mundo']
+var worlds = {'World', '世界', 'दुनिया', 'mundo'}
worlds.append(math.random())
for worlds -> w:
print "Hello, $(w)!"
@@ -1082,12 +1087,12 @@ Statements. #
month > 0 and month <= 11:
print 'Valid'
-Any token inside a delimited syntax (such as parentheses or brackets) can be wrapped to the next line:
+Any token inside a delimited syntax (such as parentheses or braces) can be wrapped to the next line:
var sum = add(1, 2, 3, 4,
100, 200, 300, 400)
-var colors = ['red', 'blue', 'green',
- 'purple', 'orange', 'yellow']
+var colors = {'red', 'blue', 'green',
+ 'purple', 'orange', 'yellow'}
^topic
Subsequent statements in the block must follow the same indentation. The block ends when a statement recedes from this indentation:
-var items = [10, 20, 30]
+var items = {10, 20, 30}
for items -> it:
if it == 20:
print it
@@ -1314,10 +1319,10 @@ Comparison Operators. 1 != 1 -- Evaluates to `false`
@@ -1378,7 +1383,7 @@ Operator overloading. Zero values. #
Uninitialized type fields currently default to their zero values. However, this implicit behavior will be removed in the future in favor of a default value clause. Zero values must then be expressed using the reserved zero
literal.
The following shows the zero values of builtin or created types:
-
Type Zero value boolean
false
int
0
float
0.0
String
''
Array
Array('')
List
[]
Map
Map{}
type S
S{}
@host type S
S.$zero()
dyn
int(0)
any
int(0)
?S
Option[S].none
^topic
+Type Zero value boolean
false
int
0
float
0.0
String
''
List[T]
List[T]{}
Map
Map{}
type S
S{}
@host type S
S.$zero()
dyn
int(0)
any
int(0)
?S
Option[S].none
^topic
Type casting. #
The as
keyword can be used to cast a value to a specific type. Casting lets the compiler know what the expected type is and does not perform any conversions.
If the compiler knows the cast will always fail at runtime, a compile error is returned instead.
@@ -1408,11 +1413,11 @@ CYON. #
'age' = 25,
-- This is a comment
- cities = [
+ cities = {
'New York',
'San Francisco',
'Tokyo',
- ],
+ },
}
^topic
@@ -1439,13 +1444,23 @@ Basic Types. #
Mutable strings.
+Symbols.
+- Optionals.
+
- Arrays.
- Lists.
-- Tuples.
- Tables.
- Table indexing.
- Check field existence.
@@ -1458,16 +1473,14 @@ Basic Types. #
- Map block.
-- Symbols.
any
.
dyn
.
-In Cyber, there are primitive types and object types. By default, primitives are copied around by value and don't need additional heap memory or reference counts.
-Primitives include Booleans, Floats, Integers, Enums, Symbols, and Error Values.
-Object types include Lists, Tuples, Maps, Strings, Arrays, Objects, Lambdas, Fibers, Choices, Optionals, Pointers, and several internal object types.
+In Cyber, there are value types and reference types.
+This section will describe common builtin types.
Booleans. #
Booleans can be true
or false
. See type bool
.
var a = true
@@ -1617,48 +1630,124 @@ Line-join literal. #
Allows each line to be indented along with the surrounding syntax.
The starting whitespace for each line is made explicit.
-var paragraph = [
+var paragraph = {
\'the line-join literal
\'hello\nworld
\"hello $(name)
\'last line
\'
-]
+}
^topic
Mutable strings. #
To mutate an existing string, use type MutString. Planned Feature
^topic
+Optionals. #
+An Optional is a value type that provides null safety by forcing the inner value to be unwrapped before it can be used.
+The Option
template type is a choice type that either holds a none
value or contains some
value. The option template is defined as:
+type Option[T type] enum:
+ case none
+ case some T
+
+A type prefixed with ?
is the idiomatic way to create an option type. The following String optional types are equivalent:
+Option[String]
+?String
+
+^topic
+Wrap value. #
+A value is automatically wrapped into the inferred optional's some
case:
+var a ?String = 'abc'
+print a --> some(abc)'
+
+^topic
+Wrap none
. #
+none
is automatically initialized to the inferred optional's none
case:
+var a ?String = none
+print a --> none
+
+^topic
+Unwrap or panic. #
+The .?
access operator is used to unwrap an optional. If the expression evaluates to the none
case, the runtime panics:
+var opt ?int = 123
+var v = opt.?
+print v --> 123
+
+^topic
+Unwrap or default. #
+The ?else
control flow operator either returns the unwrapped value or a default value when the optional is none
:
+var opt ?int = none
+var v = opt ?else 123
+print v --> 123
+
+?else
can be used in an assignment block: Planned Feature
+var v = opt ?else:
+ break 'empty'
+
+var v = opt ?else:
+ throw error.Missing
+
+^topic
+Optional chaining. #
+Given the last member's type T
in a chain of ?.
access operators, the chain's execution will either return Option[T].none
on the first encounter of none
or returns the last member as an Option[T].some
: Planned Feature
+print root?.a?.b?.c?.last
+
+^topic
+if
unwrap. #
+The if
statement can be amended to unwrap an optional value using the capture ->
operator:
+var opt ?String = 'abc'
+if opt -> v:
+ print v -- Prints 'abc'
+
+^topic
+while
unwrap. #
+The while
statement can be amended to unwrap an optional value using the capture ->
operator.
+The loop exits when none
is encountered:
+var iter = dir.walk()
+while iter.next() -> entry:
+ print entry.name
+
+^topic
Arrays. #
-An Array
is an immutable sequence of bytes.
+
An array type is a value type and is denoted as [N]T
where N
is the size of the array and T
is the element type.
See type Array
.
-var a = Array('abcd')
-a = a.insertByte(1, 255)
-print a[0] -- "97"
-print a[1] -- "255"
+Arrays are initialized with its type followed by the initializer literal:
+var a = [3]int{1, 2, 3}
+
+The number of elements can be inferred using pseudo type [.]T
: Planned Feature
+var a = [.]int{1, 2, 3}
+
+The array type can be inferred by the dot initializer literal:
+var a [3]int = .{1, 2, 3}
+
+Arrays can be indexed:
+a[2] = 300
+print a[2] --> 300
^topic
Lists. #
Lists are a builtin type that holds an ordered collection of elements. Lists grow or shrink as you insert or remove elements. See type List
.
--- Construct a new list.
-var list = [1, 2, 3]
-
--- The first element of the list starts at index 0.
-print list[0] -- Prints '1'
+The first element of the list starts at index 0.
+Lists are initialized with elements in braces:
+var list = {1, 2, 3}
+print list[0] --> 1
+
+The empty list is initialized with an underscore as the only element:
+var list = {_}
+print list.len() --> 0
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.
-var list = [ 1, 2, 3, 4, 5 ]
-list[0..0] -- []
-list[0..3] -- [ 1, 2, 3 ]
-list[3..] -- [ 4, 5 ]
-list[..3] -- [ 1, 2, 3 ]
+var list = {1, 2, 3, 4, 5}
+print list[0..0] --> {_}
+print list[0..3] --> {1, 2, 3}
+print list[3..] --> {4, 5}
+print list[..3] --> {1, 2, 3}
The +..
invokes the slice operator with an end position that is an increment from the start: Planned Feature
-var list = [ 1, 2, 3, 4, 5 ]
-list[2+..2] -- [ 3, 4 ]
+var list = {1, 2, 3, 4, 5}
+print list[2+..2] --> {3, 4}
List operations.
-var list = [234]
+var list = {234}
-- Append a value.
list.append(123)
@@ -1680,15 +1769,12 @@ Lists. #
list.remove(1)
Since List
is a generic type, an explicit List type can be attached to the initializer:
-var a = List[int].[1, 2, 3]
+var a = List[int]{1, 2, 3}
When the intializer is only prefixed with a dot, it will infer the List type constraint:
-var a List[int] = .[1, 2, 3]
+var a List[int] = .{1, 2, 3}
^topic
-Tuples. #
-Incomplete: Tuples can only be created from @host funcs at the moment.
-
^topic
Tables. #
A Table
is a versatile object that can have an arbitrary set of fields.
By default, the record literal initializes a Table
:
@@ -1752,7 +1838,7 @@ Map operations. #
map.remove(123)
-- Iterating a map.
-for map -> [val, key]:
+for map -> {val, key}:
print "$(key) -> $(val)"
^topic
@@ -1792,7 +1878,7 @@ any
. #
return i * i
var a any = 123
-a = ['a', 'list'] --> Valid assignment to a value with a different type.
+a = {'a', 'list'} --> Valid assignment to a value with a different type.
a = 10
print square(a) --> CompileError. Expected `int`, got `any`.
@@ -1826,6 +1912,10 @@ Custom Types. #
Copy structs.
+Tuples.
+
+
+
- Enums.
- Choices.
- Initialize choice.
@@ -1833,19 +1923,6 @@ Custom Types. #
- Access choice.
-
-
-
-- Optionals.
-
- Type aliases.
- Distinct types.
- Traits.
@@ -2052,6 +2129,9 @@ Copy structs. #
print v.x -- Prints '100'
^topic
+Tuples. #
+Incomplete: Tuples can only be created from @host funcs at the moment.
+
^topic
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.
@@ -2126,71 +2206,6 @@
Access choice. #
print s.!line --> 20
^topic
-Optionals. #
-Optionals provide Null Safety by forcing values to be unwrapped before they can be used.
-The Option
template type is a choice type that either holds a none
value or contains some
value. The option template is defined as:
-
type Option[T type] enum:
- case none
- case some T
-
-A type prefixed with ?
is the idiomatic way to create an option type. The following String optional types are equivalent:
-Option[String]
-?String
-
-^topic
-Wrap value. #
-A value is automatically wrapped into the inferred optional's some
case:
-var a ?String = 'abc'
-print a --> some(abc)'
-
-^topic
-Wrap none
. #
-none
is automatically initialized to the inferred optional's none
case:
-var a ?String = none
-print a --> none
-
-^topic
-Unwrap or panic. #
-The .?
access operator is used to unwrap an optional. If the expression evaluates to the none
case, the runtime panics:
-var opt ?int = 123
-var v = opt.?
-print v --> 123
-
-^topic
-Unwrap or default. #
-The ?else
control flow operator either returns the unwrapped value or a default value when the optional is none
:
-var opt ?int = none
-var v = opt ?else 123
-print v --> 123
-
-?else
can be used in an assignment block: Planned Feature
-var v = opt ?else:
- break 'empty'
-
-var v = opt ?else:
- throw error.Missing
-
-^topic
-Optional chaining. #
-Given the last member's type T
in a chain of ?.
access operators, the chain's execution will either return Option[T].none
on the first encounter of none
or returns the last member as an Option[T].some
: Planned Feature
-print root?.a?.b?.c?.last
-
-^topic
-if
unwrap. #
-The if
statement can be amended to unwrap an optional value using the capture ->
operator:
-var opt ?String = 'abc'
-if opt -> v:
- print v -- Prints 'abc'
-
-^topic
-while
unwrap. #
-The while
statement can be amended to unwrap an optional value using the capture ->
operator.
-The loop exits when none
is encountered:
-var iter = dir.walk()
-while iter.next() -> entry:
- print entry.name
-
-^topic
Type aliases. #
A type alias refers to a different target type.
Once declared, the alias and the target type can be used interchangeably.
@@ -2302,7 +2317,7 @@ C Types. #
Pointer conversions.
-Slices.
+Pointer slices.
Union types.
@@ -2320,7 +2335,7 @@ C structs. #
x float
y float
ptr *int
- str []byte
+ str [*]byte
A C struct
may contain:
@@ -2361,13 +2376,13 @@ C struct methods. #
^topic
C struct reference. #
-
The &
operator is used to obtain the reference to a C struct
value:
+The *
operator is used to obtain the pointer to a C struct
value:
type Vec2 cstruct:
x float
y float
var v = Vec2{x=1, y=2}
-var ref = &v
+var ref = *v
ref.x = 4
print v --> Vec2{x=4, y=2}
@@ -2377,30 +2392,30 @@ C struct reference. #<
a.x *= n
a.y *= n
-add(&v, 10)
+add(*v, 10)
print v --> Vec2{x=40, y=20}
^topic
Pointers. #
A pointer
is a reference to a memory location. Its type is denoted as *T
where T
is the type that the pointer references in memory:
-func setName(p *Person, name []byte):
+func setName(p *Person, name [*]byte):
p.name = name
var p = Person{}
-setName(&p, 'Spock')
+setName(*p, 'Spock')
Depending on the target architecture, the alignment of the pointer will either be at least 8 bytes on a 64-bit machine or 4 bytes on a 32-bit machine. Aligned pointers will be supported in a future version.
Pointers are unsafe since they can reference corrupted or invalidated memory.
When runtime memory checks are enabled, pointers will occupy an extra word size in order to set traps and prevent unsafe uses of pointers. See Memory / Runtime memory checks.
A pointer can be created with an explicit address using pointer
.
-var ptr = pointer(*void, 0xDEADBEEF)
+var ptr = pointer(void, 0xDEADBEEF)
print ptr.value() --'3735928559'
^topic
Dereferencing pointers. #
Pointers are dereferenced using the accessor operator .*
:
var a = 123
-var ptr = &a
+var ptr = *a
print ptr.* --> 123
ptr.* = 10
@@ -2422,7 +2437,7 @@ Pointer casting. #
Planned Feature
^topic
Pointer slicing. #
-The slice operator will produce a new slice of type []T
from a *T
pointer type:
+The slice operator will produce a new slice of type [*]T
from a *T
pointer type:
var ints = mem.alloc(int, 10)
var slice = ints[0..5]
print typeof(slice) --> []int
@@ -2431,11 +2446,11 @@ Pointer slicing. #
Pointer conversions. #
Planned Feature
^topic
-Slices. #
-Slices are pointers with a length field. They are denoted as []T
where T is the element type.
-A slice can be created by taking the address of an array: Planned feature
-var arr = [_]int.[1, 2, 3]
-var s = &arr
+Pointer slices. #
+Slices are pointers with a length field. They are denoted as [*]T
where T is the element type.
+A slice can be created by taking the pointer of an array: Planned feature
+var arr = [3]int{1, 2, 3}
+var s = *arr
Read and write an element with the index operator:
print s[0] --> 1
@@ -2446,7 +2461,7 @@ Slices. #
print s[100] --> Panic. Out of bounds.
Slice operations can be applied: Planned feature
-print s[0..2] --> [1, 2]
+print s[0..2] --> {1, 2}
^topic
Union types. #
@@ -2558,26 +2573,26 @@ for
range. #
for
each. #
The for
clause can iterate over any type that implements the Iterable
trait. An Iterable contains an iterator()
method which returns an Iterator
object. The for loop continually invokes the Iterator's next()
method until none
is returned.
Lists can be iterated since they implement the Iterable trait. The capture operator ->
is used to capture the value returned from an iterator's next()
:
-var list = [1, 2, 3, 4, 5]
+var list = {1, 2, 3, 4, 5}
for list -> n:
print n
Maps can be iterated. next()
returns a key and value tuple:
-var map = Map{ a=123, b=234 }
+var map = Map{a=123, b=234}
for map -> entry:
print entry[0]
print entry[1]
Use the destructure syntax to extract the key and value into two separate variables:
-for map -> [ key, val ]:
+for map -> {key, val}:
print "key $(key) -> value $(val)"
^topic
for
each with index. #
A counting index can be declared after the each variable. The count starts at 0 for the first value:
-var list = [1, 2, 3, 4, 5]
+var list = {1, 2, 3, 4, 5}
for list -> val, i:
print "index $(i), value $(val)"
@@ -2700,10 +2715,10 @@ Static functions. #Functions can only return one value. However, the value can be destructured: Planned Feature
use {cos, sin} 'math'
-func compute(rad float) [float, float]:
- return [ cos(rad), sin(rad) ]
+func compute(rad float) [2]float:
+ return .{cos(rad), sin(rad)}
-var [ x, y ] = compute(pi)
+var {x, y} = compute(pi)
^topic
Function overloading. #
@@ -3078,6 +3093,7 @@ mod core
#
Prints a value. The host determines how it is printed.
func queueTask(fn any)
Queues a callback function as an async task.
+
func runestr(val int) String
Converts a rune to a string.
func sizeof(T type) int
@@ -3163,6 +3179,8 @@ type float
#
^topic
type placeholder1
#
^topic
+type placeholder2
#
+^topic
type taglit
#
^topic
type dyn
#
@@ -3230,7 +3248,11 @@ type MapIterator
^topic
type String
#
-func $infix+(self any, o any) String
+func $index(self any, idx int) int
+Returns the rune at byte index idx
. The replacement character (0xFFFD) is returned for an invalid UTF-8 rune.
+
func $index(self any, range Range) String
+Returns a slice into this string from a Range
with start
(inclusive) to end
(exclusive) byte indexes.
+
func $infix+(self any, o any) String
Returns a new string that concats this string and str
.
func concat(self any, o String) String
Returns a new string that concats this string and str
.
@@ -3240,12 +3262,26 @@ type String
#
Returns whether the string ends with suffix
.
func find(self any, needle String) ?int
Returns the first byte index of substring needle
in the string or none
if not found. SIMD enabled.
+
func findAnyByte(self any, set List[byte]) ?int
+Returns the first index of any byte in set
or none
if not found.
func findAnyRune(self any, runes List[int]) ?int
Returns the first byte index of any rune in runes
or none
if not found. SIMD enabled.
+
func findByte(self any, b byte) ?int
+Returns the first index of byte
in the array or none
if not found.
func findRune(self any, rune int) ?int
Returns the first byte index of a rune needle
in the string or none
if not found. SIMD enabled.
+
func fmtBytes(self any, format NumberFormat) String
+Formats each byte in the string using a NumberFormat. Each byte is zero padded.
+
func getByte(self any, idx int) byte
+Returns the byte value (0-255) at the given index idx
.
+
func getInt(self any, idx int, endian symbol) int
+Returns the int value of the 6 bytes starting from idx
with the given endianness (.little or .big).
+
func getInt32(self any, idx int, endian symbol) int
+Returns the int value of the 4 bytes starting from idx
with the given endianness (.little or .big).
func insert(self any, idx int, str String) String
Returns a new string with str
inserted at byte index idx
.
+
func insertByte(self any, idx int, byte int) String
+Returns a new array with byte
inserted at index idx
.
func isAscii(self any) bool
Returns whether the string contains all ASCII runes.
func len(self any) int
@@ -3262,10 +3298,6 @@ type String
#
Returns the starting byte index for the rune index idx
.
func sliceAt(self any, idx int) String
Returns the UTF-8 rune starting at byte index idx
as a string.
-
func $index(self any, idx int) int
-Returns the rune at byte index idx
. The replacement character (0xFFFD) is returned for an invalid UTF-8 rune.
-
func $index(self any, range Range) String
-Returns a slice into this string from a Range
with start
(inclusive) to end
(exclusive) byte indexes.
func split(self any, sep String) List[String]
Returns a list of UTF-8 strings split at occurrences of sep
.
func startsWith(self any, prefix String) bool
@@ -3277,73 +3309,31 @@ type String
#
func String.$call(val any) String
Converts a value to a string.
^topic
+type array_t
#
+^topic
type Array
#
-func $infix+(self any, o any) Array
-
func concat(self any, other Array) Array
-Returns a new array that concats this array and other
.
-
func decode(self any) String
-Calls decode(.utf8)
-
func decode(self any, encoding symbol) String
-Decodes the array based on an encoding
. Supported encodings: .utf8
. Returns the decoded string or throws error.Decode
.
-
func endsWith(self any, suffix Array) bool
-Returns whether the array ends with suffix
.
-
func find(self any, needle Array) ?int
-Returns the first index of needle
in the array or none
if not found.
-
func findAnyByte(self any, bytes Array) ?int
-Returns the first index of any bytes
in arrays
or none
if not found.
-
func findByte(self any, byte int) ?int
-Returns the first index of byte
in the array or none
if not found.
-
func fmt(self any, format NumberFormat) String
-Formats each byte in the array using a NumberFormat. Each byte is zero padded.
-
func getByte(self any, idx int) int
-Returns the byte value (0-255) at the given index idx
.
-
func getInt(self any, idx int, endian symbol) int
-Returns the int value of the 6 bytes starting from idx
with the given endianness (.little or .big).
-
func getInt32(self any, idx int, endian symbol) int
-Returns the int value of the 4 bytes starting from idx
with the given endianness (.little or .big).
-
func insert(self any, idx int, arr Array) Array
-Returns a new array with arr
inserted at index idx
.
-
func insertByte(self any, idx int, byte int) Array
-Returns a new array with byte
inserted at index idx
.
-
func iterator(self any) ArrayIterator
-Returns a new iterator over the array bytes.
+
+
func iterator(self any) RefSliceIterator[T]
+Returns a new iterator over the array.
func len(self any) int
-Returns the number of bytes in the array.
-
func repeat(self any, n int) Array
-Returns a new array with this array repeated n
times.
-
func replace(self any, needle Array, replacement Array) Array
-Returns a new array with all occurrences of needle
replaced with replacement
.
-
func $index(self any, idx int) int
-
func $index(self any, range Range) Array
-Returns a slice into this array from a Range
with start
(inclusive) to end
(exclusive) indexes.
-
func split(self any, sep Array) List[Array]
-Returns a list of arrays split at occurrences of sep
.
-
func startsWith(self any, prefix Array) bool
-Returns whether the array starts with prefix
.
-
func trim(self any, mode symbol, delims Array) Array
-Returns the array with ends trimmed from runes in delims
. mode
can be .left, .right, or .ends.
-
func Array.$call(val any) Array
-Converts a string to an byte Array
.
-
^topic
-type ArrayIterator
#
-func next(self any) ?any
+Returns the number of elements in the array.
^topic
type pointer
#
func $index(self any, idx int) *T
-
func $index(self any, range Range) []T
+
func $index(self any, range Range) [*]T
func $setIndex(self any, idx int, val T)
func addr(self any) int
When pointer runtime safety is enabled, this returns the raw pointer address as an int64
. Otherwise, the pointer itself is bitcasted to an int64
.
func asObject(self any) any
Casts the pointer to a Cyber object. The object is retained before it's returned.
-
func fromCstr(self any, offset int) Array
-Returns an Array
from a null terminated C string.
+
func fromCstr(self any, offset int) String
+Returns a String
from a null terminated C string.
func get(self any, offset int, ctype symbol) dyn
Dereferences the pointer at a byte offset and returns the C value converted to Cyber.
+
func getString(self any, offset int, len int) String
+Returns a String
with a copy of the byte data starting from an offset to the specified length.
func set(self any, offset int, ctype symbol, val any)
Converts the value to a compatible C value and writes it to a byte offset from this pointer.
-
func toArray(self any, offset int, len int) Array
-Returns an Array
with a copy of the byte data starting from an offset to the specified length.
func pointer.$call(T type, addr int) *T
Converts an int
to a pointer
value.
^topic
@@ -3363,6 +3353,7 @@ type Fiber
#
^topic
type metatype
#
func id(self any) int
+
func metatype.$call(T type) metatype
^topic
type Range
#
^topic
@@ -3378,25 +3369,47 @@ type FutureResolver
^topic
-type Slice
#
+type Ref
#
+^topic
+type RefSlice
#
+
+
func $index(self any, range Range)
+
func $setIndex(self any, idx int, val T)
+
func iterator(self any) RefSliceIterator[T]
+
func len(self any) int
+
^topic
+type RefSliceIterator
#
+func next(self any) ?T
+
^topic
+type PtrSlice
#
func $index(self any, idx int) *T
-
func $index(self any, range Range) []T
+
func $index(self any, range Range) [*]T
func $setIndex(self any, idx int, val T)
+
func endsWith(self any, suffix [*]T) bool
+Returns whether the array ends with suffix
.
+
func findScalar(self any, needle T) ?int
+Returns the first index of needle
in the slice or none
if not found.
+
func iterator(self any) PtrSliceIterator[T]
func len(self any) int
+
func startsWith(self any, prefix [*]T) bool
+Returns whether the array starts with prefix
.
+
^topic
+type PtrSliceIterator
#
+func next(self any) ?T
^topic
type IMemory trait
#
-func alloc(self any, len int) []byte
-
func free(self any, buf []byte)
+func alloc(self any, len int) [*]byte
+
func free(self any, buf [*]byte)
^topic
type Memory
#
func new(self any, T type) *T
-
func alloc(self any, T type, n int) []T
+
func alloc(self any, T type, n int) [*]T
func free(self any, ptr *T)
-
func free(self any, slice []T)
+
func free(self any, slice [*]T)
^topic
type DefaultMemory
#
-func alloc(self any, len int) []byte
-
func free(self any, buf []byte)
+func alloc(self any, len int) [*]byte
+
func free(self any, buf [*]byte)
^topic
mod cy
#
@@ -3590,7 +3603,7 @@ mod os
#
use os
var map = os.getEnvAll()
-for map -> [k, v]:
+for map -> {k, v}:
print "$(k) -> $(v)"
@@ -3621,7 +3634,7 @@ mod os
#
Creates the directory at path
. Returns true
if successful.
func createFile(path String, truncate bool) File
Creates and opens the file at path
. If truncate
is true, an existing file will be truncated.
-
func cstr(s any) *
+
func cstr(s String) *
Returns a null terminated C string.
func cwd() String
Returns the current working directory.
@@ -3633,7 +3646,7 @@ mod os
#
Returns the current executable's path.
func exit(status int)
Exits the program with a status code.
-
func fetchUrl(url String) Array
+
func fetchUrl(url String) String
Fetches the contents at url
using the HTTP GET request method.
func free(ptr *)
Frees the memory located at ptr
.
@@ -3675,17 +3688,17 @@ mod os
#
Pauses the current thread for given milliseconds.
func unsetEnv(key String)
Removes an environment variable by key.
-
func writeFile(path String, contents any)
+
func writeFile(path String, contents String)
Writes contents
as a string or bytes to a file.
^topic
type File
#
func close(self any)
Closes the file handle. File ops invoked afterwards will return error.Closed
.
-
func iterator(self any) any
-
func next(self any) any
-
func read(self any, n int) Array
+
func iterator(self any) File
+
func next(self any) String
+
func read(self any, n int) String
Reads at most n
bytes as an Array
. n
must be at least 1. A result with length 0 indicates the end of file was reached.
-
func readAll(self any) Array
+
func readAll(self any) String
Reads to the end of the file and returns the content as an Array
.
func seek(self any, n int)
Seeks the read/write position to pos
bytes from the start. Negative pos
is invalid.
@@ -3699,8 +3712,8 @@ type File
#
Equivalent to streamLines(4096)
.
func streamLines(self any, bufSize int) File
Returns an iterable that streams lines ending in \n
, \r
, \r\n
, or the EOF
. The lines returned include the new line character(s). A buffer size of bufSize
bytes is allocated for reading. If \r
is found at the end of the read buffer, the line is returned instead of waiting to see if the next read has a connecting \n
.
-
func write(self any, val any) int
-Writes a String
or Array
at the current file position. The number of bytes written is returned.
+
func write(self any, val String) int
+Writes a String
at the current file position. The number of bytes written is returned.
^topic
type Dir
#
func iterator(self any) DirIterator
@@ -3758,6 +3771,8 @@ mod test
#
Returns whether two values are equal. Panics with error.AssertError
if types or values do not match up.
func eqList(a List[T], b List[T]) bool
Returns true
if two lists have the same size and the elements are equal as if eq
was called on those corresponding elements.
+
func eqSlice(a , b ) bool
+Returns true
if two slices have the same size and the elements are equal as if eq
was called on those corresponding elements.
func eqNear(a T, b T) bool
Returns true
if two numbers are near each other within epsilon 1e-5.
func fail()
@@ -3794,7 +3809,7 @@ FFI context. #
^topic
Declare functions. #
Functions from a library are first declared using cfunc
which accepts C types in the form of symbols. In a future update they will accept C syntax instead.
-ffi.cfunc('add', [.int, .int], .int)
+ffi.cfunc('add', {.int, .int}, .int)
The first argument refers to the symbol's name in the dynamic library.
The second argument contains the function's parameter types and finally the last argument is the function's return type.
@@ -3838,8 +3853,8 @@ Bind to Cyber type. #<
b *void
c bool
-ffi.cbind(MyObject, [.float, .voidPtr, .bool])
-ffi.cfunc('foo', [MyObject], MyObject)
+ffi.cbind(MyObject, {.float, .voidPtr, .bool})
+ffi.cfunc('foo', {MyObject}, MyObject)
let lib = ffi.bindLib('./mylib.so')
var res = lib.foo(MyObject{a=123.0, b=os.cstr('foo'), c=true})
@@ -3856,7 +3871,7 @@ Bind to Cyber type. #<
}
cbind
also generates ptrTo[Type]
as a helper function to dereference an opaque ptr to a new Cyber object:
-ffi.cfunc('foo', [MyObject], .voidPtr)
+ffi.cfunc('foo', {MyObject}, .voidPtr)
let lib = ffi.bindLib('./mylib.so')
var ptr = lib.foo(MyObject{a=123, b=os.cstr('foo'), c=true})
@@ -4376,7 +4391,7 @@ Dynamic variables. #
--> CompileError: Can not find the symbol `$infix/` in `String`
When a
is assigned a different type of value, its recent type is updated so the compiler can continue to surface errors ahead of time:
-a = [1, 2, 3]
+a = {1, 2, 3}
print a / 2
--> CompileError: Can not find the symbol `$infix/` in `List`
@@ -4527,7 +4542,7 @@ Operator overloading. ^topic
@@ -4912,18 +4927,37 @@ finalizer
#
^topic
Memory. #
+
+
+
+- Automatic memory.
-
-- Heap.
- Manual memory.
@@ -4932,14 +4966,274 @@ Memory. #
+
+
-Cyber provides memory safety by default.
-ARC. #
-Cyber uses ARC or automatic reference counting to manage memory.
-ARC is deterministic and has less overhead compared to a tracing garbage collector. Reference counting distributes memory management, which reduces GC pauses and makes ARC suitable for realtime applications. One common issue in ARC implementations is reference cycles which Cyber addresses with Weak References and it's very own Cycle Detection.
+Cyber provides memory safety by default with structured and automatic memory.
+Manual memory is also supported but discouraged.
+Structured memory. #
+Cyber uses single value ownership and variable scopes to determine the lifetime of values.
+When lifetimes are known at compile-time, the memory occupied by values do not need to be manually managed which prevents memory bugs such as:
+
+- Use after free.
+- Use after invalidation.
+- Free with wrong allocator.
+- Double free.
+- Memory leaks.
+- Null pointer dereferencing.
+
+At the same time, structured memory allows performant code to be written since it provides safe semantics to directly reference values and child values.
^topic
-Reference counting. #
-In Cyber, there are primitive and object values. Primitives don't need any memory management, since they are copied by value and no heap allocation is required (with the exception of primitives being captured by a closure.
+Value ownership. #
+Every value in safe memory has a single owner.
+An owner can be a variable that binds to a value. Otherwise, the owner can be a parent value or the value itself.
+The owner is responsible for deinitializing or dropping the value when it has gone out of scope (no longer reachable).
+For example, at the end of the block, a variable can no longer be accessed so it drops the value that it owns:
+var a = 123
+print a --> 123
+-- Deinit `a`.
+
+In this case, there is nothing to deinitialize since the value is an integer.
+If the value was a String
, the deinit logic would release (-1) on a reference counted byte buffer since strings are just immutable views over byte buffers:
+var a = 'hello'
+print a --> hello
+-- Deinit `a`.
+-- `a.buf` is released.
+-- `a.buf` is freed.
+
+Since the string buffer's reference count reaches 0, it's freed as well.
+Finally, let's take a look at ListValue
which manages a dynamically sized array of elements:
+var a = ListValue[int]{1, 2, 3}
+print a --> {1, 2, 3}
+-- Deinit `a`.
+-- `a.buf` is freed.
+
+When a
is deinitialized, the buffer that holds the 3 integer elements is freed.
+You may have surmised that it's named ListValue
because it's a value type (it can only be passed around by copying itself). The object type, List
, wraps ListValue
and can be passed around by reference.
+The concept of a value having a single owner is very simple yet powerful.
+A value can represent any data structure from primitives to dynamically allocated buffers.
+A value always knows how to deinitialize itself, and the owner knows when to deinitialize the value.
+Later, we'll see that this same concept also applies to shared ownership.
+^topic
+Copy semantics. #
+By default, values are passed around by copying (shallow copying), but now all values can perform a copy.
+A primitive, such as an integer, can always be copied:
+var a = 123
+var b = a
+-- Deinit `b`.
+-- Deinit `a`.
+
+After a copy, a new value is created and given the owner of b
. At the end of the block, both a
and b
are deinitialized (which does nothing since they are just primitives).
+Strings are also copyable since they are immutable views over byte buffers:
+var a = 'hello'
+var b = a
+-- Deinit `b`.
+-- `b.buf` is released.
+-- Deinit `a`.
+-- `a.buf` is released.
+-- `a.buf` is freed.
+
+The copy b
also reuses the byte buffer of a
by retaining (+1) on the reference counted byte buffer. The byte buffer is finally freed once there are no references pointing to it.
+Unlike the integer and string, a ListValue
can not be copied since doing so requires duping a heap allocated buffer which is considered expensive:
+var a = ListValue[int]{1, 2, 3}
+var b = a --> error: Can not copy `ListValue`. Can only be cloned or moved.
+
+Instead a
can only be cloned or moved.
+By default, a declared value type is copyable if all of it's members are also copyable:
+type Foo struct:
+ a int
+ b String
+
+var a = Foo{a=123, b='hello'}
+var b = a
+
+Since integers and strings are both copyable, Foo
is also copyable.
+Foo
is non-copyable if it contains at least one non-copyable member:
+type Foo struct:
+ a int
+ b String
+ c ListValue[int]
+
+var a = Foo{a=123, b='hello'}
+var b = a --> error: Can not copy `Foo`. Can only be moved.
+
+Foo
is also non-copyable if it contains unsafe types such as pointers or pointer slices:
+type Foo struct:
+ a int
+ b String
+ c *Bar
+ d [*]float
+
+Foo
can implement Copyable
to override the default behavior and define it's own copy logic:
+type Foo struct:
+ with Copyable
+ a int
+ b String
+ c *Bar
+ d [*]float
+
+ func copy(self) Foo:
+ return .{
+ a = self.a,
+ b = self.b,
+ c = self.c,
+ d = self.d,
+ }
+
+Likewise, Foo
can implement NonCopyable
which indicates that it can never be copied:
+type Foo struct:
+ with NonCopyable
+ a int
+ b String
+
+^topic
+Cloning. #
+Some value types are not allowed to be copied by default and must be cloned instead:
+var a = ListValue[int]{1, 2, 3}
+var b = a.clone()
+
+Any Copyable
type is also Cloneable
. For example, performing a clone on an integer will simply perform a copy:
+var a = 123
+var b = a.clone()
+
+A value type can implement Cloneable
to override the default behavior and define it's own clone logic:
+type Foo struct:
+ with Cloneable
+ a int
+ b String
+
+ func clone(self) Foo:
+ return .{
+ a = self.a + 1,
+ b = self.b,
+ }
+
+Likewise, Foo
can implement NonCloneable
which indicates that it can never be cloned:
+type Foo struct:
+ with NonCloneable
+ a int
+ b String
+
+^topic
+Moving. #
+Values can be moved, thereby transfering ownership from one variable to another:
+var a = 123
+var b = move a
+print a --> error: `a` does not own a value.
+
+Some types such as ListValue
can not be passed around by default without moving (or cloning) the value:
+var a = ListValue[int]{1, 2, 3}
+print computeSum(move a) --> 6
+
+In this case, the list value is moved into the computeSum
function, so the list is deinitialized in the function before the function returns.
+^topic
+References. #
+References are safe pointers to values.
+Unlike unsafe pointers, a reference is never concerned with when to free or deinitialize a value since that responsibility always belongs to the value's owner.
+They are considered safe pointers because they are guaranteed to point to their values and never outlive the lifetime of their values.
+References grant stable mutability which allows a value to be modified as long as it does not invalidate other references.
+Multiple references can be alive at once as long as an exclusive reference is not also alive.
+The &
operator is used to obtain a reference to a value:
+var a = 123
+var ref = &a
+ref.* = 234
+print a --> 234
+
+A reference can not outlive the value it's referencing:
+var a = 123
+var ref = &a
+if true:
+ var b = 234
+ ref = &b --> error: `ref` can not oulive `b`.
+
+A reference type is denoted as &T
where T
is the type that the reference points to:
+var a = 123
+
+func inc(a &int):
+ a.* = a.* + 1
+
+References allow stable mutation:
+var a = ListValue[int]{1, 2, 3}
+var third = &a[2]
+third.* = 300
+print a --> {1, 2, 300}
+
+The element that third
points to can be mutated because it does not invalidate other references.
+References however can not perform a unstable mutation.
+An unstable mutation requires an exclusive reference:
+var a = ListValue[int]{1, 2, 3}
+var ref = &a
+ref.append(4) --> error: Expected exclusive reference.
+
+^topic
+Exclusive reference. #
+An exclusive reference grants full mutability which allows a value to be modified even if could potentially invalidate unsafe pointers.
+A single exclusive reference can be alive as long as no other references are also alive.
+Since no other references (safe pointers) are allowed to be alive at the same time, no references can become invalidated.
+The &!
operator is used to obtain an exclusive reference to a value.
+An exclusive reference type is denoted as &!T
where T
is the type that the reference points to.
+ListValue
is an example of a type that requires an exclusive reference for operations that can resize or reallocate its dynamic buffer:
+var a = ListValue[int]{1, 2, 3}
+a.append(4)
+print a --> {1, 2, 3, 4}
+
+Note that invoking the method append
here automatically obtains an exclusive reference for self
without an explicit &!
operator.
+If another reference is alive before append
, the compiler will not allow an exclusive reference to be obtained from a
.
+Doing so would allow append
to potentially reallocate its dynamic buffer, thereby invalidating other references:
+var a = ListValue[int]{1, 2, 3}
+var third = &a[2]
+a.append(4) --> error: Can not obtain exclusive reference, `third` is still alive.
+print third
+
+^topic
+self
reference. #
+By default self
has a type of &T
when declared in a value type's method:
+type Pair struct:
+ a int
+ b int
+
+ func sum(self) int:
+ return self.a + self.b
+
+If self
requires an exclusive reference, then it must be prepended with !
:
+type Pair struct:
+ a int
+ b int
+
+ func sum(!self) int:
+ return self.a + self.b
+
+Invoking methods automatically obtains the correct reference as specified by the method:
+var p = Pair{a=1, b=2}
+print p.sum() --> 3
+
+^topic
+Lifted values. #
+Planned Feature
+
^topic
+Deferred references. #
+Planned Feature
+
^topic
+Implicit lifetimes. #
+Planned Feature
+
^topic
+Reference lifetimes. #
+Planned Feature
+
^topic
+Shared ownership. #
+Planned Feature
+
^topic
+Deinitializer. #
+Planned Feature
+
^topic
+Pointer interop. #
+Planned Feature
+
^topic
+Automatic memory. #
+Cyber uses an ARC/GC hybrid to automatically manage objects instantiated from object types. Value types typically do not need to be automatically managed unless they were lifted by a closure or a dynamic container.
+^topic
+ARC. #
+ARC also known as automatic reference counting is deterministic and has less overhead compared to a tracing garbage collector. Reference counting distributes memory management, which reduces GC pauses and makes ARC suitable for realtime applications. One common issue in ARC implementations is reference cycles which Cyber addresses with a GC supplement when it is required.
Objects are managed by ARC. Each object has its own reference counter. Upon creating a new object, it receives a reference count of 1. When the object is copied, it's retained and the reference count increments by 1. When an object value is removed from it's parent or is no longer reachable in the current stack frame, it is released and the reference count decrements by 1.
Once the reference count reaches 0 the object begins its destruction procedure.
^topic
@@ -4954,8 +5248,11 @@ Object destructor. #
Since objects freed by the GC either belongs to a reference cycle or branched from one, the GC will still end up invoking the destructor of all unreachable objects.
This implies that the destructor order is not reliable, but destructors are guaranteed to be invoked for all unreachable objects.
^topic
-Optimizations. #
-
The compiler can reduce the number of retain/release ops since it can infer value types even though they are dynamically typed to the user. Arguments passed to functions are only retained depending on the analysis from the callsite.
+Retain optimizations. #
+When the lifetime of an object's reference is known on the stack, a large amount of retain/release ops can be avoided.
+For example, calling a function with an object doesn't need a retain since it is guaranteed to be alive when the function returns.
+This leaves only cases where an object must retain to ensure correctness such as escaping the stack.
+When using dynamic types, the compiler can omit retain/release ops when it can infer the actual type even though they are dynamically typed to the user.
^topic
Closures. #
When primitive variables are captured by a closure, they are boxed and allocated on the heap. This means they are managed by ARC and cleaned up when there are no more references to them.
@@ -4963,20 +5260,19 @@ Closures. #
Fibers. #
Fibers are freed by ARC just like any other object. Once there are no references to the fiber, it begins to release it's child references by unwinding it's call stack.
^topic
-Heap. #
-Many object types in Cyber are small enough to be at or under 40 bytes. To take advantage of this, Cyber can reserve object pools to quickly allocate and free these small objects with very little bookkeeping. Bigger objects are allocated and managed by mimalloc
which has proven to be a fast and reliable general-purpose heap allocator.
+Heap objects. #
+Many object types are small enough to be at or under 40 bytes. To take advantage of this, object pools are reserved to quickly allocate and free these small objects with very little bookkeeping. Bigger objects are allocated and managed by mimalloc
which has proven to be a fast and reliable general-purpose heap allocator.
^topic
-Weak references. #
-Planned Feature
-
^topic
-Cycle detection. #
-The cycle detector is also considered a GC and frees abandoned objects managed by ARC. Although weak references can remove cycles altogether, Cyber does not force you to use them and provides a manual GC as a one-time catch all solution.
-Incomplete Feature: Only the main fiber stack is cleaned up at the moment.
-
To invoke the GC, call the builtin function: performGC
.
+GC. #
+The garbage collector is only used if the program may contain objects that form reference cycles. This property is statically determined by the compiler. Since ARC frees most objects, the GC's only responsibility is to free abandoned objects that form reference cycles.
+This reduces the amount of work for GC marking since only cyclable objects (objects that may contain a reference cycle) are considered.
+Weak references are not supported for object types because objects are intended to behave like GC objects (the user should not be concerned with reference cycles). If weak references do get supported in the future, they will be introduced as a Weak[T]
type that is used with an explicit reference counted Rc[T]
type.
+Currently, the GC can be manually invoked. However, the plan is for this to be automatic by either running in a separate thread or per virtual thread by running the GC incrementally.
+To invoke the GC, call the builtin function: performGC
. Incomplete Feature: Only the main fiber stack is cleaned up at the moment.
func foo():
-- Create a reference cycle.
- var a = []
- var b = []
+ var a = {_}
+ var b = {_}
a.append(b)
b.append(a)
@@ -5458,7 +5754,7 @@ C backend. #
keyword: [
'func', 'mod', 'for', 'coinit', 'coresume', 'coyield', 'use', 'await', 'context',
'return', 'if', 'else', 'as', 'while', 'var', 'let', 'dynobject', 'object', 'struct', 'cstruct', 'with', 'caught',
- 'break', 'continue', 'switch', 'pass', 'or', 'and', 'not', 'is', 'error', 'throws',
+ 'break', 'continue', 'switch', 'pass', 'or', 'and', 'not', 'is', 'error', 'throws', 'move',
'true', 'false', 'none', 'throw', 'try', 'catch', 'recover', 'enum', 'type', 'case', 'trait'
],
type: [