aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md1
-rw-r--r--docs/lists.md17
-rw-r--r--src/ast.h1
-rw-r--r--src/compile/assignments.c16
-rw-r--r--src/compile/expressions.c2
-rw-r--r--src/compile/indexing.c36
-rw-r--r--src/compile/indexing.h4
-rw-r--r--src/compile/optionals.c4
-rw-r--r--src/parse/suffixes.c3
-rw-r--r--src/stdlib/lists.h47
-rw-r--r--src/stdlib/tables.h12
-rw-r--r--src/stdlib/text.c5
-rw-r--r--src/stdlib/text.h12
-rw-r--r--src/typecheck.c4
-rw-r--r--test/enums.tm4
-rw-r--r--test/iterators.tm3
-rw-r--r--test/text.tm3
17 files changed, 104 insertions, 70 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 41fd55cc..ca8a3523 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,7 @@
- Core libraries are no longer shipped with the compiler, they have moved to
separate repositories.
- Library installation has been cleaned up a bit.
+- List indexing now gives an optional value
- Added a `--format` flag to the `tomo` binary that autoformats your code
(currently unstable, do not rely on it just yet).
- Standardized text methods for Unicode encodings:
diff --git a/docs/lists.md b/docs/lists.md
index dfb64aad..d12a0b5b 100644
--- a/docs/lists.md
+++ b/docs/lists.md
@@ -62,24 +62,23 @@ last item, `-2` is the second-to-last, and so on.
```tomo
list := [10, 20, 30, 40]
>> list[1]
-= 10
+= 10?
>> list[2]
-= 20
+= 20?
+
+>> list[999]
+= none
>> list[-1]
-= 40
+= 40?
>> list[-2]
-= 30
+= 30?
```
If a list index of `0` or any value larger than the length of the list is
-used, it will trigger a runtime error that will print what the invalid list
-index was, the length of the list, and a stack trace. As a performance
-operation, if list bounds checking proves to be a performance hot spot, you
-can explicitly disable bounds checking by adding `list[i; unchecked]` to the
-list access.
+used, a `none` value will be returned.
## Iteration
diff --git a/src/ast.h b/src/ast.h
index 7396555e..62dbff0e 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -438,7 +438,6 @@ struct ast_s {
} LangDef;
struct {
ast_t *indexed, *index;
- bool unchecked;
} Index;
struct {
ast_t *fielded;
diff --git a/src/compile/assignments.c b/src/compile/assignments.c
index 3cb60fd5..6848e298 100644
--- a/src/compile/assignments.c
+++ b/src/compile/assignments.c
@@ -92,7 +92,8 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) {
if (assign->targets && !assign->targets->next) {
type_t *lhs_t = get_type(env, assign->targets->ast);
if (assign->targets->ast->tag == Index && lhs_t->tag == OptionalType
- && value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType)
+ && (value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType
+ || value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == ListType))
lhs_t = Match(lhs_t, OptionalType)->type;
if (has_stack_memory(lhs_t))
code_err(ast, "Stack references cannot be assigned to "
@@ -110,7 +111,8 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) {
value = value->next, target = target->next) {
type_t *lhs_t = get_type(env, target->ast);
if (target->ast->tag == Index && lhs_t->tag == OptionalType
- && value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType)
+ && (value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType
+ || value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == ListType))
lhs_t = Match(lhs_t, OptionalType)->type;
if (has_stack_memory(lhs_t))
code_err(ast, "Stack references cannot be assigned to "
@@ -166,13 +168,8 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) {
? compile_int_to_type(env, index->index, Type(IntType, .bits = TYPE_IBITS64))
: (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, index->index), ", no)")
: Texts("(Int64_t)(", compile(env, index->index), ")"));
- if (index->unchecked) {
- return Texts("List_lvalue_unchecked(", compile_type(item_type), ", ", target_code, ", ", index_code,
- ")");
- } else {
- return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ",
- (int64_t)(ast->start - ast->file->text), ", ", (int64_t)(ast->end - ast->file->text), ")");
- }
+ return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ",
+ (int64_t)(ast->start - ast->file->text), ", ", (int64_t)(ast->end - ast->file->text), ")");
} else if (container_t->tag == TableType) {
DeclareMatch(table_type, container_t, TableType);
if (table_type->default_value) {
@@ -183,7 +180,6 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) {
compile_to_type(env, table_type->default_value, table_type->value_type), ", ",
compile_type_info(container_t), ")");
}
- if (index->unchecked) code_err(ast, "Table indexes cannot be unchecked");
return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(",
compile_to_pointer_depth(env, index->indexed, 1, false), ", ",
compile_to_type(env, index->index, Type(PointerType, table_type->key_type, .is_stack = true)),
diff --git a/src/compile/expressions.c b/src/compile/expressions.c
index 544b7723..e888ce16 100644
--- a/src/compile/expressions.c
+++ b/src/compile/expressions.c
@@ -234,7 +234,7 @@ Text_t compile(env_t *env, ast_t *ast) {
case If: return compile_if_expression(env, ast);
case Reduction: return compile_reduction(env, ast);
case FieldAccess: return compile_field_access(env, ast);
- case Index: return compile_indexing(env, ast);
+ case Index: return compile_indexing(env, ast, false);
case InlineCCode: {
type_t *t = get_type(env, ast);
if (Match(ast, InlineCCode)->type_ast != NULL) return Texts("({", compile_statement(env, ast), "; })");
diff --git a/src/compile/indexing.c b/src/compile/indexing.c
index 1510e924..bb7bf6b9 100644
--- a/src/compile/indexing.c
+++ b/src/compile/indexing.c
@@ -1,5 +1,7 @@
// This file defines how to compile indexing like `list[i]` or `ptr[]`
+#include <stdbool.h>
+
#include "../ast.h"
#include "../config.h"
#include "../environment.h"
@@ -9,7 +11,7 @@
#include "compilation.h"
public
-Text_t compile_indexing(env_t *env, ast_t *ast) {
+Text_t compile_indexing(env_t *env, ast_t *ast, bool checked) {
DeclareMatch(indexing, ast, Index);
type_t *indexed_type = get_type(env, indexing->indexed);
if (!indexing->index) {
@@ -34,27 +36,33 @@ Text_t compile_indexing(env_t *env, ast_t *ast) {
code_err(indexing->index, "Lists can only be indexed by integers, not ", type_to_str(index_t));
type_t *item_type = Match(container_t, ListType)->item_type;
Text_t list = compile_to_pointer_depth(env, indexing->indexed, 0, false);
- file_t *f = indexing->index->file;
Text_t index_code =
indexing->index->tag == Int
? compile_int_to_type(env, indexing->index, Type(IntType, .bits = TYPE_IBITS64))
: (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, indexing->index), ", no)")
: Texts("(Int64_t)(", compile(env, indexing->index), ")"));
- if (indexing->unchecked)
- return Texts("List_get_unchecked(", compile_type(item_type), ", ", list, ", ", index_code, ")");
- else
- return Texts("List_get(", compile_type(item_type), ", ", list, ", ", index_code, ", ",
- (int64_t)(indexing->index->start - f->text), ", ", (int64_t)(indexing->index->end - f->text),
- ")");
+ if (checked) {
+ int64_t start = (int64_t)(ast->start - ast->file->text), end = (int64_t)(ast->end - ast->file->text);
+ return Texts("List_get_checked(", list, ", ", index_code, ", ", compile_type(item_type), ", ", start, ", ",
+ end, ")");
+ } else {
+ return Texts("List_get(", list, ", ", index_code, ", ", compile_type(item_type), ", value, ",
+ promote_to_optional(item_type, Text("value")), ", ", compile_none(item_type), ")");
+ }
} else if (container_t->tag == TableType) {
DeclareMatch(table_type, container_t, TableType);
- if (indexing->unchecked) code_err(ast, "Table indexes cannot be unchecked");
if (table_type->default_value) {
return Texts("Table$get_or_default(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ",
compile_to_type(env, indexing->index, table_type->key_type), ", ",
compile_to_type(env, table_type->default_value, table_type->value_type), ", ",
compile_type_info(container_t), ")");
+ } else if (checked) {
+ int64_t start = (int64_t)(ast->start - ast->file->text), end = (int64_t)(ast->end - ast->file->text);
+ return Texts("Table$get_checked(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
+ compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ",
+ compile(env, indexing->index), ", ", start, ", ", end, ", ", compile_type_info(container_t),
+ ")");
} else {
return Texts("Table$get_optional(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ",
@@ -65,8 +73,14 @@ Text_t compile_indexing(env_t *env, ast_t *ast) {
compile_none(table_type->value_type), ", ", compile_type_info(container_t), ")");
}
} else if (container_t->tag == TextType) {
- return Texts("Text$cluster(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
- compile_to_type(env, indexing->index, Type(BigIntType)), ")");
+ if (checked) {
+ int64_t start = (int64_t)(ast->start - ast->file->text), end = (int64_t)(ast->end - ast->file->text);
+ return Texts("Text$cluster_checked(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
+ compile_to_type(env, indexing->index, Type(BigIntType)), ", ", start, ", ", end, ")");
+ } else {
+ return Texts("Text$cluster(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
+ compile_to_type(env, indexing->index, Type(BigIntType)), ")");
+ }
} else {
code_err(ast, "Indexing is not supported for type: ", type_to_str(container_t));
}
diff --git a/src/compile/indexing.h b/src/compile/indexing.h
index bf30f98a..5b76692b 100644
--- a/src/compile/indexing.h
+++ b/src/compile/indexing.h
@@ -2,8 +2,10 @@
#pragma once
+#include <stdbool.h>
+
#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
-Text_t compile_indexing(env_t *env, ast_t *ast);
+Text_t compile_indexing(env_t *env, ast_t *ast, bool checked);
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
index cd50b1bf..8340ecaa 100644
--- a/src/compile/optionals.c
+++ b/src/compile/optionals.c
@@ -8,6 +8,7 @@
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"
+#include "indexing.h"
Text_t optional_into_nonnone(type_t *t, Text_t value) {
if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
@@ -122,6 +123,7 @@ Text_t compile_optional(env_t *env, ast_t *ast) {
public
Text_t compile_non_optional(env_t *env, ast_t *ast) {
ast_t *value = Match(ast, NonOptional)->value;
+ if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true);
type_t *t = get_type(env, value);
Text_t value_code = compile(env, value);
int64_t line = get_line_number(ast->file, ast->start);
@@ -129,5 +131,5 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) {
"({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ",
(int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ",
- "\"This was expected to be a value, but it's none\");\n", optional_into_nonnone(t, Text("opt")), "; })");
+ "\"This was expected to be a value, but it's `none`\\n\");\n", optional_into_nonnone(t, Text("opt")), "; })");
}
diff --git a/src/parse/suffixes.c b/src/parse/suffixes.c
index cb54b2f6..312f958f 100644
--- a/src/parse/suffixes.c
+++ b/src/parse/suffixes.c
@@ -47,9 +47,8 @@ ast_t *parse_index_suffix(parse_ctx_t *ctx, ast_t *lhs) {
whitespace(ctx, &pos);
ast_t *index = optional(ctx, &pos, parse_extended_expr);
whitespace(ctx, &pos);
- bool unchecked = match(&pos, ";") && (spaces(&pos), match_word(&pos, "unchecked") != 0);
expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this index");
- return NewAST(ctx->file, start, pos, Index, .indexed = lhs, .index = index, .unchecked = unchecked);
+ return NewAST(ctx->file, start, pos, Index, .indexed = lhs, .index = index);
}
ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr) {
diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h
index 1ba46222..1386b2fa 100644
--- a/src/stdlib/lists.h
+++ b/src/stdlib/lists.h
@@ -10,26 +10,29 @@
#include "util.h"
// Convert negative indices to back-indexed without branching: index0 = index + (index < 0)*(len+1)) - 1
-#define List_get(item_type, arr_expr, index_expr, start, end) \
- *({ \
- const List_t list = arr_expr; \
+#define List_get_checked(list_expr, index_expr, item_type, start, end) \
+ ({ \
+ const List_t list = list_expr; \
int64_t index = index_expr; \
int64_t off = index + (index < 0) * (list.length + 1) - 1; \
if (unlikely(off < 0 || off >= list.length)) \
fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \
(int64_t)list.length, ")\n"); \
- (item_type *)(list.data + list.stride * off); \
+ *(item_type *)(list.data + list.stride * off); \
})
-#define List_get_unchecked(type, x, i) \
- *({ \
- const List_t list = x; \
- int64_t index = i; \
- int64_t off = index + (index < 0) * (list.length + 1) - 1; \
- (type *)(list.data + list.stride * off); \
+#define List_get(list_expr, index_expr, item_type, var, optional_expr, none_expr) \
+ ({ \
+ const List_t list = list_expr; \
+ int64_t index = index_expr; \
+ int64_t offset = index + (index < 0) * (list.length + 1) - 1; \
+ unlikely(offset < 0 || offset >= list.length) ? none_expr : ({ \
+ item_type var = *(item_type *)(list.data + list.stride * offset); \
+ optional_expr; \
+ }); \
})
-#define List_lvalue(item_type, arr_expr, index_expr, start, end) \
+#define List_lvalue(item_type, list_expr, index_expr, start, end) \
*({ \
- List_t *list = arr_expr; \
+ List_t *list = list_expr; \
int64_t index = index_expr; \
int64_t off = index + (index < 0) * (list->length + 1) - 1; \
if (unlikely(off < 0 || off >= list->length)) \
@@ -38,15 +41,7 @@
if (list->data_refcount > 0) List$compact(list, sizeof(item_type)); \
(item_type *)(list->data + list->stride * off); \
})
-#define List_lvalue_unchecked(item_type, arr_expr, index_expr) \
- *({ \
- List_t *list = arr_expr; \
- int64_t index = index_expr; \
- int64_t off = index + (index < 0) * (list->length + 1) - 1; \
- if (list->data_refcount > 0) List$compact(list, sizeof(item_type)); \
- (item_type *)(list->data + list->stride * off); \
- })
-#define List_set(item_type, list, index, value, start, end) List_lvalue(item_type, arr_expr, index, start, end) = value
+#define List_set(item_type, list, index, value, start, end) List_lvalue(item_type, list_expr, index, start, end) = value
#define is_atomic(x) \
_Generic(x, \
bool: true, \
@@ -103,9 +98,9 @@ void List$remove_item(List_t *list, void *item, Int_t max_removals, const TypeIn
#define List$remove_item_value(list, item_expr, max, type) \
List$remove_item(list, (__typeof(item_expr)[1]){item_expr}, max, type)
-#define List$pop(arr_expr, index_expr, item_type, nonnone_var, nonnone_expr, none_expr) \
+#define List$pop(list_expr, index_expr, item_type, nonnone_var, nonnone_expr, none_expr) \
({ \
- List_t *list = arr_expr; \
+ List_t *list = list_expr; \
Int_t index = index_expr; \
int64_t index64 = Int64$from_int(index, false); \
int64_t off = index64 + (index64 < 0) * (list->length + 1) - 1; \
@@ -131,9 +126,9 @@ List_t List$shuffled(List_t list, OptionalClosure_t random_int64, int64_t padded
void *List$random(List_t list, OptionalClosure_t random_int64);
#define List$random_value(list, random_int64, t) \
({ \
- List_t _arr = list; \
- if (_arr.length == 0) fail("Cannot get a random value from an empty list!"); \
- *(t *)List$random(_arr, random_int64); \
+ List_t _list_expr = list; \
+ if (_list_expr.length == 0) fail("Cannot get a random value from an empty list!"); \
+ *(t *)List$random(_list_expr, random_int64); \
})
List_t List$sample(List_t list, Int_t n, List_t weights, Closure_t random_num, int64_t padded_item_size);
Table_t List$counts(List_t list, const TypeInfo_t *type);
diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h
index 208bf6b2..129aa5ec 100644
--- a/src/stdlib/tables.h
+++ b/src/stdlib/tables.h
@@ -49,6 +49,18 @@ void *Table$get(Table_t t, const void *key, const TypeInfo_t *type);
val_t *nonnull_var = Table$get(t, &k, info_expr); \
nonnull_var ? nonnull_expr : null_expr; \
})
+#define Table$get_checked(table_expr, key_t, val_t, key_expr, start, end, info_expr) \
+ ({ \
+ const Table_t t = table_expr; \
+ const key_t key = key_expr; \
+ const TypeInfo_t *info = info_expr; \
+ val_t *value = Table$get(t, &key, info); \
+ if (unlikely(value == NULL)) \
+ fail_source(__SOURCE_FILE__, start, end, \
+ "This key was not found in the table: ", generic_as_text(&key, false, info->TableInfo.key), \
+ "\n"); \
+ *value; \
+ })
#define Table$get_or_setdefault(table_expr, key_t, val_t, key_expr, default_expr, info_expr) \
({ \
Table_t *t = table_expr; \
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index b6e43f5a..dace7c9f 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -726,7 +726,10 @@ Text_t Text$reversed(Text_t text) {
}
public
-PUREFUNC Text_t Text$cluster(Text_t text, Int_t index) { return Text$slice(text, index, index); }
+PUREFUNC OptionalText_t Text$cluster(Text_t text, Int_t index) {
+ Text_t slice = Text$slice(text, index, index);
+ return slice.length <= 0 ? NONE_TEXT : slice;
+}
static Text_t Text$from_components(List_t graphemes, Table_t unique_clusters) {
size_t blob_size = (sizeof(int32_t[unique_clusters.entries.length]) + sizeof(uint8_t[graphemes.length]));
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 4d2f16b8..281c1880 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -53,7 +53,17 @@ Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int);
Text_t Text$from(Text_t text, Int_t first);
Text_t Text$to(Text_t text, Int_t last);
Text_t Text$reversed(Text_t text);
-Text_t Text$cluster(Text_t text, Int_t index_int);
+OptionalText_t Text$cluster(Text_t text, Int_t index_int);
+#define Text$cluster_checked(text_expr, index_expr, start, end) \
+ ({ \
+ const Text_t text = text_expr; \
+ Int_t index = index_expr; \
+ OptionalText_t cluster = Text$cluster(text, index); \
+ if (unlikely(cluster.length < 0)) \
+ fail_source(__SOURCE_FILE__, start, end, "Invalid text index: ", index, " (text has length ", \
+ (int64_t)text.length, ")\n"); \
+ cluster; \
+ })
OptionalText_t Text$from_str(const char *str);
OptionalText_t Text$from_strn(const char *str, size_t len);
PUREFUNC uint64_t Text$hash(const void *text, const TypeInfo_t *);
diff --git a/src/typecheck.c b/src/typecheck.c
index 89a21fc3..e9968f09 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -933,14 +933,14 @@ type_t *get_type(env_t *env, ast_t *ast) {
if (!indexing->index) return indexed_t;
type_t *index_t = get_type(env, indexing->index);
if (index_t->tag == IntType || index_t->tag == BigIntType || index_t->tag == ByteType)
- return Match(value_t, ListType)->item_type;
+ return Type(OptionalType, Match(value_t, ListType)->item_type);
code_err(indexing->index, "I only know how to index lists using integers, not ", type_to_str(index_t));
} else if (value_t->tag == TableType) {
DeclareMatch(table_type, value_t, TableType);
if (table_type->default_value) return table_type->value_type;
return Type(OptionalType, table_type->value_type);
} else if (value_t->tag == TextType) {
- return value_t;
+ return Type(OptionalType, value_t);
} else {
code_err(ast, "I don't know how to index ", type_to_str(indexed_t), " values");
}
diff --git a/test/enums.tm b/test/enums.tm
index 1d00cd1a..9a0188a2 100644
--- a/test/enums.tm
+++ b/test/enums.tm
@@ -61,7 +61,7 @@ func main()
i := 1
cases := [Foo.One(1), Foo.One(2), Foo.Zero]
- repeat when cases[i] is One(x)
+ repeat when cases[i]! is One(x)
>> x
i += 1
else stop
@@ -78,7 +78,7 @@ func main()
]
= ["Zero", "Small 1", "Small 2", "Other"]
- >> expr := when cases[1] is One(y)
+ >> expr := when cases[1]! is One(y)
y + 1
else
-1
diff --git a/test/iterators.tm b/test/iterators.tm
index 08382cff..c48e572b 100644
--- a/test/iterators.tm
+++ b/test/iterators.tm
@@ -4,9 +4,8 @@ struct Pair(x:Text, y:Text)
func pairwise(strs:[Text] -> func(->Pair?))
i := 1
return func()
- if i + 1 > strs.length return none
i += 1
- return Pair(strs[i-1], strs[i])?
+ return Pair(strs[i-1] or return none, strs[i] or return none)?
func range(first:Int, last:Int -> func(->Int?))
i := first
diff --git a/test/text.tm b/test/text.tm
index 266d8c03..46e1ae41 100644
--- a/test/text.tm
+++ b/test/text.tm
@@ -34,6 +34,9 @@ func main()
>> str[9]
= "é"
+ >> str[99]
+ = none
+
>> "\{UE9}"
= "é"