Added a .length field to arrays/sets/tables, added a .max_size field to
channels, and updated the API
This commit is contained in:
parent
c338c3f08c
commit
d804b09b02
@ -42,6 +42,15 @@ Comprehensions can be combined with regular items or other comprehensions:
|
||||
= [-1, 30, 40, 50, 60, 70, 80, 1, 2, 3]
|
||||
```
|
||||
|
||||
## Length
|
||||
|
||||
Array length can be accessed by the `.length` field:
|
||||
|
||||
```tomo
|
||||
>> [10, 20, 30].length
|
||||
= 3
|
||||
```
|
||||
|
||||
## Indexing
|
||||
|
||||
Array values are accessed using square bracket indexing. Since arrays are
|
||||
|
@ -5,13 +5,13 @@ although they can also be used as a general-purpose queue.
|
||||
|
||||
## Syntax
|
||||
|
||||
The syntax to create a channel is `|T|`, where `T` is the type that will be
|
||||
The syntax to create a channel is `|:T|`, where `T` is the type that will be
|
||||
passed through the channel. You can also specify a maximum size for the
|
||||
channel, which will cause pushing to block until the recipient has popped from
|
||||
the channel if the maximum size is reached.
|
||||
|
||||
```tomo
|
||||
channel := |Int|
|
||||
channel := |:Int|
|
||||
channel:push(10)
|
||||
channel:push(20)
|
||||
>> channel:pop()
|
||||
@ -19,7 +19,7 @@ channel:push(20)
|
||||
>> channel:pop()
|
||||
= 20
|
||||
|
||||
small_channel := |Int; max_size=5|
|
||||
small_channel := |:Int; max_size=5|
|
||||
```
|
||||
|
||||
## Channel Methods
|
||||
|
104
api/pointers.md
Normal file
104
api/pointers.md
Normal file
@ -0,0 +1,104 @@
|
||||
# Pointers
|
||||
|
||||
Pointers are numeric values that represent a location in memory where some type
|
||||
of data lives. Pointers are created using either the `@` prefix operator to
|
||||
**a**llocate heap memory or the `&` prefix operator to get the address of a
|
||||
variable. Stack pointers (`&`) are more limited than heap pointers (`@`) and
|
||||
cannot be stored inside an array, set, table, struct, enum, or channel.
|
||||
However, stack pointers are useful for methods that mutate local variables and
|
||||
don't need to save the pointer anywhere.
|
||||
|
||||
Pointers are the way in Tomo that you can create mutable data. All
|
||||
datastructures are by default, immutable, but using pointers, you can create
|
||||
a region of memory where different immutable values can be held, which change
|
||||
over time.
|
||||
|
||||
```tomo
|
||||
func no_mutation_possible(nums:[Int]):
|
||||
nums[1] = 10 // Type error!
|
||||
|
||||
func do_mutation(nums:@[Int]):
|
||||
nums[1] = 10 // okay
|
||||
|
||||
...
|
||||
|
||||
my_nums := @[0, 1, 2]
|
||||
do_mutation(my_nums)
|
||||
```
|
||||
|
||||
In general, heap pointers can be used as stack pointers if necessary, since
|
||||
the usage of stack pointers is restricted, but heap pointers don't have the
|
||||
same restrictions, so it's good practice to define functions that don't need
|
||||
to store pointers to use stack references. This lets you pass references to
|
||||
local variables or pointers to heap data depending on your needs.
|
||||
|
||||
```tomo
|
||||
func swap_first_two(data:&[Int]):
|
||||
data[1], data[2] = data[2], data[1]
|
||||
|
||||
...
|
||||
|
||||
heap_nums := @[10, 20, 30]
|
||||
swap_first_two(heap_nums)
|
||||
|
||||
local_nums := [10, 20, 30]
|
||||
swap_first_two(&local_nums)
|
||||
```
|
||||
|
||||
## Dereferencing
|
||||
|
||||
Pointers can be dereferenced to access the value that's stored at the pointer's
|
||||
memory location using the `[]` postfix operator (with no value inside).
|
||||
|
||||
```tomo
|
||||
nums := @[10, 20]
|
||||
>> nums[]
|
||||
= [10, 20]
|
||||
```
|
||||
|
||||
## Equality and Comparisons
|
||||
|
||||
When comparing two pointers, the comparison operates on the _memory address_,
|
||||
not the contents of the memory. This is "referential" equality, not
|
||||
"structural" equality. The easy way to think about it is that two pointers are
|
||||
equal to each other only if doing a mutation to one of them is the same as
|
||||
doing a mutation to the other.
|
||||
|
||||
```tomo
|
||||
x := @[10, 20, 30]
|
||||
y := @[10, 20, 30]
|
||||
>> x == y
|
||||
= no
|
||||
|
||||
z := x
|
||||
>> x == z
|
||||
= yes
|
||||
```
|
||||
|
||||
Pointers are ordered by memory address, which is somewhat arbitrary, but
|
||||
consistent.
|
||||
|
||||
## Null Safety
|
||||
|
||||
Tomo pointers are, by default, guaranteed to be non-null. If you write a
|
||||
function that takes either a `&T` or `@T`, the value that will be given
|
||||
is always non-null. However, optional pointers can be used by adding a
|
||||
question mark to the type: `&T?` or `@T?`. A null value can be created
|
||||
using the syntax `!@T` or `!&T`. You can also append a question mark to
|
||||
a pointer value so the type checker knows it's supposed to be optional:
|
||||
|
||||
```
|
||||
optional := @[10, 20]?
|
||||
optional := &foo?
|
||||
```
|
||||
|
||||
The compiler will not allow you to dereference an optionally null pointer
|
||||
without explicitly checking for null. To do so, use pattern matching like
|
||||
this:
|
||||
|
||||
```
|
||||
when optional is @ptr:
|
||||
ok := ptr[]
|
||||
else:
|
||||
say("Oh, it was null")
|
||||
```
|
@ -48,6 +48,15 @@ set := {10, 20, 30}
|
||||
= [10, 20, 30]
|
||||
```
|
||||
|
||||
## Length
|
||||
|
||||
Set length can be accessed by the `.length` field:
|
||||
|
||||
```tomo
|
||||
>> {10, 20, 30}.length
|
||||
= 3
|
||||
```
|
||||
|
||||
## Iteration
|
||||
|
||||
You can iterate over the items in a table like this:
|
||||
|
@ -50,6 +50,18 @@ t2 := {"B": 20; fallback=t}
|
||||
= 10
|
||||
```
|
||||
|
||||
The fallback is available by the `.fallback` field, which returns an optional
|
||||
readonly pointer to the fallback table (if present) or null if it is not.
|
||||
|
||||
## Length
|
||||
|
||||
Table length can be accessed by the `.length` field:
|
||||
|
||||
```tomo
|
||||
>> {"A":10, "B":20}.length
|
||||
= 2
|
||||
```
|
||||
|
||||
## Accessing Keys and Values
|
||||
|
||||
The keys and values of a table can be efficiently accessed as arrays using a
|
||||
|
16
compile.c
16
compile.c
@ -2630,13 +2630,27 @@ CORD compile(env_t *env, ast_t *ast)
|
||||
}
|
||||
code_err(ast, "The field '%s' is not a valid field name of %T", f->field, value_t);
|
||||
}
|
||||
case ArrayType: {
|
||||
if (streq(f->field, "length"))
|
||||
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)");
|
||||
code_err(ast, "There is no %s field on arrays", f->field);
|
||||
}
|
||||
case ChannelType: {
|
||||
if (streq(f->field, "max_size"))
|
||||
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ")->max_size)");
|
||||
code_err(ast, "There is no %s field on arrays", f->field);
|
||||
}
|
||||
case SetType: {
|
||||
if (streq(f->field, "items"))
|
||||
return CORD_all("(", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries");
|
||||
else if (streq(f->field, "length"))
|
||||
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries.length)");
|
||||
code_err(ast, "There is no '%s' field on sets", f->field);
|
||||
}
|
||||
case TableType: {
|
||||
if (streq(f->field, "keys")) {
|
||||
if (streq(f->field, "length")) {
|
||||
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries.length)");
|
||||
} else if (streq(f->field, "keys")) {
|
||||
return CORD_all("(", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries");
|
||||
} else if (streq(f->field, "values")) {
|
||||
auto table = Match(value_t, TableType);
|
||||
|
18
types.c
18
types.c
@ -595,14 +595,16 @@ type_t *get_field_type(type_t *t, const char *field_name)
|
||||
return NULL;
|
||||
}
|
||||
case SetType: {
|
||||
if (streq(field_name, "items"))
|
||||
if (streq(field_name, "length"))
|
||||
return INT_TYPE;
|
||||
else if (streq(field_name, "items"))
|
||||
return Type(ArrayType, .item_type=Match(t, SetType)->item_type);
|
||||
else if (streq(field_name, "fallback"))
|
||||
return Type(PointerType, .pointed=t, .is_readonly=true, .is_optional=true);
|
||||
return NULL;
|
||||
}
|
||||
case TableType: {
|
||||
if (streq(field_name, "keys"))
|
||||
if (streq(field_name, "length"))
|
||||
return INT_TYPE;
|
||||
else if (streq(field_name, "keys"))
|
||||
return Type(ArrayType, Match(t, TableType)->key_type);
|
||||
else if (streq(field_name, "values"))
|
||||
return Type(ArrayType, Match(t, TableType)->value_type);
|
||||
@ -612,6 +614,14 @@ type_t *get_field_type(type_t *t, const char *field_name)
|
||||
return Type(PointerType, .pointed=t, .is_readonly=true, .is_optional=true);
|
||||
return NULL;
|
||||
}
|
||||
case ArrayType: {
|
||||
if (streq(field_name, "length")) return INT_TYPE;
|
||||
return NULL;
|
||||
}
|
||||
case ChannelType: {
|
||||
if (streq(field_name, "max_size")) return INT_TYPE;
|
||||
return NULL;
|
||||
}
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user