Added a .length field to arrays/sets/tables, added a .max_size field to

channels, and updated the API
This commit is contained in:
Bruce Hill 2024-08-18 20:28:16 -04:00
parent c338c3f08c
commit d804b09b02
7 changed files with 166 additions and 8 deletions

View File

@ -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

View File

@ -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
View 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")
```

View File

@ -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:

View File

@ -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

View File

@ -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
View File

@ -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;
}
}