diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-08-24 17:07:41 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-08-24 17:07:41 -0400 |
| commit | 476bacc27671de92e8fdbc5b2a904bbf8bc80377 (patch) | |
| tree | 035183cccc5aa42cb5885dbbccdb6f9cf4f2aa23 | |
| parent | e4d080f598b8528d5200deccde279ff02ac5750e (diff) | |
More splitting out into separate files.
| -rw-r--r-- | src/compile.c | 1790 | ||||
| -rw-r--r-- | src/compile.h | 8 | ||||
| -rw-r--r-- | src/compile/assignments.c | 201 | ||||
| -rw-r--r-- | src/compile/assignments.h | 11 | ||||
| -rw-r--r-- | src/compile/enums.c | 4 | ||||
| -rw-r--r-- | src/compile/files.c | 195 | ||||
| -rw-r--r-- | src/compile/files.h | 6 | ||||
| -rw-r--r-- | src/compile/functions.c | 209 | ||||
| -rw-r--r-- | src/compile/functions.h | 1 | ||||
| -rw-r--r-- | src/compile/lists.c | 2 | ||||
| -rw-r--r-- | src/compile/optionals.c | 27 | ||||
| -rw-r--r-- | src/compile/optionals.h | 2 | ||||
| -rw-r--r-- | src/compile/pointers.c | 1 | ||||
| -rw-r--r-- | src/compile/promotion.c | 3 | ||||
| -rw-r--r-- | src/compile/sets.c | 2 | ||||
| -rw-r--r-- | src/compile/statements.c | 1072 | ||||
| -rw-r--r-- | src/compile/statements.h | 7 | ||||
| -rw-r--r-- | src/compile/structs.c | 5 | ||||
| -rw-r--r-- | src/compile/tables.c | 2 | ||||
| -rw-r--r-- | src/compile/text.c | 1 | ||||
| -rw-r--r-- | src/compile/text.h | 5 | ||||
| -rw-r--r-- | src/compile/types.c | 154 | ||||
| -rw-r--r-- | src/compile/types.h | 6 | ||||
| -rw-r--r-- | src/tomo.c | 1 |
24 files changed, 1920 insertions, 1795 deletions
diff --git a/src/compile.c b/src/compile.c index 3f7585fd..6cecc10a 100644 --- a/src/compile.c +++ b/src/compile.c @@ -7,6 +7,7 @@ #include "ast.h" #include "compile.h" +#include "compile/assignments.h" #include "compile/enums.h" #include "compile/functions.h" #include "compile/integers.h" @@ -15,9 +16,11 @@ #include "compile/pointers.h" #include "compile/promotion.h" #include "compile/sets.h" +#include "compile/statements.h" #include "compile/structs.h" #include "compile/tables.h" #include "compile/text.h" +#include "compile/types.h" #include "config.h" #include "environment.h" #include "modules.h" @@ -29,16 +32,10 @@ #include "stdlib/util.h" #include "typecheck.h" -typedef ast_t *(*comprehension_body_t)(ast_t *, ast_t *); - static Text_t compile_unsigned_type(type_t *t); -static Text_t compile_declared_value(env_t *env, ast_t *declaration_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("\"")); } - -static Text_t with_source_info(env_t *env, ast_t *ast, Text_t code) { +public +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); @@ -54,94 +51,6 @@ Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { return compile_to_type(env, ast, t); } -Text_t compile_declaration(type_t *t, Text_t name) { - if (t->tag == FunctionType) { - DeclareMatch(fn, t, FunctionType); - Text_t code = Texts(compile_type(fn->ret), " (*", name, ")("); - for (arg_t *arg = fn->args; arg; arg = arg->next) { - code = Texts(code, compile_type(arg->type)); - if (arg->next) code = Texts(code, ", "); - } - if (!fn->args) code = Texts(code, "void"); - return Texts(code, ")"); - } else if (t->tag != ModuleType) { - return Texts(compile_type(t), " ", name); - } else { - return EMPTY_TEXT; - } -} - -static Text_t compile_update_assignment(env_t *env, ast_t *ast) { - if (!is_update_assignment(ast)) code_err(ast, "This is not an update assignment"); - - binary_operands_t update = UPDATE_OPERANDS(ast); - - type_t *lhs_t = get_type(env, update.lhs); - - bool needs_idemotency_fix = !is_idempotent(update.lhs); - Text_t lhs = needs_idemotency_fix ? Text("(*lhs)") : compile_lvalue(env, update.lhs); - - Text_t update_assignment = EMPTY_TEXT; - switch (ast->tag) { - case PlusUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " += ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case MinusUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " -= ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case MultiplyUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " *= ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case DivideUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " /= ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case LeftShiftUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " <<= ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case RightShiftUpdate: { - if (lhs_t->tag == IntType || lhs_t->tag == ByteType) - update_assignment = Texts(lhs, " >>= ", compile_to_type(env, update.rhs, lhs_t), ";"); - break; - } - case AndUpdate: { - if (lhs_t->tag == BoolType) - update_assignment = - Texts("if (", lhs, ") ", lhs, " = ", compile_to_type(env, update.rhs, Type(BoolType)), ";"); - break; - } - case OrUpdate: { - if (lhs_t->tag == BoolType) - update_assignment = - Texts("if (!", lhs, ") ", lhs, " = ", compile_to_type(env, update.rhs, Type(BoolType)), ";"); - break; - } - default: break; - } - - if (update_assignment.length == 0) { - ast_t *binop = new (ast_t); - *binop = *ast; - binop->tag = binop_tag(binop->tag); - if (needs_idemotency_fix) binop->__data.Plus.lhs = LiteralCode(Text("*lhs"), .type = lhs_t); - update_assignment = Texts(lhs, " = ", compile_to_type(env, binop, lhs_t), ";"); - } - - if (needs_idemotency_fix) - return Texts("{ ", compile_declaration(Type(PointerType, .pointed = lhs_t), Text("lhs")), " = &", - compile_lvalue(env, update.lhs), "; ", update_assignment, "; }"); - else return update_assignment; -} - static Text_t compile_binary_op(env_t *env, ast_t *ast) { binary_operands_t binop = BINARY_OPERANDS(ast); type_t *lhs_t = get_type(env, binop.lhs); @@ -387,1229 +296,6 @@ PUREFUNC Text_t compile_unsigned_type(type_t *t) { return EMPTY_TEXT; } -Text_t compile_type(type_t *t) { - if (t == PATH_TYPE) return Text("Path_t"); - else if (t == PATH_TYPE_TYPE) return Text("PathType_t"); - - switch (t->tag) { - case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type"); - case AbortType: return Text("void"); - case VoidType: return Text("void"); - case MemoryType: return Text("void"); - case BoolType: return Text("Bool_t"); - case ByteType: return Text("Byte_t"); - case CStringType: return Text("const char*"); - case BigIntType: return Text("Int_t"); - case IntType: return Texts("Int", String(Match(t, IntType)->bits), "_t"); - case NumType: - return Match(t, NumType)->bits == TYPE_NBITS64 ? Text("Num_t") - : Texts("Num", String(Match(t, NumType)->bits), "_t"); - case TextType: { - DeclareMatch(text, t, TextType); - if (!text->lang || streq(text->lang, "Text")) return Text("Text_t"); - else return namespace_name(text->env, text->env->namespace, Text("$type")); - } - case ListType: return Text("List_t"); - case SetType: return Text("Table_t"); - case TableType: return Text("Table_t"); - case FunctionType: { - DeclareMatch(fn, t, FunctionType); - Text_t code = Texts(compile_type(fn->ret), " (*)("); - for (arg_t *arg = fn->args; arg; arg = arg->next) { - code = Texts(code, compile_type(arg->type)); - if (arg->next) code = Texts(code, ", "); - } - if (!fn->args) code = Texts(code, "void"); - return Texts(code, ")"); - } - case ClosureType: return Text("Closure_t"); - case PointerType: return Texts(compile_type(Match(t, PointerType)->pointed), "*"); - case StructType: { - DeclareMatch(s, t, StructType); - if (s->external) return Text$from_str(s->name); - return Texts("struct ", namespace_name(s->env, s->env->namespace, Text("$struct"))); - } - case EnumType: { - DeclareMatch(e, t, EnumType); - return namespace_name(e->env, e->env->namespace, Text("$type")); - } - case OptionalType: { - type_t *nonnull = Match(t, OptionalType)->type; - switch (nonnull->tag) { - case CStringType: - case FunctionType: - case ClosureType: - case PointerType: - case EnumType: return compile_type(nonnull); - case TextType: return Match(nonnull, TextType)->lang ? compile_type(nonnull) : Text("OptionalText_t"); - case IntType: - case BigIntType: - case NumType: - case BoolType: - case ByteType: - case ListType: - case TableType: - case SetType: return Texts("Optional", compile_type(nonnull)); - case StructType: { - if (nonnull == PATH_TYPE) return Text("OptionalPath_t"); - if (nonnull == PATH_TYPE_TYPE) return Text("OptionalPathType_t"); - DeclareMatch(s, nonnull, StructType); - return namespace_name(s->env, s->env->namespace->parent, Texts("$Optional", s->name, "$$type")); - } - default: compiler_err(NULL, NULL, NULL, "Optional types are not supported for: ", type_to_str(t)); - } - } - case TypeInfoType: return Text("TypeInfo_t"); - default: compiler_err(NULL, NULL, NULL, "Compiling type is not implemented for type with tag ", t->tag); - } - return EMPTY_TEXT; -} - -Text_t compile_lvalue(env_t *env, ast_t *ast) { - if (!can_be_mutated(env, ast)) { - if (ast->tag == Index) { - ast_t *subject = Match(ast, Index)->indexed; - code_err(subject, "This is an immutable value, you can't mutate " - "its contents"); - } else if (ast->tag == FieldAccess) { - ast_t *subject = Match(ast, FieldAccess)->fielded; - type_t *t = get_type(env, subject); - code_err(subject, "This is an immutable ", type_to_str(t), " value, you can't assign to its fields"); - } else { - code_err(ast, "This is a value of type ", type_to_str(get_type(env, ast)), - " and can't be used as an assignment target"); - } - } - - if (ast->tag == Index) { - DeclareMatch(index, ast, Index); - type_t *container_t = get_type(env, index->indexed); - if (container_t->tag == OptionalType) - code_err(index->indexed, "This value might be none, so it can't be " - "safely used as an assignment target"); - - if (!index->index && container_t->tag == PointerType) return compile(env, ast); - - container_t = value_type(container_t); - type_t *index_t = get_type(env, index->index); - if (container_t->tag == ListType) { - Text_t target_code = compile_to_pointer_depth(env, index->indexed, 1, false); - type_t *item_type = Match(container_t, ListType)->item_type; - Text_t index_code = - index->index->tag == Int - ? compile_int_to_type(env, index->index, Type(IntType, .bits = TYPE_IBITS64)) - : (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, index->index), ", no)") - : Texts("(Int64_t)(", compile(env, index->index), ")")); - if (index->unchecked) { - return Texts("List_lvalue_unchecked(", compile_type(item_type), ", ", target_code, ", ", index_code, - ")"); - } else { - return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ", - String((int)(ast->start - ast->file->text)), ", ", - String((int)(ast->end - ast->file->text)), ")"); - } - } else if (container_t->tag == TableType) { - DeclareMatch(table_type, container_t, TableType); - if (table_type->default_value) { - type_t *value_type = get_type(env, table_type->default_value); - return Texts("*Table$get_or_setdefault(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", - compile_type(table_type->key_type), ", ", compile_type(value_type), ", ", - compile_to_type(env, index->index, table_type->key_type), ", ", - compile_to_type(env, table_type->default_value, table_type->value_type), ", ", - compile_type_info(container_t), ")"); - } - if (index->unchecked) code_err(ast, "Table indexes cannot be unchecked"); - return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(", - compile_to_pointer_depth(env, index->indexed, 1, false), ", ", - compile_to_type(env, index->index, Type(PointerType, table_type->key_type, .is_stack = true)), - ", NULL,", compile_type_info(container_t), ")"); - } else { - code_err(ast, "I don't know how to assign to this target"); - } - } else if (ast->tag == Var || ast->tag == FieldAccess || ast->tag == InlineCCode) { - return compile(env, ast); - } else { - code_err(ast, "I don't know how to assign to this"); - } - return EMPTY_TEXT; -} - -static Text_t compile_assignment(env_t *env, ast_t *target, Text_t value) { - return Texts(compile_lvalue(env, target), " = ", value); -} - -static Text_t compile_inline_block(env_t *env, ast_t *ast) { - if (ast->tag != Block) return compile_statement(env, ast); - - Text_t code = EMPTY_TEXT; - ast_list_t *stmts = Match(ast, Block)->statements; - deferral_t *prev_deferred = env->deferred; - env = fresh_scope(env); - for (ast_list_t *stmt = stmts; stmt; stmt = stmt->next) - prebind_statement(env, stmt->ast); - for (ast_list_t *stmt = stmts; stmt; stmt = stmt->next) { - code = Texts(code, compile_statement(env, stmt->ast), "\n"); - bind_statement(env, stmt->ast); - } - for (deferral_t *deferred = env->deferred; deferred && deferred != prev_deferred; deferred = deferred->next) { - code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); - } - return code; -} - -Text_t check_none(type_t *t, Text_t value) { - t = Match(t, OptionalType)->type; - // NOTE: these use statement expressions ({...;}) because some compilers - // complain about excessive parens around equality comparisons - if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) - return Texts("({", value, " == NULL;})"); - else if (t == PATH_TYPE) return Texts("({(", value, ").type.$tag == PATH_NONE;})"); - else if (t == PATH_TYPE_TYPE) return Texts("({(", value, ").$tag == PATH_NONE;})"); - else if (t->tag == BigIntType) return Texts("({(", value, ").small == 0;})"); - else if (t->tag == ClosureType) return Texts("({(", value, ").fn == NULL;})"); - else if (t->tag == NumType) - return Texts(Match(t, NumType)->bits == TYPE_NBITS64 ? "Num$isnan(" : "Num32$isnan(", value, ")"); - else if (t->tag == ListType) return Texts("({(", value, ").length < 0;})"); - else if (t->tag == TableType || t->tag == SetType) return Texts("({(", value, ").entries.length < 0;})"); - else if (t->tag == BoolType) return Texts("({(", value, ") == NONE_BOOL;})"); - else if (t->tag == TextType) return Texts("({(", value, ").length < 0;})"); - else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("(", value, ").is_none"); - else if (t->tag == EnumType) { - if (enum_has_fields(t)) return Texts("({(", value, ").$tag == 0;})"); - else return Texts("((", value, ") == 0)"); - } - print_err("Optional check not implemented for: ", type_to_str(t)); - return EMPTY_TEXT; -} - -static Text_t compile_condition(env_t *env, ast_t *ast) { - type_t *t = get_type(env, ast); - if (t->tag == BoolType) { - return compile(env, ast); - } else if (t->tag == TextType) { - return Texts("(", compile(env, ast), ").length"); - } else if (t->tag == ListType) { - return Texts("(", compile(env, ast), ").length"); - } else if (t->tag == TableType || t->tag == SetType) { - return Texts("(", compile(env, ast), ").entries.length"); - } else if (t->tag == OptionalType) { - return Texts("!", check_none(t, compile(env, ast))); - } else if (t->tag == PointerType) { - code_err(ast, "This pointer will always be non-none, so it should not be " - "used in a conditional."); - } else { - code_err(ast, type_to_str(t), " values cannot be used for conditionals"); - } - return EMPTY_TEXT; -} - -static Text_t _compile_statement(env_t *env, ast_t *ast) { - switch (ast->tag) { - case When: { - // Typecheck to verify exhaustiveness: - type_t *result_t = get_type(env, ast); - (void)result_t; - - DeclareMatch(when, ast, When); - type_t *subject_t = get_type(env, when->subject); - - if (subject_t->tag != EnumType) { - Text_t prefix = EMPTY_TEXT, suffix = EMPTY_TEXT; - ast_t *subject = when->subject; - if (!is_idempotent(when->subject)) { - prefix = Texts("{\n", compile_declaration(subject_t, Text("_when_subject")), " = ", - compile(env, subject), ";\n"); - suffix = Text("}\n"); - subject = LiteralCode(Text("_when_subject"), .type = subject_t); - } - - Text_t code = EMPTY_TEXT; - for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { - ast_t *comparison = WrapAST(clause->pattern, Equals, .lhs = subject, .rhs = clause->pattern); - (void)get_type(env, comparison); - if (code.length > 0) code = Texts(code, "else "); - code = Texts(code, "if (", compile(env, comparison), ")", compile_statement(env, clause->body)); - } - if (when->else_body) code = Texts(code, "else ", compile_statement(env, when->else_body)); - code = Texts(prefix, code, suffix); - return code; - } - - DeclareMatch(enum_t, subject_t, EnumType); - - Text_t code; - if (enum_has_fields(subject_t)) - code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); - else code = Texts("switch(", compile(env, when->subject), ") {\n"); - - for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { - if (clause->pattern->tag == Var) { - const char *clause_tag_name = Match(clause->pattern, Var)->name; - type_t *clause_type = clause->body ? get_type(env, clause->body) : Type(VoidType); - code = Texts( - code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)), - ": {\n", compile_inline_block(env, clause->body), - (clause_type->tag == ReturnType || clause_type->tag == AbortType) ? EMPTY_TEXT : Text("break;\n"), - "}\n"); - continue; - } - - if (clause->pattern->tag != FunctionCall || Match(clause->pattern, FunctionCall)->fn->tag != Var) - code_err(clause->pattern, "This is not a valid pattern for a ", type_to_str(subject_t), " enum type"); - - const char *clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name; - code = Texts(code, "case ", - namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)), ": {\n"); - type_t *tag_type = NULL; - for (tag_t *tag = enum_t->tags; tag; tag = tag->next) { - if (streq(tag->name, clause_tag_name)) { - tag_type = tag->type; - break; - } - } - assert(tag_type); - env_t *scope = env; - - DeclareMatch(tag_struct, tag_type, StructType); - arg_ast_t *args = Match(clause->pattern, FunctionCall)->args; - if (args && !args->next && tag_struct->fields && tag_struct->fields->next) { - if (args->value->tag != Var) code_err(args->value, "This is not a valid variable to bind to"); - const char *var_name = Match(args->value, Var)->name; - if (!streq(var_name, "_")) { - Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(tag_type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ";\n"); - scope = fresh_scope(scope); - set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT); - } - } else if (args) { - scope = fresh_scope(scope); - arg_t *field = tag_struct->fields; - for (arg_ast_t *arg = args; arg || field; arg = arg->next) { - if (!arg) - code_err(ast, "The field ", type_to_str(subject_t), ".", clause_tag_name, ".", field->name, - " wasn't accounted for"); - if (!field) code_err(arg->value, "This is one more field than ", type_to_str(subject_t), " has"); - if (arg->name) code_err(arg->value, "Named arguments are not currently supported"); - - const char *var_name = Match(arg->value, Var)->name; - if (!streq(var_name, "_")) { - Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(field->type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ".", valid_c_name(field->name), ";\n"); - set_binding(scope, Match(arg->value, Var)->name, field->type, var); - } - field = field->next; - } - } - if (clause->body->tag == Block) { - ast_list_t *statements = Match(clause->body, Block)->statements; - if (!statements || (statements->ast->tag == Pass && !statements->next)) - code = Texts(code, "break;\n}\n"); - else code = Texts(code, compile_inline_block(scope, clause->body), "\nbreak;\n}\n"); - } else { - code = Texts(code, compile_statement(scope, clause->body), "\nbreak;\n}\n"); - } - } - if (when->else_body) { - if (when->else_body->tag == Block) { - ast_list_t *statements = Match(when->else_body, Block)->statements; - if (!statements || (statements->ast->tag == Pass && !statements->next)) - code = Texts(code, "default: break;"); - else code = Texts(code, "default: {\n", compile_inline_block(env, when->else_body), "\nbreak;\n}\n"); - } else { - code = Texts(code, "default: {\n", compile_statement(env, when->else_body), "\nbreak;\n}\n"); - } - } else { - code = Texts(code, "default: errx(1, \"Invalid tag!\");\n"); - } - code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n"); - return code; - } - case DocTest: { - DeclareMatch(test, ast, DocTest); - type_t *expr_t = get_type(env, test->expr); - if (!expr_t) code_err(test->expr, "I couldn't figure out the type of this expression"); - - Text_t setup = EMPTY_TEXT; - Text_t test_code; - if (test->expr->tag == Declare) { - DeclareMatch(decl, test->expr, Declare); - type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (t->tag == FunctionType) t = Type(ClosureType, t); - Text_t var = Texts("_$", Match(decl->var, Var)->name); - Text_t val_code = compile_declared_value(env, test->expr); - setup = Texts(compile_declaration(t, var), ";\n"); - test_code = Texts("(", var, " = ", val_code, ")"); - expr_t = t; - } else if (test->expr->tag == Assign) { - DeclareMatch(assign, test->expr, Assign); - if (!assign->targets->next && assign->targets->ast->tag == Var && is_idempotent(assign->targets->ast)) { - // Common case: assigning to one variable: - type_t *lhs_t = get_type(env, assign->targets->ast); - if (assign->targets->ast->tag == Index && lhs_t->tag == OptionalType - && value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType) - lhs_t = Match(lhs_t, OptionalType)->type; - if (has_stack_memory(lhs_t)) - code_err(test->expr, "Stack references cannot be assigned " - "to variables because the " - "variable's scope may outlive the " - "scope of the stack memory."); - env_t *val_scope = with_enum_scope(env, lhs_t); - Text_t value = compile_to_type(val_scope, assign->values->ast, lhs_t); - test_code = Texts("(", compile_assignment(env, assign->targets->ast, value), ")"); - expr_t = lhs_t; - } else { - // Multi-assign or assignment to potentially non-idempotent - // targets - if (test->expected && assign->targets->next) - code_err(ast, "Sorry, but doctesting with '=' is not " - "supported for " - "multi-assignments"); - - test_code = Text("({ // Assignment\n"); - - int64_t i = 1; - for (ast_list_t *target = assign->targets, *value = assign->values; target && value; - target = target->next, value = value->next) { - type_t *lhs_t = get_type(env, target->ast); - if (target->ast->tag == Index && lhs_t->tag == OptionalType - && value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType) - lhs_t = Match(lhs_t, OptionalType)->type; - if (has_stack_memory(lhs_t)) - code_err(ast, "Stack references cannot be assigned to " - "variables because the " - "variable's scope may outlive the scope " - "of the stack memory."); - if (target == assign->targets) expr_t = lhs_t; - env_t *val_scope = with_enum_scope(env, lhs_t); - Text_t val_code = compile_to_type(val_scope, value->ast, lhs_t); - test_code = Texts(test_code, compile_type(lhs_t), " $", String(i), " = ", val_code, ";\n"); - i += 1; - } - i = 1; - for (ast_list_t *target = assign->targets; target; target = target->next) { - test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); - i += 1; - } - - test_code = Texts(test_code, "$1; })"); - } - } else if (is_update_assignment(test->expr)) { - binary_operands_t update = UPDATE_OPERANDS(test->expr); - type_t *lhs_t = get_type(env, update.lhs); - if (update.lhs->tag == Index) { - type_t *indexed = value_type(get_type(env, Match(update.lhs, Index)->indexed)); - if (indexed->tag == TableType && Match(indexed, TableType)->default_value == NULL) - code_err(update.lhs, "Update assignments are not currently " - "supported for tables"); - } - - ast_t *update_var = new (ast_t); - *update_var = *test->expr; - update_var->__data.PlusUpdate.lhs = LiteralCode(Text("(*expr)"), .type = lhs_t); // UNSAFE - test_code = - Texts("({", compile_declaration(Type(PointerType, lhs_t), Text("expr")), " = &(", - compile_lvalue(env, update.lhs), "); ", compile_statement(env, update_var), "; *expr; })"); - expr_t = lhs_t; - } else if (expr_t->tag == VoidType || expr_t->tag == AbortType || expr_t->tag == ReturnType) { - test_code = Texts("({", compile_statement(env, test->expr), " NULL;})"); - } else { - test_code = compile(env, test->expr); - } - if (test->expected) { - return Texts(setup, "test(", compile_type(expr_t), ", ", test_code, ", ", - compile_to_type(env, test->expected, expr_t), ", ", compile_type_info(expr_t), ", ", - String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); - } else { - if (expr_t->tag == VoidType || expr_t->tag == AbortType) { - return Texts(setup, "inspect_void(", test_code, ", ", compile_type_info(expr_t), ", ", - String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); - } - return Texts(setup, "inspect(", compile_type(expr_t), ", ", test_code, ", ", compile_type_info(expr_t), - ", ", String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); - } - } - case Assert: { - ast_t *expr = Match(ast, Assert)->expr; - ast_t *message = Match(ast, Assert)->message; - const char *failure = NULL; - switch (expr->tag) { - case And: { - DeclareMatch(and_, ast, And); - return Texts(compile_statement(env, WrapAST(ast, Assert, .expr = and_->lhs, .message = message)), - compile_statement(env, WrapAST(ast, Assert, .expr = and_->rhs, .message = message))); - } - case Equals: failure = "!="; goto assert_comparison; - case NotEquals: failure = "=="; goto assert_comparison; - case LessThan: failure = ">="; goto assert_comparison; - case LessThanOrEquals: failure = ">"; goto assert_comparison; - case GreaterThan: failure = "<="; goto assert_comparison; - case GreaterThanOrEquals: - failure = "<"; - goto assert_comparison; - { - assert_comparison:; - binary_operands_t cmp = BINARY_OPERANDS(expr); - type_t *lhs_t = get_type(env, cmp.lhs); - type_t *rhs_t = get_type(env, cmp.rhs); - type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { - operand_t = rhs_t; - } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, cmp.rhs, lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, cmp.lhs, rhs_t)) { - operand_t = rhs_t; - } else { - code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); - } - - ast_t *lhs_var = - FakeAST(InlineCCode, .chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, Text("_lhs"))), - .type = operand_t); - ast_t *rhs_var = - FakeAST(InlineCCode, .chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, Text("_rhs"))), - .type = operand_t); - ast_t *var_comparison = new (ast_t, .file = expr->file, .start = expr->start, .end = expr->end, - .tag = expr->tag, .__data.Equals = {.lhs = lhs_var, .rhs = rhs_var}); - int64_t line = get_line_number(ast->file, ast->start); - return Texts("{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ", - compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", String(line), "\n", - compile_declaration(operand_t, Text("_rhs")), " = ", - compile_to_type(env, cmp.rhs, operand_t), ";\n", "\n#line ", String(line), "\n", "if (!(", - compile_condition(env, var_comparison), "))\n", "#line ", String(line), "\n", - Texts("fail_source(", quoted_str(ast->file->filename), ", ", - String((int64_t)(expr->start - expr->file->text)), ", ", - String((int64_t)(expr->end - expr->file->text)), ", ", - message - ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") - : Text("\"This assertion failed!\""), - ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")), - ", " - "\" ", - failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"), - "}\n"); - } - default: { - int64_t line = get_line_number(ast->file, ast->start); - return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", String(line), "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", String((int64_t)(expr->start - expr->file->text)), ", ", - String((int64_t)(expr->end - expr->file->text)), ", ", - message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") - : Text("\"This assertion failed!\""), - ");\n"); - } - } - } - case Declare: { - DeclareMatch(decl, ast, Declare); - const char *name = Match(decl->var, Var)->name; - if (streq(name, "_")) { // Explicit discard - if (decl->value) return Texts("(void)", compile(env, decl->value), ";"); - else return EMPTY_TEXT; - } else { - type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (t->tag == FunctionType) t = Type(ClosureType, t); - if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) - code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value"); - - Text_t val_code = compile_declared_value(env, ast); - return Texts(compile_declaration(t, Texts("_$", name)), " = ", val_code, ";"); - } - } - case Assign: { - DeclareMatch(assign, ast, Assign); - // Single assignment, no temp vars needed: - if (assign->targets && !assign->targets->next) { - type_t *lhs_t = get_type(env, assign->targets->ast); - if (assign->targets->ast->tag == Index && lhs_t->tag == OptionalType - && value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType) - lhs_t = Match(lhs_t, OptionalType)->type; - if (has_stack_memory(lhs_t)) - code_err(ast, "Stack references cannot be assigned to " - "variables because the " - "variable's scope may outlive the scope of the " - "stack memory."); - env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, assign->values->ast, lhs_t); - return Texts(compile_assignment(env, assign->targets->ast, val), ";\n"); - } - - Text_t code = Text("{ // Assignment\n"); - int64_t i = 1; - for (ast_list_t *value = assign->values, *target = assign->targets; value && target; - value = value->next, target = target->next) { - type_t *lhs_t = get_type(env, target->ast); - if (target->ast->tag == Index && lhs_t->tag == OptionalType - && value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType) - lhs_t = Match(lhs_t, OptionalType)->type; - if (has_stack_memory(lhs_t)) - code_err(ast, "Stack references cannot be assigned to " - "variables because the " - "variable's scope may outlive the scope of the " - "stack memory."); - env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, value->ast, lhs_t); - code = Texts(code, compile_type(lhs_t), " $", String(i), " = ", val, ";\n"); - i += 1; - } - i = 1; - for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); - i += 1; - } - return Texts(code, "\n}"); - } - case PlusUpdate: { - DeclareMatch(update, ast, PlusUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " += ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case MinusUpdate: { - DeclareMatch(update, ast, MinusUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " -= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case MultiplyUpdate: { - DeclareMatch(update, ast, MultiplyUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " *= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case DivideUpdate: { - DeclareMatch(update, ast, DivideUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " /= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case ModUpdate: { - DeclareMatch(update, ast, ModUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " %= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case PowerUpdate: - case Mod1Update: - case ConcatUpdate: - case LeftShiftUpdate: - case UnsignedLeftShiftUpdate: - case RightShiftUpdate: - case UnsignedRightShiftUpdate: - case AndUpdate: - case OrUpdate: - case XorUpdate: { - return compile_update_assignment(env, ast); - } - case StructDef: - case EnumDef: - case LangDef: - case Extend: - case FunctionDef: - case ConvertDef: { - return EMPTY_TEXT; - } - case Skip: { - const char *target = Match(ast, Skip)->target; - for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) { - bool matched = !target || strcmp(target, ctx->loop_name) == 0; - for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : NULL) - matched = (strcmp(target, Match(var->ast, Var)->name) == 0); - - if (matched) { - if (ctx->skip_label.length == 0) { - static int64_t skip_label_count = 1; - ctx->skip_label = Texts("skip_", String(skip_label_count)); - ++skip_label_count; - } - Text_t code = EMPTY_TEXT; - for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; - deferred = deferred->next) - code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); - if (code.length > 0) return Texts("{\n", code, "goto ", ctx->skip_label, ";\n}\n"); - else return Texts("goto ", ctx->skip_label, ";"); - } - } - if (env->loop_ctx) code_err(ast, "This is not inside any loop"); - else if (target) code_err(ast, "No loop target named '", target, "' was found"); - else return Text("continue;"); - } - case Stop: { - const char *target = Match(ast, Stop)->target; - for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) { - bool matched = !target || strcmp(target, ctx->loop_name) == 0; - for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : var) - matched = (strcmp(target, Match(var->ast, Var)->name) == 0); - - if (matched) { - if (ctx->stop_label.length == 0) { - static int64_t stop_label_count = 1; - ctx->stop_label = Texts("stop_", String(stop_label_count)); - ++stop_label_count; - } - Text_t code = EMPTY_TEXT; - for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; - deferred = deferred->next) - code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); - if (code.length > 0) return Texts("{\n", code, "goto ", ctx->stop_label, ";\n}\n"); - else return Texts("goto ", ctx->stop_label, ";"); - } - } - if (env->loop_ctx) code_err(ast, "This is not inside any loop"); - else if (target) code_err(ast, "No loop target named '", target, "' was found"); - else return Text("break;"); - } - case Pass: return Text(";"); - case Defer: { - ast_t *body = Match(ast, Defer)->body; - Table_t closed_vars = get_closed_vars(env, NULL, body); - - static int defer_id = 0; - env_t *defer_env = fresh_scope(env); - Text_t code = EMPTY_TEXT; - for (int64_t i = 0; i < closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (entry->b->type->tag == ModuleType) continue; - if (Text$starts_with(entry->b->code, Text("userdata->"), NULL)) { - Table$str_set(defer_env->locals, entry->name, entry->b); - } else { - Text_t defer_name = Texts("defer$", String(++defer_id), "$", entry->name); - defer_id += 1; - code = Texts(code, compile_declaration(entry->b->type, defer_name), " = ", entry->b->code, ";\n"); - set_binding(defer_env, entry->name, entry->b->type, defer_name); - } - } - env->deferred = new (deferral_t, .defer_env = defer_env, .block = body, .next = env->deferred); - return code; - } - case Return: { - if (!env->fn_ret) code_err(ast, "This return statement is not inside any function"); - ast_t *ret = Match(ast, Return)->value; - - Text_t code = EMPTY_TEXT; - for (deferral_t *deferred = env->deferred; deferred; deferred = deferred->next) { - code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); - } - - if (ret) { - if (env->fn_ret->tag == VoidType || env->fn_ret->tag == AbortType) - code_err(ast, "This function is not supposed to return any values, " - "according to its type signature"); - - env = with_enum_scope(env, env->fn_ret); - Text_t value = compile_to_type(env, ret, env->fn_ret); - if (env->deferred) { - code = Texts(compile_declaration(env->fn_ret, Text("ret")), " = ", value, ";\n", code); - value = Text("ret"); - } - - return Texts(code, "return ", value, ";"); - } else { - if (env->fn_ret->tag != VoidType) - code_err(ast, "This function expects you to return a ", type_to_str(env->fn_ret), " value"); - return Texts(code, "return;"); - } - } - case While: { - DeclareMatch(while_, ast, While); - env_t *scope = fresh_scope(env); - loop_ctx_t loop_ctx = (loop_ctx_t){ - .loop_name = "while", - .deferred = scope->deferred, - .next = env->loop_ctx, - }; - scope->loop_ctx = &loop_ctx; - Text_t body = compile_statement(scope, while_->body); - if (loop_ctx.skip_label.length > 0) body = Texts(body, "\n", loop_ctx.skip_label, ": continue;"); - Text_t loop = Texts("while (", while_->condition ? compile(scope, while_->condition) : Text("yes"), ") {\n\t", - body, "\n}"); - if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;"); - return loop; - } - case Repeat: { - ast_t *body = Match(ast, Repeat)->body; - env_t *scope = fresh_scope(env); - loop_ctx_t loop_ctx = (loop_ctx_t){ - .loop_name = "repeat", - .deferred = scope->deferred, - .next = env->loop_ctx, - }; - scope->loop_ctx = &loop_ctx; - Text_t body_code = compile_statement(scope, body); - if (loop_ctx.skip_label.length > 0) body_code = Texts(body_code, "\n", loop_ctx.skip_label, ": continue;"); - Text_t loop = Texts("for (;;) {\n\t", body_code, "\n}"); - if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;"); - return loop; - } - case For: { - DeclareMatch(for_, ast, For); - - // If we're iterating over a comprehension, that's actually just doing - // one loop, we don't need to compile the comprehension as a list - // comprehension. This is a common case for reducers like `(+: i*2 for i - // in 5)` or `(and) x.is_good() for x in xs` - if (for_->iter->tag == Comprehension) { - DeclareMatch(comp, for_->iter, Comprehension); - ast_t *body = for_->body; - if (for_->vars) { - if (for_->vars->next) code_err(for_->vars->next->ast, "This is too many variables for iteration"); - - body = WrapAST( - ast, Block, - .statements = new ( - ast_list_t, .ast = WrapAST(ast, Declare, .var = for_->vars->ast, .value = comp->expr), - .next = body->tag == Block ? Match(body, Block)->statements : new (ast_list_t, .ast = body))); - } - - if (comp->filter) body = WrapAST(for_->body, If, .condition = comp->filter, .body = body); - ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); - return compile_statement(env, loop); - } - - env_t *body_scope = for_scope(env, ast); - loop_ctx_t loop_ctx = (loop_ctx_t){ - .loop_name = "for", - .loop_vars = for_->vars, - .deferred = body_scope->deferred, - .next = body_scope->loop_ctx, - }; - body_scope->loop_ctx = &loop_ctx; - // Naked means no enclosing braces: - Text_t naked_body = compile_inline_block(body_scope, for_->body); - if (loop_ctx.skip_label.length > 0) naked_body = Texts(naked_body, "\n", loop_ctx.skip_label, ": continue;"); - Text_t stop = loop_ctx.stop_label.length > 0 ? Texts("\n", loop_ctx.stop_label, ":;") : EMPTY_TEXT; - - // Special case for improving performance for numeric iteration: - if (for_->iter->tag == MethodCall && streq(Match(for_->iter, MethodCall)->name, "to") - && is_int_type(get_type(env, Match(for_->iter, MethodCall)->self))) { - // TODO: support other integer types - arg_ast_t *args = Match(for_->iter, MethodCall)->args; - if (!args) code_err(for_->iter, "to() needs at least one argument"); - - type_t *int_type = get_type(env, Match(for_->iter, MethodCall)->self); - type_t *step_type = int_type->tag == ByteType ? Type(IntType, .bits = TYPE_IBITS8) : int_type; - - Text_t last = EMPTY_TEXT, step = EMPTY_TEXT, optional_step = EMPTY_TEXT; - if (!args->name || streq(args->name, "last")) { - last = compile_to_type(env, args->value, int_type); - if (args->next) { - if (args->next->name && !streq(args->next->name, "step")) - code_err(args->next->value, "Invalid argument name: ", args->next->name); - if (get_type(env, args->next->value)->tag == OptionalType) - optional_step = compile_to_type(env, args->next->value, Type(OptionalType, step_type)); - else step = compile_to_type(env, args->next->value, step_type); - } - } else if (streq(args->name, "step")) { - if (get_type(env, args->value)->tag == OptionalType) - optional_step = compile_to_type(env, args->value, Type(OptionalType, step_type)); - else step = compile_to_type(env, args->value, step_type); - if (args->next) { - if (args->next->name && !streq(args->next->name, "last")) - code_err(args->next->value, "Invalid argument name: ", args->next->name); - last = compile_to_type(env, args->next->value, int_type); - } - } - - if (last.length == 0) code_err(for_->iter, "No `last` argument was given"); - - Text_t type_code = compile_type(int_type); - Text_t value = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); - if (int_type->tag == BigIntType) { - if (optional_step.length > 0) - step = Texts("({ OptionalInt_t maybe_step = ", optional_step, - "; maybe_step->small == 0 ? " - "(Int$compare_value(last, first) >= 0 " - "? I_small(1) : I_small(-1)) : (Int_t)maybe_step; " - "})"); - else if (step.length == 0) - step = Text("Int$compare_value(last, first) >= 0 ? " - "I_small(1) : I_small(-1)"); - return Texts("for (", type_code, " first = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", - value, " = first, last = ", last, ", step = ", step, - "; " - "Int$compare_value(", - value, ", last) != Int$compare_value(step, I_small(0)); ", value, " = Int$plus(", value, - ", step)) {\n" - "\t", - naked_body, "}", stop); - } else { - if (optional_step.length > 0) - step = Texts("({ ", compile_type(Type(OptionalType, step_type)), " maybe_step = ", optional_step, - "; " - "maybe_step.is_none ? (", - type_code, ")(last >= first ? 1 : -1) : maybe_step.value; })"); - else if (step.length == 0) step = Texts("(", type_code, ")(last >= first ? 1 : -1)"); - return Texts("for (", type_code, " first = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", - value, " = first, last = ", last, ", step = ", step, - "; " - "step > 0 ? ", - value, " <= last : ", value, " >= last; ", value, - " += step) {\n" - "\t", - naked_body, "}", stop); - } - } else if (for_->iter->tag == MethodCall && streq(Match(for_->iter, MethodCall)->name, "onward") - && get_type(env, Match(for_->iter, MethodCall)->self)->tag == BigIntType) { - // Special case for Int.onward() - arg_ast_t *args = Match(for_->iter, MethodCall)->args; - arg_t *arg_spec = - new (arg_t, .name = "step", .type = INT_TYPE, .default_val = FakeAST(Int, .str = "1"), .next = NULL); - Text_t step = compile_arguments(env, for_->iter, arg_spec, args); - Text_t value = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); - return Texts("for (Int_t ", value, " = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", - "step = ", step, "; ; ", value, " = Int$plus(", value, - ", step)) {\n" - "\t", - naked_body, "}", stop); - } - - type_t *iter_t = get_type(env, for_->iter); - type_t *iter_value_t = value_type(iter_t); - - switch (iter_value_t->tag) { - case ListType: { - type_t *item_t = Match(iter_value_t, ListType)->item_type; - Text_t index = EMPTY_TEXT; - Text_t value = EMPTY_TEXT; - if (for_->vars) { - if (for_->vars->next) { - if (for_->vars->next->next) - code_err(for_->vars->next->next->ast, "This is too many variables for this loop"); - - index = compile(body_scope, for_->vars->ast); - value = compile(body_scope, for_->vars->next->ast); - } else { - value = compile(body_scope, for_->vars->ast); - } - } - - Text_t loop = EMPTY_TEXT; - loop = Texts(loop, "for (int64_t i = 1; i <= iterating.length; ++i)"); - - if (index.length > 0) naked_body = Texts("Int_t ", index, " = I(i);\n", naked_body); - - if (value.length > 0) { - loop = Texts(loop, "{\n", compile_declaration(item_t, value), " = *(", compile_type(item_t), - "*)(iterating.data + (i-1)*iterating.stride);\n", naked_body, "\n}"); - } else { - loop = Texts(loop, "{\n", naked_body, "\n}"); - } - - if (for_->empty) - loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty)); - - if (iter_t->tag == PointerType) { - loop = Texts("{\n" - "List_t *ptr = ", - compile_to_pointer_depth(env, for_->iter, 1, false), - ";\n" - "\nLIST_INCREF(*ptr);\n" - "List_t iterating = *ptr;\n", - loop, stop, - "\nLIST_DECREF(*ptr);\n" - "}\n"); - - } else { - loop = Texts("{\n" - "List_t iterating = ", - compile_to_pointer_depth(env, for_->iter, 0, false), ";\n", loop, stop, "}\n"); - } - return loop; - } - case SetType: - case TableType: { - Text_t loop = Text("for (int64_t i = 0; i < iterating.length; ++i) {\n"); - if (for_->vars) { - if (iter_value_t->tag == SetType) { - if (for_->vars->next) code_err(for_->vars->next->ast, "This is too many variables for this loop"); - Text_t item = compile(body_scope, for_->vars->ast); - type_t *item_type = Match(iter_value_t, SetType)->item_type; - loop = Texts(loop, compile_declaration(item_type, item), " = *(", compile_type(item_type), "*)(", - "iterating.data + i*iterating.stride);\n"); - } else { - Text_t key = compile(body_scope, for_->vars->ast); - type_t *key_t = Match(iter_value_t, TableType)->key_type; - loop = Texts(loop, compile_declaration(key_t, key), " = *(", compile_type(key_t), "*)(", - "iterating.data + i*iterating.stride);\n"); - - if (for_->vars->next) { - if (for_->vars->next->next) - code_err(for_->vars->next->next->ast, "This is too many variables for this loop"); - - type_t *value_t = Match(iter_value_t, TableType)->value_type; - Text_t value = compile(body_scope, for_->vars->next->ast); - Text_t value_offset = Texts("offsetof(struct { ", compile_declaration(key_t, Text("k")), "; ", - compile_declaration(value_t, Text("v")), "; }, v)"); - loop = Texts(loop, compile_declaration(value_t, value), " = *(", compile_type(value_t), "*)(", - "iterating.data + i*iterating.stride + ", value_offset, ");\n"); - } - } - } - - loop = Texts(loop, naked_body, "\n}"); - - if (for_->empty) { - loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty)); - } - - if (iter_t->tag == PointerType) { - loop = Texts("{\n", "Table_t *t = ", compile_to_pointer_depth(env, for_->iter, 1, false), - ";\n" - "LIST_INCREF(t->entries);\n" - "List_t iterating = t->entries;\n", - loop, - "LIST_DECREF(t->entries);\n" - "}\n"); - } else { - loop = Texts("{\n", "List_t iterating = (", compile_to_pointer_depth(env, for_->iter, 0, false), - ").entries;\n", loop, "}\n"); - } - return loop; - } - case BigIntType: { - Text_t n; - if (for_->iter->tag == Int) { - const char *str = Match(for_->iter, Int)->str; - Int_t int_val = Int$from_str(str); - if (int_val.small == 0) code_err(for_->iter, "Failed to parse this integer"); - mpz_t i; - mpz_init_set_int(i, int_val); - if (mpz_cmpabs_ui(i, BIGGEST_SMALL_INT) <= 0) n = Text$from_str(mpz_get_str(NULL, 10, i)); - else goto big_n; - - if (for_->empty && mpz_cmp_si(i, 0) <= 0) { - return compile_statement(env, for_->empty); - } else { - return Texts("for (int64_t i = 1; i <= ", n, "; ++i) {\n", - for_->vars - ? Texts("\tInt_t ", compile(body_scope, for_->vars->ast), " = I_small(i);\n") - : EMPTY_TEXT, - "\t", naked_body, "}\n", stop, "\n"); - } - } - - big_n: - n = compile_to_pointer_depth(env, for_->iter, 0, false); - Text_t i = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); - Text_t n_var = for_->vars ? Texts("max", i) : Text("n"); - if (for_->empty) { - return Texts("{\n" - "Int_t ", - n_var, " = ", n, - ";\n" - "if (Int$compare_value(", - n_var, - ", I(0)) > 0) {\n" - "for (Int_t ", - i, " = I(1); Int$compare_value(", i, ", ", n_var, ") <= 0; ", i, " = Int$plus(", i, - ", I(1))) {\n", "\t", naked_body, - "}\n" - "} else ", - compile_statement(env, for_->empty), stop, - "\n" - "}\n"); - } else { - return Texts("for (Int_t ", i, " = I(1), ", n_var, " = ", n, "; Int$compare_value(", i, ", ", n_var, - ") <= 0; ", i, " = Int$plus(", i, ", I(1))) {\n", "\t", naked_body, "}\n", stop, "\n"); - } - } - case FunctionType: - case ClosureType: { - // Iterator function: - Text_t code = Text("{\n"); - - Text_t next_fn; - if (is_idempotent(for_->iter)) { - next_fn = compile_to_pointer_depth(env, for_->iter, 0, false); - } else { - code = Texts(code, compile_declaration(iter_value_t, Text("next")), " = ", - compile_to_pointer_depth(env, for_->iter, 0, false), ";\n"); - next_fn = Text("next"); - } - - __typeof(iter_value_t->__data.FunctionType) *fn = - iter_value_t->tag == ClosureType ? Match(Match(iter_value_t, ClosureType)->fn, FunctionType) - : Match(iter_value_t, FunctionType); - - Text_t get_next; - if (iter_value_t->tag == ClosureType) { - type_t *fn_t = Match(iter_value_t, ClosureType)->fn; - arg_t *closure_fn_args = NULL; - for (arg_t *arg = Match(fn_t, FunctionType)->args; arg; arg = arg->next) - closure_fn_args = new (arg_t, .name = arg->name, .type = arg->type, .default_val = arg->default_val, - .next = closure_fn_args); - closure_fn_args = new (arg_t, .name = "userdata", - .type = Type(PointerType, .pointed = Type(MemoryType)), .next = closure_fn_args); - REVERSE_LIST(closure_fn_args); - Text_t fn_type_code = - compile_type(Type(FunctionType, .args = closure_fn_args, .ret = Match(fn_t, FunctionType)->ret)); - get_next = Texts("((", fn_type_code, ")", next_fn, ".fn)(", next_fn, ".userdata)"); - } else { - get_next = Texts(next_fn, "()"); - } - - if (fn->ret->tag == OptionalType) { - // Use an optional variable `cur` for each iteration step, which - // will be checked for none - code = Texts(code, compile_declaration(fn->ret, Text("cur")), ";\n"); - get_next = Texts("(cur=", get_next, ", !", check_none(fn->ret, Text("cur")), ")"); - if (for_->vars) { - naked_body = Texts(compile_declaration(Match(fn->ret, OptionalType)->type, - Texts("_$", Match(for_->vars->ast, Var)->name)), - " = ", optional_into_nonnone(fn->ret, Text("cur")), ";\n", naked_body); - } - if (for_->empty) { - code = Texts(code, "if (", get_next, - ") {\n" - "\tdo{\n\t\t", - naked_body, "\t} while(", get_next, - ");\n" - "} else {\n\t", - compile_statement(env, for_->empty), "}", stop, "\n}\n"); - } else { - code = Texts(code, "while(", get_next, ") {\n\t", naked_body, "}\n", stop, "\n}\n"); - } - } else { - if (for_->vars) { - naked_body = Texts(compile_declaration(fn->ret, Texts("_$", Match(for_->vars->ast, Var)->name)), - " = ", get_next, ";\n", naked_body); - } else { - naked_body = Texts(get_next, ";\n", naked_body); - } - if (for_->empty) - code_err(for_->empty, "This iteration loop will always have values, " - "so this block will never run"); - code = Texts(code, "for (;;) {\n\t", naked_body, "}\n", stop, "\n}\n"); - } - - return code; - } - default: code_err(for_->iter, "Iteration is not implemented for type: ", type_to_str(iter_t)); - } - } - case If: { - DeclareMatch(if_, ast, If); - ast_t *condition = if_->condition; - if (condition->tag == Declare) { - if (Match(condition, Declare)->value == NULL) code_err(condition, "This declaration must have a value"); - env_t *truthy_scope = fresh_scope(env); - Text_t code = Texts("IF_DECLARE(", compile_statement(truthy_scope, condition), ", "); - bind_statement(truthy_scope, condition); - ast_t *var = Match(condition, Declare)->var; - code = Texts(code, compile_condition(truthy_scope, var), ", "); - type_t *cond_t = get_type(truthy_scope, var); - if (cond_t->tag == OptionalType) { - set_binding(truthy_scope, Match(var, Var)->name, Match(cond_t, OptionalType)->type, - optional_into_nonnone(cond_t, compile(truthy_scope, var))); - } - code = Texts(code, compile_statement(truthy_scope, if_->body), ")"); - if (if_->else_body) code = Texts(code, "\nelse ", compile_statement(env, if_->else_body)); - return code; - } else { - Text_t code = Texts("if (", compile_condition(env, condition), ")"); - env_t *truthy_scope = env; - type_t *cond_t = get_type(env, condition); - if (condition->tag == Var && cond_t->tag == OptionalType) { - truthy_scope = fresh_scope(env); - set_binding(truthy_scope, Match(condition, Var)->name, Match(cond_t, OptionalType)->type, - optional_into_nonnone(cond_t, compile(truthy_scope, condition))); - } - code = Texts(code, compile_statement(truthy_scope, if_->body)); - if (if_->else_body) code = Texts(code, "\nelse ", compile_statement(env, if_->else_body)); - return code; - } - } - case Block: { - return Texts("{\n", compile_inline_block(env, ast), "}\n"); - } - case Comprehension: { - if (!env->comprehension_action) code_err(ast, "I don't know what to do with this comprehension!"); - DeclareMatch(comp, ast, Comprehension); - if (comp->expr->tag == Comprehension) { // Nested comprehension - ast_t *body = comp->filter ? WrapAST(ast, If, .condition = comp->filter, .body = comp->expr) : comp->expr; - ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); - return compile_statement(env, loop); - } - - // List/Set/Table comprehension: - comprehension_body_t get_body = (void *)env->comprehension_action->fn; - ast_t *body = get_body(comp->expr, env->comprehension_action->userdata); - if (comp->filter) body = WrapAST(comp->expr, If, .condition = comp->filter, .body = body); - ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); - return compile_statement(env, loop); - } - case Extern: return EMPTY_TEXT; - case InlineCCode: { - DeclareMatch(inline_code, ast, InlineCCode); - Text_t code = EMPTY_TEXT; - for (ast_list_t *chunk = inline_code->chunks; chunk; chunk = chunk->next) { - if (chunk->ast->tag == TextLiteral) { - code = Texts(code, Match(chunk->ast, TextLiteral)->text); - } else { - code = Texts(code, compile(env, chunk->ast)); - } - } - return code; - } - case Use: { - DeclareMatch(use, ast, Use); - if (use->what == USE_LOCAL) { - Path_t path = Path$from_str(Match(ast, Use)->path); - Path_t in_file = Path$from_str(ast->file->filename); - path = Path$resolved(path, Path$parent(in_file)); - Text_t suffix = get_id_suffix(Path$as_c_string(path)); - return with_source_info(env, ast, Texts("$initialize", suffix, "();\n")); - } else if (use->what == USE_MODULE) { - module_info_t mod = get_module_info(ast); - glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, - NULL, &tm_files) - != 0) { - if (!try_install_module(mod)) code_err(ast, "Could not find library"); - } - - Text_t initialization = EMPTY_TEXT; - - for (size_t i = 0; i < tm_files.gl_pathc; i++) { - const char *filename = tm_files.gl_pathv[i]; - initialization = Texts( - initialization, with_source_info(env, ast, Texts("$initialize", get_id_suffix(filename), "();\n"))); - } - globfree(&tm_files); - return initialization; - } else { - return EMPTY_TEXT; - } - } - default: - // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", - // is_discardable(env, ast)); - if (!is_discardable(env, ast)) - code_err(ast, "The ", type_to_str(get_type(env, ast)), " result of this statement cannot be discarded"); - return Texts("(void)", compile(env, ast), ";"); - } -} - -Text_t compile_statement(env_t *env, ast_t *ast) { - Text_t stmt = _compile_statement(env, ast); - return with_source_info(env, ast, stmt); -} - 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"); @@ -1654,29 +340,6 @@ Text_t compile_empty(type_t *t) { return EMPTY_TEXT; } -static Text_t compile_declared_value(env_t *env, ast_t *declare_ast) { - DeclareMatch(decl, declare_ast, Declare); - type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - - if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) - code_err(declare_ast, "You can't declare a variable with a ", type_to_str(t), " value"); - - if (decl->value) { - Text_t val_code = compile_maybe_incref(env, decl->value, t); - if (t->tag == FunctionType) { - assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); - t = Type(ClosureType, t); - } - return val_code; - } else { - Text_t val_code = compile_empty(t); - if (val_code.length == 0) - code_err(declare_ast, "This type (", type_to_str(t), - ") cannot be uninitialized. You must provide a value."); - return val_code; - } -} - Text_t compile(env_t *env, ast_t *ast) { switch (ast->tag) { case None: { @@ -2422,69 +1085,6 @@ Text_t compile(env_t *env, ast_t *ast) { return EMPTY_TEXT; } -Text_t compile_type_info(type_t *t) { - if (t == NULL) compiler_err(NULL, NULL, NULL, "Attempt to compile a NULL type"); - if (t == PATH_TYPE) return Text("&Path$info"); - else if (t == PATH_TYPE_TYPE) return Text("&PathType$info"); - - switch (t->tag) { - case BoolType: - case ByteType: - case IntType: - case BigIntType: - case NumType: - case CStringType: return Texts("&", type_to_text(t), "$info"); - case TextType: { - DeclareMatch(text, t, TextType); - if (!text->lang || streq(text->lang, "Text")) return Text("&Text$info"); - return Texts("(&", namespace_name(text->env, text->env->namespace, Text("$info")), ")"); - } - case StructType: { - DeclareMatch(s, t, StructType); - return Texts("(&", namespace_name(s->env, s->env->namespace, Text("$info")), ")"); - } - case EnumType: { - DeclareMatch(e, t, EnumType); - return Texts("(&", namespace_name(e->env, e->env->namespace, Text("$info")), ")"); - } - case ListType: { - type_t *item_t = Match(t, ListType)->item_type; - return Texts("List$info(", compile_type_info(item_t), ")"); - } - case SetType: { - type_t *item_type = Match(t, SetType)->item_type; - return Texts("Set$info(", compile_type_info(item_type), ")"); - } - case TableType: { - DeclareMatch(table, t, TableType); - type_t *key_type = table->key_type; - type_t *value_type = table->value_type; - return Texts("Table$info(", compile_type_info(key_type), ", ", compile_type_info(value_type), ")"); - } - case PointerType: { - DeclareMatch(ptr, t, PointerType); - const char *sigil = ptr->is_stack ? "&" : "@"; - return Texts("Pointer$info(", quoted_str(sigil), ", ", compile_type_info(ptr->pointed), ")"); - } - case FunctionType: { - return Texts("Function$info(", quoted_text(type_to_text(t)), ")"); - } - case ClosureType: { - return Texts("Closure$info(", quoted_text(type_to_text(t)), ")"); - } - case OptionalType: { - type_t *non_optional = Match(t, OptionalType)->type; - return Texts("Optional$info(sizeof(", compile_type(non_optional), "), __alignof__(", compile_type(non_optional), - "), ", compile_type_info(non_optional), ")"); - } - case TypeInfoType: return Texts("Type$info(", quoted_text(type_to_text(Match(t, TypeInfoType)->type)), ")"); - case MemoryType: return Text("&Memory$info"); - case VoidType: return Text("&Void$info"); - default: compiler_err(NULL, 0, 0, "I couldn't convert to a type info: ", type_to_str(t)); - } - return EMPTY_TEXT; -} - static Text_t get_flag_options(type_t *t, const char *separator) { if (t->tag == BoolType) { return Text("yes|no"); @@ -2584,386 +1184,6 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c return code; } -Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { - bool is_private = false; - const char *function_name; - arg_ast_t *args; - type_t *ret_t; - ast_t *body; - ast_t *cache; - bool is_inline; - if (ast->tag == FunctionDef) { - DeclareMatch(fndef, ast, FunctionDef); - function_name = Match(fndef->name, Var)->name; - is_private = function_name[0] == '_'; - args = fndef->args; - ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType); - body = fndef->body; - cache = fndef->cache; - is_inline = fndef->is_inline; - } else { - DeclareMatch(convertdef, ast, ConvertDef); - args = convertdef->args; - ret_t = convertdef->ret_type ? parse_type_ast(env, convertdef->ret_type) : Type(VoidType); - function_name = get_type_name(ret_t); - if (!function_name) - code_err(ast, - "Conversions are only supported for text, struct, and enum " - "types, not ", - type_to_str(ret_t)); - body = convertdef->body; - cache = convertdef->cache; - is_inline = convertdef->is_inline; - } - - Text_t arg_signature = Text("("); - Table_t used_names = {}; - for (arg_ast_t *arg = args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - arg_signature = Texts(arg_signature, compile_declaration(arg_type, Texts("_$", arg->name))); - if (arg->next) arg_signature = Texts(arg_signature, ", "); - if (Table$str_get(used_names, arg->name)) - code_err(ast, "The argument name '", arg->name, "' is used more than once"); - Table$str_set(&used_names, arg->name, arg->name); - } - arg_signature = Texts(arg_signature, ")"); - - Text_t ret_type_code = compile_type(ret_t); - if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code); - - if (is_private) *staticdefs = Texts(*staticdefs, "static ", ret_type_code, " ", name_code, arg_signature, ";\n"); - - Text_t code; - if (cache) { - code = Texts("static ", ret_type_code, " ", name_code, "$uncached", arg_signature); - } else { - code = Texts(ret_type_code, " ", name_code, arg_signature); - if (is_inline) code = Texts("INLINE ", code); - if (!is_private) code = Texts("public ", code); - } - - env_t *body_scope = fresh_scope(env); - while (body_scope->namespace) { - body_scope->locals->fallback = body_scope->locals->fallback->fallback; - body_scope->namespace = body_scope->namespace->parent; - } - - body_scope->deferred = NULL; - for (arg_ast_t *arg = args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); - } - - body_scope->fn_ret = ret_t; - - type_t *body_type = get_type(body_scope, body); - if (ret_t->tag == AbortType) { - if (body_type->tag != AbortType) code_err(ast, "This function can reach the end without aborting!"); - } else if (ret_t->tag == VoidType) { - if (body_type->tag == AbortType) - code_err(ast, "This function will always abort before it reaches the " - "end, but it's declared as having a Void return. It should " - "be declared as an Abort return instead."); - } else { - if (body_type->tag != ReturnType && body_type->tag != AbortType) - code_err(ast, - "This function looks like it can reach the end without " - "returning a ", - type_to_str(ret_t), - " value! \n " - "If this is not the case, please add a call to " - "`fail(\"Unreachable\")` at the end of the function to " - "help the " - "compiler out."); - } - - Text_t body_code = Texts("{\n", compile_inline_block(body_scope, body), "}\n"); - Text_t definition = with_source_info(env, ast, Texts(code, " ", body_code, "\n")); - - if (cache && args == NULL) { // no-args cache just uses a static var - Text_t wrapper = - Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, - "(void) {\n" - "static ", - compile_declaration(ret_t, Text("cached_result")), ";\n", "static bool initialized = false;\n", - "if (!initialized) {\n" - "\tcached_result = ", - name_code, "$uncached();\n", "\tinitialized = true;\n", "}\n", - "return cached_result;\n" - "}\n"); - definition = Texts(definition, wrapper); - } else if (cache && cache->tag == Int) { - assert(args); - OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL); - Text_t pop_code = EMPTY_TEXT; - if (cache->tag == Int && !cache_size.is_none && cache_size.value > 0) { - // FIXME: this currently just deletes the first entry, but this - // should be more like a least-recently-used cache eviction policy - // or least-frequently-used - pop_code = Texts("if (cache.entries.length > ", String(cache_size.value), - ") Table$remove(&cache, cache.entries.data + " - "cache.entries.stride*0, table_type);\n"); - } - - if (!args->next) { - // Single-argument functions have simplified caching logic - type_t *arg_type = get_arg_ast_type(env, args); - Text_t wrapper = - Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, - "{\n" - "static Table_t cache = {};\n", - "const TypeInfo_t *table_type = Table$info(", compile_type_info(arg_type), ", ", - compile_type_info(ret_t), ");\n", - compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), - " = Table$get_raw(cache, &_$", args->name, - ", table_type);\n" - "if (cached) return *cached;\n", - compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(_$", args->name, ");\n", - pop_code, "Table$set(&cache, &_$", args->name, - ", &ret, table_type);\n" - "return ret;\n" - "}\n"); - definition = Texts(definition, wrapper); - } else { - // Multi-argument functions use a custom struct type (only defined - // internally) as a cache key: - arg_t *fields = NULL; - for (arg_ast_t *arg = args; arg; arg = arg->next) - fields = new (arg_t, .name = arg->name, .type = get_arg_ast_type(env, arg), .next = fields); - REVERSE_LIST(fields); - type_t *t = Type(StructType, .name = String("func$", get_line_number(ast->file, ast->start), "$args"), - .fields = fields, .env = env); - - int64_t num_fields = used_names.entries.length; - const char *metamethods = is_packed_data(t) ? "PackedData$metamethods" : "Struct$metamethods"; - Text_t args_typeinfo = - Texts("((TypeInfo_t[1]){{.size=sizeof(args), " - ".align=__alignof__(args), .metamethods=", - metamethods, - ", .tag=StructInfo, " - ".StructInfo.name=\"FunctionArguments\", " - ".StructInfo.num_fields=", - String(num_fields), ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){"); - Text_t args_type = Text("struct { "); - for (arg_t *f = fields; f; f = f->next) { - args_typeinfo = Texts(args_typeinfo, "{\"", f->name, "\", ", compile_type_info(f->type), "}"); - args_type = Texts(args_type, compile_declaration(f->type, Text$from_str(f->name)), "; "); - if (f->next) args_typeinfo = Texts(args_typeinfo, ", "); - } - args_type = Texts(args_type, "}"); - args_typeinfo = Texts(args_typeinfo, "}}})"); - - Text_t all_args = EMPTY_TEXT; - for (arg_ast_t *arg = args; arg; arg = arg->next) - all_args = Texts(all_args, "_$", arg->name, arg->next ? Text(", ") : EMPTY_TEXT); - - Text_t wrapper = Texts( - is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, - "{\n" - "static Table_t cache = {};\n", - args_type, " args = {", all_args, - "};\n" - "const TypeInfo_t *table_type = Table$info(", - args_typeinfo, ", ", compile_type_info(ret_t), ");\n", - compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), - " = Table$get_raw(cache, &args, table_type);\n" - "if (cached) return *cached;\n", - compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(", all_args, ");\n", pop_code, - "Table$set(&cache, &args, &ret, table_type);\n" - "return ret;\n" - "}\n"); - definition = Texts(definition, wrapper); - } - } - - Text_t qualified_name = Text$from_str(function_name); - if (env->namespace && env->namespace->parent && env->namespace->name) - qualified_name = Texts(env->namespace->name, ".", qualified_name); - Text_t text = Texts("func ", qualified_name, "("); - for (arg_ast_t *arg = args; arg; arg = arg->next) { - text = Texts(text, type_to_text(get_arg_ast_type(env, arg))); - if (arg->next) text = Texts(text, ", "); - } - if (ret_t && ret_t->tag != VoidType) text = Texts(text, "->", type_to_text(ret_t)); - text = Texts(text, ")"); - return definition; -} - -Text_t compile_top_level_code(env_t *env, ast_t *ast) { - if (!ast) return EMPTY_TEXT; - - switch (ast->tag) { - case Use: { - // DeclareMatch(use, ast, Use); - // if (use->what == USE_C_CODE) { - // Path_t path = Path$relative_to(Path$from_str(use->path), - // Path(".build")); return Texts("#include \"", - // Path$as_c_string(path), - // "\"\n"); - // } - return EMPTY_TEXT; - } - case Declare: { - DeclareMatch(decl, ast, Declare); - const char *decl_name = Match(decl->var, Var)->name; - Text_t full_name = namespace_name(env, env->namespace, Text$from_str(decl_name)); - type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (t->tag == FunctionType) t = Type(ClosureType, t); - Text_t val_code = compile_declared_value(env, ast); - bool is_private = decl_name[0] == '_'; - if ((decl->value && is_constant(env, decl->value)) || (!decl->value && !has_heap_memory(t))) { - set_binding(env, decl_name, t, full_name); - return Texts(is_private ? "static " : "public ", compile_declaration(t, full_name), " = ", val_code, ";\n"); - } else { - Text_t init_var = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); - Text_t checked_access = Texts("check_initialized(", full_name, ", ", init_var, ", \"", decl_name, "\")"); - set_binding(env, decl_name, t, checked_access); - - Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); - return Texts("static bool ", initialized_name, " = false;\n", is_private ? "static " : "public ", - compile_declaration(t, full_name), ";\n"); - } - } - case FunctionDef: { - Text_t name_code = - namespace_name(env, env->namespace, Text$from_str(Match(Match(ast, FunctionDef)->name, Var)->name)); - return compile_function(env, name_code, ast, &env->code->staticdefs); - } - case ConvertDef: { - type_t *type = get_function_def_type(env, ast); - const char *name = get_type_name(Match(type, FunctionType)->ret); - if (!name) - code_err(ast, - "Conversions are only supported for text, struct, and enum " - "types, not ", - type_to_str(Match(type, FunctionType)->ret)); - Text_t name_code = - namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start)))); - return compile_function(env, name_code, ast, &env->code->staticdefs); - } - case StructDef: { - DeclareMatch(def, ast, StructDef); - type_t *t = Table$str_get(*env->types, def->name); - assert(t && t->tag == StructType); - Text_t code = compile_struct_typeinfo(env, t, def->name, def->fields, def->secret, def->opaque); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); - } - case EnumDef: { - DeclareMatch(def, ast, EnumDef); - Text_t code = compile_enum_typeinfo(env, ast); - code = Texts(code, compile_enum_constructors(env, ast)); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); - } - case LangDef: { - DeclareMatch(def, ast, LangDef); - Text_t code = - Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {", - String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)), - ", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", quoted_str(def->name), "}};\n"); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); - } - case Extend: { - DeclareMatch(extend, ast, Extend); - binding_t *b = get_binding(env, extend->name); - if (!b || b->type->tag != TypeInfoType) - code_err(ast, "'", extend->name, "' is not the name of any type I recognize."); - env_t *ns_env = Match(b->type, TypeInfoType)->env; - env_t *extended = new (env_t); - *extended = *ns_env; - extended->locals = new (Table_t, .fallback = env->locals); - extended->namespace_bindings = new (Table_t, .fallback = env->namespace_bindings); - extended->id_suffix = env->id_suffix; - return compile_top_level_code(extended, extend->body); - } - case Extern: return EMPTY_TEXT; - case Block: { - Text_t code = EMPTY_TEXT; - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { - code = Texts(code, compile_top_level_code(env, stmt->ast)); - } - return code; - } - default: return EMPTY_TEXT; - } -} - -static void initialize_vars_and_statics(env_t *env, ast_t *ast) { - if (!ast) return; - - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { - if (stmt->ast->tag == InlineCCode) { - Text_t code = compile_statement(env, stmt->ast); - env->code->staticdefs = Texts(env->code->staticdefs, code, "\n"); - } else if (stmt->ast->tag == Declare) { - DeclareMatch(decl, stmt->ast, Declare); - const char *decl_name = Match(decl->var, Var)->name; - Text_t full_name = namespace_name(env, env->namespace, Text$from_str(decl_name)); - type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (t->tag == FunctionType) t = Type(ClosureType, t); - Text_t val_code = compile_declared_value(env, stmt->ast); - if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { - Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); - env->code->variable_initializers = - Texts(env->code->variable_initializers, - with_source_info(env, stmt->ast, - Texts(full_name, " = ", val_code, ",\n", initialized_name, " = true;\n"))); - } - } else if (stmt->ast->tag == StructDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, StructDef)->name), - Match(stmt->ast, StructDef)->namespace); - } else if (stmt->ast->tag == EnumDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, EnumDef)->name), - Match(stmt->ast, EnumDef)->namespace); - } else if (stmt->ast->tag == LangDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, LangDef)->name), - Match(stmt->ast, LangDef)->namespace); - } else if (stmt->ast->tag == Extend) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, Extend)->name), - Match(stmt->ast, Extend)->body); - } else if (stmt->ast->tag == Use) { - continue; - } else { - Text_t code = compile_statement(env, stmt->ast); - if (code.length > 0) code_err(stmt->ast, "I did not expect this to generate code"); - } - } -} - -Text_t compile_file(env_t *env, ast_t *ast) { - Text_t top_level_code = compile_top_level_code(env, ast); - Text_t includes = EMPTY_TEXT; - Text_t use_imports = EMPTY_TEXT; - - // First prepare variable initializers to prevent unitialized access: - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { - if (stmt->ast->tag == Use) { - use_imports = Texts(use_imports, compile_statement(env, stmt->ast)); - - DeclareMatch(use, stmt->ast, Use); - if (use->what == USE_C_CODE) { - Path_t path = Path$relative_to(Path$from_str(use->path), Path(".build")); - includes = Texts(includes, "#include \"", Path$as_c_string(path), "\"\n"); - } - } - } - - initialize_vars_and_statics(env, ast); - - const char *name = file_base_name(ast->file->filename); - return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, - "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", - "#include <tomo_" TOMO_VERSION "/tomo.h>\n" - "#include \"", - name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n", - env->code->staticdefs, "\n", top_level_code, "public void ", - namespace_name(env, env->namespace, Text("$initialize")), "(void) {\n", - "static bool initialized = false;\n", "if (initialized) return;\n", "initialized = true;\n", - use_imports, env->code->variable_initializers, "}\n"); -} - Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) { switch (ast->tag) { case Use: { diff --git a/src/compile.h b/src/compile.h index f3758643..e0447e2e 100644 --- a/src/compile.h +++ b/src/compile.h @@ -9,17 +9,11 @@ Text_t compile(env_t *env, ast_t *ast); 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_type(type_t *t); -Text_t compile_type_info(type_t *t); +Text_t with_source_info(env_t *env, ast_t *ast, Text_t code); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/compile/assignments.c b/src/compile/assignments.c new file mode 100644 index 00000000..83dbd287 --- /dev/null +++ b/src/compile/assignments.c @@ -0,0 +1,201 @@ +#include "assignments.h" +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" +#include "../typecheck.h" +#include "integers.h" +#include "pointers.h" +#include "promotion.h" +#include "types.h" + +public +Text_t compile_update_assignment(env_t *env, ast_t *ast) { + if (!is_update_assignment(ast)) code_err(ast, "This is not an update assignment"); + + binary_operands_t update = UPDATE_OPERANDS(ast); + + type_t *lhs_t = get_type(env, update.lhs); + + bool needs_idemotency_fix = !is_idempotent(update.lhs); + Text_t lhs = needs_idemotency_fix ? Text("(*lhs)") : compile_lvalue(env, update.lhs); + + Text_t update_assignment = EMPTY_TEXT; + switch (ast->tag) { + case PlusUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " += ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case MinusUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " -= ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case MultiplyUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " *= ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case DivideUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " /= ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case LeftShiftUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " <<= ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case RightShiftUpdate: { + if (lhs_t->tag == IntType || lhs_t->tag == ByteType) + update_assignment = Texts(lhs, " >>= ", compile_to_type(env, update.rhs, lhs_t), ";"); + break; + } + case AndUpdate: { + if (lhs_t->tag == BoolType) + update_assignment = + Texts("if (", lhs, ") ", lhs, " = ", compile_to_type(env, update.rhs, Type(BoolType)), ";"); + break; + } + case OrUpdate: { + if (lhs_t->tag == BoolType) + update_assignment = + Texts("if (!", lhs, ") ", lhs, " = ", compile_to_type(env, update.rhs, Type(BoolType)), ";"); + break; + } + default: break; + } + + if (update_assignment.length == 0) { + ast_t *binop = new (ast_t); + *binop = *ast; + binop->tag = binop_tag(binop->tag); + if (needs_idemotency_fix) binop->__data.Plus.lhs = LiteralCode(Text("*lhs"), .type = lhs_t); + update_assignment = Texts(lhs, " = ", compile_to_type(env, binop, lhs_t), ";"); + } + + if (needs_idemotency_fix) + return Texts("{ ", compile_declaration(Type(PointerType, .pointed = lhs_t), Text("lhs")), " = &", + compile_lvalue(env, update.lhs), "; ", update_assignment, "; }"); + else return update_assignment; +} + +public +Text_t compile_declaration(type_t *t, Text_t name) { + if (t->tag == FunctionType) { + DeclareMatch(fn, t, FunctionType); + Text_t code = Texts(compile_type(fn->ret), " (*", name, ")("); + for (arg_t *arg = fn->args; arg; arg = arg->next) { + code = Texts(code, compile_type(arg->type)); + if (arg->next) code = Texts(code, ", "); + } + if (!fn->args) code = Texts(code, "void"); + return Texts(code, ")"); + } else if (t->tag != ModuleType) { + return Texts(compile_type(t), " ", name); + } else { + return EMPTY_TEXT; + } +} + +public +Text_t compile_declared_value(env_t *env, ast_t *declare_ast) { + DeclareMatch(decl, declare_ast, Declare); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + + if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) + code_err(declare_ast, "You can't declare a variable with a ", type_to_str(t), " value"); + + if (decl->value) { + Text_t val_code = compile_maybe_incref(env, decl->value, t); + if (t->tag == FunctionType) { + assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); + t = Type(ClosureType, t); + } + return val_code; + } else { + Text_t val_code = compile_empty(t); + if (val_code.length == 0) + code_err(declare_ast, "This type (", type_to_str(t), + ") cannot be uninitialized. You must provide a value."); + return val_code; + } +} + +public +Text_t compile_assignment(env_t *env, ast_t *target, Text_t value) { + return Texts(compile_lvalue(env, target), " = ", value); +} + +public +Text_t compile_lvalue(env_t *env, ast_t *ast) { + if (!can_be_mutated(env, ast)) { + if (ast->tag == Index) { + ast_t *subject = Match(ast, Index)->indexed; + code_err(subject, "This is an immutable value, you can't mutate " + "its contents"); + } else if (ast->tag == FieldAccess) { + ast_t *subject = Match(ast, FieldAccess)->fielded; + type_t *t = get_type(env, subject); + code_err(subject, "This is an immutable ", type_to_str(t), " value, you can't assign to its fields"); + } else { + code_err(ast, "This is a value of type ", type_to_str(get_type(env, ast)), + " and can't be used as an assignment target"); + } + } + + if (ast->tag == Index) { + DeclareMatch(index, ast, Index); + type_t *container_t = get_type(env, index->indexed); + if (container_t->tag == OptionalType) + code_err(index->indexed, "This value might be none, so it can't be " + "safely used as an assignment target"); + + if (!index->index && container_t->tag == PointerType) return compile(env, ast); + + container_t = value_type(container_t); + type_t *index_t = get_type(env, index->index); + if (container_t->tag == ListType) { + Text_t target_code = compile_to_pointer_depth(env, index->indexed, 1, false); + type_t *item_type = Match(container_t, ListType)->item_type; + Text_t index_code = + index->index->tag == Int + ? compile_int_to_type(env, index->index, Type(IntType, .bits = TYPE_IBITS64)) + : (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, index->index), ", no)") + : Texts("(Int64_t)(", compile(env, index->index), ")")); + if (index->unchecked) { + return Texts("List_lvalue_unchecked(", compile_type(item_type), ", ", target_code, ", ", index_code, + ")"); + } else { + return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ", + String((int)(ast->start - ast->file->text)), ", ", + String((int)(ast->end - ast->file->text)), ")"); + } + } else if (container_t->tag == TableType) { + DeclareMatch(table_type, container_t, TableType); + if (table_type->default_value) { + type_t *value_type = get_type(env, table_type->default_value); + return Texts("*Table$get_or_setdefault(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", + compile_type(table_type->key_type), ", ", compile_type(value_type), ", ", + compile_to_type(env, index->index, table_type->key_type), ", ", + compile_to_type(env, table_type->default_value, table_type->value_type), ", ", + compile_type_info(container_t), ")"); + } + if (index->unchecked) code_err(ast, "Table indexes cannot be unchecked"); + return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(", + compile_to_pointer_depth(env, index->indexed, 1, false), ", ", + compile_to_type(env, index->index, Type(PointerType, table_type->key_type, .is_stack = true)), + ", NULL,", compile_type_info(container_t), ")"); + } else { + code_err(ast, "I don't know how to assign to this target"); + } + } else if (ast->tag == Var || ast->tag == FieldAccess || ast->tag == InlineCCode) { + return compile(env, ast); + } else { + code_err(ast, "I don't know how to assign to this"); + } + return EMPTY_TEXT; +} diff --git a/src/compile/assignments.h b/src/compile/assignments.h new file mode 100644 index 00000000..a906db33 --- /dev/null +++ b/src/compile/assignments.h @@ -0,0 +1,11 @@ + +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t compile_update_assignment(env_t *env, ast_t *ast); +Text_t compile_declaration(type_t *t, Text_t name); +Text_t compile_declared_value(env_t *env, ast_t *declare_ast); +Text_t compile_assignment(env_t *env, ast_t *target, Text_t value); +Text_t compile_lvalue(env_t *env, ast_t *ast); diff --git a/src/compile/enums.c b/src/compile/enums.c index f09d41ba..1e20f5bf 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -9,8 +9,10 @@ #include "../stdlib/tables.h" #include "../stdlib/text.h" #include "../typecheck.h" +#include "assignments.h" #include "pointers.h" #include "structs.h" +#include "types.h" Text_t compile_enum_typeinfo(env_t *env, ast_t *ast) { DeclareMatch(def, ast, EnumDef); @@ -157,7 +159,7 @@ Text_t compile_empty_enum(type_t *t) { public Text_t compile_enum_field_access(env_t *env, ast_t *ast) { DeclareMatch(f, ast, FieldAccess); - type_t *fielded_t = get_type(env, ast); + type_t *fielded_t = get_type(env, f->fielded); type_t *value_t = value_type(fielded_t); DeclareMatch(e, value_t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { diff --git a/src/compile/files.c b/src/compile/files.c new file mode 100644 index 00000000..d9f4052a --- /dev/null +++ b/src/compile/files.c @@ -0,0 +1,195 @@ +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../naming.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/paths.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" +#include "../typecheck.h" +#include "../types.h" +#include "assignments.h" +#include "enums.h" +#include "functions.h" +#include "statements.h" +#include "structs.h" +#include "types.h" + +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("\"")); } + +static void initialize_vars_and_statics(env_t *env, ast_t *ast) { + if (!ast) return; + + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->ast->tag == InlineCCode) { + Text_t code = compile_statement(env, stmt->ast); + env->code->staticdefs = Texts(env->code->staticdefs, code, "\n"); + } else if (stmt->ast->tag == Declare) { + DeclareMatch(decl, stmt->ast, Declare); + const char *decl_name = Match(decl->var, Var)->name; + Text_t full_name = namespace_name(env, env->namespace, Text$from_str(decl_name)); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + if (t->tag == FunctionType) t = Type(ClosureType, t); + Text_t val_code = compile_declared_value(env, stmt->ast); + if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { + Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); + env->code->variable_initializers = + Texts(env->code->variable_initializers, + with_source_info(env, stmt->ast, + Texts(full_name, " = ", val_code, ",\n", initialized_name, " = true;\n"))); + } + } else if (stmt->ast->tag == StructDef) { + initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, StructDef)->name), + Match(stmt->ast, StructDef)->namespace); + } else if (stmt->ast->tag == EnumDef) { + initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, EnumDef)->name), + Match(stmt->ast, EnumDef)->namespace); + } else if (stmt->ast->tag == LangDef) { + initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, LangDef)->name), + Match(stmt->ast, LangDef)->namespace); + } else if (stmt->ast->tag == Extend) { + initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, Extend)->name), + Match(stmt->ast, Extend)->body); + } else if (stmt->ast->tag == Use) { + continue; + } else { + Text_t code = compile_statement(env, stmt->ast); + if (code.length > 0) code_err(stmt->ast, "I did not expect this to generate code"); + } + } +} + +static Text_t compile_top_level_code(env_t *env, ast_t *ast) { + if (!ast) return EMPTY_TEXT; + + switch (ast->tag) { + case Use: { + // DeclareMatch(use, ast, Use); + // if (use->what == USE_C_CODE) { + // Path_t path = Path$relative_to(Path$from_str(use->path), + // Path(".build")); return Texts("#include \"", + // Path$as_c_string(path), + // "\"\n"); + // } + return EMPTY_TEXT; + } + case Declare: { + DeclareMatch(decl, ast, Declare); + const char *decl_name = Match(decl->var, Var)->name; + Text_t full_name = namespace_name(env, env->namespace, Text$from_str(decl_name)); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + if (t->tag == FunctionType) t = Type(ClosureType, t); + Text_t val_code = compile_declared_value(env, ast); + bool is_private = decl_name[0] == '_'; + if ((decl->value && is_constant(env, decl->value)) || (!decl->value && !has_heap_memory(t))) { + set_binding(env, decl_name, t, full_name); + return Texts(is_private ? "static " : "public ", compile_declaration(t, full_name), " = ", val_code, ";\n"); + } else { + Text_t init_var = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); + Text_t checked_access = Texts("check_initialized(", full_name, ", ", init_var, ", \"", decl_name, "\")"); + set_binding(env, decl_name, t, checked_access); + + Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); + return Texts("static bool ", initialized_name, " = false;\n", is_private ? "static " : "public ", + compile_declaration(t, full_name), ";\n"); + } + } + case FunctionDef: { + Text_t name_code = + namespace_name(env, env->namespace, Text$from_str(Match(Match(ast, FunctionDef)->name, Var)->name)); + return compile_function(env, name_code, ast, &env->code->staticdefs); + } + case ConvertDef: { + type_t *type = get_function_def_type(env, ast); + const char *name = get_type_name(Match(type, FunctionType)->ret); + if (!name) + code_err(ast, + "Conversions are only supported for text, struct, and enum " + "types, not ", + type_to_str(Match(type, FunctionType)->ret)); + Text_t name_code = + namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start)))); + return compile_function(env, name_code, ast, &env->code->staticdefs); + } + case StructDef: { + DeclareMatch(def, ast, StructDef); + type_t *t = Table$str_get(*env->types, def->name); + assert(t && t->tag == StructType); + Text_t code = compile_struct_typeinfo(env, t, def->name, def->fields, def->secret, def->opaque); + env_t *ns_env = namespace_env(env, def->name); + return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + } + case EnumDef: { + DeclareMatch(def, ast, EnumDef); + Text_t code = compile_enum_typeinfo(env, ast); + code = Texts(code, compile_enum_constructors(env, ast)); + env_t *ns_env = namespace_env(env, def->name); + return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + } + case LangDef: { + DeclareMatch(def, ast, LangDef); + Text_t code = + Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {", + String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)), + ", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", quoted_str(def->name), "}};\n"); + env_t *ns_env = namespace_env(env, def->name); + return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + } + case Extend: { + DeclareMatch(extend, ast, Extend); + binding_t *b = get_binding(env, extend->name); + if (!b || b->type->tag != TypeInfoType) + code_err(ast, "'", extend->name, "' is not the name of any type I recognize."); + env_t *ns_env = Match(b->type, TypeInfoType)->env; + env_t *extended = new (env_t); + *extended = *ns_env; + extended->locals = new (Table_t, .fallback = env->locals); + extended->namespace_bindings = new (Table_t, .fallback = env->namespace_bindings); + extended->id_suffix = env->id_suffix; + return compile_top_level_code(extended, extend->body); + } + case Extern: return EMPTY_TEXT; + case Block: { + Text_t code = EMPTY_TEXT; + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + code = Texts(code, compile_top_level_code(env, stmt->ast)); + } + return code; + } + default: return EMPTY_TEXT; + } +} + +Text_t compile_file(env_t *env, ast_t *ast) { + Text_t top_level_code = compile_top_level_code(env, ast); + Text_t includes = EMPTY_TEXT; + Text_t use_imports = EMPTY_TEXT; + + // First prepare variable initializers to prevent unitialized access: + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->ast->tag == Use) { + use_imports = Texts(use_imports, compile_statement(env, stmt->ast)); + + DeclareMatch(use, stmt->ast, Use); + if (use->what == USE_C_CODE) { + Path_t path = Path$relative_to(Path$from_str(use->path), Path(".build")); + includes = Texts(includes, "#include \"", Path$as_c_string(path), "\"\n"); + } + } + } + + initialize_vars_and_statics(env, ast); + + const char *name = file_base_name(ast->file->filename); + return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, + "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", + "#include <tomo_" TOMO_VERSION "/tomo.h>\n" + "#include \"", + name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n", + env->code->staticdefs, "\n", top_level_code, "public void ", + namespace_name(env, env->namespace, Text("$initialize")), "(void) {\n", + "static bool initialized = false;\n", "if (initialized) return;\n", "initialized = true;\n", + use_imports, env->code->variable_initializers, "}\n"); +} diff --git a/src/compile/files.h b/src/compile/files.h new file mode 100644 index 00000000..8b8b4012 --- /dev/null +++ b/src/compile/files.h @@ -0,0 +1,6 @@ +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" + +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); diff --git a/src/compile/functions.c b/src/compile/functions.c index 4015f0ca..5197b6f1 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -11,10 +11,13 @@ #include "../stdlib/util.h" #include "../typecheck.h" #include "../types.h" +#include "assignments.h" #include "integers.h" #include "promotion.h" +#include "statements.h" #include "structs.h" #include "text.h" +#include "types.h" public Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) { @@ -571,3 +574,209 @@ Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) { add_closed_vars(&closed_vars, env, body_scope, block); return closed_vars; } + +public +Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { + bool is_private = false; + const char *function_name; + arg_ast_t *args; + type_t *ret_t; + ast_t *body; + ast_t *cache; + bool is_inline; + if (ast->tag == FunctionDef) { + DeclareMatch(fndef, ast, FunctionDef); + function_name = Match(fndef->name, Var)->name; + is_private = function_name[0] == '_'; + args = fndef->args; + ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType); + body = fndef->body; + cache = fndef->cache; + is_inline = fndef->is_inline; + } else { + DeclareMatch(convertdef, ast, ConvertDef); + args = convertdef->args; + ret_t = convertdef->ret_type ? parse_type_ast(env, convertdef->ret_type) : Type(VoidType); + function_name = get_type_name(ret_t); + if (!function_name) + code_err(ast, + "Conversions are only supported for text, struct, and enum " + "types, not ", + type_to_str(ret_t)); + body = convertdef->body; + cache = convertdef->cache; + is_inline = convertdef->is_inline; + } + + Text_t arg_signature = Text("("); + Table_t used_names = {}; + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + arg_signature = Texts(arg_signature, compile_declaration(arg_type, Texts("_$", arg->name))); + if (arg->next) arg_signature = Texts(arg_signature, ", "); + if (Table$str_get(used_names, arg->name)) + code_err(ast, "The argument name '", arg->name, "' is used more than once"); + Table$str_set(&used_names, arg->name, arg->name); + } + arg_signature = Texts(arg_signature, ")"); + + Text_t ret_type_code = compile_type(ret_t); + if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code); + + if (is_private) *staticdefs = Texts(*staticdefs, "static ", ret_type_code, " ", name_code, arg_signature, ";\n"); + + Text_t code; + if (cache) { + code = Texts("static ", ret_type_code, " ", name_code, "$uncached", arg_signature); + } else { + code = Texts(ret_type_code, " ", name_code, arg_signature); + if (is_inline) code = Texts("INLINE ", code); + if (!is_private) code = Texts("public ", code); + } + + env_t *body_scope = fresh_scope(env); + while (body_scope->namespace) { + body_scope->locals->fallback = body_scope->locals->fallback->fallback; + body_scope->namespace = body_scope->namespace->parent; + } + + body_scope->deferred = NULL; + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn_ret = ret_t; + + type_t *body_type = get_type(body_scope, body); + if (ret_t->tag == AbortType) { + if (body_type->tag != AbortType) code_err(ast, "This function can reach the end without aborting!"); + } else if (ret_t->tag == VoidType) { + if (body_type->tag == AbortType) + code_err(ast, "This function will always abort before it reaches the " + "end, but it's declared as having a Void return. It should " + "be declared as an Abort return instead."); + } else { + if (body_type->tag != ReturnType && body_type->tag != AbortType) + code_err(ast, + "This function looks like it can reach the end without " + "returning a ", + type_to_str(ret_t), + " value! \n " + "If this is not the case, please add a call to " + "`fail(\"Unreachable\")` at the end of the function to " + "help the " + "compiler out."); + } + + Text_t body_code = Texts("{\n", compile_inline_block(body_scope, body), "}\n"); + Text_t definition = with_source_info(env, ast, Texts(code, " ", body_code, "\n")); + + if (cache && args == NULL) { // no-args cache just uses a static var + Text_t wrapper = + Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, + "(void) {\n" + "static ", + compile_declaration(ret_t, Text("cached_result")), ";\n", "static bool initialized = false;\n", + "if (!initialized) {\n" + "\tcached_result = ", + name_code, "$uncached();\n", "\tinitialized = true;\n", "}\n", + "return cached_result;\n" + "}\n"); + definition = Texts(definition, wrapper); + } else if (cache && cache->tag == Int) { + assert(args); + OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL); + Text_t pop_code = EMPTY_TEXT; + if (cache->tag == Int && !cache_size.is_none && cache_size.value > 0) { + // FIXME: this currently just deletes the first entry, but this + // should be more like a least-recently-used cache eviction policy + // or least-frequently-used + pop_code = Texts("if (cache.entries.length > ", String(cache_size.value), + ") Table$remove(&cache, cache.entries.data + " + "cache.entries.stride*0, table_type);\n"); + } + + if (!args->next) { + // Single-argument functions have simplified caching logic + type_t *arg_type = get_arg_ast_type(env, args); + Text_t wrapper = + Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, + "{\n" + "static Table_t cache = {};\n", + "const TypeInfo_t *table_type = Table$info(", compile_type_info(arg_type), ", ", + compile_type_info(ret_t), ");\n", + compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), + " = Table$get_raw(cache, &_$", args->name, + ", table_type);\n" + "if (cached) return *cached;\n", + compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(_$", args->name, ");\n", + pop_code, "Table$set(&cache, &_$", args->name, + ", &ret, table_type);\n" + "return ret;\n" + "}\n"); + definition = Texts(definition, wrapper); + } else { + // Multi-argument functions use a custom struct type (only defined + // internally) as a cache key: + arg_t *fields = NULL; + for (arg_ast_t *arg = args; arg; arg = arg->next) + fields = new (arg_t, .name = arg->name, .type = get_arg_ast_type(env, arg), .next = fields); + REVERSE_LIST(fields); + type_t *t = Type(StructType, .name = String("func$", get_line_number(ast->file, ast->start), "$args"), + .fields = fields, .env = env); + + int64_t num_fields = used_names.entries.length; + const char *metamethods = is_packed_data(t) ? "PackedData$metamethods" : "Struct$metamethods"; + Text_t args_typeinfo = + Texts("((TypeInfo_t[1]){{.size=sizeof(args), " + ".align=__alignof__(args), .metamethods=", + metamethods, + ", .tag=StructInfo, " + ".StructInfo.name=\"FunctionArguments\", " + ".StructInfo.num_fields=", + String(num_fields), ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){"); + Text_t args_type = Text("struct { "); + for (arg_t *f = fields; f; f = f->next) { + args_typeinfo = Texts(args_typeinfo, "{\"", f->name, "\", ", compile_type_info(f->type), "}"); + args_type = Texts(args_type, compile_declaration(f->type, Text$from_str(f->name)), "; "); + if (f->next) args_typeinfo = Texts(args_typeinfo, ", "); + } + args_type = Texts(args_type, "}"); + args_typeinfo = Texts(args_typeinfo, "}}})"); + + Text_t all_args = EMPTY_TEXT; + for (arg_ast_t *arg = args; arg; arg = arg->next) + all_args = Texts(all_args, "_$", arg->name, arg->next ? Text(", ") : EMPTY_TEXT); + + Text_t wrapper = Texts( + is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, + "{\n" + "static Table_t cache = {};\n", + args_type, " args = {", all_args, + "};\n" + "const TypeInfo_t *table_type = Table$info(", + args_typeinfo, ", ", compile_type_info(ret_t), ");\n", + compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), + " = Table$get_raw(cache, &args, table_type);\n" + "if (cached) return *cached;\n", + compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(", all_args, ");\n", pop_code, + "Table$set(&cache, &args, &ret, table_type);\n" + "return ret;\n" + "}\n"); + definition = Texts(definition, wrapper); + } + } + + Text_t qualified_name = Text$from_str(function_name); + if (env->namespace && env->namespace->parent && env->namespace->name) + qualified_name = Texts(env->namespace->name, ".", qualified_name); + Text_t text = Texts("func ", qualified_name, "("); + for (arg_ast_t *arg = args; arg; arg = arg->next) { + text = Texts(text, type_to_text(get_arg_ast_type(env, arg))); + if (arg->next) text = Texts(text, ", "); + } + if (ret_t && ret_t->tag != VoidType) text = Texts(text, "->", type_to_text(ret_t)); + text = Texts(text, ")"); + return definition; +} diff --git a/src/compile/functions.h b/src/compile/functions.h index f7edd2aa..da9a6992 100644 --- a/src/compile/functions.h +++ b/src/compile/functions.h @@ -7,3 +7,4 @@ Text_t compile_function_call(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_lambda(env_t *env, ast_t *ast); Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block); +Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs); diff --git a/src/compile/lists.c b/src/compile/lists.c index e5b024ee..9e9fa2fa 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -16,6 +16,8 @@ #include "optionals.h" #include "pointers.h" #include "promotion.h" +#include "statements.h" +#include "types.h" static ast_t *add_to_list_comprehension(ast_t *item, ast_t *subject) { return WrapAST(item, MethodCall, .name = "insert", .self = subject, .args = new (arg_ast_t, .value = item)); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 4b360b31..fa7f6f29 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -5,6 +5,7 @@ #include "../stdlib/text.h" #include "../stdlib/util.h" #include "../types.h" +#include "types.h" Text_t optional_into_nonnone(type_t *t, Text_t value) { if (t->tag == OptionalType) t = Match(t, OptionalType)->type; @@ -83,3 +84,29 @@ Text_t compile_none(type_t *t) { } return EMPTY_TEXT; } + +public +Text_t check_none(type_t *t, Text_t value) { + t = Match(t, OptionalType)->type; + // NOTE: these use statement expressions ({...;}) because some compilers + // complain about excessive parens around equality comparisons + if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) + return Texts("({", value, " == NULL;})"); + else if (t == PATH_TYPE) return Texts("({(", value, ").type.$tag == PATH_NONE;})"); + else if (t == PATH_TYPE_TYPE) return Texts("({(", value, ").$tag == PATH_NONE;})"); + else if (t->tag == BigIntType) return Texts("({(", value, ").small == 0;})"); + else if (t->tag == ClosureType) return Texts("({(", value, ").fn == NULL;})"); + else if (t->tag == NumType) + return Texts(Match(t, NumType)->bits == TYPE_NBITS64 ? "Num$isnan(" : "Num32$isnan(", value, ")"); + else if (t->tag == ListType) return Texts("({(", value, ").length < 0;})"); + else if (t->tag == TableType || t->tag == SetType) return Texts("({(", value, ").entries.length < 0;})"); + else if (t->tag == BoolType) return Texts("({(", value, ") == NONE_BOOL;})"); + else if (t->tag == TextType) return Texts("({(", value, ").length < 0;})"); + else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("(", value, ").is_none"); + else if (t->tag == EnumType) { + if (enum_has_fields(t)) return Texts("({(", value, ").$tag == 0;})"); + else return Texts("((", value, ") == 0)"); + } + print_err("Optional check not implemented for: ", type_to_str(t)); + return EMPTY_TEXT; +} diff --git a/src/compile/optionals.h b/src/compile/optionals.h index ec5d1954..f8d18b86 100644 --- a/src/compile/optionals.h +++ b/src/compile/optionals.h @@ -1,7 +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); +Text_t check_none(type_t *t, Text_t value); diff --git a/src/compile/pointers.c b/src/compile/pointers.c index ee67b18d..7efb5179 100644 --- a/src/compile/pointers.c +++ b/src/compile/pointers.c @@ -10,6 +10,7 @@ #include "../environment.h" #include "../stdlib/text.h" #include "../typecheck.h" +#include "assignments.h" #include "promotion.h" public diff --git a/src/compile/promotion.c b/src/compile/promotion.c index fbac5888..30c8f69b 100644 --- a/src/compile/promotion.c +++ b/src/compile/promotion.c @@ -6,12 +6,15 @@ #include "../stdlib/text.h" #include "../typecheck.h" #include "../types.h" +#include "assignments.h" +#include "functions.h" #include "integers.h" #include "lists.h" #include "optionals.h" #include "pointers.h" #include "sets.h" #include "tables.h" +#include "types.h" static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } diff --git a/src/compile/sets.c b/src/compile/sets.c index 7cb56fff..2abd4a08 100644 --- a/src/compile/sets.c +++ b/src/compile/sets.c @@ -10,6 +10,8 @@ #include "optionals.h" #include "pointers.h" #include "promotion.h" +#include "statements.h" +#include "types.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)); diff --git a/src/compile/statements.c b/src/compile/statements.c new file mode 100644 index 00000000..8649b978 --- /dev/null +++ b/src/compile/statements.c @@ -0,0 +1,1072 @@ +#include <glob.h> + +#include "../ast.h" +#include "../compile.h" +#include "../environment.h" +#include "../modules.h" +#include "../naming.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/paths.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" +#include "../typecheck.h" +#include "assignments.h" +#include "functions.h" +#include "optionals.h" +#include "pointers.h" +#include "promotion.h" +#include "statements.h" +#include "text.h" +#include "types.h" + +typedef ast_t *(*comprehension_body_t)(ast_t *, ast_t *); + +public +Text_t compile_inline_block(env_t *env, ast_t *ast) { + if (ast->tag != Block) return compile_statement(env, ast); + + Text_t code = EMPTY_TEXT; + ast_list_t *stmts = Match(ast, Block)->statements; + deferral_t *prev_deferred = env->deferred; + env = fresh_scope(env); + for (ast_list_t *stmt = stmts; stmt; stmt = stmt->next) + prebind_statement(env, stmt->ast); + for (ast_list_t *stmt = stmts; stmt; stmt = stmt->next) { + code = Texts(code, compile_statement(env, stmt->ast), "\n"); + bind_statement(env, stmt->ast); + } + for (deferral_t *deferred = env->deferred; deferred && deferred != prev_deferred; deferred = deferred->next) { + code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); + } + return code; +} + +public +Text_t compile_condition(env_t *env, ast_t *ast) { + type_t *t = get_type(env, ast); + if (t->tag == BoolType) { + return compile(env, ast); + } else if (t->tag == TextType) { + return Texts("(", compile(env, ast), ").length"); + } else if (t->tag == ListType) { + return Texts("(", compile(env, ast), ").length"); + } else if (t->tag == TableType || t->tag == SetType) { + return Texts("(", compile(env, ast), ").entries.length"); + } else if (t->tag == OptionalType) { + return Texts("!", check_none(t, compile(env, ast))); + } else if (t->tag == PointerType) { + code_err(ast, "This pointer will always be non-none, so it should not be " + "used in a conditional."); + } else { + code_err(ast, type_to_str(t), " values cannot be used for conditionals"); + } + return EMPTY_TEXT; +} + +static Text_t _compile_statement(env_t *env, ast_t *ast) { + switch (ast->tag) { + case When: { + // Typecheck to verify exhaustiveness: + type_t *result_t = get_type(env, ast); + (void)result_t; + + DeclareMatch(when, ast, When); + type_t *subject_t = get_type(env, when->subject); + + if (subject_t->tag != EnumType) { + Text_t prefix = EMPTY_TEXT, suffix = EMPTY_TEXT; + ast_t *subject = when->subject; + if (!is_idempotent(when->subject)) { + prefix = Texts("{\n", compile_declaration(subject_t, Text("_when_subject")), " = ", + compile(env, subject), ";\n"); + suffix = Text("}\n"); + subject = LiteralCode(Text("_when_subject"), .type = subject_t); + } + + Text_t code = EMPTY_TEXT; + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + ast_t *comparison = WrapAST(clause->pattern, Equals, .lhs = subject, .rhs = clause->pattern); + (void)get_type(env, comparison); + if (code.length > 0) code = Texts(code, "else "); + code = Texts(code, "if (", compile(env, comparison), ")", compile_statement(env, clause->body)); + } + if (when->else_body) code = Texts(code, "else ", compile_statement(env, when->else_body)); + code = Texts(prefix, code, suffix); + return code; + } + + DeclareMatch(enum_t, subject_t, EnumType); + + Text_t code; + if (enum_has_fields(subject_t)) + code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); + else code = Texts("switch(", compile(env, when->subject), ") {\n"); + + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + if (clause->pattern->tag == Var) { + const char *clause_tag_name = Match(clause->pattern, Var)->name; + type_t *clause_type = clause->body ? get_type(env, clause->body) : Type(VoidType); + code = Texts( + code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)), + ": {\n", compile_inline_block(env, clause->body), + (clause_type->tag == ReturnType || clause_type->tag == AbortType) ? EMPTY_TEXT : Text("break;\n"), + "}\n"); + continue; + } + + if (clause->pattern->tag != FunctionCall || Match(clause->pattern, FunctionCall)->fn->tag != Var) + code_err(clause->pattern, "This is not a valid pattern for a ", type_to_str(subject_t), " enum type"); + + const char *clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name; + code = Texts(code, "case ", + namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)), ": {\n"); + type_t *tag_type = NULL; + for (tag_t *tag = enum_t->tags; tag; tag = tag->next) { + if (streq(tag->name, clause_tag_name)) { + tag_type = tag->type; + break; + } + } + assert(tag_type); + env_t *scope = env; + + DeclareMatch(tag_struct, tag_type, StructType); + arg_ast_t *args = Match(clause->pattern, FunctionCall)->args; + if (args && !args->next && tag_struct->fields && tag_struct->fields->next) { + if (args->value->tag != Var) code_err(args->value, "This is not a valid variable to bind to"); + const char *var_name = Match(args->value, Var)->name; + if (!streq(var_name, "_")) { + Text_t var = Texts("_$", var_name); + code = Texts(code, compile_declaration(tag_type, var), " = _when_subject.", + valid_c_name(clause_tag_name), ";\n"); + scope = fresh_scope(scope); + set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT); + } + } else if (args) { + scope = fresh_scope(scope); + arg_t *field = tag_struct->fields; + for (arg_ast_t *arg = args; arg || field; arg = arg->next) { + if (!arg) + code_err(ast, "The field ", type_to_str(subject_t), ".", clause_tag_name, ".", field->name, + " wasn't accounted for"); + if (!field) code_err(arg->value, "This is one more field than ", type_to_str(subject_t), " has"); + if (arg->name) code_err(arg->value, "Named arguments are not currently supported"); + + const char *var_name = Match(arg->value, Var)->name; + if (!streq(var_name, "_")) { + Text_t var = Texts("_$", var_name); + code = Texts(code, compile_declaration(field->type, var), " = _when_subject.", + valid_c_name(clause_tag_name), ".", valid_c_name(field->name), ";\n"); + set_binding(scope, Match(arg->value, Var)->name, field->type, var); + } + field = field->next; + } + } + if (clause->body->tag == Block) { + ast_list_t *statements = Match(clause->body, Block)->statements; + if (!statements || (statements->ast->tag == Pass && !statements->next)) + code = Texts(code, "break;\n}\n"); + else code = Texts(code, compile_inline_block(scope, clause->body), "\nbreak;\n}\n"); + } else { + code = Texts(code, compile_statement(scope, clause->body), "\nbreak;\n}\n"); + } + } + if (when->else_body) { + if (when->else_body->tag == Block) { + ast_list_t *statements = Match(when->else_body, Block)->statements; + if (!statements || (statements->ast->tag == Pass && !statements->next)) + code = Texts(code, "default: break;"); + else code = Texts(code, "default: {\n", compile_inline_block(env, when->else_body), "\nbreak;\n}\n"); + } else { + code = Texts(code, "default: {\n", compile_statement(env, when->else_body), "\nbreak;\n}\n"); + } + } else { + code = Texts(code, "default: errx(1, \"Invalid tag!\");\n"); + } + code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n"); + return code; + } + case DocTest: { + DeclareMatch(test, ast, DocTest); + type_t *expr_t = get_type(env, test->expr); + if (!expr_t) code_err(test->expr, "I couldn't figure out the type of this expression"); + + Text_t setup = EMPTY_TEXT; + Text_t test_code; + if (test->expr->tag == Declare) { + DeclareMatch(decl, test->expr, Declare); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + if (t->tag == FunctionType) t = Type(ClosureType, t); + Text_t var = Texts("_$", Match(decl->var, Var)->name); + Text_t val_code = compile_declared_value(env, test->expr); + setup = Texts(compile_declaration(t, var), ";\n"); + test_code = Texts("(", var, " = ", val_code, ")"); + expr_t = t; + } else if (test->expr->tag == Assign) { + DeclareMatch(assign, test->expr, Assign); + if (!assign->targets->next && assign->targets->ast->tag == Var && is_idempotent(assign->targets->ast)) { + // Common case: assigning to one variable: + type_t *lhs_t = get_type(env, assign->targets->ast); + if (assign->targets->ast->tag == Index && lhs_t->tag == OptionalType + && value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType) + lhs_t = Match(lhs_t, OptionalType)->type; + if (has_stack_memory(lhs_t)) + code_err(test->expr, "Stack references cannot be assigned " + "to variables because the " + "variable's scope may outlive the " + "scope of the stack memory."); + env_t *val_scope = with_enum_scope(env, lhs_t); + Text_t value = compile_to_type(val_scope, assign->values->ast, lhs_t); + test_code = Texts("(", compile_assignment(env, assign->targets->ast, value), ")"); + expr_t = lhs_t; + } else { + // Multi-assign or assignment to potentially non-idempotent + // targets + if (test->expected && assign->targets->next) + code_err(ast, "Sorry, but doctesting with '=' is not " + "supported for " + "multi-assignments"); + + test_code = Text("({ // Assignment\n"); + + int64_t i = 1; + for (ast_list_t *target = assign->targets, *value = assign->values; target && value; + target = target->next, value = value->next) { + type_t *lhs_t = get_type(env, target->ast); + if (target->ast->tag == Index && lhs_t->tag == OptionalType + && value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType) + lhs_t = Match(lhs_t, OptionalType)->type; + if (has_stack_memory(lhs_t)) + code_err(ast, "Stack references cannot be assigned to " + "variables because the " + "variable's scope may outlive the scope " + "of the stack memory."); + if (target == assign->targets) expr_t = lhs_t; + env_t *val_scope = with_enum_scope(env, lhs_t); + Text_t val_code = compile_to_type(val_scope, value->ast, lhs_t); + test_code = Texts(test_code, compile_type(lhs_t), " $", String(i), " = ", val_code, ";\n"); + i += 1; + } + i = 1; + for (ast_list_t *target = assign->targets; target; target = target->next) { + test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); + i += 1; + } + + test_code = Texts(test_code, "$1; })"); + } + } else if (is_update_assignment(test->expr)) { + binary_operands_t update = UPDATE_OPERANDS(test->expr); + type_t *lhs_t = get_type(env, update.lhs); + if (update.lhs->tag == Index) { + type_t *indexed = value_type(get_type(env, Match(update.lhs, Index)->indexed)); + if (indexed->tag == TableType && Match(indexed, TableType)->default_value == NULL) + code_err(update.lhs, "Update assignments are not currently " + "supported for tables"); + } + + ast_t *update_var = new (ast_t); + *update_var = *test->expr; + update_var->__data.PlusUpdate.lhs = LiteralCode(Text("(*expr)"), .type = lhs_t); // UNSAFE + test_code = + Texts("({", compile_declaration(Type(PointerType, lhs_t), Text("expr")), " = &(", + compile_lvalue(env, update.lhs), "); ", compile_statement(env, update_var), "; *expr; })"); + expr_t = lhs_t; + } else if (expr_t->tag == VoidType || expr_t->tag == AbortType || expr_t->tag == ReturnType) { + test_code = Texts("({", compile_statement(env, test->expr), " NULL;})"); + } else { + test_code = compile(env, test->expr); + } + if (test->expected) { + return Texts(setup, "test(", compile_type(expr_t), ", ", test_code, ", ", + compile_to_type(env, test->expected, expr_t), ", ", compile_type_info(expr_t), ", ", + String((int64_t)(test->expr->start - test->expr->file->text)), ", ", + String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + } else { + if (expr_t->tag == VoidType || expr_t->tag == AbortType) { + return Texts(setup, "inspect_void(", test_code, ", ", compile_type_info(expr_t), ", ", + String((int64_t)(test->expr->start - test->expr->file->text)), ", ", + String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + } + return Texts(setup, "inspect(", compile_type(expr_t), ", ", test_code, ", ", compile_type_info(expr_t), + ", ", String((int64_t)(test->expr->start - test->expr->file->text)), ", ", + String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + } + } + case Assert: { + ast_t *expr = Match(ast, Assert)->expr; + ast_t *message = Match(ast, Assert)->message; + const char *failure = NULL; + switch (expr->tag) { + case And: { + DeclareMatch(and_, ast, And); + return Texts(compile_statement(env, WrapAST(ast, Assert, .expr = and_->lhs, .message = message)), + compile_statement(env, WrapAST(ast, Assert, .expr = and_->rhs, .message = message))); + } + case Equals: failure = "!="; goto assert_comparison; + case NotEquals: failure = "=="; goto assert_comparison; + case LessThan: failure = ">="; goto assert_comparison; + case LessThanOrEquals: failure = ">"; goto assert_comparison; + case GreaterThan: failure = "<="; goto assert_comparison; + case GreaterThanOrEquals: + failure = "<"; + goto assert_comparison; + { + assert_comparison:; + binary_operands_t cmp = BINARY_OPERANDS(expr); + type_t *lhs_t = get_type(env, cmp.lhs); + type_t *rhs_t = get_type(env, cmp.rhs); + type_t *operand_t; + if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + operand_t = rhs_t; + } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, cmp.rhs, lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, cmp.lhs, rhs_t)) { + operand_t = rhs_t; + } else { + code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); + } + + ast_t *lhs_var = + FakeAST(InlineCCode, .chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, Text("_lhs"))), + .type = operand_t); + ast_t *rhs_var = + FakeAST(InlineCCode, .chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, Text("_rhs"))), + .type = operand_t); + ast_t *var_comparison = new (ast_t, .file = expr->file, .start = expr->start, .end = expr->end, + .tag = expr->tag, .__data.Equals = {.lhs = lhs_var, .rhs = rhs_var}); + int64_t line = get_line_number(ast->file, ast->start); + return Texts("{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ", + compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", String(line), "\n", + compile_declaration(operand_t, Text("_rhs")), " = ", + compile_to_type(env, cmp.rhs, operand_t), ";\n", "\n#line ", String(line), "\n", "if (!(", + compile_condition(env, var_comparison), "))\n", "#line ", String(line), "\n", + Texts("fail_source(", quoted_str(ast->file->filename), ", ", + String((int64_t)(expr->start - expr->file->text)), ", ", + String((int64_t)(expr->end - expr->file->text)), ", ", + message + ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") + : Text("\"This assertion failed!\""), + ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")), + ", " + "\" ", + failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"), + "}\n"); + } + default: { + int64_t line = get_line_number(ast->file, ast->start); + return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", String(line), "\n", "fail_source(", + quoted_str(ast->file->filename), ", ", String((int64_t)(expr->start - expr->file->text)), ", ", + String((int64_t)(expr->end - expr->file->text)), ", ", + message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") + : Text("\"This assertion failed!\""), + ");\n"); + } + } + } + case Declare: { + DeclareMatch(decl, ast, Declare); + const char *name = Match(decl->var, Var)->name; + if (streq(name, "_")) { // Explicit discard + if (decl->value) return Texts("(void)", compile(env, decl->value), ";"); + else return EMPTY_TEXT; + } else { + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + if (t->tag == FunctionType) t = Type(ClosureType, t); + if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) + code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value"); + + Text_t val_code = compile_declared_value(env, ast); + return Texts(compile_declaration(t, Texts("_$", name)), " = ", val_code, ";"); + } + } + case Assign: { + DeclareMatch(assign, ast, Assign); + // Single assignment, no temp vars needed: + if (assign->targets && !assign->targets->next) { + type_t *lhs_t = get_type(env, assign->targets->ast); + if (assign->targets->ast->tag == Index && lhs_t->tag == OptionalType + && value_type(get_type(env, Match(assign->targets->ast, Index)->indexed))->tag == TableType) + lhs_t = Match(lhs_t, OptionalType)->type; + if (has_stack_memory(lhs_t)) + code_err(ast, "Stack references cannot be assigned to " + "variables because the " + "variable's scope may outlive the scope of the " + "stack memory."); + env_t *val_env = with_enum_scope(env, lhs_t); + Text_t val = compile_to_type(val_env, assign->values->ast, lhs_t); + return Texts(compile_assignment(env, assign->targets->ast, val), ";\n"); + } + + Text_t code = Text("{ // Assignment\n"); + int64_t i = 1; + for (ast_list_t *value = assign->values, *target = assign->targets; value && target; + value = value->next, target = target->next) { + type_t *lhs_t = get_type(env, target->ast); + if (target->ast->tag == Index && lhs_t->tag == OptionalType + && value_type(get_type(env, Match(target->ast, Index)->indexed))->tag == TableType) + lhs_t = Match(lhs_t, OptionalType)->type; + if (has_stack_memory(lhs_t)) + code_err(ast, "Stack references cannot be assigned to " + "variables because the " + "variable's scope may outlive the scope of the " + "stack memory."); + env_t *val_env = with_enum_scope(env, lhs_t); + Text_t val = compile_to_type(val_env, value->ast, lhs_t); + code = Texts(code, compile_type(lhs_t), " $", String(i), " = ", val, ";\n"); + i += 1; + } + i = 1; + for (ast_list_t *target = assign->targets; target; target = target->next) { + code = Texts(code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); + i += 1; + } + return Texts(code, "\n}"); + } + case PlusUpdate: { + DeclareMatch(update, ast, PlusUpdate); + type_t *lhs_t = get_type(env, update->lhs); + if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update->lhs), " += ", compile_to_type(env, update->rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); + } + case MinusUpdate: { + DeclareMatch(update, ast, MinusUpdate); + type_t *lhs_t = get_type(env, update->lhs); + if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update->lhs), " -= ", compile_to_type(env, update->rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); + } + case MultiplyUpdate: { + DeclareMatch(update, ast, MultiplyUpdate); + type_t *lhs_t = get_type(env, update->lhs); + if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update->lhs), " *= ", compile_to_type(env, update->rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); + } + case DivideUpdate: { + DeclareMatch(update, ast, DivideUpdate); + type_t *lhs_t = get_type(env, update->lhs); + if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update->lhs), " /= ", compile_to_type(env, update->rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); + } + case ModUpdate: { + DeclareMatch(update, ast, ModUpdate); + type_t *lhs_t = get_type(env, update->lhs); + if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update->lhs), " %= ", compile_to_type(env, update->rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); + } + case PowerUpdate: + case Mod1Update: + case ConcatUpdate: + case LeftShiftUpdate: + case UnsignedLeftShiftUpdate: + case RightShiftUpdate: + case UnsignedRightShiftUpdate: + case AndUpdate: + case OrUpdate: + case XorUpdate: { + return compile_update_assignment(env, ast); + } + case StructDef: + case EnumDef: + case LangDef: + case Extend: + case FunctionDef: + case ConvertDef: { + return EMPTY_TEXT; + } + case Skip: { + const char *target = Match(ast, Skip)->target; + for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) { + bool matched = !target || strcmp(target, ctx->loop_name) == 0; + for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : NULL) + matched = (strcmp(target, Match(var->ast, Var)->name) == 0); + + if (matched) { + if (ctx->skip_label.length == 0) { + static int64_t skip_label_count = 1; + ctx->skip_label = Texts("skip_", String(skip_label_count)); + ++skip_label_count; + } + Text_t code = EMPTY_TEXT; + for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; + deferred = deferred->next) + code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); + if (code.length > 0) return Texts("{\n", code, "goto ", ctx->skip_label, ";\n}\n"); + else return Texts("goto ", ctx->skip_label, ";"); + } + } + if (env->loop_ctx) code_err(ast, "This is not inside any loop"); + else if (target) code_err(ast, "No loop target named '", target, "' was found"); + else return Text("continue;"); + } + case Stop: { + const char *target = Match(ast, Stop)->target; + for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) { + bool matched = !target || strcmp(target, ctx->loop_name) == 0; + for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : var) + matched = (strcmp(target, Match(var->ast, Var)->name) == 0); + + if (matched) { + if (ctx->stop_label.length == 0) { + static int64_t stop_label_count = 1; + ctx->stop_label = Texts("stop_", String(stop_label_count)); + ++stop_label_count; + } + Text_t code = EMPTY_TEXT; + for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; + deferred = deferred->next) + code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); + if (code.length > 0) return Texts("{\n", code, "goto ", ctx->stop_label, ";\n}\n"); + else return Texts("goto ", ctx->stop_label, ";"); + } + } + if (env->loop_ctx) code_err(ast, "This is not inside any loop"); + else if (target) code_err(ast, "No loop target named '", target, "' was found"); + else return Text("break;"); + } + case Pass: return Text(";"); + case Defer: { + ast_t *body = Match(ast, Defer)->body; + Table_t closed_vars = get_closed_vars(env, NULL, body); + + static int defer_id = 0; + env_t *defer_env = fresh_scope(env); + Text_t code = EMPTY_TEXT; + for (int64_t i = 0; i < closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (entry->b->type->tag == ModuleType) continue; + if (Text$starts_with(entry->b->code, Text("userdata->"), NULL)) { + Table$str_set(defer_env->locals, entry->name, entry->b); + } else { + Text_t defer_name = Texts("defer$", String(++defer_id), "$", entry->name); + defer_id += 1; + code = Texts(code, compile_declaration(entry->b->type, defer_name), " = ", entry->b->code, ";\n"); + set_binding(defer_env, entry->name, entry->b->type, defer_name); + } + } + env->deferred = new (deferral_t, .defer_env = defer_env, .block = body, .next = env->deferred); + return code; + } + case Return: { + if (!env->fn_ret) code_err(ast, "This return statement is not inside any function"); + ast_t *ret = Match(ast, Return)->value; + + Text_t code = EMPTY_TEXT; + for (deferral_t *deferred = env->deferred; deferred; deferred = deferred->next) { + code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); + } + + if (ret) { + if (env->fn_ret->tag == VoidType || env->fn_ret->tag == AbortType) + code_err(ast, "This function is not supposed to return any values, " + "according to its type signature"); + + env = with_enum_scope(env, env->fn_ret); + Text_t value = compile_to_type(env, ret, env->fn_ret); + if (env->deferred) { + code = Texts(compile_declaration(env->fn_ret, Text("ret")), " = ", value, ";\n", code); + value = Text("ret"); + } + + return Texts(code, "return ", value, ";"); + } else { + if (env->fn_ret->tag != VoidType) + code_err(ast, "This function expects you to return a ", type_to_str(env->fn_ret), " value"); + return Texts(code, "return;"); + } + } + case While: { + DeclareMatch(while_, ast, While); + env_t *scope = fresh_scope(env); + loop_ctx_t loop_ctx = (loop_ctx_t){ + .loop_name = "while", + .deferred = scope->deferred, + .next = env->loop_ctx, + }; + scope->loop_ctx = &loop_ctx; + Text_t body = compile_statement(scope, while_->body); + if (loop_ctx.skip_label.length > 0) body = Texts(body, "\n", loop_ctx.skip_label, ": continue;"); + Text_t loop = Texts("while (", while_->condition ? compile(scope, while_->condition) : Text("yes"), ") {\n\t", + body, "\n}"); + if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;"); + return loop; + } + case Repeat: { + ast_t *body = Match(ast, Repeat)->body; + env_t *scope = fresh_scope(env); + loop_ctx_t loop_ctx = (loop_ctx_t){ + .loop_name = "repeat", + .deferred = scope->deferred, + .next = env->loop_ctx, + }; + scope->loop_ctx = &loop_ctx; + Text_t body_code = compile_statement(scope, body); + if (loop_ctx.skip_label.length > 0) body_code = Texts(body_code, "\n", loop_ctx.skip_label, ": continue;"); + Text_t loop = Texts("for (;;) {\n\t", body_code, "\n}"); + if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;"); + return loop; + } + case For: { + DeclareMatch(for_, ast, For); + + // If we're iterating over a comprehension, that's actually just doing + // one loop, we don't need to compile the comprehension as a list + // comprehension. This is a common case for reducers like `(+: i*2 for i + // in 5)` or `(and) x.is_good() for x in xs` + if (for_->iter->tag == Comprehension) { + DeclareMatch(comp, for_->iter, Comprehension); + ast_t *body = for_->body; + if (for_->vars) { + if (for_->vars->next) code_err(for_->vars->next->ast, "This is too many variables for iteration"); + + body = WrapAST( + ast, Block, + .statements = new ( + ast_list_t, .ast = WrapAST(ast, Declare, .var = for_->vars->ast, .value = comp->expr), + .next = body->tag == Block ? Match(body, Block)->statements : new (ast_list_t, .ast = body))); + } + + if (comp->filter) body = WrapAST(for_->body, If, .condition = comp->filter, .body = body); + ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); + return compile_statement(env, loop); + } + + env_t *body_scope = for_scope(env, ast); + loop_ctx_t loop_ctx = (loop_ctx_t){ + .loop_name = "for", + .loop_vars = for_->vars, + .deferred = body_scope->deferred, + .next = body_scope->loop_ctx, + }; + body_scope->loop_ctx = &loop_ctx; + // Naked means no enclosing braces: + Text_t naked_body = compile_inline_block(body_scope, for_->body); + if (loop_ctx.skip_label.length > 0) naked_body = Texts(naked_body, "\n", loop_ctx.skip_label, ": continue;"); + Text_t stop = loop_ctx.stop_label.length > 0 ? Texts("\n", loop_ctx.stop_label, ":;") : EMPTY_TEXT; + + // Special case for improving performance for numeric iteration: + if (for_->iter->tag == MethodCall && streq(Match(for_->iter, MethodCall)->name, "to") + && is_int_type(get_type(env, Match(for_->iter, MethodCall)->self))) { + // TODO: support other integer types + arg_ast_t *args = Match(for_->iter, MethodCall)->args; + if (!args) code_err(for_->iter, "to() needs at least one argument"); + + type_t *int_type = get_type(env, Match(for_->iter, MethodCall)->self); + type_t *step_type = int_type->tag == ByteType ? Type(IntType, .bits = TYPE_IBITS8) : int_type; + + Text_t last = EMPTY_TEXT, step = EMPTY_TEXT, optional_step = EMPTY_TEXT; + if (!args->name || streq(args->name, "last")) { + last = compile_to_type(env, args->value, int_type); + if (args->next) { + if (args->next->name && !streq(args->next->name, "step")) + code_err(args->next->value, "Invalid argument name: ", args->next->name); + if (get_type(env, args->next->value)->tag == OptionalType) + optional_step = compile_to_type(env, args->next->value, Type(OptionalType, step_type)); + else step = compile_to_type(env, args->next->value, step_type); + } + } else if (streq(args->name, "step")) { + if (get_type(env, args->value)->tag == OptionalType) + optional_step = compile_to_type(env, args->value, Type(OptionalType, step_type)); + else step = compile_to_type(env, args->value, step_type); + if (args->next) { + if (args->next->name && !streq(args->next->name, "last")) + code_err(args->next->value, "Invalid argument name: ", args->next->name); + last = compile_to_type(env, args->next->value, int_type); + } + } + + if (last.length == 0) code_err(for_->iter, "No `last` argument was given"); + + Text_t type_code = compile_type(int_type); + Text_t value = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); + if (int_type->tag == BigIntType) { + if (optional_step.length > 0) + step = Texts("({ OptionalInt_t maybe_step = ", optional_step, + "; maybe_step->small == 0 ? " + "(Int$compare_value(last, first) >= 0 " + "? I_small(1) : I_small(-1)) : (Int_t)maybe_step; " + "})"); + else if (step.length == 0) + step = Text("Int$compare_value(last, first) >= 0 ? " + "I_small(1) : I_small(-1)"); + return Texts("for (", type_code, " first = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", + value, " = first, last = ", last, ", step = ", step, + "; " + "Int$compare_value(", + value, ", last) != Int$compare_value(step, I_small(0)); ", value, " = Int$plus(", value, + ", step)) {\n" + "\t", + naked_body, "}", stop); + } else { + if (optional_step.length > 0) + step = Texts("({ ", compile_type(Type(OptionalType, step_type)), " maybe_step = ", optional_step, + "; " + "maybe_step.is_none ? (", + type_code, ")(last >= first ? 1 : -1) : maybe_step.value; })"); + else if (step.length == 0) step = Texts("(", type_code, ")(last >= first ? 1 : -1)"); + return Texts("for (", type_code, " first = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", + value, " = first, last = ", last, ", step = ", step, + "; " + "step > 0 ? ", + value, " <= last : ", value, " >= last; ", value, + " += step) {\n" + "\t", + naked_body, "}", stop); + } + } else if (for_->iter->tag == MethodCall && streq(Match(for_->iter, MethodCall)->name, "onward") + && get_type(env, Match(for_->iter, MethodCall)->self)->tag == BigIntType) { + // Special case for Int.onward() + arg_ast_t *args = Match(for_->iter, MethodCall)->args; + arg_t *arg_spec = + new (arg_t, .name = "step", .type = INT_TYPE, .default_val = FakeAST(Int, .str = "1"), .next = NULL); + Text_t step = compile_arguments(env, for_->iter, arg_spec, args); + Text_t value = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); + return Texts("for (Int_t ", value, " = ", compile(env, Match(for_->iter, MethodCall)->self), ", ", + "step = ", step, "; ; ", value, " = Int$plus(", value, + ", step)) {\n" + "\t", + naked_body, "}", stop); + } + + type_t *iter_t = get_type(env, for_->iter); + type_t *iter_value_t = value_type(iter_t); + + switch (iter_value_t->tag) { + case ListType: { + type_t *item_t = Match(iter_value_t, ListType)->item_type; + Text_t index = EMPTY_TEXT; + Text_t value = EMPTY_TEXT; + if (for_->vars) { + if (for_->vars->next) { + if (for_->vars->next->next) + code_err(for_->vars->next->next->ast, "This is too many variables for this loop"); + + index = compile(body_scope, for_->vars->ast); + value = compile(body_scope, for_->vars->next->ast); + } else { + value = compile(body_scope, for_->vars->ast); + } + } + + Text_t loop = EMPTY_TEXT; + loop = Texts(loop, "for (int64_t i = 1; i <= iterating.length; ++i)"); + + if (index.length > 0) naked_body = Texts("Int_t ", index, " = I(i);\n", naked_body); + + if (value.length > 0) { + loop = Texts(loop, "{\n", compile_declaration(item_t, value), " = *(", compile_type(item_t), + "*)(iterating.data + (i-1)*iterating.stride);\n", naked_body, "\n}"); + } else { + loop = Texts(loop, "{\n", naked_body, "\n}"); + } + + if (for_->empty) + loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty)); + + if (iter_t->tag == PointerType) { + loop = Texts("{\n" + "List_t *ptr = ", + compile_to_pointer_depth(env, for_->iter, 1, false), + ";\n" + "\nLIST_INCREF(*ptr);\n" + "List_t iterating = *ptr;\n", + loop, stop, + "\nLIST_DECREF(*ptr);\n" + "}\n"); + + } else { + loop = Texts("{\n" + "List_t iterating = ", + compile_to_pointer_depth(env, for_->iter, 0, false), ";\n", loop, stop, "}\n"); + } + return loop; + } + case SetType: + case TableType: { + Text_t loop = Text("for (int64_t i = 0; i < iterating.length; ++i) {\n"); + if (for_->vars) { + if (iter_value_t->tag == SetType) { + if (for_->vars->next) code_err(for_->vars->next->ast, "This is too many variables for this loop"); + Text_t item = compile(body_scope, for_->vars->ast); + type_t *item_type = Match(iter_value_t, SetType)->item_type; + loop = Texts(loop, compile_declaration(item_type, item), " = *(", compile_type(item_type), "*)(", + "iterating.data + i*iterating.stride);\n"); + } else { + Text_t key = compile(body_scope, for_->vars->ast); + type_t *key_t = Match(iter_value_t, TableType)->key_type; + loop = Texts(loop, compile_declaration(key_t, key), " = *(", compile_type(key_t), "*)(", + "iterating.data + i*iterating.stride);\n"); + + if (for_->vars->next) { + if (for_->vars->next->next) + code_err(for_->vars->next->next->ast, "This is too many variables for this loop"); + + type_t *value_t = Match(iter_value_t, TableType)->value_type; + Text_t value = compile(body_scope, for_->vars->next->ast); + Text_t value_offset = Texts("offsetof(struct { ", compile_declaration(key_t, Text("k")), "; ", + compile_declaration(value_t, Text("v")), "; }, v)"); + loop = Texts(loop, compile_declaration(value_t, value), " = *(", compile_type(value_t), "*)(", + "iterating.data + i*iterating.stride + ", value_offset, ");\n"); + } + } + } + + loop = Texts(loop, naked_body, "\n}"); + + if (for_->empty) { + loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty)); + } + + if (iter_t->tag == PointerType) { + loop = Texts("{\n", "Table_t *t = ", compile_to_pointer_depth(env, for_->iter, 1, false), + ";\n" + "LIST_INCREF(t->entries);\n" + "List_t iterating = t->entries;\n", + loop, + "LIST_DECREF(t->entries);\n" + "}\n"); + } else { + loop = Texts("{\n", "List_t iterating = (", compile_to_pointer_depth(env, for_->iter, 0, false), + ").entries;\n", loop, "}\n"); + } + return loop; + } + case BigIntType: { + Text_t n; + if (for_->iter->tag == Int) { + const char *str = Match(for_->iter, Int)->str; + Int_t int_val = Int$from_str(str); + if (int_val.small == 0) code_err(for_->iter, "Failed to parse this integer"); + mpz_t i; + mpz_init_set_int(i, int_val); + if (mpz_cmpabs_ui(i, BIGGEST_SMALL_INT) <= 0) n = Text$from_str(mpz_get_str(NULL, 10, i)); + else goto big_n; + + if (for_->empty && mpz_cmp_si(i, 0) <= 0) { + return compile_statement(env, for_->empty); + } else { + return Texts("for (int64_t i = 1; i <= ", n, "; ++i) {\n", + for_->vars + ? Texts("\tInt_t ", compile(body_scope, for_->vars->ast), " = I_small(i);\n") + : EMPTY_TEXT, + "\t", naked_body, "}\n", stop, "\n"); + } + } + + big_n: + n = compile_to_pointer_depth(env, for_->iter, 0, false); + Text_t i = for_->vars ? compile(body_scope, for_->vars->ast) : Text("i"); + Text_t n_var = for_->vars ? Texts("max", i) : Text("n"); + if (for_->empty) { + return Texts("{\n" + "Int_t ", + n_var, " = ", n, + ";\n" + "if (Int$compare_value(", + n_var, + ", I(0)) > 0) {\n" + "for (Int_t ", + i, " = I(1); Int$compare_value(", i, ", ", n_var, ") <= 0; ", i, " = Int$plus(", i, + ", I(1))) {\n", "\t", naked_body, + "}\n" + "} else ", + compile_statement(env, for_->empty), stop, + "\n" + "}\n"); + } else { + return Texts("for (Int_t ", i, " = I(1), ", n_var, " = ", n, "; Int$compare_value(", i, ", ", n_var, + ") <= 0; ", i, " = Int$plus(", i, ", I(1))) {\n", "\t", naked_body, "}\n", stop, "\n"); + } + } + case FunctionType: + case ClosureType: { + // Iterator function: + Text_t code = Text("{\n"); + + Text_t next_fn; + if (is_idempotent(for_->iter)) { + next_fn = compile_to_pointer_depth(env, for_->iter, 0, false); + } else { + code = Texts(code, compile_declaration(iter_value_t, Text("next")), " = ", + compile_to_pointer_depth(env, for_->iter, 0, false), ";\n"); + next_fn = Text("next"); + } + + __typeof(iter_value_t->__data.FunctionType) *fn = + iter_value_t->tag == ClosureType ? Match(Match(iter_value_t, ClosureType)->fn, FunctionType) + : Match(iter_value_t, FunctionType); + + Text_t get_next; + if (iter_value_t->tag == ClosureType) { + type_t *fn_t = Match(iter_value_t, ClosureType)->fn; + arg_t *closure_fn_args = NULL; + for (arg_t *arg = Match(fn_t, FunctionType)->args; arg; arg = arg->next) + closure_fn_args = new (arg_t, .name = arg->name, .type = arg->type, .default_val = arg->default_val, + .next = closure_fn_args); + closure_fn_args = new (arg_t, .name = "userdata", + .type = Type(PointerType, .pointed = Type(MemoryType)), .next = closure_fn_args); + REVERSE_LIST(closure_fn_args); + Text_t fn_type_code = + compile_type(Type(FunctionType, .args = closure_fn_args, .ret = Match(fn_t, FunctionType)->ret)); + get_next = Texts("((", fn_type_code, ")", next_fn, ".fn)(", next_fn, ".userdata)"); + } else { + get_next = Texts(next_fn, "()"); + } + + if (fn->ret->tag == OptionalType) { + // Use an optional variable `cur` for each iteration step, which + // will be checked for none + code = Texts(code, compile_declaration(fn->ret, Text("cur")), ";\n"); + get_next = Texts("(cur=", get_next, ", !", check_none(fn->ret, Text("cur")), ")"); + if (for_->vars) { + naked_body = Texts(compile_declaration(Match(fn->ret, OptionalType)->type, + Texts("_$", Match(for_->vars->ast, Var)->name)), + " = ", optional_into_nonnone(fn->ret, Text("cur")), ";\n", naked_body); + } + if (for_->empty) { + code = Texts(code, "if (", get_next, + ") {\n" + "\tdo{\n\t\t", + naked_body, "\t} while(", get_next, + ");\n" + "} else {\n\t", + compile_statement(env, for_->empty), "}", stop, "\n}\n"); + } else { + code = Texts(code, "while(", get_next, ") {\n\t", naked_body, "}\n", stop, "\n}\n"); + } + } else { + if (for_->vars) { + naked_body = Texts(compile_declaration(fn->ret, Texts("_$", Match(for_->vars->ast, Var)->name)), + " = ", get_next, ";\n", naked_body); + } else { + naked_body = Texts(get_next, ";\n", naked_body); + } + if (for_->empty) + code_err(for_->empty, "This iteration loop will always have values, " + "so this block will never run"); + code = Texts(code, "for (;;) {\n\t", naked_body, "}\n", stop, "\n}\n"); + } + + return code; + } + default: code_err(for_->iter, "Iteration is not implemented for type: ", type_to_str(iter_t)); + } + } + case If: { + DeclareMatch(if_, ast, If); + ast_t *condition = if_->condition; + if (condition->tag == Declare) { + if (Match(condition, Declare)->value == NULL) code_err(condition, "This declaration must have a value"); + env_t *truthy_scope = fresh_scope(env); + Text_t code = Texts("IF_DECLARE(", compile_statement(truthy_scope, condition), ", "); + bind_statement(truthy_scope, condition); + ast_t *var = Match(condition, Declare)->var; + code = Texts(code, compile_condition(truthy_scope, var), ", "); + type_t *cond_t = get_type(truthy_scope, var); + if (cond_t->tag == OptionalType) { + set_binding(truthy_scope, Match(var, Var)->name, Match(cond_t, OptionalType)->type, + optional_into_nonnone(cond_t, compile(truthy_scope, var))); + } + code = Texts(code, compile_statement(truthy_scope, if_->body), ")"); + if (if_->else_body) code = Texts(code, "\nelse ", compile_statement(env, if_->else_body)); + return code; + } else { + Text_t code = Texts("if (", compile_condition(env, condition), ")"); + env_t *truthy_scope = env; + type_t *cond_t = get_type(env, condition); + if (condition->tag == Var && cond_t->tag == OptionalType) { + truthy_scope = fresh_scope(env); + set_binding(truthy_scope, Match(condition, Var)->name, Match(cond_t, OptionalType)->type, + optional_into_nonnone(cond_t, compile(truthy_scope, condition))); + } + code = Texts(code, compile_statement(truthy_scope, if_->body)); + if (if_->else_body) code = Texts(code, "\nelse ", compile_statement(env, if_->else_body)); + return code; + } + } + case Block: { + return Texts("{\n", compile_inline_block(env, ast), "}\n"); + } + case Comprehension: { + if (!env->comprehension_action) code_err(ast, "I don't know what to do with this comprehension!"); + DeclareMatch(comp, ast, Comprehension); + if (comp->expr->tag == Comprehension) { // Nested comprehension + ast_t *body = comp->filter ? WrapAST(ast, If, .condition = comp->filter, .body = comp->expr) : comp->expr; + ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); + return compile_statement(env, loop); + } + + // List/Set/Table comprehension: + comprehension_body_t get_body = (void *)env->comprehension_action->fn; + ast_t *body = get_body(comp->expr, env->comprehension_action->userdata); + if (comp->filter) body = WrapAST(comp->expr, If, .condition = comp->filter, .body = body); + ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); + return compile_statement(env, loop); + } + case Extern: return EMPTY_TEXT; + case InlineCCode: { + DeclareMatch(inline_code, ast, InlineCCode); + Text_t code = EMPTY_TEXT; + for (ast_list_t *chunk = inline_code->chunks; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + code = Texts(code, Match(chunk->ast, TextLiteral)->text); + } else { + code = Texts(code, compile(env, chunk->ast)); + } + } + return code; + } + case Use: { + DeclareMatch(use, ast, Use); + if (use->what == USE_LOCAL) { + Path_t path = Path$from_str(Match(ast, Use)->path); + Path_t in_file = Path$from_str(ast->file->filename); + path = Path$resolved(path, Path$parent(in_file)); + Text_t suffix = get_id_suffix(Path$as_c_string(path)); + return with_source_info(env, ast, Texts("$initialize", suffix, "();\n")); + } else if (use->what == USE_MODULE) { + module_info_t mod = get_module_info(ast); + glob_t tm_files; + const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; + if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, + NULL, &tm_files) + != 0) { + if (!try_install_module(mod)) code_err(ast, "Could not find library"); + } + + Text_t initialization = EMPTY_TEXT; + + for (size_t i = 0; i < tm_files.gl_pathc; i++) { + const char *filename = tm_files.gl_pathv[i]; + initialization = Texts( + initialization, with_source_info(env, ast, Texts("$initialize", get_id_suffix(filename), "();\n"))); + } + globfree(&tm_files); + return initialization; + } else { + return EMPTY_TEXT; + } + } + default: + // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", + // is_discardable(env, ast)); + if (!is_discardable(env, ast)) + code_err(ast, "The ", type_to_str(get_type(env, ast)), " result of this statement cannot be discarded"); + return Texts("(void)", compile(env, ast), ";"); + } +} + +Text_t compile_statement(env_t *env, ast_t *ast) { + Text_t stmt = _compile_statement(env, ast); + return with_source_info(env, ast, stmt); +} diff --git a/src/compile/statements.h b/src/compile/statements.h new file mode 100644 index 00000000..abe34a09 --- /dev/null +++ b/src/compile/statements.h @@ -0,0 +1,7 @@ +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" + +Text_t compile_condition(env_t *env, ast_t *ast); +Text_t compile_inline_block(env_t *env, ast_t *ast); +Text_t compile_statement(env_t *env, ast_t *ast); diff --git a/src/compile/structs.c b/src/compile/structs.c index a5889ff8..3244f6f3 100644 --- a/src/compile/structs.c +++ b/src/compile/structs.c @@ -8,7 +8,10 @@ #include "../stdlib/tables.h" #include "../stdlib/text.h" #include "../typecheck.h" +#include "assignments.h" +#include "functions.h" #include "pointers.h" +#include "types.h" public Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_t *fields, bool is_secret, @@ -99,7 +102,7 @@ Text_t compile_empty_struct(type_t *t) { public Text_t compile_struct_field_access(env_t *env, ast_t *ast) { DeclareMatch(f, ast, FieldAccess); - type_t *fielded_t = get_type(env, ast); + type_t *fielded_t = get_type(env, f->fielded); type_t *value_t = value_type(fielded_t); for (arg_t *field = Match(value_t, StructType)->fields; field; field = field->next) { if (streq(field->name, f->field)) { diff --git a/src/compile/tables.c b/src/compile/tables.c index b53aacb0..c9feca5e 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -10,6 +10,8 @@ #include "optionals.h" #include "pointers.h" #include "promotion.h" +#include "statements.h" +#include "types.h" static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) { DeclareMatch(e, entry, TableEntry); diff --git a/src/compile/text.c b/src/compile/text.c index 049f8f94..9a95fd4d 100644 --- a/src/compile/text.c +++ b/src/compile/text.c @@ -10,6 +10,7 @@ #include "../typecheck.h" #include "../types.h" #include "functions.h" +#include "types.h" public Text_t expr_as_text(Text_t expr, type_t *t, Text_t color) { diff --git a/src/compile/text.h b/src/compile/text.h index cdee4a5d..80502971 100644 --- a/src/compile/text.h +++ b/src/compile/text.h @@ -2,9 +2,14 @@ #include "../ast.h" #include "../environment.h" #include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" #include "../types.h" Text_t compile_text_ast(env_t *env, ast_t *ast); Text_t compile_text(env_t *env, ast_t *ast, Text_t color); Text_t compile_text_literal(Text_t literal); Text_t expr_as_text(Text_t expr, type_t *t, Text_t color); + +MACROLIKE Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } +MACROLIKE Text_t quoted_text(Text_t text) { return Text$quoted(text, false, Text("\"")); } diff --git a/src/compile/types.c b/src/compile/types.c new file mode 100644 index 00000000..9b7cf4df --- /dev/null +++ b/src/compile/types.c @@ -0,0 +1,154 @@ +#include "../types.h" +#include "../ast.h" +#include "../environment.h" +#include "../naming.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" + +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("\"")); } + +public +Text_t compile_type(type_t *t) { + if (t == PATH_TYPE) return Text("Path_t"); + else if (t == PATH_TYPE_TYPE) return Text("PathType_t"); + + switch (t->tag) { + case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type"); + case AbortType: return Text("void"); + case VoidType: return Text("void"); + case MemoryType: return Text("void"); + case BoolType: return Text("Bool_t"); + case ByteType: return Text("Byte_t"); + case CStringType: return Text("const char*"); + case BigIntType: return Text("Int_t"); + case IntType: return Texts("Int", String(Match(t, IntType)->bits), "_t"); + case NumType: + return Match(t, NumType)->bits == TYPE_NBITS64 ? Text("Num_t") + : Texts("Num", String(Match(t, NumType)->bits), "_t"); + case TextType: { + DeclareMatch(text, t, TextType); + if (!text->lang || streq(text->lang, "Text")) return Text("Text_t"); + else return namespace_name(text->env, text->env->namespace, Text("$type")); + } + case ListType: return Text("List_t"); + case SetType: return Text("Table_t"); + case TableType: return Text("Table_t"); + case FunctionType: { + DeclareMatch(fn, t, FunctionType); + Text_t code = Texts(compile_type(fn->ret), " (*)("); + for (arg_t *arg = fn->args; arg; arg = arg->next) { + code = Texts(code, compile_type(arg->type)); + if (arg->next) code = Texts(code, ", "); + } + if (!fn->args) code = Texts(code, "void"); + return Texts(code, ")"); + } + case ClosureType: return Text("Closure_t"); + case PointerType: return Texts(compile_type(Match(t, PointerType)->pointed), "*"); + case StructType: { + DeclareMatch(s, t, StructType); + if (s->external) return Text$from_str(s->name); + return Texts("struct ", namespace_name(s->env, s->env->namespace, Text("$struct"))); + } + case EnumType: { + DeclareMatch(e, t, EnumType); + return namespace_name(e->env, e->env->namespace, Text("$type")); + } + case OptionalType: { + type_t *nonnull = Match(t, OptionalType)->type; + switch (nonnull->tag) { + case CStringType: + case FunctionType: + case ClosureType: + case PointerType: + case EnumType: return compile_type(nonnull); + case TextType: return Match(nonnull, TextType)->lang ? compile_type(nonnull) : Text("OptionalText_t"); + case IntType: + case BigIntType: + case NumType: + case BoolType: + case ByteType: + case ListType: + case TableType: + case SetType: return Texts("Optional", compile_type(nonnull)); + case StructType: { + if (nonnull == PATH_TYPE) return Text("OptionalPath_t"); + if (nonnull == PATH_TYPE_TYPE) return Text("OptionalPathType_t"); + DeclareMatch(s, nonnull, StructType); + return namespace_name(s->env, s->env->namespace->parent, Texts("$Optional", s->name, "$$type")); + } + default: compiler_err(NULL, NULL, NULL, "Optional types are not supported for: ", type_to_str(t)); + } + } + case TypeInfoType: return Text("TypeInfo_t"); + default: compiler_err(NULL, NULL, NULL, "Compiling type is not implemented for type with tag ", t->tag); + } + return EMPTY_TEXT; +} + +public +Text_t compile_type_info(type_t *t) { + if (t == NULL) compiler_err(NULL, NULL, NULL, "Attempt to compile a NULL type"); + if (t == PATH_TYPE) return Text("&Path$info"); + else if (t == PATH_TYPE_TYPE) return Text("&PathType$info"); + + switch (t->tag) { + case BoolType: + case ByteType: + case IntType: + case BigIntType: + case NumType: + case CStringType: return Texts("&", type_to_text(t), "$info"); + case TextType: { + DeclareMatch(text, t, TextType); + if (!text->lang || streq(text->lang, "Text")) return Text("&Text$info"); + return Texts("(&", namespace_name(text->env, text->env->namespace, Text("$info")), ")"); + } + case StructType: { + DeclareMatch(s, t, StructType); + return Texts("(&", namespace_name(s->env, s->env->namespace, Text("$info")), ")"); + } + case EnumType: { + DeclareMatch(e, t, EnumType); + return Texts("(&", namespace_name(e->env, e->env->namespace, Text("$info")), ")"); + } + case ListType: { + type_t *item_t = Match(t, ListType)->item_type; + return Texts("List$info(", compile_type_info(item_t), ")"); + } + case SetType: { + type_t *item_type = Match(t, SetType)->item_type; + return Texts("Set$info(", compile_type_info(item_type), ")"); + } + case TableType: { + DeclareMatch(table, t, TableType); + type_t *key_type = table->key_type; + type_t *value_type = table->value_type; + return Texts("Table$info(", compile_type_info(key_type), ", ", compile_type_info(value_type), ")"); + } + case PointerType: { + DeclareMatch(ptr, t, PointerType); + const char *sigil = ptr->is_stack ? "&" : "@"; + return Texts("Pointer$info(", quoted_str(sigil), ", ", compile_type_info(ptr->pointed), ")"); + } + case FunctionType: { + return Texts("Function$info(", quoted_text(type_to_text(t)), ")"); + } + case ClosureType: { + return Texts("Closure$info(", quoted_text(type_to_text(t)), ")"); + } + case OptionalType: { + type_t *non_optional = Match(t, OptionalType)->type; + return Texts("Optional$info(sizeof(", compile_type(non_optional), "), __alignof__(", compile_type(non_optional), + "), ", compile_type_info(non_optional), ")"); + } + case TypeInfoType: return Texts("Type$info(", quoted_text(type_to_text(Match(t, TypeInfoType)->type)), ")"); + case MemoryType: return Text("&Memory$info"); + case VoidType: return Text("&Void$info"); + default: compiler_err(NULL, 0, 0, "I couldn't convert to a type info: ", type_to_str(t)); + } + return EMPTY_TEXT; +} diff --git a/src/compile/types.h b/src/compile/types.h new file mode 100644 index 00000000..81bce10d --- /dev/null +++ b/src/compile/types.h @@ -0,0 +1,6 @@ + +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t compile_type(type_t *t); +Text_t compile_type_info(type_t *t); @@ -15,6 +15,7 @@ #include "ast.h" #include "changes.md.h" #include "compile.h" +#include "compile/files.h" #include "config.h" #include "modules.h" #include "naming.h" |
