aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compile.c1790
-rw-r--r--src/compile.h8
-rw-r--r--src/compile/assignments.c201
-rw-r--r--src/compile/assignments.h11
-rw-r--r--src/compile/enums.c4
-rw-r--r--src/compile/files.c195
-rw-r--r--src/compile/files.h6
-rw-r--r--src/compile/functions.c209
-rw-r--r--src/compile/functions.h1
-rw-r--r--src/compile/lists.c2
-rw-r--r--src/compile/optionals.c27
-rw-r--r--src/compile/optionals.h2
-rw-r--r--src/compile/pointers.c1
-rw-r--r--src/compile/promotion.c3
-rw-r--r--src/compile/sets.c2
-rw-r--r--src/compile/statements.c1072
-rw-r--r--src/compile/statements.h7
-rw-r--r--src/compile/structs.c5
-rw-r--r--src/compile/tables.c2
-rw-r--r--src/compile/text.c1
-rw-r--r--src/compile/text.h5
-rw-r--r--src/compile/types.c154
-rw-r--r--src/compile/types.h6
-rw-r--r--src/tomo.c1
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);
diff --git a/src/tomo.c b/src/tomo.c
index 02a611d4..5d932ef1 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -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"