From 03a7d5f44dfd8c455706b2c5c23217fd140f18c2 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 17 Sep 2024 16:20:30 -0400 Subject: Overhaul of header logic so it now uses topological ordering and deduplication for libraries with multiple files. --- ast.c | 24 +++++++--- compile.c | 150 ++++++++++++++++++++------------------------------------------ compile.h | 7 ++- enums.c | 2 +- tomo.c | 81 +++++++++++++++++++++++++++------ 5 files changed, 138 insertions(+), 126 deletions(-) diff --git a/ast.c b/ast.c index b4a75277..0d2d6e5b 100644 --- a/ast.c +++ b/ast.c @@ -1,8 +1,8 @@ // Some basic operations defined on AST nodes, mainly converting to // strings for debugging. #include -#include #include +#include #include "ast.h" #include "stdlib/datatypes.h" @@ -264,9 +264,11 @@ void _visit_topologically(ast_t *ast, Table_t definitions, Table_t *visited, Clo void visit_topologically(ast_list_t *asts, Closure_t fn) { - // Visit each top-level statement in topological order, where typedefs are - // visited first, and each typedef's referenced types are visited before it - // is, when applicable. + // Visit each top-level statement in topological order: + // - 'use' statements first + // - then typedefs + // - visiting typedefs' dependencies first + // - then function/variable declarations Table_t definitions = {}; for (ast_list_t *stmt = asts; stmt; stmt = stmt->next) { @@ -282,14 +284,24 @@ void visit_topologically(ast_list_t *asts, Closure_t fn) } } + void (*visit)(void*, ast_t*) = (void*)fn.fn; Table_t visited = {}; + // First: 'use' statements in order: + for (ast_list_t *stmt = asts; stmt; stmt = stmt->next) { + if (stmt->ast->tag == Use || (stmt->ast->tag == Declare && Match(stmt->ast, Declare)->value->tag == Use)) + visit(fn.userdata, stmt->ast); + } + // Then typedefs in topological order: for (ast_list_t *stmt = asts; stmt; stmt = stmt->next) { if (stmt->ast->tag == StructDef || stmt->ast->tag == EnumDef || stmt->ast->tag == LangDef) _visit_topologically(stmt->ast, definitions, &visited, fn); } + // Then everything else in order: for (ast_list_t *stmt = asts; stmt; stmt = stmt->next) { - if (!(stmt->ast->tag == StructDef || stmt->ast->tag == EnumDef || stmt->ast->tag == LangDef)) - _visit_topologically(stmt->ast, definitions, &visited, fn); + if (!(stmt->ast->tag == StructDef || stmt->ast->tag == EnumDef || stmt->ast->tag == LangDef + || stmt->ast->tag == Use || (stmt->ast->tag == Declare && Match(stmt->ast, Declare)->value->tag == Use))) { + visit(fn.userdata, stmt->ast); + } } } diff --git a/compile.c b/compile.c index c0a5eed9..8ade5f16 100644 --- a/compile.c +++ b/compile.c @@ -2468,16 +2468,25 @@ CORD compile(env_t *env, ast_t *ast) } } + Table_t *closed_vars = get_closed_vars(env, ast); + if (Table$length(*closed_vars) > 0) { // Create a typedef for the lambda's closure userdata + CORD def = "typedef struct {"; + for (int64_t i = 1; i <= Table$length(*closed_vars); i++) { + struct { const char *name; binding_t *b; } *entry = Table$entry(*closed_vars, i); + if (entry->b->type->tag == ModuleType) + continue; + def = CORD_all(def, compile_declaration(entry->b->type, entry->name), "; "); + } + def = CORD_all(def, "} ", name, "$userdata_t;"); + env->code->local_typedefs = CORD_all(env->code->local_typedefs, def); + } + CORD code = CORD_all("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 = CORD_all(code, compile_type(arg_type), " $", arg->name, ", "); } - CORD args_typedef = compile_statement_typedefs(env, ast); - env->code->local_typedefs = CORD_all(env->code->local_typedefs, args_typedef); - - Table_t *closed_vars = get_closed_vars(env, ast); CORD userdata; if (Table$length(*closed_vars) == 0) { code = CORD_cat(code, "void *userdata)"); @@ -3312,12 +3321,12 @@ void compile_namespace(env_t *env, const char *ns_name, ast_t *block) } } -CORD compile_namespace_definitions(env_t *env, const char *ns_name, ast_t *block) +CORD compile_namespace_header(env_t *env, const char *ns_name, ast_t *block) { env_t *ns_env = namespace_env(env, ns_name); CORD header = CORD_EMPTY; for (ast_list_t *stmt = block ? Match(block, Block)->statements : NULL; stmt; stmt = stmt->next) { - header = CORD_all(header, compile_statement_definitions(ns_env, stmt->ast)); + header = CORD_all(header, compile_statement_header(ns_env, stmt->ast)); } return header; } @@ -3749,18 +3758,29 @@ CORD compile_file(env_t *env, ast_t *ast) "}\n"); } -CORD compile_statement_imports(env_t *env, ast_t *ast) +CORD compile_statement_header(env_t *env, ast_t *ast) { switch (ast->tag) { - case DocTest: { - auto test = Match(ast, DocTest); - return compile_statement_imports(env, test->expr); - } case Declare: { auto decl = Match(ast, Declare); if (decl->value->tag == Use) - return compile_statement_imports(env, decl->value); - return CORD_EMPTY; + return compile_statement_header(env, decl->value); + + const char *decl_name = Match(decl->var, Var)->name; + bool is_private = (decl_name[0] == '_'); + if (is_private) + return CORD_EMPTY; + + type_t *t = 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 %T value", t); + + return CORD_all( + compile_statement_header(env, decl->value), + "extern ", compile_declaration(t, CORD_cat(namespace_prefix(env->libname, env->namespace), decl_name)), ";\n"); } case Use: { auto use = Match(ast, Use); @@ -3778,25 +3798,21 @@ CORD compile_statement_imports(env_t *env, ast_t *ast) return CORD_EMPTY; } } - default: return CORD_EMPTY; - } -} - -CORD compile_statement_typedefs(env_t *env, ast_t *ast) -{ - switch (ast->tag) { - case DocTest: { - auto test = Match(ast, DocTest); - return compile_statement_typedefs(env, test->expr); - } case StructDef: { - return compile_struct_typedef(env, ast); + auto def = Match(ast, StructDef); + CORD full_name = CORD_cat(namespace_prefix(env->libname, env->namespace), def->name); + return CORD_all( + compile_struct_typedef(env, ast), + "extern const TypeInfo ", full_name, ";\n", + compile_namespace_header(env, def->name, def->namespace)); } case EnumDef: { - return compile_enum_typedef(env, ast); + return CORD_all(compile_enum_typedef(env, ast), + compile_enum_declarations(env, ast)); } case LangDef: { auto def = Match(ast, LangDef); + CORD full_name = CORD_cat(namespace_prefix(env->libname, env->namespace), def->name); return CORD_all( "typedef Text_t ", namespace_prefix(env->libname, env->namespace), def->name, "_t;\n" // Constructor macro: @@ -3804,73 +3820,9 @@ CORD compile_statement_typedefs(env_t *env, ast_t *ast) "(text) ((", namespace_prefix(env->libname, env->namespace), def->name, "_t){.length=sizeof(text)-1, .tag=TEXT_ASCII, .ascii=\"\" text})\n" "#define ", namespace_prefix(env->libname, env->namespace), def->name, "s(...) ((", namespace_prefix(env->libname, env->namespace), def->name, "_t)Texts(__VA_ARGS__))\n" - ); - } - case Lambda: { - auto lambda = Match(ast, Lambda); - Table_t *closed_vars = get_closed_vars(env, ast); - if (Table$length(*closed_vars) == 0) - return CORD_EMPTY; - - CORD def = "typedef struct {"; - for (int64_t i = 1; i <= Table$length(*closed_vars); i++) { - struct { const char *name; binding_t *b; } *entry = Table$entry(*closed_vars, i); - if (entry->b->type->tag == ModuleType) - continue; - def = CORD_all(def, compile_declaration(entry->b->type, entry->name), "; "); - } - CORD name = CORD_asprintf("%rlambda$%ld", namespace_prefix(env->libname, env->namespace), lambda->id); - return CORD_all(def, "} ", name, "$userdata_t;"); - } - default: - return CORD_EMPTY; - } -} - -CORD compile_statement_definitions(env_t *env, ast_t *ast) -{ - switch (ast->tag) { - case DocTest: { - auto test = Match(ast, DocTest); - return compile_statement_definitions(env, test->expr); - } - case Declare: { - auto decl = Match(ast, Declare); - if (decl->value->tag == Use) { - return compile_statement_definitions(env, decl->value); - } - type_t *t = 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 %T value", t); - const char *decl_name = Match(decl->var, Var)->name; - bool is_private = (decl_name[0] == '_'); - CORD code = (decl->value->tag == Use) ? compile_statement_definitions(env, decl->value) : CORD_EMPTY; - if (is_private) { - return code; - } else { - return CORD_all( - code, "\n" "extern ", compile_declaration(t, CORD_cat(namespace_prefix(env->libname, env->namespace), decl_name)), ";\n"); - } - } - case StructDef: { - auto def = Match(ast, StructDef); - CORD full_name = CORD_cat(namespace_prefix(env->libname, env->namespace), def->name); - return CORD_all( - "extern const TypeInfo ", full_name, ";\n", - compile_namespace_definitions(env, def->name, def->namespace)); - } - case EnumDef: { - return compile_enum_declarations(env, ast); - } - case LangDef: { - auto def = Match(ast, LangDef); - CORD full_name = CORD_cat(namespace_prefix(env->libname, env->namespace), def->name); - return CORD_all( "extern const TypeInfo ", full_name, ";\n", - compile_namespace_definitions(env, def->name, def->namespace)); + compile_namespace_header(env, def->name, def->namespace) + ); } case FunctionDef: { auto fndef = Match(ast, FunctionDef); @@ -3917,26 +3869,20 @@ typedef struct { CORD *header; } compile_typedef_info_t; -static void _visit_typedef(compile_typedef_info_t *info, ast_t *ast) +static void _visit_statement(compile_typedef_info_t *info, ast_t *ast) { - *info->header = CORD_all(*info->header, compile_statement_typedefs(info->env, ast)); + *info->header = CORD_all(*info->header, compile_statement_header(info->env, ast)); } -CORD compile_header(env_t *env, ast_t *ast) +CORD compile_file_header(env_t *env, ast_t *ast) { CORD header = CORD_all( "#pragma once\n" // "#line 1 ", CORD_quoted(ast->file->filename), "\n", "#include \n"); - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) - header = CORD_all(header, compile_statement_imports(env, stmt->ast)); - compile_typedef_info_t info = {.env=env, .header=&header}; - visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn=(void*)_visit_typedef, &info}); - - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) - header = CORD_all(header, compile_statement_definitions(env, stmt->ast)); + visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn=(void*)_visit_statement, &info}); header = CORD_all(header, "void ", env->namespace->name, "$$initialize(void);\n"); return header; diff --git a/compile.h b/compile.h index d53262da..37ed3f45 100644 --- a/compile.h +++ b/compile.h @@ -10,16 +10,15 @@ #include "environment.h" CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color); -CORD compile_header(env_t *env, ast_t *ast); CORD compile_file(env_t *env, ast_t *ast); +CORD compile_file_header(env_t *env, ast_t *ast); CORD compile_declaration(type_t *t, const char *name); CORD compile_type(type_t *t); CORD compile(env_t *env, ast_t *ast); void compile_namespace(env_t *env, const char *ns_name, ast_t *block); -CORD compile_namespace_definitions(env_t *env, const char *ns_name, ast_t *block); +CORD compile_namespace_header(env_t *env, const char *ns_name, ast_t *block); CORD compile_statement(env_t *env, ast_t *ast); -CORD compile_statement_typedefs(env_t *env, ast_t *ast); -CORD compile_statement_definitions(env_t *env, ast_t *ast); +CORD compile_statement_header(env_t *env, ast_t *ast); CORD compile_type_info(env_t *env, type_t *t); CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type); diff --git a/enums.c b/enums.c index e06ca0e0..538a809c 100644 --- a/enums.c +++ b/enums.c @@ -247,7 +247,7 @@ CORD compile_enum_declarations(env_t *env, ast_t *ast) all_defs = CORD_cat(all_defs, constructor_def); } } - return CORD_all(all_defs, compile_namespace_definitions(env, def->name, def->namespace)); + return CORD_all(all_defs, compile_namespace_header(env, def->name, def->namespace)); } // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/tomo.c b/tomo.c index 246c57c6..aa93b492 100644 --- a/tomo.c +++ b/tomo.c @@ -167,6 +167,68 @@ const char *escape_lib_name(const char *lib_name) Text$replace(Text$from_str(lib_name), Pattern("{1+ !alphanumeric}"), Text("_"), Pattern(""), false)); } +typedef struct { + env_t *env; + Table_t *used_imports; + FILE *output; +} libheader_info_t; + +static void _compile_statement_header_for_library(libheader_info_t *info, ast_t *ast) +{ + if (ast->tag == Declare && Match(ast, Declare)->value->tag == Use) + ast = Match(ast, Declare)->value; + + if (ast->tag == Use) { + auto use = Match(ast, Use); + if (use->what == USE_LOCAL) + return; + + if (!Table$str_get(*info->used_imports, use->path)) { + Table$str_set(info->used_imports, use->path, use->path); + CORD_put(compile_statement_header(info->env, ast), info->output); + } + } else { + CORD_put(compile_statement_header(info->env, ast), info->output); + } +} + +static void _compile_file_header_for_library(env_t *env, const char *filename, Table_t *visited_files, Table_t *used_imports, FILE *output) +{ + if (Table$str_get(*visited_files, filename)) + return; + + Table$str_set(visited_files, filename, filename); + + ast_t *file_ast = parse_file(filename, NULL); + if (!file_ast) errx(1, "Could not parse file %s", filename); + env_t *module_env = load_module_env(env, file_ast); + + libheader_info_t info = { + .env=module_env, + .used_imports=used_imports, + .output=output, + }; + + // Visit files in topological order: + for (ast_list_t *stmt = Match(file_ast, Block)->statements; stmt; stmt = stmt->next) { + ast_t *ast = stmt->ast; + if (ast->tag == Declare) + ast = Match(ast, Declare)->value; + if (ast->tag != Use) continue; + + auto use = Match(ast, Use); + if (use->what == USE_LOCAL) { + const char *used_filename = resolve_path(use->path, filename, "."); + _compile_file_header_for_library(env, used_filename, visited_files, used_imports, output); + } + } + + visit_topologically( + Match(file_ast, Block)->statements, (Closure_t){.fn=(void*)_compile_statement_header_for_library, &info}); + + CORD_fprintf(output, "void %r$initialize(void);\n", namespace_prefix(module_env->libname, module_env->namespace)); +} + void build_library(const char *lib_base_name) { glob_t tm_files; @@ -185,20 +247,13 @@ void build_library(const char *lib_base_name) // Build a "whatever.h" header that loads all the headers: FILE *header_prog = CORD_RUN(autofmt ? autofmt : "cat", " 2>/dev/null >", libname, ".h"); fputs("#pragma once\n", header_prog); + fputs("#include \n", header_prog); + Table_t visited_files = {}; + Table_t used_imports = {}; for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; - ast_t *ast = parse_file(filename, NULL); - if (!ast) errx(1, "Could not parse file %s", filename); - env_t *module_env = load_module_env(env, ast); - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { - CORD h = compile_statement_typedefs(module_env, stmt->ast); - if (h) CORD_put(h, header_prog); - } - for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { - CORD h = compile_statement_definitions(module_env, stmt->ast); - if (h) CORD_put(h, header_prog); - } - fprintf(header_prog, "void %s$%s$$initialize(void);\n", libname, file_base_name(filename)); + filename = resolve_path(filename, ".", "."); + _compile_file_header_for_library(env, filename, &visited_files, &used_imports, header_prog); } if (pclose(header_prog) == -1) errx(1, "Failed to run autoformat program on header file: %s", autofmt); @@ -405,7 +460,7 @@ void transpile_header(env_t *base_env, const char *filename, bool force_retransp env_t *module_env = load_module_env(base_env, ast); - CORD h_code = compile_header(module_env, ast); + CORD h_code = compile_file_header(module_env, ast); if (autofmt) { FILE *prog = CORD_RUN(autofmt, " 2>/dev/null >", h_filename); -- cgit v1.2.3