aboutsummaryrefslogtreecommitdiff
path: root/types.c
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-02-08 00:52:18 -0500
committerBruce Hill <bruce@bruce-hill.com>2024-02-08 00:52:18 -0500
commit930c09f46d1e249fc889e8f1179046a48c1eaf32 (patch)
tree4900307e726dcbc643b12341a9bb7168edebdff5 /types.c
parentee0f45e2959484d390c30a8a1430a0f040f56631 (diff)
More features and progress
Diffstat (limited to 'types.c')
-rw-r--r--types.c557
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