main
Download
213 lines 9.4 KiBCModified 2026-04-27 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
// This file defines how to compile files

#include <glob.h>

#include "../ast.h"
#include "../config.h"
#include "../environment.h"
#include "../naming.h"
#include "../packages.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/paths.h"
#include "../stdlib/text.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"

public
Text_t compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *ast) {
    env_t *ns_env = NULL;
    ast_t *block = NULL;
    switch (ast->tag) {
    case LangDef: {
        DeclareMatch(def, ast, LangDef);
        ns_env = namespace_env(env, def->name);
        block = def->namespace;
        break;
    }
    case StructDef: {
        DeclareMatch(def, ast, StructDef);
        ns_env = namespace_env(env, def->name);
        block = def->namespace;
        break;
    }
    case EnumDef: {
        DeclareMatch(def, ast, EnumDef);
        ns_env = namespace_env(env, def->name);
        block = def->namespace;
        break;
    }
    case Declare: {
        DeclareMatch(decl, ast, Declare);
        const char *decl_name = Match(decl->var, Var)->name;
        bool is_private = (decl_name[0] == '_');
        if (is_private) return EMPTY_TEXT;

        type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
        if (t->tag == FunctionType) t = Type(ClosureType, t);
        assert(t->tag != ModuleType);
        if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType)
            code_err(ast, "You can't declare a variable with a ", type_to_text(t), " value");

        return Texts(decl->value ? compile_statement_type_header(env, header_path, decl->value) : EMPTY_TEXT, "extern ",
                     compile_declaration(t, namespace_name(env, env->namespace, Text$from_str(decl_name))), ";\n");
    }
    case FunctionDef: return compile_function_declaration(env, ast);
    case ConvertDef: return compile_convert_declaration(env, ast);
    default: return EMPTY_TEXT;
    }
    assert(ns_env);
    Text_t header = EMPTY_TEXT;
    for (ast_list_t *stmt = block ? Match(block, Block)->statements : NULL; stmt; stmt = stmt->next) {
        header = Texts(header, compile_statement_namespace_header(ns_env, header_path, stmt->ast));
    }
    return header;
}

typedef struct {
    env_t *env;
    Text_t *header;
    Path_t header_path;
} compile_typedef_info_t;

static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) {
    if (ast->tag == StructDef) {
        DeclareMatch(def, ast, StructDef);
        if (def->external) return;
        Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct"));
        Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type"));
        *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
    } else if (ast->tag == EnumDef) {
        DeclareMatch(def, ast, EnumDef);
        Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct"));
        Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type"));
        *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");

        for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
            Text_t tag_struct =
                namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct"));
            Text_t tag_type =
                namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type"));
            *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
        }
    } else if (ast->tag == LangDef) {
        DeclareMatch(def, ast, LangDef);
        *info->header = Texts(*info->header, "typedef Text_t ",
                              namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")), ";\n");
        *info->header = Texts(*info->header, "typedef Text_t ", "Optional",
                              namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")), ";\n");
    }
}

static void _define_types_and_funcs(compile_typedef_info_t *info, ast_t *ast) {
    *info->header = Texts(*info->header, compile_statement_type_header(info->env, info->header_path, ast),
                          compile_statement_namespace_header(info->env, info->header_path, ast));
}

static visit_behavior_t add_type_headers(type_ast_t *type_ast, void *userdata) {
    if (!type_ast) return VISIT_STOP;

    if (type_ast->tag == EnumTypeAST) {
        compile_typedef_info_t *info = (compile_typedef_info_t *)userdata;
        // Force the type to get defined:
        (void)parse_type_ast(info->env, type_ast);
        DeclareMatch(enum_, type_ast, EnumTypeAST);
        const char *name = String("enum$", (int64_t)(type_ast->start - type_ast->file->text));
        Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct"));
        Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type"));
        *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");

        for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) {
            Text_t tag_struct =
                namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct"));
            Text_t tag_type = namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type"));
            *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
        }

        *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags));
    }

    return VISIT_PROCEED;
}

public
Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast) {
    Text_t header =
        Texts("#pragma once\n",
              env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT,
              "#include <tomo@", TOMO_VERSION, "/tomo.h>\n");

    compile_typedef_info_t info = {.env = env, .header = &header, .header_path = header_path};
    visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_make_typedefs, &info});

    type_ast_visit(ast, add_type_headers, &info);

    visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_define_types_and_funcs, &info});

    header = Texts(header, "void ", namespace_name(env, env->namespace, Text("$initialize")), "(void);\n");
    return header;
}

public
Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) {
    switch (ast->tag) {
    case Use: {
        DeclareMatch(use, ast, Use);
        Path_t source_path = Path$from_str(ast->file->filename);
        Path_t source_dir = Path$parent(source_path);
        Path_t build_dir = Path$resolved(Path$parent(header_path), Path$current_dir());
        switch (use->what) {
        case USE_PACKAGE: {
            OptionalPath_t installed = find_installed_package(env->build_info, ast);
            assert(installed);
            List_t children = Path$glob(Path$child(installed, Text("/[!._0-9]*.tm")));
            Text_t includes = EMPTY_TEXT;
            for (int64_t i = 0; i < (int64_t)children.length; i++) {
                Path_t tm_file = *(Path_t *)(children.data + i * children.stride);
                Path_t lib_build_dir = Path$sibling(tm_file, Text(".build"));
                Path_t header = Path$child(lib_build_dir, Texts(Path$base_name(tm_file), Text(".h")));
                includes = Texts(includes, "#include \"", Path$as_c_string(header), "\"\n");
            }
            return with_source_info(env, ast, includes);
        }
        case USE_LOCAL: {
            Path_t used_path = Path$resolved(Path$from_str(use->path), source_dir);
            Path_t used_build_dir = Path$sibling(used_path, Text(".build"));
            Path_t used_header_path = Path$child(used_build_dir, Texts(Path$base_name(used_path), Text(".h")));
            return Texts("#include \"", Path$as_c_string(Path$relative_to(used_header_path, build_dir)), "\"\n");
        }
        case USE_HEADER:
            if (use->path[0] == '<') {
                return Texts("#include ", use->path, "\n");
            } else {
                Path_t used_path = Path$resolved(Path$from_str(use->path), source_dir);
                return Texts("#include \"", Path$as_c_string(Path$relative_to(used_path, build_dir)), "\"\n");
            }
        default: return EMPTY_TEXT;
        }
    }
    case StructDef: {
        return compile_struct_header(env, ast);
    }
    case EnumDef: {
        DeclareMatch(def, ast, EnumDef);
        return compile_enum_header(env, def->name, def->tags);
    }
    case LangDef: {
        DeclareMatch(def, ast, LangDef);
        return Texts(
            // Constructor macro:
            "#define ", namespace_name(env, env->namespace, Text$from_str(def->name)), "(text) ((",
            namespace_name(env, env->namespace, Texts(def->name, "$$type")),
            "){.length=sizeof(text)-1, .tag=TEXT_ASCII, .ascii=\"\" "
            "text})\n"
            "#define ",
            namespace_name(env, env->namespace, Text$from_str(def->name)), "s(...) ((",
            namespace_name(env, env->namespace, Texts(def->name, "$$type")),
            ")Texts(__VA_ARGS__))\n"
            "extern const TypeInfo_t ",
            namespace_name(env, env->namespace, Texts(def->name, Text("$$info"))), ";\n");
    }
    default: return EMPTY_TEXT;
    }
}