aboutsummaryrefslogtreecommitdiff
path: root/src/compile/functions.c.orig
diff options
context:
space:
mode:
Diffstat (limited to 'src/compile/functions.c.orig')
-rw-r--r--src/compile/functions.c.orig955
1 files changed, 955 insertions, 0 deletions
diff --git a/src/compile/functions.c.orig b/src/compile/functions.c.orig
new file mode 100644
index 00000000..d62eb4f7
--- /dev/null
+++ b/src/compile/functions.c.orig
@@ -0,0 +1,955 @@
+// This file defines how to compile functions
+
+#include "../ast.h"
+#include "../environment.h"
+#include "../naming.h"
+#include "../stdlib/c_strings.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/integers.h"
+#include "../stdlib/nums.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
+#include "../typecheck.h"
+#include "../types.h"
+#include "../util.h"
+#include "compilation.h"
+
+public
+Text_t compile_function_declaration(env_t *env, ast_t *ast) {
+ DeclareMatch(fndef, ast, FunctionDef);
+ const char *decl_name = Match(fndef->name, Var)->name;
+ bool is_private = decl_name[0] == '_';
+ if (is_private) return EMPTY_TEXT;
+ Text_t arg_signature = Text("(");
+ for (arg_ast_t *arg = fndef->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, ", ");
+ }
+ arg_signature = Texts(arg_signature, ")");
+
+ type_t *ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType);
+ Text_t ret_type_code = compile_type(ret_t);
+ if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code);
+ Text_t name = namespace_name(env, env->namespace, Text$from_str(decl_name));
+ if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name))
+ name = namespace_name(env, env->namespace, Texts(get_line_number(ast->file, ast->start)));
+ return Texts(ret_type_code, " ", name, arg_signature, ";\n");
+}
+
+public
+Text_t compile_convert_declaration(env_t *env, ast_t *ast) {
+ DeclareMatch(def, ast, ConvertDef);
+
+ Text_t arg_signature = Text("(");
+ for (arg_ast_t *arg = def->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, ", ");
+ }
+ arg_signature = Texts(arg_signature, ")");
+
+ type_t *ret_t = def->ret_type ? parse_type_ast(env, def->ret_type) : Type(VoidType);
+ Text_t ret_type_code = compile_type(ret_t);
+ Text_t name = Text$from_str(get_type_name(ret_t));
+ if (name.length == 0)
+ code_err(ast,
+ "Conversions are only supported for text, struct, and enum "
+ "types, not ",
+ type_to_text(ret_t));
+ Text_t name_code = namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start)));
+ return Texts(ret_type_code, " ", name_code, arg_signature, ";\n");
+}
+
+public
+Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) {
+ Table_t used_args = EMPTY_TABLE;
+ Text_t code = EMPTY_TEXT;
+ env_t *default_scope = new (env_t);
+ *default_scope = *env;
+ default_scope->locals = new (Table_t, .fallback = env->namespace_bindings ? env->namespace_bindings : env->globals);
+ for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) {
+ int64_t i = 1;
+ // Find keyword:
+ assert(spec_arg->name);
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (!call_arg->name) continue;
+ if (!(streq(call_arg->name, spec_arg->name) || (spec_arg->alias && streq(call_arg->name, spec_arg->alias))))
+ continue;
+
+ Text_t value;
+ if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
+ value = compile_int_to_type(env, call_arg->value, spec_arg->type);
+ } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
+ OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
+ if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
+ if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
+ value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
+ else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
+ } else {
+ env_t *arg_env = with_enum_scope(env, spec_arg->type);
+ value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
+ }
+ Table$str_set(&used_args, call_arg->name, call_arg);
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, value);
+ goto found_it;
+ }
+
+ // Find positional:
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (call_arg->name) continue;
+ const char *pseudoname = String(i++);
+ if (!Table$str_get(used_args, pseudoname)) {
+ Text_t value;
+ if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
+ value = compile_int_to_type(env, call_arg->value, spec_arg->type);
+ } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
+ OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
+ if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
+ if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
+ value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
+ else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
+ } else {
+ env_t *arg_env = with_enum_scope(env, spec_arg->type);
+ value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
+ }
+
+ Table$str_set(&used_args, pseudoname, call_arg);
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, value);
+ goto found_it;
+ }
+ }
+
+ if (spec_arg->default_val) {
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, compile_maybe_incref(default_scope, spec_arg->default_val, get_arg_type(env, spec_arg)));
+ goto found_it;
+ }
+
+ assert(spec_arg->name);
+ code_err(call_ast, "The required argument '", spec_arg->name, "' was not provided");
+ found_it:
+ continue;
+ }
+
+ int64_t i = 1;
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (call_arg->name) {
+ if (!Table$str_get(used_args, call_arg->name))
+ code_err(call_arg->value, "There is no argument with the name '", call_arg->name, "'");
+ } else {
+ const char *pseudoname = String(i++);
+ if (!Table$str_get(used_args, pseudoname)) code_err(call_arg->value, "This is one argument too many!");
+ }
+ }
+ return code;
+}
+
+public
+Text_t compile_function_call(env_t *env, ast_t *ast) {
+ DeclareMatch(call, ast, FunctionCall);
+ type_t *fn_t = get_type(env, call->fn);
+ if (fn_t->tag == FunctionType) {
+ Text_t fn = compile(env, call->fn);
+ if (!is_valid_call(env, Match(fn_t, FunctionType)->args, call->args, (call_opts_t){.promotion = true})) {
+ if (is_valid_call(env, Match(fn_t, FunctionType)->args, call->args,
+ (call_opts_t){.promotion = true, .underscores = true})) {
+ code_err(ast, "You can't pass underscore arguments to this function as positional arguments. You must "
+ "use keyword arguments.");
+ } else {
+ arg_t *args = NULL;
+ for (arg_ast_t *a = call->args; a; a = a->next)
+ args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args);
+ REVERSE_LIST(args);
+ code_err(ast,
+ "This function's signature doesn't match this call site. \n"
+ " The function takes these args: (",
+ arg_types_to_text(Match(fn_t, FunctionType)->args, ", "),
+ ") \n"
+ " But it's being called with: (",
+ arg_types_to_text(args, ", "), ")");
+ }
+ }
+ return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")");
+ } else if (fn_t->tag == TypeInfoType) {
+ type_t *t = Match(fn_t, TypeInfoType)->type;
+
+ // Literal constructors for numeric types like `Byte(123)` should
+ // not go through any conversion, just a cast:
+ if (is_numeric_type(t) && call->args && !call->args->next && call->args->value->tag == Int)
+ return compile_to_type(env, call->args->value, t);
+ else if (t->tag == NumType && call->args && !call->args->next && call->args->value->tag == Num)
+ return compile_to_type(env, call->args->value, t);
+
+ binding_t *constructor =
+ get_constructor(env, t, call->args, env->current_type != NULL && type_eq(env->current_type, t));
+ if (constructor) {
+ arg_t *arg_spec = Match(constructor->type, FunctionType)->args;
+ return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, call->args), ")");
+ }
+
+ if (t->tag == TextType) {
+ if (!call->args) code_err(ast, "This constructor needs a value");
+ if (!type_eq(t, TEXT_TYPE))
+ code_err(call->fn, "I don't have a constructor defined for "
+ "these arguments");
+ // Text constructor:
+ if (!call->args || call->args->next) code_err(call->fn, "This constructor takes exactly 1 argument");
+ type_t *actual = call->args ? get_type(env, call->args->value) : NULL;
+ if (type_eq(actual, t)) return compile(env, call->args->value);
+ return expr_as_text(compile(env, call->args->value), actual, Text("no"));
+ } else if (t->tag == CStringType) {
+ // C String constructor:
+ if (!call->args || call->args->next) code_err(call->fn, "This constructor takes exactly 1 argument");
+ if (call->args->value->tag == TextLiteral)
+ return compile_text_literal(Match(call->args->value, TextLiteral)->text);
+ else if (call->args->value->tag == TextJoin && Match(call->args->value, TextJoin)->children == NULL)
+ return Text("\"\"");
+ else if (call->args->value->tag == TextJoin && Match(call->args->value, TextJoin)->children->next == NULL)
+ return compile_text_literal(
+ Match(Match(call->args->value, TextJoin)->children->ast, TextLiteral)->text);
+ type_t *actual = call->args ? get_type(env, call->args->value) : NULL;
+ return Texts("Text$as_c_string(", expr_as_text(compile(env, call->args->value), actual, Text("no")), ")");
+ } else if (t->tag == StructType) {
+ return compile_struct_literal(env, ast, t, call->args);
+ }
+ code_err(ast,
+ "I could not find a constructor matching these arguments "
+ "for ",
+ type_to_text(t));
+ } else if (fn_t->tag == ClosureType) {
+ fn_t = Match(fn_t, ClosureType)->fn;
+ arg_t *type_args = Match(fn_t, FunctionType)->args;
+
+ 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));
+
+ Text_t closure = compile(env, call->fn);
+ Text_t arg_code = compile_arguments(env, ast, type_args, call->args);
+ if (arg_code.length > 0) arg_code = Texts(arg_code, ", ");
+ if (call->fn->tag == Var) {
+ return Texts("((", fn_type_code, ")", closure, ".fn)(", arg_code, closure, ".userdata)");
+ } else {
+ return Texts("({ Closure_t closure = ", closure, "; ((", fn_type_code, ")closure.fn)(", arg_code,
+ "closure.userdata); })");
+ }
+ } else {
+ code_err(call->fn, "This is not a function, it's a ", type_to_text(fn_t));
+ }
+}
+
+static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) {
+ if (ast == NULL) return;
+
+ switch (ast->tag) {
+ case Var: {
+ binding_t *b = get_binding(enclosing_scope, Match(ast, Var)->name);
+ if (b) {
+ binding_t *shadow = get_binding(env, Match(ast, Var)->name);
+ if (!shadow || shadow == b) Table$str_set(closed_vars, Match(ast, Var)->name, b);
+ }
+ break;
+ }
+ case TextJoin: {
+ for (ast_list_t *child = Match(ast, TextJoin)->children; child; child = child->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, child->ast);
+ break;
+ }
+ case Declare: {
+ ast_t *value = Match(ast, Declare)->value;
+ add_closed_vars(closed_vars, enclosing_scope, env, value);
+ bind_statement(env, ast);
+ break;
+ }
+ case Assign: {
+ for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, target->ast);
+ for (ast_list_t *value = Match(ast, Assign)->values; value; value = value->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, value->ast);
+ break;
+ }
+ case BINOP_CASES: {
+ binary_operands_t binop = BINARY_OPERANDS(ast);
+ add_closed_vars(closed_vars, enclosing_scope, env, binop.lhs);
+ add_closed_vars(closed_vars, enclosing_scope, env, binop.rhs);
+ break;
+ }
+ case Not:
+ case Negative:
+ case HeapAllocate:
+ case StackReference: {
+ // UNSAFE:
+ ast_t *value = ast->__data.Not.value;
+ // END UNSAFE
+ add_closed_vars(closed_vars, enclosing_scope, env, value);
+ break;
+ }
+ case Min: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->lhs);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->rhs);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->key);
+ break;
+ }
+ case Max: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->lhs);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->rhs);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->key);
+ break;
+ }
+ case List: {
+ for (ast_list_t *item = Match(ast, List)->items; item; item = item->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, item->ast);
+ break;
+ }
+ case Table: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Table)->default_value);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Table)->fallback);
+ for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, entry->ast);
+ break;
+ }
+ case TableEntry: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, TableEntry)->key);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, TableEntry)->value);
+ break;
+ }
+ case 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 add_closed_vars(closed_vars, enclosing_scope, env, loop);
+ }
+
+ // List/Table comprehension:
+ ast_t *body = comp->expr;
+ 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);
+ add_closed_vars(closed_vars, enclosing_scope, env, loop);
+ break;
+ }
+ case Lambda: {
+ DeclareMatch(lambda, ast, Lambda);
+ env_t *lambda_scope = fresh_scope(env);
+ for (arg_ast_t *arg = lambda->args; arg; arg = arg->next)
+ set_binding(lambda_scope, arg->name, get_arg_ast_type(env, arg), Texts("_$", arg->name));
+ add_closed_vars(closed_vars, enclosing_scope, lambda_scope, lambda->body);
+ break;
+ }
+ case FunctionCall: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, FunctionCall)->fn);
+ for (arg_ast_t *arg = Match(ast, FunctionCall)->args; arg; arg = arg->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, arg->value);
+ break;
+ }
+ case MethodCall: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, MethodCall)->self);
+ for (arg_ast_t *arg = Match(ast, MethodCall)->args; arg; arg = arg->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, arg->value);
+ break;
+ }
+ case Block: {
+ env = fresh_scope(env);
+ for (ast_list_t *statement = Match(ast, Block)->statements; statement; statement = statement->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, statement->ast);
+ break;
+ }
+ case For: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, For)->iter);
+ env_t *body_scope = for_scope(env, ast);
+ add_closed_vars(closed_vars, enclosing_scope, body_scope, Match(ast, For)->body);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, For)->empty);
+ break;
+ }
+ case While: {
+ DeclareMatch(while_, ast, While);
+ add_closed_vars(closed_vars, enclosing_scope, env, while_->condition);
+ env_t *scope = fresh_scope(env);
+ add_closed_vars(closed_vars, enclosing_scope, scope, while_->body);
+ break;
+ }
+ case If: {
+ DeclareMatch(if_, ast, If);
+ ast_t *condition = if_->condition;
+ if (condition->tag == Declare) {
+ env_t *truthy_scope = fresh_scope(env);
+ bind_statement(truthy_scope, condition);
+ if (!Match(condition, Declare)->value)
+ code_err(condition, "This declared variable must have an initial value");
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(condition, Declare)->value);
+ ast_t *var = Match(condition, Declare)->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, EMPTY_TEXT);
+ }
+ add_closed_vars(closed_vars, enclosing_scope, truthy_scope, if_->body);
+ add_closed_vars(closed_vars, enclosing_scope, env, if_->else_body);
+ } else {
+ add_closed_vars(closed_vars, enclosing_scope, 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, EMPTY_TEXT);
+ }
+ add_closed_vars(closed_vars, enclosing_scope, truthy_scope, if_->body);
+ add_closed_vars(closed_vars, enclosing_scope, env, if_->else_body);
+ }
+ break;
+ }
+ case When: {
+ DeclareMatch(when, ast, When);
+ add_closed_vars(closed_vars, enclosing_scope, env, when->subject);
+ type_t *subject_t = get_type(env, when->subject);
+
+ if (subject_t->tag != EnumType) {
+ for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
+ add_closed_vars(closed_vars, enclosing_scope, env, clause->pattern);
+ add_closed_vars(closed_vars, enclosing_scope, env, clause->body);
+ }
+
+ if (when->else_body) add_closed_vars(closed_vars, enclosing_scope, env, when->else_body);
+ return;
+ }
+
+ DeclareMatch(enum_t, subject_t, EnumType);
+ for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
+ const char *clause_tag_name;
+ if (clause->pattern->tag == Var) clause_tag_name = Match(clause->pattern, Var)->name;
+ else if (clause->pattern->tag == FunctionCall && Match(clause->pattern, FunctionCall)->fn->tag == Var)
+ clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name;
+ else code_err(clause->pattern, "This is not a valid pattern for a ", type_to_text(subject_t), " enum");
+
+ 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 = when_clause_scope(env, subject_t, clause);
+ add_closed_vars(closed_vars, enclosing_scope, scope, clause->body);
+ }
+ if (when->else_body) add_closed_vars(closed_vars, enclosing_scope, env, when->else_body);
+ break;
+ }
+ case Repeat: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Repeat)->body);
+ break;
+ }
+ case Reduction: {
+ DeclareMatch(reduction, ast, Reduction);
+ static int64_t next_id = 1;
+ ast_t *item = FakeAST(Var, String("$it", next_id++));
+ ast_t *loop =
+ FakeAST(For, .vars = new (ast_list_t, .ast = item), .iter = reduction->iter, .body = FakeAST(Pass));
+ env_t *scope = for_scope(env, loop);
+ add_closed_vars(closed_vars, enclosing_scope, scope, reduction->key ? reduction->key : item);
+ break;
+ }
+ case Defer: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Defer)->body);
+ break;
+ }
+ case Return: {
+ ast_t *ret = Match(ast, Return)->value;
+ if (ret) add_closed_vars(closed_vars, enclosing_scope, env, ret);
+ break;
+ }
+ case Index: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Index)->indexed);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Index)->index);
+ break;
+ }
+ case FieldAccess: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, FieldAccess)->fielded);
+ break;
+ }
+ case NonOptional: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, NonOptional)->value);
+ break;
+ }
+ case DebugLog: {
+ for (ast_list_t *value = Match(ast, DebugLog)->values; value; value = value->next)
+ add_closed_vars(closed_vars, enclosing_scope, env, value->ast);
+ break;
+ }
+ case Assert: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Assert)->expr);
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Assert)->message);
+ break;
+ }
+ case ExplicitlyTyped: {
+ add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, ExplicitlyTyped)->ast);
+ break;
+ }
+ case Use:
+ case FunctionDef:
+ case ConvertDef:
+ case StructDef:
+ case EnumDef:
+ case LangDef: errx(1, "Definitions should not be reachable in a closure.");
+ default: break;
+ }
+}
+
+public
+Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) {
+ env_t *body_scope = fresh_scope(env);
+ 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));
+ }
+
+ Table_t closed_vars = EMPTY_TABLE;
+ add_closed_vars(&closed_vars, env, body_scope, block);
+ return closed_vars;
+}
+
+static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) {
+ Table_t *vars = (Table_t *)userdata;
+ switch (ast->tag) {
+ case Var: {
+ const char *name = Match(ast, Var)->name;
+ Table$str_set(vars, name, ast);
+ return VISIT_STOP;
+ }
+ case Assign: {
+ for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) {
+ ast_t *var = target->ast;
+ for (;;) {
+ if (var->tag == Index) {
+ ast_t *index = Match(var, Index)->index;
+ if (index) ast_visit(index, find_used_variables, userdata);
+ var = Match(var, Index)->indexed;
+ } else if (var->tag == FieldAccess) {
+ var = Match(var, FieldAccess)->fielded;
+ } else {
+ break;
+ }
+ }
+ }
+ for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) {
+ ast_visit(val->ast, find_used_variables, userdata);
+ }
+ return VISIT_STOP;
+ }
+ case UPDATE_CASES: {
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ ast_t *lhs = operands.lhs;
+ for (;;) {
+ if (lhs->tag == Index) {
+ ast_t *index = Match(lhs, Index)->index;
+ if (index) ast_visit(index, find_used_variables, userdata);
+ lhs = Match(lhs, Index)->indexed;
+ } else if (lhs->tag == FieldAccess) {
+ lhs = Match(lhs, FieldAccess)->fielded;
+ } else {
+ break;
+ }
+ }
+ ast_visit(operands.rhs, find_used_variables, userdata);
+ return VISIT_STOP;
+ }
+ case Declare: {
+ ast_visit(Match(ast, Declare)->value, find_used_variables, userdata);
+ return VISIT_STOP;
+ }
+ default: return VISIT_PROCEED;
+ }
+}
+
+static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) {
+ Table_t *vars = (Table_t *)userdata;
+ switch (ast->tag) {
+ case Assign:
+ for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) {
+ ast_t *var = target->ast;
+ for (;;) {
+ if (var->tag == Index) var = Match(var, Index)->indexed;
+ else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded;
+ else break;
+ }
+ if (var->tag == Var) {
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ }
+ }
+ return VISIT_STOP;
+ case UPDATE_CASES: {
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ ast_t *var = operands.lhs;
+ for (;;) {
+ if (var->tag == Index) var = Match(var, Index)->indexed;
+ else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded;
+ else break;
+ }
+ if (var->tag == Var) {
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ }
+ return VISIT_STOP;
+ }
+ case Declare: {
+ ast_t *var = Match(ast, Declare)->var;
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ return VISIT_STOP;
+ }
+ default: return VISIT_PROCEED;
+ }
+}
+
+static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) {
+ Table_t used_vars = EMPTY_TABLE;
+ ast_visit(body, find_used_variables, &used_vars);
+ Table_t assigned_vars = EMPTY_TABLE;
+ ast_visit(body, find_assigned_variables, &assigned_vars);
+
+ for (arg_ast_t *arg = args; arg; arg = arg->next) {
+ type_t *arg_type = get_arg_ast_type(env, arg);
+ if (arg_type->tag == PointerType) {
+ Table$str_remove(&assigned_vars, arg->name);
+ }
+ }
+
+ Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info));
+ for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) {
+ struct {
+ const char *name;
+ } *entry = unused.entries.data + i * unused.entries.stride;
+ if (streq(entry->name, "_")) continue;
+ // Global/file scoped vars are okay to mutate without reading
+ if (get_binding(env, entry->name) != NULL) continue;
+ ast_t *var = Table$str_get(assigned_vars, entry->name);
+<<<<<<< HEAD
+ if (var) code_err(var, "This variable was assigned to, but never read from.");
+||||||| 0f9af5f4
+ code_err(var, "This variable was assigned to, but never read from.");
+=======
+ assert(var);
+ code_err(var, "This variable was assigned to, but never read from.");
+>>>>>>> dev
+ }
+}
+
+public
+Text_t compile_lambda(env_t *env, ast_t *ast) {
+ DeclareMatch(lambda, ast, Lambda);
+ Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id));
+
+ env_t *body_scope = fresh_scope(env);
+ body_scope->deferred = NULL;
+ for (arg_ast_t *arg = lambda->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 = ast;
+
+ Table_t closed_vars = get_closed_vars(env, lambda->args, ast);
+ if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata
+ Text_t def = Text("typedef struct {");
+ for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
+ struct {
+ const char *name;
+ binding_t *b;
+ } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
+ if (has_stack_memory(entry->b->type))
+ code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type),
+ " stack memory in the variable `", entry->name,
+ "`, but the function may outlive the stack memory");
+ if (entry->b->type->tag == ModuleType) continue;
+ set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name));
+ def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; ");
+ }
+ def = Texts(def, "} ", name, "$userdata_t;");
+ env->code->local_typedefs = Texts(env->code->local_typedefs, def);
+ }
+
+ type_t *ret_t = get_function_return_type(env, ast);
+ Text_t code = Texts("static ", compile_type(ret_t), " ", name, "(");
+ for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
+ type_t *arg_type = get_arg_ast_type(env, arg);
+ code = Texts(code, compile_type(arg_type), " _$", arg->name, ", ");
+ }
+
+ Text_t userdata;
+ if (Table$length(closed_vars) == 0) {
+ code = Texts(code, "void *_)");
+ userdata = Text("NULL");
+ } else {
+ userdata = Texts("heap(((", name, "$userdata_t){");
+ for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
+ if (i > 0) userdata = Text$concat(userdata, Text(", "));
+ struct {
+ const char *name;
+ binding_t *b;
+ } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
+ if (entry->b->type->tag == ModuleType) continue;
+ binding_t *b = get_binding(env, entry->name);
+ assert(b);
+ Text_t binding_code = b->code;
+ if (entry->b->type->tag == ListType) userdata = Texts(userdata, "LIST_COPY(", binding_code, ")");
+ else if (entry->b->type->tag == TableType) userdata = Texts(userdata, "TABLE_COPY(", binding_code, ")");
+ else userdata = Texts(userdata, binding_code);
+ }
+ userdata = Texts(userdata, "}))");
+ code = Texts(code, name, "$userdata_t *userdata)");
+ }
+
+ Text_t body = EMPTY_TEXT;
+ for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) {
+ if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType
+ || get_type(body_scope, stmt->ast)->tag == ReturnType)
+ body = Texts(body, compile_statement(body_scope, stmt->ast), "\n");
+ else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n");
+ bind_statement(body_scope, stmt->ast);
+ }
+ if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred)
+ body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n");
+
+ env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n");
+
+ check_unused_vars(env, lambda->args, lambda->body);
+
+ return Texts("((Closure_t){", name, ", ", userdata, "})");
+}
+
+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 = get_function_return_type(env, ast);
+ 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;
+ body = fndef->body;
+ cache = fndef->cache;
+ is_inline = fndef->is_inline;
+ } else {
+ DeclareMatch(convertdef, ast, ConvertDef);
+ args = convertdef->args;
+ 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_text(ret_t));
+ body = convertdef->body;
+ cache = convertdef->cache;
+ is_inline = convertdef->is_inline;
+ }
+
+ Text_t arg_signature = Text("(");
+ Table_t used_names = EMPTY_TABLE;
+ 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 = ast;
+
+ 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_text(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), NONE_INT, NULL);
+ Text_t pop_code = EMPTY_TEXT;
+ if (cache->tag == Int && cache_size.has_value && 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 > ", 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 = EMPTY_TABLE;\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 = (int64_t)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=",
+ num_fields, ", .StructInfo.fields=(NamedType_t[", 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 = EMPTY_TABLE;\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);
+ }
+ }
+
+ check_unused_vars(env, args, body);
+
+ return definition;
+}
+
+public
+Text_t compile_method_call(env_t *env, ast_t *ast) {
+ DeclareMatch(call, ast, MethodCall);
+ type_t *self_t = get_type(env, call->self);
+ type_t *self_value_t = value_type(self_t);
+ if (self_value_t->tag == TypeInfoType || self_value_t->tag == ModuleType) {
+ return compile(env, WrapAST(ast, FunctionCall,
+ .fn = WrapAST(call->self, FieldAccess, .fielded = call->self, .field = call->name),
+ .args = call->args));
+ }
+
+ type_t *field_type = get_field_type(self_value_t, call->name);
+ if (field_type && field_type->tag == ClosureType) field_type = Match(field_type, ClosureType)->fn;
+ if (field_type && field_type->tag == FunctionType)
+ return compile(env, WrapAST(ast, FunctionCall,
+ .fn = WrapAST(call->self, FieldAccess, .fielded = call->self, .field = call->name),
+ .args = call->args));
+
+ switch (self_value_t->tag) {
+ case ListType: return compile_list_method_call(env, ast);
+ case TableType: return compile_table_method_call(env, ast);
+ default: {
+ DeclareMatch(methodcall, ast, MethodCall);
+ type_t *fn_t = get_method_type(env, methodcall->self, methodcall->name);
+ arg_ast_t *args = new (arg_ast_t, .value = methodcall->self, .next = methodcall->args);
+ binding_t *b = get_namespace_binding(env, methodcall->self, methodcall->name);
+ if (!b) code_err(ast, "No such method");
+ return Texts(b->code, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, args), ")");
+ }
+ }
+}