123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 | // 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/path.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 = NULL;
bool lazy = needs_runtime_initialization(env, ast, &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");
// A variable that `$initialize()` assigns is read through a guard on
// its `$$initialized` flag, so an importer needs the flag declared
// here too, not just the variable:
Text_t initialized_decl =
lazy ? Texts("extern bool ", namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")), ";\n")
: EMPTY_TEXT;
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",
initialized_decl);
}
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.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);
OptionalPath_t source_dir = Path$parent(source_path);
OptionalPath_t header_dir = Path$parent(header_path);
assert(source_dir && header_dir); // Both name files, so both have a parent
Path_t build_dir = Path$resolved(header_dir, Path$current_dir());
switch (use->what) {
case USE_PACKAGE: {
OptionalPath_t installed = find_installed_package(env->build_info, ast);
assert(installed);
OptionalList_t children = Path$glob(installed, Text("[!._0-9]*.tm"));
if (children.data == NULL) print_err("Could not read the installed package directory: ", installed);
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 = tm_build_dir(tm_file);
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 = tm_build_dir(used_path);
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;
}
}
|