diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | src/compile.c | 550 | ||||
| -rw-r--r-- | src/compile.h | 9 | ||||
| -rw-r--r-- | src/compile/enums.h | 1 | ||||
| -rw-r--r-- | src/compile/functions.c | 96 | ||||
| -rw-r--r-- | src/compile/functions.h | 6 | ||||
| -rw-r--r-- | src/compile/integers.c | 75 | ||||
| -rw-r--r-- | src/compile/integers.h | 6 | ||||
| -rw-r--r-- | src/compile/optionals.c | 85 | ||||
| -rw-r--r-- | src/compile/optionals.h | 7 | ||||
| -rw-r--r-- | src/compile/pointers.c | 21 | ||||
| -rw-r--r-- | src/compile/pointers.h | 3 | ||||
| -rw-r--r-- | src/compile/promotion.c | 195 | ||||
| -rw-r--r-- | src/compile/promotion.h | 7 | ||||
| -rw-r--r-- | src/compile/sets.c | 51 | ||||
| -rw-r--r-- | src/compile/sets.h | 7 | ||||
| -rw-r--r-- | src/compile/structs.h | 1 | ||||
| -rw-r--r-- | src/compile/tables.c | 76 | ||||
| -rw-r--r-- | src/compile/tables.h | 6 |
19 files changed, 655 insertions, 549 deletions
@@ -129,7 +129,7 @@ build/lib/$(AR_FILE): $(STDLIB_OBJS) ar -rcs $@ $^ tags: - ctags src/*.[ch] src/stdlib/*.[ch] + ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} config.mk: configure.sh bash ./configure.sh diff --git a/src/compile.c b/src/compile.c index c03ab58d..859b17f3 100644 --- a/src/compile.c +++ b/src/compile.c @@ -9,15 +9,19 @@ #include "ast.h" #include "compile.h" #include "compile/enums.h" +#include "compile/integers.h" #include "compile/lists.h" +#include "compile/optionals.h" #include "compile/pointers.h" +#include "compile/promotion.h" +#include "compile/sets.h" #include "compile/structs.h" +#include "compile/tables.h" #include "config.h" #include "environment.h" #include "modules.h" #include "naming.h" #include "stdlib/integers.h" -#include "stdlib/nums.h" #include "stdlib/paths.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -28,153 +32,20 @@ typedef ast_t *(*comprehension_body_t)(ast_t *, ast_t *); static Text_t compile_text(env_t *env, ast_t *ast, Text_t color); static Text_t compile_text_literal(Text_t literal); -static Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t); -static Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target); static Text_t compile_unsigned_type(type_t *t); -static Text_t promote_to_optional(type_t *t, Text_t code); -static Text_t compile_none(type_t *t); static Text_t compile_declared_value(env_t *env, ast_t *declaration_ast); -static Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type); -static Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type); -static Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type); -static Text_t check_none(type_t *t, Text_t value); -static Text_t optional_into_nonnone(type_t *t, Text_t value); -static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject); -static ast_t *add_to_set_comprehension(ast_t *item, ast_t *subject); -static Text_t compile_lvalue(env_t *env, ast_t *ast); static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } static inline Text_t quoted_text(Text_t text) { return Text$quoted(text, false, Text("\"")); } -Text_t promote_to_optional(type_t *t, Text_t code) { - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) { - return code; - } else if (t->tag == IntType) { - switch (Match(t, IntType)->bits) { - case TYPE_IBITS8: return Texts("((OptionalInt8_t){.value=", code, "})"); - case TYPE_IBITS16: return Texts("((OptionalInt16_t){.value=", code, "})"); - case TYPE_IBITS32: return Texts("((OptionalInt32_t){.value=", code, "})"); - case TYPE_IBITS64: return Texts("((OptionalInt64_t){.value=", code, "})"); - default: errx(1, "Unsupported in type: %s", type_to_str(t)); - } - return code; - } else if (t->tag == ByteType) { - return Texts("((OptionalByte_t){.value=", code, "})"); - } else if (t->tag == StructType) { - return Texts("({ ", compile_type(Type(OptionalType, .type = t)), " nonnull = {.value=", code, - "}; nonnull.is_none = false; nonnull; })"); - } else { - return code; - } -} - static Text_t with_source_info(env_t *env, ast_t *ast, Text_t code) { if (code.length == 0 || !ast || !ast->file || !env->do_source_mapping) return code; int64_t line = get_line_number(ast->file, ast->start); return Texts("\n#line ", String(line), "\n", code); } -static bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed) { - if (type_eq(actual, needed)) return true; - - if (!can_promote(actual, needed)) return false; - - if (needed->tag == ClosureType && actual->tag == FunctionType) { - *code = Texts("((Closure_t){", *code, ", NULL})"); - return true; - } - - // Empty promotion: - type_t *more_complete = most_complete_type(actual, needed); - if (more_complete) return true; - - // Optional promotion: - if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) { - *code = promote_to_optional(actual, *code); - return true; - } - - // Optional -> Bool promotion - if (actual->tag == OptionalType && needed->tag == BoolType) { - *code = Texts("(!", check_none(actual, *code), ")"); - return true; - } - - // Lang to Text_t: - if (actual->tag == TextType && needed->tag == TextType && streq(Match(needed, TextType)->lang, "Text")) return true; - - // Automatic optional checking for nums: - if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) { - int64_t line = get_line_number(ast->file, ast->start); - *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", - check_none(actual, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", String((int64_t)(ast->start - ast->file->text)), ", ", - String((int64_t)(ast->end - ast->file->text)), ", ", - "\"This was expected to be a value, but it's none\");\n", - optional_into_nonnone(actual, Text("opt")), "; })"); - return true; - } - - // Numeric promotions/demotions - if ((is_numeric_type(actual) || actual->tag == BoolType) && (is_numeric_type(needed) || needed->tag == BoolType)) { - arg_ast_t *args = new (arg_ast_t, .value = LiteralCode(*code, .type = actual)); - binding_t *constructor = get_constructor( - env, needed, args, env->current_type != NULL && type_eq(env->current_type, value_type(needed))); - if (constructor) { - DeclareMatch(fn, constructor->type, FunctionType); - if (fn->args->next == NULL) { - *code = Texts(constructor->code, "(", compile_arguments(env, ast, fn->args, args), ")"); - return true; - } - } - } - - if (needed->tag == EnumType) { - const char *tag = enum_single_value_tag(needed, actual); - binding_t *b = get_binding(Match(needed, EnumType)->env, tag); - assert(b && b->type->tag == FunctionType); - // Single-value enum constructor: - if (!promote(env, ast, code, actual, Match(b->type, FunctionType)->args->type)) return false; - *code = Texts(b->code, "(", *code, ")"); - return true; - } - - // Text_t to C String - if (actual->tag == TextType && type_eq(actual, TEXT_TYPE) && needed->tag == CStringType) { - *code = Texts("Text$as_c_string(", *code, ")"); - return true; - } - - // Automatic dereferencing: - if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) { - *code = Texts("*(", *code, ")"); - return promote(env, ast, code, Match(actual, PointerType)->pointed, needed); - } - - // Stack ref promotion: - if (actual->tag == PointerType && needed->tag == PointerType) return true; - - // Cross-promotion between tables with default values and without - if (needed->tag == TableType && actual->tag == TableType) return true; - - if (needed->tag == ClosureType && actual->tag == ClosureType) return true; - - if (needed->tag == FunctionType && actual->tag == FunctionType) { - *code = Texts("(", compile_type(needed), ")", *code); - return true; - } - - // Set -> List promotion: - if (needed->tag == ListType && actual->tag == SetType - && type_eq(Match(needed, ListType)->item_type, Match(actual, SetType)->item_type)) { - *code = Texts("(", *code, ").entries"); - return true; - } - - return false; -} - +public Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { if (is_idempotent(ast) && can_be_mutated(env, ast)) { if (t->tag == ListType) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); @@ -970,18 +841,6 @@ static Text_t compile_inline_block(env_t *env, ast_t *ast) { return code; } -Text_t optional_into_nonnone(type_t *t, Text_t value) { - if (t->tag == OptionalType) t = Match(t, OptionalType)->type; - switch (t->tag) { - case IntType: - case ByteType: return Texts(value, ".value"); - case StructType: - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value; - return Texts(value, ".value"); - default: return value; - } -} - Text_t check_none(type_t *t, Text_t value) { t = Match(t, OptionalType)->type; // NOTE: these use statement expressions ({...;}) because some compilers @@ -2071,351 +1930,6 @@ Text_t compile_text(env_t *env, ast_t *ast, Text_t color) { return expr_as_text(expr, t, color); } -public -Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) { - assert(!is_incomplete_type(t)); - if (ast->tag == Int && is_numeric_type(non_optional(t))) { - return compile_int_to_type(env, ast, t); - } else if (ast->tag == Num && t->tag == NumType) { - double n = Match(ast, Num)->n; - switch (Match(t, NumType)->bits) { - case TYPE_NBITS64: return Text$from_str(String(hex_double(n))); - case TYPE_NBITS32: return Text$from_str(String(hex_double(n), "f")); - default: code_err(ast, "This is not a valid number bit width"); - } - } else if (ast->tag == None) { - if (t->tag != OptionalType) code_err(ast, "This is not supposed to be an optional type"); - else if (Match(t, OptionalType)->type == NULL) - code_err(ast, "I don't know what kind of `none` this is supposed to " - "be!\nPlease " - "tell me by declaring a variable like `foo : Type = none`"); - return compile_none(t); - } else if (t->tag == PointerType && (ast->tag == HeapAllocate || ast->tag == StackReference)) { - return compile_typed_allocation(env, ast, t); - } else if (t->tag == ListType && ast->tag == List) { - return compile_typed_list(env, ast, t); - } else if (t->tag == TableType && ast->tag == Table) { - return compile_typed_table(env, ast, t); - } else if (t->tag == SetType && ast->tag == Set) { - return compile_typed_set(env, ast, t); - } - - type_t *actual = get_type(env, ast); - - // Edge case: there are some situations where a method call needs to have - // the `self` value get compiled to a specific type that can't be fully - // inferred from the expression itself. We can infer what the specific type - // should be from what we know the specific type of the return value is, - // but it requires a bit of special logic. - // For example: - // x : [Int?] = [none].sorted() - // Here, we know that `[none]` is `[Int?]`, but we need to thread that - // information through the compiler using an `ExplicitlyTyped` node. - if (ast->tag == MethodCall) { - DeclareMatch(methodcall, ast, MethodCall); - type_t *self_type = get_type(env, methodcall->self); - // Currently, this is only implemented for cases where you have the - // return type and the self type equal to each other, because that's the - // main case I care about with list and set methods (e.g. - // `List.sorted()`) - if (is_incomplete_type(self_type) && type_eq(self_type, actual)) { - type_t *completed_self = most_complete_type(self_type, t); - if (completed_self) { - ast_t *explicit_self = - WrapAST(methodcall->self, ExplicitlyTyped, .ast = methodcall->self, .type = completed_self); - ast_t *new_methodcall = - WrapAST(ast, MethodCall, .self = explicit_self, .name = methodcall->name, .args = methodcall->args); - return compile_to_type(env, new_methodcall, t); - } - } - } - - // Promote values to views-of-values if needed: - if (t->tag == PointerType && Match(t, PointerType)->is_stack && actual->tag != PointerType) - return Texts("stack(", compile_to_type(env, ast, Match(t, PointerType)->pointed), ")"); - - if (!is_incomplete_type(actual)) { - Text_t code = compile(env, ast); - if (promote(env, ast, &code, actual, t)) return code; - } - - arg_ast_t *constructor_args = new (arg_ast_t, .value = ast); - binding_t *constructor = get_constructor(env, t, constructor_args, true); - if (constructor) { - arg_t *arg_spec = Match(constructor->type, FunctionType)->args; - return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, constructor_args), ")"); - } - - code_err(ast, "I expected a ", type_to_str(t), " here, but this is a ", type_to_str(actual)); -} - -Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type) { - DeclareMatch(set, ast, Set); - if (!set->items) return Text("((Table_t){})"); - - type_t *item_type = Match(set_type, SetType)->item_type; - - int64_t n = 0; - for (ast_list_t *item = set->items; item; item = item->next) { - ++n; - if (item->ast->tag == Comprehension) goto set_comprehension; - } - - { // No comprehension: - Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", String(n)); - env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env; - for (ast_list_t *item = set->items; item; item = item->next) { - code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type)); - } - return Texts(code, ")"); - } - -set_comprehension: { - static int64_t comp_num = 1; - env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : fresh_scope(env); - const char *comprehension_name = String("set$", comp_num++); - ast_t *comprehension_var = - LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = set_type, .is_stack = true)); - Text_t code = Texts("({ Table_t ", comprehension_name, " = {};"); - Closure_t comp_action = {.fn = add_to_set_comprehension, .userdata = comprehension_var}; - scope->comprehension_action = &comp_action; - for (ast_list_t *item = set->items; item; item = item->next) { - if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast)); - else code = Texts(code, compile_statement(env, add_to_set_comprehension(item->ast, comprehension_var))); - } - code = Texts(code, " ", comprehension_name, "; })"); - return code; -} -} - -Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { - DeclareMatch(table, ast, Table); - if (!table->entries) { - Text_t code = Text("((Table_t){"); - if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), ")"); - return Texts(code, "})"); - } - - type_t *key_t = Match(table_type, TableType)->key_type; - type_t *value_t = Match(table_type, TableType)->value_type; - - if (value_t->tag == OptionalType) - code_err(ast, "Tables whose values are optional (", type_to_str(value_t), ") are not currently supported."); - - for (ast_list_t *entry = table->entries; entry; entry = entry->next) { - if (entry->ast->tag == Comprehension) goto table_comprehension; - } - - { // No comprehension: - env_t *key_scope = key_t->tag == EnumType ? with_enum_scope(env, key_t) : env; - env_t *value_scope = value_t->tag == EnumType ? with_enum_scope(env, value_t) : env; - Text_t code = Texts("Table(", compile_type(key_t), ", ", compile_type(value_t), ", ", compile_type_info(key_t), - ", ", compile_type_info(value_t)); - if (table->fallback) code = Texts(code, ", /*fallback:*/ heap(", compile(env, table->fallback), ")"); - else code = Texts(code, ", /*fallback:*/ NULL"); - - size_t n = 0; - for (ast_list_t *entry = table->entries; entry; entry = entry->next) - ++n; - code = Texts(code, ", ", String((int64_t)n)); - - 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), "}"); - } - return Texts(code, ")"); - } - -table_comprehension: { - static int64_t comp_num = 1; - env_t *scope = fresh_scope(env); - const char *comprehension_name = String("table$", comp_num++); - ast_t *comprehension_var = - LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = table_type, .is_stack = true)); - - Text_t code = Texts("({ Table_t ", comprehension_name, " = {"); - if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), "), "); - - code = Texts(code, "};"); - - Closure_t comp_action = {.fn = add_to_table_comprehension, .userdata = comprehension_var}; - scope->comprehension_action = &comp_action; - for (ast_list_t *entry = table->entries; entry; entry = entry->next) { - if (entry->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, entry->ast)); - else code = Texts(code, compile_statement(env, add_to_table_comprehension(entry->ast, comprehension_var))); - } - code = Texts(code, " ", comprehension_name, "; })"); - return code; -} -} - -Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) { - // TODO: for constructors, do new(T, ...) instead of heap((T){...}) - type_t *pointed = Match(pointer_type, PointerType)->pointed; - switch (ast->tag) { - case HeapAllocate: { - return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")"); - } - case StackReference: { - ast_t *subject = Match(ast, StackReference)->value; - if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject))) - return Texts("(&", compile_lvalue(env, subject), ")"); - else return Texts("stack(", compile_to_type(env, subject, pointed), ")"); - } - default: code_err(ast, "Not an allocation!"); - } - return EMPTY_TEXT; -} - -Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target) { - if (ast->tag != Int) { - Text_t code = compile(env, ast); - type_t *actual_type = get_type(env, ast); - if (!promote(env, ast, &code, actual_type, target)) - code_err(ast, "I couldn't promote this ", type_to_str(actual_type), " to a ", type_to_str(target)); - return code; - } - - if (target->tag == BigIntType) return compile(env, ast); - - if (target->tag == OptionalType && Match(target, OptionalType)->type) - return compile_int_to_type(env, ast, Match(target, OptionalType)->type); - - const char *literal = Match(ast, Int)->str; - OptionalInt_t int_val = Int$from_str(literal); - if (int_val.small == 0) code_err(ast, "Failed to parse this integer"); - - mpz_t i; - mpz_init_set_int(i, int_val); - - char *c_literal; - if (strncmp(literal, "0x", 2) == 0 || strncmp(literal, "0X", 2) == 0 || strncmp(literal, "0b", 2) == 0) { - gmp_asprintf(&c_literal, "0x%ZX", i); - } else if (strncmp(literal, "0o", 2) == 0) { - gmp_asprintf(&c_literal, "%#Zo", i); - } else { - gmp_asprintf(&c_literal, "%#Zd", i); - } - - if (target->tag == ByteType) { - if (mpz_cmp_si(i, UINT8_MAX) <= 0 && mpz_cmp_si(i, 0) >= 0) return Texts("(Byte_t)(", c_literal, ")"); - code_err(ast, "This integer cannot fit in a byte"); - } else if (target->tag == NumType) { - if (Match(target, NumType)->bits == TYPE_NBITS64) { - return Texts("N64(", c_literal, ")"); - } else { - return Texts("N32(", c_literal, ")"); - } - } else if (target->tag == IntType) { - int64_t target_bits = (int64_t)Match(target, IntType)->bits; - switch (target_bits) { - case TYPE_IBITS64: - if (mpz_cmp_si(i, INT64_MIN) == 0) return Text("I64(INT64_MIN)"); - if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) return Texts("I64(", c_literal, "L)"); - break; - case TYPE_IBITS32: - if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0) return Texts("I32(", c_literal, ")"); - break; - case TYPE_IBITS16: - if (mpz_cmp_si(i, INT16_MAX) <= 0 && mpz_cmp_si(i, INT16_MIN) >= 0) return Texts("I16(", c_literal, ")"); - break; - case TYPE_IBITS8: - if (mpz_cmp_si(i, INT8_MAX) <= 0 && mpz_cmp_si(i, INT8_MIN) >= 0) return Texts("I8(", c_literal, ")"); - break; - default: break; - } - code_err(ast, "This integer cannot fit in a ", target_bits, "-bit value"); - } else { - code_err(ast, "I don't know how to compile this to a ", type_to_str(target)); - } - return EMPTY_TEXT; -} - -public -Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) { - Table_t used_args = {}; - Text_t code = EMPTY_TEXT; - env_t *default_scope = new (env_t); - *default_scope = *env; - default_scope->locals = new (Table_t, .fallback = env->namespace_bindings ? env->namespace_bindings : env->globals); - for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { - int64_t i = 1; - // Find keyword: - if (spec_arg->name) { - for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { - if (call_arg->name && streq(call_arg->name, spec_arg->name)) { - Text_t value; - if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { - value = compile_int_to_type(env, call_arg->value, spec_arg->type); - } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { - OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); - if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); - if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) - value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); - else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); - } else { - env_t *arg_env = with_enum_scope(env, spec_arg->type); - value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); - } - Table$str_set(&used_args, call_arg->name, call_arg); - if (code.length > 0) code = Texts(code, ", "); - code = Texts(code, value); - goto found_it; - } - } - } - // Find positional: - for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { - if (call_arg->name) continue; - const char *pseudoname = String(i++); - if (!Table$str_get(used_args, pseudoname)) { - Text_t value; - if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { - value = compile_int_to_type(env, call_arg->value, spec_arg->type); - } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { - OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); - if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); - if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) - value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); - else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); - } else { - env_t *arg_env = with_enum_scope(env, spec_arg->type); - value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); - } - - Table$str_set(&used_args, pseudoname, call_arg); - if (code.length > 0) code = Texts(code, ", "); - code = Texts(code, value); - goto found_it; - } - } - - if (spec_arg->default_val) { - if (code.length > 0) code = Texts(code, ", "); - code = Texts(code, compile_maybe_incref(default_scope, spec_arg->default_val, get_arg_type(env, spec_arg))); - goto found_it; - } - - assert(spec_arg->name); - code_err(call_ast, "The required argument '", spec_arg->name, "' was not provided"); - found_it: - continue; - } - - int64_t i = 1; - for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { - if (call_arg->name) { - if (!Table$str_get(used_args, call_arg->name)) - code_err(call_arg->value, "There is no argument with the name '", call_arg->name, "'"); - } else { - const char *pseudoname = String(i++); - if (!Table$str_get(used_args, pseudoname)) code_err(call_arg->value, "This is one argument too many!"); - } - } - return code; -} - Text_t compile_text_literal(Text_t literal) { Text_t code = Text("\""); const char *utf8 = Text$as_c_string(literal); @@ -2452,48 +1966,6 @@ PUREFUNC static bool string_literal_is_all_ascii(Text_t literal) { return true; } -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->tag == OptionalType) t = Match(t, OptionalType)->type; - - 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){})"); - - switch (t->tag) { - case BigIntType: return Text("NONE_INT"); - case IntType: { - switch (Match(t, IntType)->bits) { - case TYPE_IBITS8: return Text("NONE_INT8"); - case TYPE_IBITS16: return Text("NONE_INT16"); - case TYPE_IBITS32: return Text("NONE_INT32"); - case TYPE_IBITS64: return Text("NONE_INT64"); - default: errx(1, "Invalid integer bit size"); - } - break; - } - case BoolType: return Text("NONE_BOOL"); - case ByteType: return Text("NONE_BYTE"); - case ListType: return Text("NONE_LIST"); - case TableType: return Text("NONE_TABLE"); - case SetType: return Text("NONE_TABLE"); - case TextType: return Text("NONE_TEXT"); - case CStringType: return Text("NULL"); - case PointerType: return Texts("((", compile_type(t), ")NULL)"); - case ClosureType: return Text("NONE_CLOSURE"); - case NumType: return Text("nan(\"none\")"); - case StructType: return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.is_none=true})"); - case EnumType: { - env_t *enum_env = Match(t, EnumType)->env; - return Texts("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, Text("none")), "})"); - } - default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_str(t)); - } - return EMPTY_TEXT; -} - public Text_t compile_empty(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a value with no type"); @@ -2561,16 +2033,6 @@ static Text_t compile_declared_value(env_t *env, ast_t *declare_ast) { } } -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))); -} - -ast_t *add_to_set_comprehension(ast_t *item, ast_t *subject) { - return WrapAST(item, MethodCall, .name = "add", .self = subject, .args = new (arg_ast_t, .value = item)); -} - Text_t compile(env_t *env, ast_t *ast) { switch (ast->tag) { case None: { diff --git a/src/compile.h b/src/compile.h index 6b0b4dc5..6ab62f9e 100644 --- a/src/compile.h +++ b/src/compile.h @@ -2,25 +2,26 @@ // Compilation functions -#include <gc.h> - +#include "ast.h" #include "environment.h" #include "stdlib/datatypes.h" +#include "types.h" Text_t compile(env_t *env, ast_t *ast); +Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args); Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version); Text_t compile_declaration(type_t *t, Text_t name); Text_t compile_empty(type_t *t); Text_t compile_file(env_t *env, ast_t *ast); Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast); +Text_t compile_lvalue(env_t *env, ast_t *ast); +Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t); Text_t compile_namespace_header(env_t *env, const char *ns_name, ast_t *block); Text_t compile_statement(env_t *env, ast_t *ast); Text_t compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *ast); Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast); -Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t); Text_t compile_type(type_t *t); Text_t compile_type_info(type_t *t); Text_t expr_as_text(Text_t expr, type_t *t, Text_t color); -Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/compile/enums.h b/src/compile/enums.h index c272086a..21235532 100644 --- a/src/compile/enums.h +++ b/src/compile/enums.h @@ -5,6 +5,7 @@ #include "../ast.h" #include "../environment.h" #include "../stdlib/datatypes.h" +#include "../types.h" Text_t compile_empty_enum(type_t *t); Text_t compile_enum_constructors(env_t *env, ast_t *ast); diff --git a/src/compile/functions.c b/src/compile/functions.c new file mode 100644 index 00000000..ac4e27ea --- /dev/null +++ b/src/compile/functions.c @@ -0,0 +1,96 @@ +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/integers.h" +#include "../stdlib/nums.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" +#include "../typecheck.h" +#include "../types.h" +#include "integers.h" + +public +Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) { + Table_t used_args = {}; + Text_t code = EMPTY_TEXT; + env_t *default_scope = new (env_t); + *default_scope = *env; + default_scope->locals = new (Table_t, .fallback = env->namespace_bindings ? env->namespace_bindings : env->globals); + for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { + int64_t i = 1; + // Find keyword: + if (spec_arg->name) { + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name && streq(call_arg->name, spec_arg->name)) { + Text_t value; + if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { + value = compile_int_to_type(env, call_arg->value, spec_arg->type); + } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { + OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); + if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); + if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) + value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); + else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); + } else { + env_t *arg_env = with_enum_scope(env, spec_arg->type); + value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); + } + Table$str_set(&used_args, call_arg->name, call_arg); + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, value); + goto found_it; + } + } + } + // Find positional: + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) continue; + const char *pseudoname = String(i++); + if (!Table$str_get(used_args, pseudoname)) { + Text_t value; + if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { + value = compile_int_to_type(env, call_arg->value, spec_arg->type); + } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { + OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); + if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); + if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) + value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); + else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); + } else { + env_t *arg_env = with_enum_scope(env, spec_arg->type); + value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); + } + + Table$str_set(&used_args, pseudoname, call_arg); + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, value); + goto found_it; + } + } + + if (spec_arg->default_val) { + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, compile_maybe_incref(default_scope, spec_arg->default_val, get_arg_type(env, spec_arg))); + goto found_it; + } + + assert(spec_arg->name); + code_err(call_ast, "The required argument '", spec_arg->name, "' was not provided"); + found_it: + continue; + } + + int64_t i = 1; + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) { + if (!Table$str_get(used_args, call_arg->name)) + code_err(call_arg->value, "There is no argument with the name '", call_arg->name, "'"); + } else { + const char *pseudoname = String(i++); + if (!Table$str_get(used_args, pseudoname)) code_err(call_arg->value, "This is one argument too many!"); + } + } + return code; +} diff --git a/src/compile/functions.h b/src/compile/functions.h new file mode 100644 index 00000000..1d2362a4 --- /dev/null +++ b/src/compile/functions.h @@ -0,0 +1,6 @@ + +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" + +Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args); diff --git a/src/compile/integers.c b/src/compile/integers.c new file mode 100644 index 00000000..97c9c61a --- /dev/null +++ b/src/compile/integers.c @@ -0,0 +1,75 @@ +#include <gmp.h> + +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/integers.h" +#include "../stdlib/text.h" +#include "../typecheck.h" +#include "../types.h" +#include "promotion.h" + +Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target) { + if (ast->tag != Int) { + Text_t code = compile(env, ast); + type_t *actual_type = get_type(env, ast); + if (!promote(env, ast, &code, actual_type, target)) + code_err(ast, "I couldn't promote this ", type_to_str(actual_type), " to a ", type_to_str(target)); + return code; + } + + if (target->tag == BigIntType) return compile(env, ast); + + if (target->tag == OptionalType && Match(target, OptionalType)->type) + return compile_int_to_type(env, ast, Match(target, OptionalType)->type); + + const char *literal = Match(ast, Int)->str; + OptionalInt_t int_val = Int$from_str(literal); + if (int_val.small == 0) code_err(ast, "Failed to parse this integer"); + + mpz_t i; + mpz_init_set_int(i, int_val); + + char *c_literal; + if (strncmp(literal, "0x", 2) == 0 || strncmp(literal, "0X", 2) == 0 || strncmp(literal, "0b", 2) == 0) { + gmp_asprintf(&c_literal, "0x%ZX", i); + } else if (strncmp(literal, "0o", 2) == 0) { + gmp_asprintf(&c_literal, "%#Zo", i); + } else { + gmp_asprintf(&c_literal, "%#Zd", i); + } + + if (target->tag == ByteType) { + if (mpz_cmp_si(i, UINT8_MAX) <= 0 && mpz_cmp_si(i, 0) >= 0) return Texts("(Byte_t)(", c_literal, ")"); + code_err(ast, "This integer cannot fit in a byte"); + } else if (target->tag == NumType) { + if (Match(target, NumType)->bits == TYPE_NBITS64) { + return Texts("N64(", c_literal, ")"); + } else { + return Texts("N32(", c_literal, ")"); + } + } else if (target->tag == IntType) { + int64_t target_bits = (int64_t)Match(target, IntType)->bits; + switch (target_bits) { + case TYPE_IBITS64: + if (mpz_cmp_si(i, INT64_MIN) == 0) return Text("I64(INT64_MIN)"); + if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) return Texts("I64(", c_literal, "L)"); + break; + case TYPE_IBITS32: + if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0) return Texts("I32(", c_literal, ")"); + break; + case TYPE_IBITS16: + if (mpz_cmp_si(i, INT16_MAX) <= 0 && mpz_cmp_si(i, INT16_MIN) >= 0) return Texts("I16(", c_literal, ")"); + break; + case TYPE_IBITS8: + if (mpz_cmp_si(i, INT8_MAX) <= 0 && mpz_cmp_si(i, INT8_MIN) >= 0) return Texts("I8(", c_literal, ")"); + break; + default: break; + } + code_err(ast, "This integer cannot fit in a ", target_bits, "-bit value"); + } else { + code_err(ast, "I don't know how to compile this to a ", type_to_str(target)); + } + return EMPTY_TEXT; +} diff --git a/src/compile/integers.h b/src/compile/integers.h new file mode 100644 index 00000000..f4479b3e --- /dev/null +++ b/src/compile/integers.h @@ -0,0 +1,6 @@ + +#include "../ast.h" +#include "../environment.h" +#include "../types.h" + +Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target); diff --git a/src/compile/optionals.c b/src/compile/optionals.c new file mode 100644 index 00000000..4b360b31 --- /dev/null +++ b/src/compile/optionals.c @@ -0,0 +1,85 @@ +#include "../compile.h" +#include "../environment.h" +#include "../naming.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" +#include "../types.h" + +Text_t optional_into_nonnone(type_t *t, Text_t value) { + if (t->tag == OptionalType) t = Match(t, OptionalType)->type; + switch (t->tag) { + case IntType: + case ByteType: return Texts(value, ".value"); + case StructType: + if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value; + return Texts(value, ".value"); + default: return value; + } +} + +public +Text_t promote_to_optional(type_t *t, Text_t code) { + if (t == PATH_TYPE || t == PATH_TYPE_TYPE) { + return code; + } else if (t->tag == IntType) { + switch (Match(t, IntType)->bits) { + case TYPE_IBITS8: return Texts("((OptionalInt8_t){.value=", code, "})"); + case TYPE_IBITS16: return Texts("((OptionalInt16_t){.value=", code, "})"); + case TYPE_IBITS32: return Texts("((OptionalInt32_t){.value=", code, "})"); + case TYPE_IBITS64: return Texts("((OptionalInt64_t){.value=", code, "})"); + default: errx(1, "Unsupported in type: %s", type_to_str(t)); + } + return code; + } else if (t->tag == ByteType) { + return Texts("((OptionalByte_t){.value=", code, "})"); + } else if (t->tag == StructType) { + return Texts("({ ", compile_type(Type(OptionalType, .type = t)), " nonnull = {.value=", code, + "}; nonnull.is_none = false; nonnull; })"); + } else { + return code; + } +} + +public +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->tag == OptionalType) t = Match(t, OptionalType)->type; + + 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){})"); + + switch (t->tag) { + case BigIntType: return Text("NONE_INT"); + case IntType: { + switch (Match(t, IntType)->bits) { + case TYPE_IBITS8: return Text("NONE_INT8"); + case TYPE_IBITS16: return Text("NONE_INT16"); + case TYPE_IBITS32: return Text("NONE_INT32"); + case TYPE_IBITS64: return Text("NONE_INT64"); + default: errx(1, "Invalid integer bit size"); + } + break; + } + case BoolType: return Text("NONE_BOOL"); + case ByteType: return Text("NONE_BYTE"); + case ListType: return Text("NONE_LIST"); + case TableType: return Text("NONE_TABLE"); + case SetType: return Text("NONE_TABLE"); + case TextType: return Text("NONE_TEXT"); + case CStringType: return Text("NULL"); + case PointerType: return Texts("((", compile_type(t), ")NULL)"); + case ClosureType: return Text("NONE_CLOSURE"); + case NumType: return Text("nan(\"none\")"); + case StructType: return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.is_none=true})"); + case EnumType: { + env_t *enum_env = Match(t, EnumType)->env; + return Texts("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, Text("none")), "})"); + } + default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_str(t)); + } + return EMPTY_TEXT; +} diff --git a/src/compile/optionals.h b/src/compile/optionals.h new file mode 100644 index 00000000..ec5d1954 --- /dev/null +++ b/src/compile/optionals.h @@ -0,0 +1,7 @@ +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t check_none(type_t *t, Text_t value); +Text_t optional_into_nonnone(type_t *t, Text_t value); +Text_t promote_to_optional(type_t *t, Text_t code); +Text_t compile_none(type_t *t); diff --git a/src/compile/pointers.c b/src/compile/pointers.c index cb11844d..ee67b18d 100644 --- a/src/compile/pointers.c +++ b/src/compile/pointers.c @@ -10,7 +10,9 @@ #include "../environment.h" #include "../stdlib/text.h" #include "../typecheck.h" +#include "promotion.h" +public Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool needs_incref) { Text_t val = compile(env, ast); type_t *t = get_type(env, ast); @@ -46,3 +48,22 @@ Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bo return val; } + +public +Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) { + // TODO: for constructors, do new(T, ...) instead of heap((T){...}) + type_t *pointed = Match(pointer_type, PointerType)->pointed; + switch (ast->tag) { + case HeapAllocate: { + return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")"); + } + case StackReference: { + ast_t *subject = Match(ast, StackReference)->value; + if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject))) + return Texts("(&", compile_lvalue(env, subject), ")"); + else return Texts("stack(", compile_to_type(env, subject, pointed), ")"); + } + default: code_err(ast, "Not an allocation!"); + } + return EMPTY_TEXT; +} diff --git a/src/compile/pointers.h b/src/compile/pointers.h index 306c3019..49e73c1c 100644 --- a/src/compile/pointers.h +++ b/src/compile/pointers.h @@ -1,6 +1,9 @@ #include <stdbool.h> +#include "../ast.h" #include "../environment.h" #include "../stdlib/datatypes.h" +#include "../types.h" Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool needs_incref); +Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type); diff --git a/src/compile/promotion.c b/src/compile/promotion.c new file mode 100644 index 00000000..fbac5888 --- /dev/null +++ b/src/compile/promotion.c @@ -0,0 +1,195 @@ +#include "promotion.h" +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../typecheck.h" +#include "../types.h" +#include "integers.h" +#include "lists.h" +#include "optionals.h" +#include "pointers.h" +#include "sets.h" +#include "tables.h" + +static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } + +public +bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed) { + if (type_eq(actual, needed)) return true; + + if (!can_promote(actual, needed)) return false; + + if (needed->tag == ClosureType && actual->tag == FunctionType) { + *code = Texts("((Closure_t){", *code, ", NULL})"); + return true; + } + + // Empty promotion: + type_t *more_complete = most_complete_type(actual, needed); + if (more_complete) return true; + + // Optional promotion: + if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) { + *code = promote_to_optional(actual, *code); + return true; + } + + // Optional -> Bool promotion + if (actual->tag == OptionalType && needed->tag == BoolType) { + *code = Texts("(!", check_none(actual, *code), ")"); + return true; + } + + // Lang to Text_t: + if (actual->tag == TextType && needed->tag == TextType && streq(Match(needed, TextType)->lang, "Text")) return true; + + // Automatic optional checking for nums: + if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) { + int64_t line = get_line_number(ast->file, ast->start); + *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", + check_none(actual, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(", + quoted_str(ast->file->filename), ", ", String((int64_t)(ast->start - ast->file->text)), ", ", + String((int64_t)(ast->end - ast->file->text)), ", ", + "\"This was expected to be a value, but it's none\");\n", + optional_into_nonnone(actual, Text("opt")), "; })"); + return true; + } + + // Numeric promotions/demotions + if ((is_numeric_type(actual) || actual->tag == BoolType) && (is_numeric_type(needed) || needed->tag == BoolType)) { + arg_ast_t *args = new (arg_ast_t, .value = LiteralCode(*code, .type = actual)); + binding_t *constructor = get_constructor( + env, needed, args, env->current_type != NULL && type_eq(env->current_type, value_type(needed))); + if (constructor) { + DeclareMatch(fn, constructor->type, FunctionType); + if (fn->args->next == NULL) { + *code = Texts(constructor->code, "(", compile_arguments(env, ast, fn->args, args), ")"); + return true; + } + } + } + + if (needed->tag == EnumType) { + const char *tag = enum_single_value_tag(needed, actual); + binding_t *b = get_binding(Match(needed, EnumType)->env, tag); + assert(b && b->type->tag == FunctionType); + // Single-value enum constructor: + if (!promote(env, ast, code, actual, Match(b->type, FunctionType)->args->type)) return false; + *code = Texts(b->code, "(", *code, ")"); + return true; + } + + // Text_t to C String + if (actual->tag == TextType && type_eq(actual, TEXT_TYPE) && needed->tag == CStringType) { + *code = Texts("Text$as_c_string(", *code, ")"); + return true; + } + + // Automatic dereferencing: + if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) { + *code = Texts("*(", *code, ")"); + return promote(env, ast, code, Match(actual, PointerType)->pointed, needed); + } + + // Stack ref promotion: + if (actual->tag == PointerType && needed->tag == PointerType) return true; + + // Cross-promotion between tables with default values and without + if (needed->tag == TableType && actual->tag == TableType) return true; + + if (needed->tag == ClosureType && actual->tag == ClosureType) return true; + + if (needed->tag == FunctionType && actual->tag == FunctionType) { + *code = Texts("(", compile_type(needed), ")", *code); + return true; + } + + // Set -> List promotion: + if (needed->tag == ListType && actual->tag == SetType + && type_eq(Match(needed, ListType)->item_type, Match(actual, SetType)->item_type)) { + *code = Texts("(", *code, ").entries"); + return true; + } + + return false; +} + +public +Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) { + assert(!is_incomplete_type(t)); + if (ast->tag == Int && is_numeric_type(non_optional(t))) { + return compile_int_to_type(env, ast, t); + } else if (ast->tag == Num && t->tag == NumType) { + double n = Match(ast, Num)->n; + switch (Match(t, NumType)->bits) { + case TYPE_NBITS64: return Text$from_str(String(hex_double(n))); + case TYPE_NBITS32: return Text$from_str(String(hex_double(n), "f")); + default: code_err(ast, "This is not a valid number bit width"); + } + } else if (ast->tag == None) { + if (t->tag != OptionalType) code_err(ast, "This is not supposed to be an optional type"); + else if (Match(t, OptionalType)->type == NULL) + code_err(ast, "I don't know what kind of `none` this is supposed to " + "be!\nPlease " + "tell me by declaring a variable like `foo : Type = none`"); + return compile_none(t); + } else if (t->tag == PointerType && (ast->tag == HeapAllocate || ast->tag == StackReference)) { + return compile_typed_allocation(env, ast, t); + } else if (t->tag == ListType && ast->tag == List) { + return compile_typed_list(env, ast, t); + } else if (t->tag == TableType && ast->tag == Table) { + return compile_typed_table(env, ast, t); + } else if (t->tag == SetType && ast->tag == Set) { + return compile_typed_set(env, ast, t); + } + + type_t *actual = get_type(env, ast); + + // Edge case: there are some situations where a method call needs to have + // the `self` value get compiled to a specific type that can't be fully + // inferred from the expression itself. We can infer what the specific type + // should be from what we know the specific type of the return value is, + // but it requires a bit of special logic. + // For example: + // x : [Int?] = [none].sorted() + // Here, we know that `[none]` is `[Int?]`, but we need to thread that + // information through the compiler using an `ExplicitlyTyped` node. + if (ast->tag == MethodCall) { + DeclareMatch(methodcall, ast, MethodCall); + type_t *self_type = get_type(env, methodcall->self); + // Currently, this is only implemented for cases where you have the + // return type and the self type equal to each other, because that's the + // main case I care about with list and set methods (e.g. + // `List.sorted()`) + if (is_incomplete_type(self_type) && type_eq(self_type, actual)) { + type_t *completed_self = most_complete_type(self_type, t); + if (completed_self) { + ast_t *explicit_self = + WrapAST(methodcall->self, ExplicitlyTyped, .ast = methodcall->self, .type = completed_self); + ast_t *new_methodcall = + WrapAST(ast, MethodCall, .self = explicit_self, .name = methodcall->name, .args = methodcall->args); + return compile_to_type(env, new_methodcall, t); + } + } + } + + // Promote values to views-of-values if needed: + if (t->tag == PointerType && Match(t, PointerType)->is_stack && actual->tag != PointerType) + return Texts("stack(", compile_to_type(env, ast, Match(t, PointerType)->pointed), ")"); + + if (!is_incomplete_type(actual)) { + Text_t code = compile(env, ast); + if (promote(env, ast, &code, actual, t)) return code; + } + + arg_ast_t *constructor_args = new (arg_ast_t, .value = ast); + binding_t *constructor = get_constructor(env, t, constructor_args, true); + if (constructor) { + arg_t *arg_spec = Match(constructor->type, FunctionType)->args; + return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, constructor_args), ")"); + } + + code_err(ast, "I expected a ", type_to_str(t), " here, but this is a ", type_to_str(actual)); +} diff --git a/src/compile/promotion.h b/src/compile/promotion.h new file mode 100644 index 00000000..2cfe0cbf --- /dev/null +++ b/src/compile/promotion.h @@ -0,0 +1,7 @@ + +#include "../ast.h" +#include "../environment.h" +#include "../types.h" + +bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed); +Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t); diff --git a/src/compile/sets.c b/src/compile/sets.c new file mode 100644 index 00000000..a1471a5a --- /dev/null +++ b/src/compile/sets.c @@ -0,0 +1,51 @@ + +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../types.h" +#include "promotion.h" + +static ast_t *add_to_set_comprehension(ast_t *item, ast_t *subject) { + return WrapAST(item, MethodCall, .name = "add", .self = subject, .args = new (arg_ast_t, .value = item)); +} + +Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type) { + DeclareMatch(set, ast, Set); + if (!set->items) return Text("((Table_t){})"); + + type_t *item_type = Match(set_type, SetType)->item_type; + + int64_t n = 0; + for (ast_list_t *item = set->items; item; item = item->next) { + ++n; + if (item->ast->tag == Comprehension) goto set_comprehension; + } + + { // No comprehension: + Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", String(n)); + env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env; + for (ast_list_t *item = set->items; item; item = item->next) { + code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type)); + } + return Texts(code, ")"); + } + +set_comprehension: { + static int64_t comp_num = 1; + env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : fresh_scope(env); + const char *comprehension_name = String("set$", comp_num++); + ast_t *comprehension_var = + LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = set_type, .is_stack = true)); + Text_t code = Texts("({ Table_t ", comprehension_name, " = {};"); + Closure_t comp_action = {.fn = add_to_set_comprehension, .userdata = comprehension_var}; + scope->comprehension_action = &comp_action; + for (ast_list_t *item = set->items; item; item = item->next) { + if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast)); + else code = Texts(code, compile_statement(env, add_to_set_comprehension(item->ast, comprehension_var))); + } + code = Texts(code, " ", comprehension_name, "; })"); + return code; +} +} diff --git a/src/compile/sets.h b/src/compile/sets.h new file mode 100644 index 00000000..bfbcf3b4 --- /dev/null +++ b/src/compile/sets.h @@ -0,0 +1,7 @@ + +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type); diff --git a/src/compile/structs.h b/src/compile/structs.h index 8185b561..42c95003 100644 --- a/src/compile/structs.h +++ b/src/compile/structs.h @@ -4,6 +4,7 @@ #include "../ast.h" #include "../environment.h" +#include "../types.h" Text_t compile_empty_struct(type_t *t); Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_t *fields, bool is_secret, diff --git a/src/compile/tables.c b/src/compile/tables.c new file mode 100644 index 00000000..0ddd310c --- /dev/null +++ b/src/compile/tables.c @@ -0,0 +1,76 @@ + +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../types.h" +#include "promotion.h" + +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))); +} + +Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { + DeclareMatch(table, ast, Table); + if (!table->entries) { + Text_t code = Text("((Table_t){"); + if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), ")"); + return Texts(code, "})"); + } + + type_t *key_t = Match(table_type, TableType)->key_type; + type_t *value_t = Match(table_type, TableType)->value_type; + + if (value_t->tag == OptionalType) + code_err(ast, "Tables whose values are optional (", type_to_str(value_t), ") are not currently supported."); + + for (ast_list_t *entry = table->entries; entry; entry = entry->next) { + if (entry->ast->tag == Comprehension) goto table_comprehension; + } + + { // No comprehension: + env_t *key_scope = key_t->tag == EnumType ? with_enum_scope(env, key_t) : env; + env_t *value_scope = value_t->tag == EnumType ? with_enum_scope(env, value_t) : env; + Text_t code = Texts("Table(", compile_type(key_t), ", ", compile_type(value_t), ", ", compile_type_info(key_t), + ", ", compile_type_info(value_t)); + if (table->fallback) code = Texts(code, ", /*fallback:*/ heap(", compile(env, table->fallback), ")"); + else code = Texts(code, ", /*fallback:*/ NULL"); + + size_t n = 0; + for (ast_list_t *entry = table->entries; entry; entry = entry->next) + ++n; + code = Texts(code, ", ", String((int64_t)n)); + + 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), "}"); + } + return Texts(code, ")"); + } + +table_comprehension: { + static int64_t comp_num = 1; + env_t *scope = fresh_scope(env); + const char *comprehension_name = String("table$", comp_num++); + ast_t *comprehension_var = + LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = table_type, .is_stack = true)); + + Text_t code = Texts("({ Table_t ", comprehension_name, " = {"); + if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), "), "); + + code = Texts(code, "};"); + + Closure_t comp_action = {.fn = add_to_table_comprehension, .userdata = comprehension_var}; + scope->comprehension_action = &comp_action; + for (ast_list_t *entry = table->entries; entry; entry = entry->next) { + if (entry->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, entry->ast)); + else code = Texts(code, compile_statement(env, add_to_table_comprehension(entry->ast, comprehension_var))); + } + code = Texts(code, " ", comprehension_name, "; })"); + return code; +} +} diff --git a/src/compile/tables.h b/src/compile/tables.h new file mode 100644 index 00000000..c8c19cb1 --- /dev/null +++ b/src/compile/tables.h @@ -0,0 +1,6 @@ +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type); |
