diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2024-02-08 00:52:18 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2024-02-08 00:52:18 -0500 |
| commit | 930c09f46d1e249fc889e8f1179046a48c1eaf32 (patch) | |
| tree | 4900307e726dcbc643b12341a9bb7168edebdff5 /types.c | |
| parent | ee0f45e2959484d390c30a8a1430a0f040f56631 (diff) | |
More features and progress
Diffstat (limited to 'types.c')
| -rw-r--r-- | types.c | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/types.c b/types.c new file mode 100644 index 00000000..aa977507 --- /dev/null +++ b/types.c @@ -0,0 +1,557 @@ +// Logic for handling type_t types +#include <gc/cord.h> +#include <stdint.h> +#include <limits.h> +#include <math.h> + +#include "builtins/table.h" +#include "types.h" +#include "util.h" + +static CORD type_to_cord(type_t *t) { + switch (t->tag) { + case UnknownType: return "???"; + case AbortType: return "Abort"; + case VoidType: return "Void"; + case MemoryType: return "Memory"; + case BoolType: return "Bool"; + case CharType: return "Char"; + case IntType: return CORD_asprintf("Int%ld", Match(t, IntType)->bits); + case NumType: return CORD_asprintf("Num%ld", Match(t, NumType)->bits); + case ArrayType: { + auto array = Match(t, ArrayType); + return CORD_asprintf("[%r]", type_to_cord(array->item_type)); + } + case TableType: { + auto table = Match(t, TableType); + return CORD_asprintf("{%r=>%r}", type_to_cord(table->key_type), type_to_cord(table->value_type)); + } + case FunctionType: { + CORD c = "func("; + auto fn = Match(t, FunctionType); + for (arg_t *arg = fn->args; arg; arg = arg->next) { + c = CORD_cat(c, type_to_cord(arg->type)); + if (arg->next) c = CORD_cat(c, ", "); + } + c = CORD_cat(c, ")->"); + c = CORD_cat(c, type_to_cord(fn->ret)); + return c; + } + case StructType: { + auto struct_ = Match(t, StructType); + CORD c = "struct("; + int64_t i = 1; + for (arg_t *field = struct_->fields; field; field = field->next) { + const char *fname = field->name ? field->name : heap_strf("_%lu", i); + ++i; + if (fname && !streq(fname, heap_strf("_%lu", i+1))) + c = CORD_cat(CORD_cat(c, fname), ":"); + else + c = CORD_cat(c, ":"); + + c = CORD_cat(c, type_to_cord(field->type)); + + if (field->next) c = CORD_cat(c, ", "); + } + c = CORD_cat(c, ")"); + return c; + } + case PointerType: { + auto ptr = Match(t, PointerType); + CORD sigil = ptr->is_stack ? "&" : (ptr->is_optional ? "?" : "@"); + if (ptr->is_readonly) sigil = CORD_cat(sigil, "(readonly)"); + return CORD_cat(sigil, type_to_cord(ptr->pointed)); + } + case EnumType: { + auto tagged = Match(t, EnumType); + + CORD c = "enum("; + int64_t next_tag = 0; + for (tag_t *tag = tagged->tags; tag; tag = tag->next) { + // name, tag_value, type + c = CORD_cat(c, tag->name); + if (tag->type) { + c = CORD_cat(c, "("); + auto struct_ = Match(tag->type, StructType); + int64_t i = 1; + for (arg_t *field = struct_->fields; field; field = field->next) { + if (field->name && !streq(field->name, heap_strf("_%lu", i))) + c = CORD_cat(CORD_cat(c, field->name), ":"); + + CORD fstr = type_to_cord(field->type); + c = CORD_cat(c, fstr); + if (field->next) c = CORD_cat(c, ","); + ++i; + } + c = CORD_cat(c, ")"); + } + + if (tag->tag_value != next_tag) { + CORD_sprintf(&c, "%r=%ld", c, tag->tag_value); + next_tag = tag->tag_value + 1; + } else { + ++next_tag; + } + + if (tag->next) + c = CORD_cat(c, "|"); + } + c = CORD_cat(c, ")"); + return c; + } + case VariantType: { + return Match(t, VariantType)->name; + } + case PlaceholderType: { + return Match(t, PlaceholderType)->name; + } + case TypeInfoType: { + return "TypeInfo"; + } + default: { + return CORD_asprintf("Unknown type: %d", t->tag); + } + } +} + +int printf_pointer_size(const struct printf_info *info, size_t n, int argtypes[n], int sizes[n]) +{ + if (n < 1) return -1; + (void)info; + argtypes[0] = PA_POINTER; + sizes[0] = sizeof(void*); + return 1; +} + +int printf_type(FILE *stream, const struct printf_info *info, const void *const args[]) +{ + (void)info; + type_t *t = *(type_t**)args[0]; + if (!t) return fputs("(null)", stream); + return CORD_put(type_to_cord(t), stream); +} + +const char *type_to_string(type_t *t) { + return CORD_to_const_char_star(type_to_cord(t)); +} + +bool type_eq(type_t *a, type_t *b) +{ + if (a == b) return true; + if (a->tag != b->tag) return false; + if (a->tag == PlaceholderType) return a == b; + return streq(type_to_string(a), type_to_string(b)); +} + +bool type_is_a(type_t *t, type_t *req) +{ + if (type_eq(t, req)) return true; + if (t->tag == PointerType && req->tag == PointerType) { + auto t_ptr = Match(t, PointerType); + auto req_ptr = Match(req, PointerType); + if (type_eq(t_ptr->pointed, req_ptr->pointed)) + return (!t_ptr->is_stack && !t_ptr->is_optional && req_ptr->is_stack) + || (!t_ptr->is_stack && req_ptr->is_optional); + } + return false; +} + +static type_t *non_optional(type_t *t) +{ + if (t->tag != PointerType) return t; + auto ptr = Match(t, PointerType); + return ptr->is_optional ? Type(PointerType, .is_optional=false, .pointed=ptr->pointed) : t; +} + +type_t *value_type(type_t *t) +{ + while (t->tag == PointerType) + t = Match(t, PointerType)->pointed; + return t; +} + +type_t *base_value_type(type_t *t) +{ + for (;;) { + if (t->tag == PointerType) + t = Match(t, PointerType)->pointed; + else if (t->tag == VariantType) + t = Match(t, VariantType)->variant_of; + else break; + } + return t; +} + +type_t *type_or_type(type_t *a, type_t *b) +{ + if (!a) return b; + if (!b) return a; + if (type_is_a(b, a)) return a; + if (type_is_a(a, b)) return b; + if (a->tag == AbortType) return non_optional(b); + if (b->tag == AbortType) return non_optional(a); + if ((a->tag == IntType || a->tag == NumType) && (b->tag == IntType || b->tag == NumType)) { + switch (compare_precision(a, b)) { + case NUM_PRECISION_EQUAL: case NUM_PRECISION_MORE: return a; + case NUM_PRECISION_LESS: return b; + case NUM_PRECISION_INCOMPARABLE: { + if (a->tag == IntType && b->tag == IntType && Match(a, IntType)->bits < 64) + return Type(IntType, .bits=Match(a, IntType)->bits * 2); + return NULL; + } + } + return NULL; + } + return NULL; +} + +bool is_integral(type_t *t) +{ + t = base_variant(t); + return t->tag == IntType || t->tag == CharType; +} + +bool is_floating_point(type_t *t) +{ + t = base_variant(t); + return t->tag == NumType; +} + +bool is_numeric(type_t *t) +{ + t = base_variant(t); + return t->tag == IntType || t->tag == NumType; +} + +static inline double type_min_magnitude(type_t *t) +{ + switch (t->tag) { + case BoolType: return (double)false; + case CharType: return (double)CHAR_MIN; + case IntType: { + switch (Match(t, IntType)->bits) { + case 8: return (double)INT8_MIN; + case 16: return (double)INT16_MIN; + case 32: return (double)INT32_MIN; + case 64: return (double)INT64_MIN; + default: return NAN; + } + } + case NumType: return -1./0.; + case VariantType: return type_min_magnitude(Match(t, VariantType)->variant_of); + default: return NAN; + } +} + +static inline double type_max_magnitude(type_t *t) +{ + switch (t->tag) { + case BoolType: return (double)true; + case CharType: return (double)CHAR_MAX; + case IntType: { + switch (Match(t, IntType)->bits) { + case 8: return (double)INT8_MAX; + case 16: return (double)INT16_MAX; + case 32: return (double)INT32_MAX; + case 64: return (double)INT64_MAX; + default: return NAN; + } + } + case NumType: return 1./0.; + case VariantType: return type_max_magnitude(Match(t, VariantType)->variant_of); + default: return NAN; + } +} + +precision_cmp_e compare_precision(type_t *a, type_t *b) +{ + double a_min = type_min_magnitude(a), + b_min = type_min_magnitude(b), + a_max = type_max_magnitude(a), + b_max = type_max_magnitude(b); + + if (isnan(a_min) || isnan(b_min) || isnan(a_max) || isnan(b_max)) + return NUM_PRECISION_INCOMPARABLE; + else if (a_min == b_min && a_max == b_max) return NUM_PRECISION_EQUAL; + else if (a_min <= b_min && b_max <= a_max) return NUM_PRECISION_MORE; + else if (b_min <= a_min && a_max <= b_max) return NUM_PRECISION_LESS; + else return NUM_PRECISION_INCOMPARABLE; +} + +bool is_orderable(type_t *t) +{ + switch (t->tag) { + case ArrayType: return is_orderable(Match(t, ArrayType)->item_type); + case PointerType: case FunctionType: case TableType: return false; + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (!is_orderable(field->type)) + return false; + } + return true; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && !is_orderable(tag->type)) + return false; + } + return true; + } + default: return true; + } +} + +bool has_heap_memory(type_t *t) +{ + switch (t->tag) { + case ArrayType: return true; + case TableType: return true; + case PointerType: return true; + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (has_heap_memory(field->type)) + return true; + } + return false; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && has_heap_memory(tag->type)) + return true; + } + return false; + } + default: return false; + } +} + +bool has_stack_memory(type_t *t) +{ + switch (t->tag) { + case PointerType: return Match(t, PointerType)->is_stack; + default: return false; + } +} + +bool can_promote(type_t *actual, type_t *needed) +{ + // No promotion necessary: + if (type_eq(actual, needed)) + return true; + + if (is_numeric(actual) && is_numeric(needed)) { + auto cmp = compare_precision(actual, needed); + return cmp == NUM_PRECISION_EQUAL || cmp == NUM_PRECISION_LESS; + } + + // Automatic dereferencing: + if (actual->tag == PointerType && !Match(actual, PointerType)->is_optional + && can_promote(Match(actual, PointerType)->pointed, needed)) + return true; + + // Optional promotion: + if (needed->tag == PointerType && actual->tag == PointerType) { + auto needed_ptr = Match(needed, PointerType); + auto actual_ptr = Match(actual, PointerType); + if (needed_ptr->pointed->tag != MemoryType && !type_eq(needed_ptr->pointed, actual_ptr->pointed)) + // Can't use @Foo for a function that wants @Baz + // But you *can* use @Foo for a function that wants @Memory + return false; + else if (actual_ptr->is_stack && !needed_ptr->is_stack) + // Can't use &x for a function that wants a @Foo or ?Foo + return false; + else if (actual_ptr->is_optional && !needed_ptr->is_optional) + // Can't use !Foo for a function that wants @Foo + return false; + else if (actual_ptr->is_readonly && !needed_ptr->is_readonly) + // Can't use pointer to readonly data when we need a pointer that can write to the data + return false; + else + return true; + } + + // Function promotion: + if (needed->tag == FunctionType && actual->tag == FunctionType) { + auto needed_fn = Match(needed, FunctionType); + auto actual_fn = Match(actual, FunctionType); + for (arg_t *needed_arg = needed_fn->args, *actual_arg = actual_fn->args; + needed_arg || actual_arg; + needed_arg = needed_arg->next, actual_arg = actual_arg->next) { + + if (!needed_arg || !actual_arg) + return false; + + if (!type_eq(needed_arg->type, actual_arg->type)) + return false; + } + return true; + } + + // If we have a DSL, it should be possible to use it as a Str + if (is_variant_of(actual, needed)) + return true; + + if (actual->tag == StructType && base_variant(needed)->tag == StructType) { + auto actual_struct = Match(actual, StructType); + auto needed_struct = Match(base_variant(needed), StructType); + // TODO: allow promoting with uninitialized or extraneous values? + for (arg_t *needed_field = needed_struct->fields, *actual_field = actual_struct->fields; + needed_field || actual_field; + needed_field = needed_field->next, actual_field = actual_field->next) { + + if (!needed_field || !actual_field) + return false; + + // TODO: check field names?? + if (!can_promote(actual_field->type, needed_field->type)) + return false; + } + return true; + } + + return false; +} + +bool can_leave_uninitialized(type_t *t) +{ + switch (t->tag) { + case PointerType: return Match(t, PointerType)->is_optional; + case ArrayType: case IntType: case NumType: case CharType: case BoolType: + return true; + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (!can_leave_uninitialized(field->type)) + return false; + } + return true; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && !can_leave_uninitialized(tag->type)) + return false; + } + return true; + } + default: return false; + } +} + +static bool _can_have_cycles(type_t *t, table_t *seen) +{ + switch (t->tag) { + case ArrayType: return _can_have_cycles(Match(t, ArrayType)->item_type, seen); + case TableType: { + auto table = Match(t, TableType); + return _can_have_cycles(table->key_type, seen) || _can_have_cycles(table->value_type, seen); + } + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (_can_have_cycles(field->type, seen)) + return true; + } + return false; + } + case PointerType: return _can_have_cycles(Match(t, PointerType)->pointed, seen); + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && _can_have_cycles(tag->type, seen)) + return true; + } + return false; + } + case VariantType: { + const char *name = Match(t, VariantType)->name; + if (name && Table_str_get(seen, name)) + return true; + Table_str_set(seen, name, t); + return _can_have_cycles(Match(t, VariantType)->variant_of, seen); + } + default: return false; + } +} + +bool can_have_cycles(type_t *t) +{ + table_t seen = {0}; + return _can_have_cycles(t, &seen); +} + +type_t *table_entry_type(type_t *table_type) +{ + static table_t cache = {0}; + arg_t *fields = new( + arg_t, .name="key", + .type=Match(table_type, TableType)->key_type); + fields->next = new( + arg_t, .name="value", + .type=Match(table_type, TableType)->value_type); + type_t *t = Type(StructType, .fields=fields); + type_t *cached = Table_str_get(&cache, type_to_string(t)); + if (cached) { + return cached; + } else { + Table_str_set(&cache, type_to_string(t), t); + return t; + } +} + +type_t *base_variant(type_t *t) +{ + while (t->tag == VariantType) + t = Match(t, VariantType)->variant_of; + return t; +} + +bool is_variant_of(type_t *t, type_t *base) +{ + for (; t->tag == VariantType; t = Match(t, VariantType)->variant_of) { + if (type_eq(Match(t, VariantType)->variant_of, base)) + return true; + } + return false; +} + +type_t *replace_type(type_t *t, type_t *target, type_t *replacement) +{ + if (type_eq(t, target)) + return replacement; + +#define COPY(t) memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t)) +#define REPLACED_MEMBER(t, tag, member) ({ t = memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t)); Match((struct type_s*)(t), tag)->member = replace_type(Match((t), tag)->member, target, replacement); t; }) + switch (t->tag) { + case ArrayType: return REPLACED_MEMBER(t, ArrayType, item_type); + case TableType: { + t = REPLACED_MEMBER(t, TableType, key_type); + t = REPLACED_MEMBER(t, TableType, value_type); + return t; + } + case FunctionType: { + auto fn = Match(t, FunctionType); + t = REPLACED_MEMBER(t, FunctionType, ret); + arg_t *args = LIST_MAP(fn->args, old_arg, .type=replace_type(old_arg->type, target, replacement)); + Match((struct type_s*)t, FunctionType)->args = args; + return t; + } + case StructType: { + auto struct_ = Match(t, StructType); + arg_t *fields = LIST_MAP(struct_->fields, field, .type=replace_type(field->type, target, replacement)); + t = COPY(t); + Match((struct type_s*)t, StructType)->fields = fields; + return t; + } + case PointerType: return REPLACED_MEMBER(t, PointerType, pointed); + case EnumType: { + auto tagged = Match(t, EnumType); + tag_t *tags = LIST_MAP(tagged->tags, tag, .type=replace_type(tag->type, target, replacement)); + t = COPY(t); + Match((struct type_s*)t, EnumType)->tags = tags; + return t; + } + case VariantType: return REPLACED_MEMBER(t, VariantType, variant_of); + default: return t; + } +#undef COPY +#undef REPLACED_MEMBER +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 |
