Overhaul of header logic so it now uses topological ordering and
deduplication for libraries with multiple files.
This commit is contained in:
parent
aaa51fc734
commit
03a7d5f44d
24
ast.c
24
ast.c
@ -1,8 +1,8 @@
|
||||
// Some basic operations defined on AST nodes, mainly converting to
|
||||
// strings for debugging.
|
||||
#include <gc/cord.h>
|
||||
#include <stdarg.h>
|
||||
#include <printf.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
150
compile.c
150
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,74 +3820,10 @@ 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"
|
||||
"extern const TypeInfo ", full_name, ";\n",
|
||||
compile_namespace_header(env, def->name, def->namespace)
|
||||
);
|
||||
}
|
||||
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));
|
||||
}
|
||||
case FunctionDef: {
|
||||
auto fndef = Match(ast, FunctionDef);
|
||||
const char *decl_name = Match(fndef->name, Var)->name;
|
||||
@ -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 <tomo/tomo.h>\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;
|
||||
|
@ -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);
|
||||
|
||||
|
2
enums.c
2
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
|
||||
|
81
tomo.c
81
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 <tomo/tomo.h>\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);
|
||||
|
Loading…
Reference in New Issue
Block a user