diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-10-11 15:31:38 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-10-11 15:31:38 -0400 |
| commit | 7e8604daeb9239e1669c5414dd6caa37af30c4ff (patch) | |
| tree | 8fcab61a296381280902a3fc7b2d8456e2a9b227 | |
| parent | 25fa8ace21f0f6874f5b3ad1248e0e5d21190c84 (diff) | |
Make `{a,b,c}` shorthand for `{a:Empty(), b:Empty(), c:Empty()}` and
display it that way. Same for type annotations.
| -rw-r--r-- | src/ast.h | 4 | ||||
| -rw-r--r-- | src/compile/expressions.c | 2 | ||||
| -rw-r--r-- | src/compile/optionals.c | 2 | ||||
| -rw-r--r-- | src/compile/tables.c | 12 | ||||
| -rw-r--r-- | src/environment.c | 6 | ||||
| -rw-r--r-- | src/environment.h | 1 | ||||
| -rw-r--r-- | src/formatter/formatter.c | 6 | ||||
| -rw-r--r-- | src/formatter/types.c | 3 | ||||
| -rw-r--r-- | src/parse/containers.c | 6 | ||||
| -rw-r--r-- | src/parse/types.c | 2 | ||||
| -rw-r--r-- | src/stdlib/tables.c | 7 | ||||
| -rw-r--r-- | src/typecheck.c | 14 | ||||
| -rw-r--r-- | src/types.c | 4 | ||||
| -rw-r--r-- | test/tables.tm | 10 |
14 files changed, 51 insertions, 28 deletions
@@ -116,7 +116,7 @@ struct type_ast_s { type_ast_t *item; } ListTypeAST; struct { - type_ast_t *key, *value; + type_ast_t *key, *value /* value may be NULL */; ast_t *default_value; } TableTypeAST; struct { @@ -339,7 +339,7 @@ struct ast_s { ast_list_t *entries; } Table; struct { - ast_t *key, *value; + ast_t *key, *value /* value may be NULL */; } TableEntry; struct { ast_list_t *vars; diff --git a/src/compile/expressions.c b/src/compile/expressions.c index fcd1d136..108bda80 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -27,7 +27,7 @@ Text_t compile_empty(type_t *t) { if (t->tag == OptionalType) return compile_none(t); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("((OptionalPathType_t){})"); + else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_ABSOLUTE"); switch (t->tag) { case BigIntType: return Text("I(0)"); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index c8a7276c..b4930d02 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -53,7 +53,7 @@ Text_t compile_none(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type"); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("((OptionalPathType_t){.type = PATHTYPE_NONE})"); + else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_NONE"); switch (t->tag) { case BigIntType: return Text("NONE_INT"); diff --git a/src/compile/tables.c b/src/compile/tables.c index 2b6d3538..958e5af5 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -10,8 +10,10 @@ static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) { DeclareMatch(e, entry, TableEntry); - return WrapAST(entry, MethodCall, .name = "set", .self = subject, - .args = new (arg_ast_t, .value = e->key, .next = new (arg_ast_t, .value = e->value))); + return WrapAST( + entry, MethodCall, .name = "set", .self = subject, + .args = new (arg_ast_t, .value = e->key, + .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "EMPTY")))); } Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { @@ -47,8 +49,10 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { for (ast_list_t *entry = table->entries; entry; entry = entry->next) { DeclareMatch(e, entry->ast, TableEntry); - code = Texts(code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", - compile_to_type(value_scope, e->value, value_t), "}"); + code = Texts( + code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", + compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "EMPTY"), value_t), + "}"); } return Texts(code, ")"); } diff --git a/src/environment.c b/src/environment.c index 7d0046de..a3cb5ba8 100644 --- a/src/environment.c +++ b/src/environment.c @@ -18,6 +18,8 @@ public type_t *PATH_TYPE = NULL; public type_t *PATH_TYPE_TYPE = NULL; +public +type_t *EMPTY_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { ast_t *ast = parse_file_str(def_str); @@ -68,7 +70,7 @@ env_t *global_env(bool source_mapping) { PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)"); PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])"); - type_t *empty_type = declare_type(env, "struct Empty()"); + EMPTY_TYPE = declare_type(env, "struct Empty()"); typedef struct { const char *name, *code, *type_str; @@ -88,7 +90,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Void", Type(VoidType), Text("void"), Text("Void$info")), MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")), MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")), - MAKE_TYPE("Empty", empty_type, Text("Empty$$type"), Text("Empty$$info")), + MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), {"parse", "Bool$parse", "func(text:Text, remainder:&Text? = none -> Bool?)"}), diff --git a/src/environment.h b/src/environment.h index c726508d..6389cc7a 100644 --- a/src/environment.h +++ b/src/environment.h @@ -87,3 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PATH_TYPE_TYPE; +extern type_t *EMPTY_TYPE; diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 55fff920..5cb5d3cc 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -223,7 +223,8 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } /*inline*/ case TableEntry: { DeclareMatch(entry, ast, TableEntry); - return Texts(fmt_inline(entry->key, comments), "=", fmt_inline(entry->value, comments)); + if (entry->value) return Texts(fmt_inline(entry->key, comments), ": ", fmt_inline(entry->value, comments)); + else return Texts(fmt_inline(entry->key, comments)); } /*inline*/ case Declare: { DeclareMatch(decl, ast, Declare); @@ -629,7 +630,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case TableEntry: { if (inlined_fits) return inlined; DeclareMatch(entry, ast, TableEntry); - return Texts(fmt(entry->key, comments, indent), ": ", fmt(entry->value, comments, indent)); + if (entry->value) return Texts(fmt(entry->key, comments, indent), ": ", fmt(entry->value, comments, indent)); + else return Texts(fmt(entry->key, comments, indent)); } /*multiline*/ case Declare: { if (inlined_fits) return inlined; diff --git a/src/formatter/types.c b/src/formatter/types.c index 670405f6..929c8ff9 100644 --- a/src/formatter/types.c +++ b/src/formatter/types.c @@ -19,7 +19,8 @@ Text_t format_type(type_ast_t *type) { } case TableTypeAST: { DeclareMatch(table, type, TableTypeAST); - Text_t code = Texts("{", format_type(table->key), ":", format_type(table->value)); + Text_t code = Texts("{", format_type(table->key)); + if (table->value != NULL) code = Texts(code, ":", format_type(table->value)); if (table->default_value) { OptionalText_t val = format_inline_code(table->default_value, (Table_t){}); assert(val.tag != TEXT_NONE); diff --git a/src/parse/containers.c b/src/parse/containers.c index 55ff336e..8f9922f3 100644 --- a/src/parse/containers.c +++ b/src/parse/containers.c @@ -2,7 +2,6 @@ #include <stdarg.h> #include <stdbool.h> -#include <string.h> #include "../ast.h" #include "../stdlib/util.h" @@ -50,8 +49,9 @@ ast_t *parse_table(parse_ctx_t *ctx, const char *pos) { ast_t *key = optional(ctx, &pos, parse_extended_expr); if (!key) break; whitespace(ctx, &pos); - if (!match(&pos, ":")) return NULL; - ast_t *value = expect(ctx, pos - 1, &pos, parse_expr, "I couldn't parse the value for this table entry"); + ast_t *value = NULL; + if (match(&pos, ":")) + value = expect(ctx, pos - 1, &pos, parse_expr, "I couldn't parse the value for this table entry"); ast_t *entry = NewAST(ctx->file, entry_start, pos, TableEntry, .key = key, .value = value); ast_t *suffixed = parse_comprehension_suffix(ctx, entry); while (suffixed) { diff --git a/src/parse/types.c b/src/parse/types.c index 192103dc..8ecc602e 100644 --- a/src/parse/types.c +++ b/src/parse/types.c @@ -27,8 +27,6 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) { type_ast_t *value_type = NULL; if (match(&pos, ":")) { value_type = expect(ctx, start, &pos, parse_type, "I couldn't parse the rest of this table type"); - } else { - return NULL; } spaces(&pos); ast_t *default_value = NULL; diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index c093e4c2..fdd85b56 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -534,8 +534,9 @@ Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { __typeof(type->TableInfo) table = type->TableInfo; if (!t) { - return Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text(":"), - generic_as_text(NULL, false, table.value), Text("}")); + return table.value->size > 0 ? Texts("{", generic_as_text(NULL, false, table.key), ":", + generic_as_text(NULL, false, table.value), "}") + : Texts("{", generic_as_text(NULL, false, table.key), "}"); } int64_t val_off = (int64_t)value_offset(type); @@ -544,7 +545,7 @@ Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { if (i > 0) text = Text$concat(text, Text(", ")); void *entry = GET_ENTRY(*t, i); text = Text$concat(text, generic_as_text(entry, colorize, table.key)); - if (table.value != &Void$info) + if (table.value->size > 0) text = Text$concat(text, Text(": "), generic_as_text(entry + val_off, colorize, table.value)); } diff --git a/src/typecheck.c b/src/typecheck.c index 429a162f..9342f069 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -73,8 +73,9 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { if (has_stack_memory(key_type)) code_err(key_type_ast, "Tables can't have stack references because the list may outlive the stack frame."); - type_t *val_type = parse_type_ast(env, table_type->value); - if (!val_type) code_err(table_type->value, "I can't figure out what type this is."); + type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : EMPTY_TYPE; + if (!val_type) code_err(ast, "I can't figure out what the value type for this entry is."); + if (has_stack_memory(val_type)) code_err(table_type->value, "Tables can't have stack references because the list may outlive the stack frame."); @@ -814,7 +815,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(e, entry_ast, TableEntry); type_t *key_t = get_type(scope, e->key); - type_t *value_t = get_type(scope, e->value); + type_t *value_t = e->value ? get_type(scope, e->value) : EMPTY_TYPE; type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t; if (!key_merged) ambiguous_key_type = true; @@ -846,8 +847,8 @@ type_t *get_type(env_t *env, ast_t *ast) { return get_type(scope, comp->expr); } else if (comp->expr->tag == TableEntry) { DeclareMatch(e, comp->expr, TableEntry); - return Type(TableType, .key_type = get_type(scope, e->key), .value_type = get_type(scope, e->value), - .env = env); + return Type(TableType, .key_type = get_type(scope, e->key), + .value_type = e->value ? get_type(scope, e->value) : EMPTY_TYPE, .env = env); } else { return Type(ListType, .item_type = get_type(scope, comp->expr)); } @@ -1711,7 +1712,8 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) { if (entry->ast->tag != TableEntry) continue; // TODO: fix this DeclareMatch(e, entry->ast, TableEntry); - if (!can_compile_to_type(env, e->key, key_type) || !can_compile_to_type(env, e->value, value_type)) + if (!can_compile_to_type(env, e->key, key_type) + || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, EMPTY_TYPE))) return false; } return true; diff --git a/src/types.c b/src/types.c index bf986c80..d08755a2 100644 --- a/src/types.c +++ b/src/types.c @@ -37,7 +37,9 @@ Text_t type_to_text(type_t *t) { } case TableType: { DeclareMatch(table, t, TableType); - return Texts("{", type_to_text(table->key_type), "=", type_to_text(table->value_type), "}"); + return (table->value_type && table->value_type != EMPTY_TYPE) + ? Texts("{", type_to_text(table->key_type), ":", type_to_text(table->value_type), "}") + : Texts("{", type_to_text(table->key_type), "}"); } case ClosureType: { return type_to_text(Match(t, ClosureType)->fn); diff --git a/test/tables.tm b/test/tables.tm index 2eb1409e..68b77baf 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -87,3 +87,13 @@ func main() assert a.intersection(b) == {"B":2} assert a.difference(b) == {"A":1, "D":40} assert a.without(b) == {"A":1, "C":3} + + do + # Set operations with sets + a := {"A", "B", "C"} + b := {"B", "C", "D"} + assert a.with(b) == {"A", "B", "C", "D"} + assert a.with(b) == a ++ b + assert a.intersection(b) == {"B", "C"} + assert a.difference(b) == {"A", "D"} + assert a.without(b) == {"A"} |
