123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 | // This file defines how to compile files
#include "../ast.h"
#include "../config.h"
#include "../environment.h"
#include "../naming.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/path.h"
#include "../stdlib/table.h"
#include "../stdlib/text.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"
// A file is compiled in three passes over the same AST:
//
// 1. compile_variables(): the file-scoped and type-namespace-scoped variables and type infos.
// 2. compile_functions(): functions and type methods.
// 3. compile_initializers(): the code that runs inside `$initialize()` to assign values
// for those variables that can't use static initializers, plus other top-level statements.
static Text_t compile_variables(env_t *env, ast_t *ast);
static Text_t compile_namespace_variables(env_t *env, const char *name, ast_t *namespace);
static Text_t compile_functions(env_t *env, ast_t *ast);
static Text_t compile_namespace_functions(env_t *env, const char *name, ast_t *namespace);
static Text_t compile_initializers(env_t *env, ast_t *ast);
static Text_t compile_namespace_initializers(env_t *env, const char *name, ast_t *namespace);
Text_t compile_namespace_variables(env_t *env, const char *name, ast_t *namespace) {
return compile_variables(namespace_env(env, name), namespace);
}
Text_t compile_variables(env_t *env, ast_t *ast) {
if (!ast) return EMPTY_TEXT;
switch (ast->tag) {
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));
// Variables and functions starting with `_` are private to the file, i.e. `static` in C.
// Everything else is visible to importers and declared in the header.
Text_t linkage = decl_name[0] == '_' ? Text("static ") : Text("public ");
type_t *t = NULL;
if (needs_runtime_initialization(env, ast, &t)) {
// Runtime-initialized variables have an accompanying boolean saying whether
// they're initialized or not, so we can do lazy and once-only initialization.
Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized"));
return Texts(linkage, "bool ", initialized_name, " = false;\n", linkage, compile_declaration(t, full_name),
";\n");
} else {
return Texts(linkage, compile_declaration(t, full_name), " = ", compile_declared_value(env, ast), ";\n");
}
}
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);
return Texts(code, compile_namespace_variables(env, def->name, def->namespace));
}
case EnumDef: {
DeclareMatch(def, ast, EnumDef);
Text_t code = compile_enum_typeinfo(env, def->name, def->tags);
return Texts(code, compile_namespace_variables(env, def->name, def->namespace));
}
case LangDef: {
DeclareMatch(def, ast, LangDef);
Text_t code =
Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {",
(int64_t)sizeof(Text_t), ", ", (int64_t)__alignof__(Text_t),
", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", quoted_str(def->name), "}};\n");
return Texts(code, compile_namespace_variables(env, def->name, def->namespace));
}
case Block: {
Text_t code = EMPTY_TEXT;
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
code = Texts(code, compile_variables(env, stmt->ast));
}
return code;
}
case InlineCCode: {
// Inline C code goes with variable declarations so it can be referenced inside functions.
// Top-level C code can be used to declare variables and macros and whatnot. If you want
// top-level C code that runs on initialization, you can wrap it in `do C_code"..."`
Text_t stmt_code = compile_statement(env, ast);
return with_source_info(env, ast, stmt_code);
}
default: return EMPTY_TEXT;
}
}
Text_t compile_namespace_functions(env_t *env, const char *name, ast_t *namespace) {
return compile_functions(namespace_env(env, name), namespace);
}
Text_t compile_functions(env_t *env, ast_t *ast) {
if (!ast) return EMPTY_TEXT;
switch (ast->tag) {
case FunctionDef: {
Text_t name_code = namespace_name(
env, env->namespace,
// Dots in subcommand names (`main.add`) become `$`s in the C name:
Text$replace(Text$from_str(Match(Match(ast, FunctionDef)->name, Var)->name), Text("."), Text("$")));
return compile_function(env, name_code, ast);
}
case ConvertDef: {
type_t *type = get_function_return_type(env, ast);
const char *name = get_type_name(type);
if (!name)
code_err(ast, "Conversions are only supported for text, struct, and enum types, not ", type_to_text(type));
Text_t name_code =
namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start)));
return compile_function(env, name_code, ast);
}
case StructDef: {
DeclareMatch(def, ast, StructDef);
return compile_namespace_functions(env, def->name, def->namespace);
}
case EnumDef: {
DeclareMatch(def, ast, EnumDef);
Text_t code = compile_enum_constructors(env, def->name, def->tags);
return Texts(code, compile_namespace_functions(env, def->name, def->namespace));
}
case LangDef: {
DeclareMatch(def, ast, LangDef);
return compile_namespace_functions(env, def->name, def->namespace);
}
case Block: {
Text_t code = EMPTY_TEXT;
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
code = Texts(code, compile_functions(env, stmt->ast));
}
return code;
}
default: return EMPTY_TEXT;
}
}
Text_t compile_namespace_initializers(env_t *env, const char *name, ast_t *namespace) {
return compile_initializers(namespace_env(env, name), namespace);
}
Text_t compile_initializers(env_t *env, ast_t *ast) {
if (!ast) return EMPTY_TEXT;
Text_t code = EMPTY_TEXT;
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
switch (stmt->ast->tag) {
case Declare: {
if (!needs_runtime_initialization(env, stmt->ast, NULL)) break;
// Compile the value here and not for a statically initialized
// variable, whose value compile_variables() emits as a static
// initializer: compiling it in both places would duplicate any
// hoisted static defs, e.g. a list literal's backing array.
const char *decl_name = Match(Match(stmt->ast, Declare)->var, Var)->name;
Text_t full_name = namespace_name(env, env->namespace, Text$from_str(decl_name));
Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized"));
Text_t val_code = compile_declared_value(env, stmt->ast);
code =
Texts(code, with_source_info(env, stmt->ast,
Texts(full_name, " = ", val_code, ",\n", initialized_name, " = true;\n")));
break;
}
case StructDef:
code = Texts(code, compile_namespace_initializers(env, Match(stmt->ast, StructDef)->name,
Match(stmt->ast, StructDef)->namespace));
break;
case EnumDef:
code = Texts(code, compile_namespace_initializers(env, Match(stmt->ast, EnumDef)->name,
Match(stmt->ast, EnumDef)->namespace));
break;
case LangDef:
code = Texts(code, compile_namespace_initializers(env, Match(stmt->ast, LangDef)->name,
Match(stmt->ast, LangDef)->namespace));
break;
case FunctionDef:
case ConvertDef:
case InlineCCode:
case Use:
case Test: break;
default: {
// A statement that evaluates to something is printed rather than
// run for its side effects alone. Compiling it as an expression is
// also what sidesteps the error compile_statement() raises for a
// value that would otherwise be silently discarded. Only a file's
// own statements are evaluated for their values, never a
// namespace's members, which an `env->namespace` identifies.
bool print_value = env->print_values && env->namespace == NULL;
type_t *t = print_value ? get_type(env, stmt->ast) : NULL;
if (t && t->tag != VoidType && t->tag != AbortType && t->tag != ReturnType) {
code = Texts(code, with_source_info(env, stmt->ast, compile_value_print(t, compile(env, stmt->ast))));
break;
}
Text_t stmt_code = compile_statement(env, stmt->ast);
if (stmt_code.length > 0) code = Texts(code, with_source_info(env, stmt->ast, stmt_code));
break;
}
}
}
return code;
}
typedef struct {
env_t *env;
Text_t *code;
} compile_info_t;
static visit_behavior_t add_type_infos(type_ast_t *type_ast, void *userdata) {
if (type_ast && type_ast->tag == EnumTypeAST) {
compile_info_t *info = (compile_info_t *)userdata;
// Force the type to get defined:
(void)parse_type_ast(info->env, type_ast);
*info->code =
Texts(*info->code,
compile_enum_typeinfo(info->env, String("enum$", (int64_t)(type_ast->start - type_ast->file->text)),
Match(type_ast, EnumTypeAST)->tags));
}
return VISIT_PROCEED;
}
static visit_behavior_t add_type_constructors(type_ast_t *type_ast, void *userdata) {
if (type_ast && type_ast->tag == EnumTypeAST) {
compile_info_t *info = (compile_info_t *)userdata;
// Force the type to get defined:
(void)parse_type_ast(info->env, type_ast);
*info->code = Texts(
*info->code,
compile_enum_constructors(info->env, String("enum$", (int64_t)(type_ast->start - type_ast->file->text)),
Match(type_ast, EnumTypeAST)->tags));
}
return VISIT_PROCEED;
}
public
Text_t compile_file(env_t *env, ast_t *ast) {
Text_t file_variables = EMPTY_TEXT;
type_ast_visit(ast, add_type_infos, (void *)(compile_info_t[1]){{.env = env, .code = &file_variables}});
file_variables = Texts(file_variables, compile_variables(env, ast));
Text_t functions = EMPTY_TEXT;
type_ast_visit(ast, add_type_constructors, (void *)(compile_info_t[1]){{.env = env, .code = &functions}});
functions = Texts(functions, compile_functions(env, ast));
// A `use` contributes to two places: an imported C file becomes an
// `#include`, and an imported module's initializer has to be called at the
// top of this file's own initializer, before any of its code runs:
Text_t includes = EMPTY_TEXT;
Text_t use_imports = EMPTY_TEXT;
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$from_str(use->path);
if (path[0] != '/') {
// If we have `use ./foo.c`, then we need to remap it in source code to
// `#include "../foo.c"`, since it will be inside the .tomo directory.
OptionalPath_t parent = Path$parent(Path(ast->file->filename));
assert(parent); // A source file always has a directory
path = Path$relative_to(Path$resolved(Path$from_str(use->path), parent), parent);
path = Path$concat("..", path);
}
includes = Texts(includes, "#include \"", Path$as_c_string(path), "\"\n");
}
}
}
Text_t initializer_body = compile_initializers(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.h>\n"
"#include \"",
name, ".tm.h\"\n\n", includes, "\n", env->code->local_typedefs, "\n", env->code->constants, "\n",
file_variables, "\n", env->code->lambdas, "\n", env->code->staticdefs, "\n", functions, "\n",
"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, initializer_body, "}\n");
}
public
Text_t compile_test_runner(env_t *env, ast_t *ast, int64_t *out_count) {
// Compile each test body first so any lambdas/static helpers they generate
// accumulate into env->code and get emitted by compile_file() below:
Text_t test_fns = EMPTY_TEXT, descriptors = EMPTY_TEXT;
int64_t count = 0;
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
if (stmt->ast->tag != Test) continue;
DeclareMatch(test, stmt->ast, Test);
if (test->expected_compile_error) continue; // handled by the driver's frontend
env_t *scope = fresh_scope(env);
Text_t body = compile_block(scope, test->body);
Text_t fn_name = Texts("test$", count);
test_fns = Texts(test_fns, "static void ", fn_name, "(void) ", body, "\n");
descriptors = Texts(descriptors, "{.label=", quoted_str(test->label), ", .fn=", fn_name,
", .expect_failure=", test->expected_failure ? "true" : "false", ", .expected_msg=",
(test->expected_failure && test->expected_failure[0]) ? quoted_str(test->expected_failure)
: Text("NULL"),
", .first_line=", get_line_number(ast->file, stmt->ast->start),
", .last_line=", get_line_number(ast->file, stmt->ast->end), "},\n");
count += 1;
}
*out_count = count;
if (count == 0) return EMPTY_TEXT;
// Emit the entire module into the runner's own translation unit, including
// its file-private (`static`) helpers, so the test bodies above can call
// them. Because the module's code lives here, the driver must NOT also link
// the module's normal object file (that would duplicate every public
// symbol); see build_test_runner(). compile_file() emits the accumulated
// lambdas/staticdefs (module + tests), so append the test functions after.
Text_t module_code = compile_file(env, ast);
return Texts(module_code, "\n", test_fns, "static tomo_test_t _tomo_tests[] = {\n", descriptors,
"};\n"
"int main(int argc, char *argv[]) {\n"
"(void)argc; (void)argv;\n"
"tomo_init();\n",
namespace_name(env, env->namespace, Text("$initialize")),
"();\n"
"return _tomo_run_tests(_tomo_tests, ",
count, ");\n}\n");
}
|