diff --git a/CMakeLists.txt b/CMakeLists.txt index ca09790..d3763ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,8 @@ set(CMAKE_C_STANDARD_REQUIRED TRUE) set(GEMSTONE_TEST_DIR ${PROJECT_SOURCE_DIR}/tests) set(GEMSTONE_BINARY_DIR ${PROJECT_SOURCE_DIR}/bin) +add_compile_definitions(GSC_VERSION="${PROJECT_VERSION}") + include(CTest) if(BUILD_TESTING) @@ -144,6 +146,9 @@ target_compile_options(release PUBLIC ${FLAGS} ${RELEASE_FLAGS}) # add src directory as include path target_include_directories(release PUBLIC src) +# disable assertions +target_compile_definitions(release PUBLIC NDEBUG="1") + # ------------------------------------------------ # # Target DEBUG # # ------------------------------------------------ # diff --git a/src/cfg/opt.c b/src/cfg/opt.c index feeafcb..ca22b6a 100644 --- a/src/cfg/opt.c +++ b/src/cfg/opt.c @@ -4,41 +4,47 @@ #include #include +#include -#define MAX_TARGETS_PER_PROJECT 100 +TargetConfig* default_target_config() { + DEBUG("generating default target config..."); -TargetConfig default_target_config() { - TargetConfig config; + TargetConfig* config = malloc(sizeof(TargetConfig)); - config.name = "debug"; - config.print_ast = false; - config.print_asm = false; - config.print_ir = false; - config.mode = Application; - config.archive_directory = "archive"; - config.archive_directory = "bin"; - config.optimization_level = 1; - config.root_module = NULL; + config->name = NULL; + config->print_ast = false; + config->print_asm = false; + config->print_ir = false; + config->mode = Application; + config->archive_directory = NULL; + config->archive_directory = NULL; + config->optimization_level = 1; + config->root_module = NULL; return config; } -TargetConfig default_target_config_from_args(int argc, char *argv[]) { - TargetConfig config = default_target_config(); +TargetConfig* default_target_config_from_args(int argc, char *argv[]) { + DEBUG("generating default target from command line..."); + + TargetConfig* config = default_target_config(); for (int i = 0; i < argc; i++) { + DEBUG("processing argument: %ld %s", i, argv[i]); char *option = argv[i]; if (strcmp(option, "--print-ast") == 0) { - config.print_ast = true; + config->print_ast = true; } else if (strcmp(option, "--print-asm") == 0) { - config.print_asm = true; + config->print_asm = true; } else if (strcmp(option, "--print-ir") == 0) { - config.print_ir = true; + config->print_ir = true; } else if (strcmp(option, "--mode=app") == 0) { - config.mode = Application; + config->mode = Application; } else if (strcmp(option, "--mode=lib") == 0) { - config.mode = Library; + config->mode = Library; + } else { + config->root_module = option; } } @@ -46,11 +52,14 @@ TargetConfig default_target_config_from_args(int argc, char *argv[]) { } void print_help(void) { + DEBUG("printing help dialog..."); + const char *lines[] = { - "Gemstone Compiler (C) GPL-2.0\n", - "Compile file(s): gsc [files]", - "Build project (build.toml): gsc [directory] [target]|all", + "Gemstone Compiler (C) GPL-2.0", + "Build a project target: gsc build [target]|all", + "Compile non-project file: gsc compile [file]", "Options:", + " --version print the version", " --print-ast print resulting abstract syntax tree to a file", " --print-asm print resulting assembly language to a file", " --print-ir print resulting LLVM-IR to a file", @@ -62,35 +71,42 @@ void print_help(void) { } } -#define PROJECT_OK 0 -#define PROJECT_TOML_ERR 1 -#define PROJECT_SEMANTIC_ERR 2 - static void get_bool(bool* boolean, toml_table_t *table, const char* name) { + DEBUG("retrieving boolean %s", name); + toml_datum_t datum = toml_bool_in(table, name); if (datum.ok) { *boolean = datum.u.b; + DEBUG("boolean has value: %d", datum.u.b); } } static void get_str(char** string, toml_table_t *table, const char* name) { + DEBUG("retrieving string %s", name); + toml_datum_t datum = toml_string_in(table, name); if (datum.ok) { *string = datum.u.s; + DEBUG("string has value: %s", datum.u.s); } } static void get_int(int* integer, toml_table_t *table, const char* name) { + DEBUG("retrieving integer %s", name); + toml_datum_t datum = toml_int_in(table, name); if (datum.ok) { *integer = (int) datum.u.i; + DEBUG("integer has value: %ld", datum.u.i); } } static int parse_project_table(ProjectConfig *config, toml_table_t *project_table) { + DEBUG("parsing project table..."); + // project name get_str(&config->name, project_table, "version"); if (config->name == NULL) { @@ -99,7 +115,7 @@ static int parse_project_table(ProjectConfig *config, toml_table_t *project_tabl } // project version - get_str(&config->name, project_table, "version"); + get_str(&config->name, project_table, "name"); // author names toml_array_t *authors = toml_array_in(project_table, "authors"); @@ -136,34 +152,44 @@ static int get_mode_from_str(TargetCompilationMode* mode, const char* name) { } static int parse_target(ProjectConfig *config, toml_table_t *target_table, const char* name) { - TargetConfig target_config = default_target_config(); + DEBUG("parsing target table..."); - target_config.name = (char*) name; + TargetConfig* target_config = default_target_config(); - get_bool(&target_config.print_ast, target_table, "print_ast"); - get_bool(&target_config.print_asm, target_table, "print_asm"); - get_bool(&target_config.print_ir, target_table, "print_ir"); + target_config->name = (char*) name; - get_str(&target_config.root_module, target_table, "root"); - get_str(&target_config.output_directory, target_table, "output"); - get_str(&target_config.archive_directory, target_table, "archive"); + get_bool(&target_config->print_ast, target_table, "print_ast"); + get_bool(&target_config->print_asm, target_table, "print_asm"); + get_bool(&target_config->print_ir, target_table, "print_ir"); - get_int(&target_config.optimization_level, target_table, "opt"); + get_str(&target_config->root_module, target_table, "root"); + get_str(&target_config->output_directory, target_table, "output"); + get_str(&target_config->archive_directory, target_table, "archive"); + + get_int(&target_config->optimization_level, target_table, "opt"); char* mode = NULL; get_str(&mode, target_table, "mode"); - int err = get_mode_from_str(&target_config.mode, mode); + int err = get_mode_from_str(&target_config->mode, mode); if (err != PROJECT_OK) { return err; } - g_array_append_val(config->targets, target_config); + g_hash_table_insert(config->targets, target_config->name, target_config); return PROJECT_OK; } static int parse_targets(ProjectConfig *config, toml_table_t *root) { + DEBUG("parsing targets of project \"%s\"", config->name); + toml_table_t *targets = toml_table_in(root, "target"); + if (targets == NULL) { + printf("Project has no targets\n"); + return PROJECT_SEMANTIC_ERR; + } + + config->targets = g_hash_table_new(g_str_hash, g_str_equal); for (int i = 0; i < MAX_TARGETS_PER_PROJECT; i++) { const char *key = toml_key_in(targets, i); @@ -179,8 +205,12 @@ static int parse_targets(ProjectConfig *config, toml_table_t *root) { } int load_project_config(ProjectConfig *config) { - FILE *config_file = fopen("build.toml", "r"); + DEBUG("loading project config..."); + + FILE *config_file = fopen(PROJECT_CONFIG_FILE, "r"); if (config_file == NULL) { + printf("Cannot open file %s: %s\n", PROJECT_CONFIG_FILE, strerror(errno)); + ERROR("project file not found"); return PROJECT_TOML_ERR; } @@ -197,7 +227,7 @@ int load_project_config(ProjectConfig *config) { toml_table_t *project = toml_table_in(conf, "project"); if (project) { if (parse_project_table(config, project) == PROJECT_OK) { - return parse_targets(config, project); + return parse_targets(config, conf); } } else { printf("Invalid project configuration: missing project table\n\n"); @@ -206,3 +236,62 @@ int load_project_config(ProjectConfig *config) { toml_free(conf); return PROJECT_SEMANTIC_ERR; } + +void delete_target_config(TargetConfig* config) { + if (config->root_module != NULL) { + free(config->root_module); + } + if (config->archive_directory != NULL) { + free(config->archive_directory); + } + if (config->name != NULL) { + free(config->name); + } + if (config->output_directory != NULL) { + free(config->output_directory); + } + free(config); +} + +void delete_project_config(ProjectConfig* config) { + if (config->name != NULL) { + free(config->name); + } + if (config->authors != NULL) { + g_array_free(config->authors, TRUE); + } + if (config->desc != NULL) { + free(config->desc); + } + if (config->license != NULL) { + free(config->license); + } + if (config->targets != NULL) { + GHashTableIter iter; + + g_hash_table_iter_init(&iter, config->targets); + + char* key; + TargetConfig* val; + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + delete_target_config(val); + } + + g_hash_table_destroy(config->targets); + } + + free(config); +} + +ProjectConfig* default_project_config() { + ProjectConfig* config = malloc(sizeof(ProjectConfig)); + + config->authors = NULL; + config->name = NULL; + config->targets = NULL; + config->license = NULL; + config->version = NULL; + config->desc = NULL; + + return config; +} diff --git a/src/cfg/opt.h b/src/cfg/opt.h index ec259e9..c54bfb2 100644 --- a/src/cfg/opt.h +++ b/src/cfg/opt.h @@ -8,6 +8,13 @@ #include #include +#define MAX_TARGETS_PER_PROJECT 100 +#define PROJECT_CONFIG_FILE "build.toml" + +#define PROJECT_OK 0 +#define PROJECT_TOML_ERR 1 +#define PROJECT_SEMANTIC_ERR 2 + typedef enum TargetCompilationMode_t { Application, Library @@ -42,13 +49,21 @@ typedef struct ProjectConfig_t { char* license; // list of authors GArray* authors; - GArray* targets; + GHashTable* targets; } ProjectConfig; -TargetConfig default_target_config(); +TargetConfig* default_target_config(); -TargetConfig default_target_config_from_args(int argc, char* argv[]); +ProjectConfig* default_project_config(); + +TargetConfig* default_target_config_from_args(int argc, char* argv[]); + +int load_project_config(ProjectConfig *config); void print_help(void); +void delete_project_config(ProjectConfig* config); + +void delete_target_config(TargetConfig*); + #endif //GEMSTONE_OPT_H diff --git a/src/main.c b/src/main.c index 4888ee5..c0be30b 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include #include #include +#include extern void yyrestart(FILE *); @@ -22,6 +23,7 @@ ModuleFile *current_file; */ [[nodiscard("AST may be in invalid state")]] [[gnu::nonnull(1), gnu::nonnull(1)]] + static size_t compile_file_to_ast(AST_NODE_PTR ast, ModuleFile *file) { assert(file->path != NULL); assert(ast != NULL); @@ -30,6 +32,7 @@ static size_t compile_file_to_ast(AST_NODE_PTR ast, ModuleFile *file) { if (file->handle == NULL) { INFO("unable to open file: %s", file->path); + printf("Cannot open file %s: %s\n", file->path, strerror(errno)); return 1; } @@ -92,43 +95,117 @@ void setup(void) { DEBUG("finished starting up gemstone..."); } +void build_target(ModuleFileStack *unit, TargetConfig *target) { + printf("Compiling file: %s\n\n", target->root_module); + + TokenLocation location = { + .line_start = 0, + .line_end = 0, + .col_start = 0, + .col_end = 0 + }; + AST_NODE_PTR ast = AST_new_node(location, AST_Module, NULL); + ModuleFile *file = push_file(unit, target->root_module); + + if (compile_file_to_ast(ast, file) == EXIT_SUCCESS) { + // TODO: parse AST to semantic values + // TODO: backend codegen + } + + AST_delete_node(ast); + + print_file_statistics(file); +} + +void compile_file(ModuleFileStack *unit, int argc, char *argv[]) { + INFO("compiling basic files..."); + + TargetConfig *target = default_target_config_from_args(argc, argv); + + if (target->root_module == NULL) { + printf("No input file specified\n"); + delete_target_config(target); + return; + } + + build_target(unit, target); + + delete_target_config(target); +} + +void build_project_targets(ModuleFileStack *unit, ProjectConfig *config, const char *filter) { + if (strcmp(filter, "all") == 0) { + GHashTableIter iter; + + g_hash_table_iter_init(&iter, config->targets); + + char* key; + TargetConfig* val; + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + build_target(unit, val); + } + } else if (g_hash_table_contains(config->targets, filter)) { + build_target(unit, g_hash_table_lookup(config->targets, filter)); + } +} + +void build_project(ModuleFileStack *unit, int argc, char *argv[]) { + if (argc >= 1) { + ProjectConfig* config = default_project_config(); + int err = load_project_config(config); + + if (err == PROJECT_OK) { + if (argc == 1) { + build_project_targets(unit, config, "all"); + } else { + build_project_targets(unit, config, argv[0]); + } + } + + delete_project_config(config); + + } else { + printf("Expected 1 target to run\n"); + } +} + +void configure_run_mode(int argc, char *argv[]) { + if (argc > 1) { + + ModuleFileStack files; + files.files = NULL; + + if (strcmp(argv[1], "build") == 0) { + build_project(&files, argc - 2, &argv[2]); + } else if (strcmp(argv[1], "compile") == 0) { + compile_file(&files, argc - 2, &argv[2]); + } else { + printf("invalid mode of operation\n"); + } + + if (files.files == NULL) { + printf("No input files, nothing to do.\n\n"); + exit(1); + } + + print_unit_statistics(&files); + delete_files(&files); + + return; + } + INFO("no arguments provided"); + + print_help(); +} + int main(int argc, char *argv[]) { setup(); atexit(close_file); - ModuleFileStack files; - files.files = NULL; + printf("running GSC version %s\n", GSC_VERSION); - for (int i = 1; i < argc; i++) { - printf("Compiling file: %s\n\n", argv[i]); + configure_run_mode(argc, argv); - TokenLocation location = { - .line_start = 0, - .line_end = 0, - .col_start = 0, - .col_end = 0 - }; - AST_NODE_PTR ast = AST_new_node(location, AST_Module, NULL); - ModuleFile *file = push_file(&files, argv[i]); - - if (compile_file_to_ast(ast, file) == EXIT_SUCCESS) { - // TODO: parse AST to semantic values - // TODO: backend codegen - } - - AST_delete_node(ast); - - print_file_statistics(file); - } - - if (files.files == NULL) { - printf("No input files, nothing to do.\n\n"); - exit(1); - } - - print_unit_statistics(&files); - - delete_files(&files); return 0; }