aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/arrays.md9
-rw-r--r--api/channels.md6
-rw-r--r--api/pointers.md104
-rw-r--r--api/sets.md9
-rw-r--r--api/tables.md12
-rw-r--r--compile.c16
-rw-r--r--types.c18
7 files changed, 166 insertions, 8 deletions
diff --git a/api/arrays.md b/api/arrays.md
index 34da6e8f..0dede5dd 100644
--- a/api/arrays.md
+++ b/api/arrays.md
@@ -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
diff --git a/api/channels.md b/api/channels.md
index 4b8d416a..48116dc5 100644
--- a/api/channels.md
+++ b/api/channels.md
@@ -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
diff --git a/api/pointers.md b/api/pointers.md
new file mode 100644
index 00000000..67043084
--- /dev/null
+++ b/api/pointers.md
@@ -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")
+```
diff --git a/api/sets.md b/api/sets.md
index 6dcc9ca9..850d1443 100644
--- a/api/sets.md
+++ b/api/sets.md
@@ -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:
diff --git a/api/tables.md b/api/tables.md
index f132d70a..f4753efb 100644
--- a/api/tables.md
+++ b/api/tables.md
@@ -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
diff --git a/compile.c b/compile.c
index cc06b92d..5e97d0ea 100644
--- a/compile.c
+++ b/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);
diff --git a/types.c b/types.c
index f666f667..cd388304 100644
--- a/types.c
+++ b/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;
}
}