From fcbfb548c335c14eac497fc8635e0019dbb09314 Mon Sep 17 00:00:00 2001 From: servostar Date: Sun, 2 Jun 2024 20:57:59 +0200 Subject: [PATCH] added doxygen and added new compiler module --- src/compiler.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++ src/compiler.h | 15 +++ src/io/files.c | 95 +++++++++++++++++- src/io/files.h | 74 ++++++++++++++ src/main.c | 176 +--------------------------------- 5 files changed, 439 insertions(+), 176 deletions(-) create mode 100644 src/compiler.c create mode 100644 src/compiler.h diff --git a/src/compiler.c b/src/compiler.c new file mode 100644 index 0000000..1889763 --- /dev/null +++ b/src/compiler.c @@ -0,0 +1,255 @@ +// +// Created by servostar on 6/2/24. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern void yyrestart(FILE *); + +// Module AST node used by the parser for AST construction. +[[maybe_unused]] +AST_NODE_PTR root; +// Current file which gets compiled the parser. +// NOTE: due to global state no concurrent compilation is possible +// on parser level. +[[maybe_unused]] +ModuleFile *current_file; + +/** + * @brief Compile the specified file into AST + * @param ast Initialized AST module node to build program rules + * @param file The file to be processed + * @return EXIT_SUCCESS in case the parsing was success full anything lese if not + */ +[[nodiscard("AST may be in invalid state")]] +[[gnu::nonnull(1), gnu::nonnull(1)]] +static int compile_file_to_ast(AST_NODE_PTR ast, ModuleFile *file) { + assert(file->path != NULL); + assert(ast != NULL); + + file->handle = fopen(file->path, "r"); + + if (file->handle == NULL) { + INFO("unable to open file: %s", file->path); + print_message(Error, "Cannot open file %s: %s", file->path, strerror(errno)); + return EXIT_FAILURE; + } + + DEBUG("parsing file: %s", file->path); + // setup global state + root = ast; + current_file = file; + yyin = file->handle; + yyrestart(yyin); + lex_reset(); + + yyparse(); + + // clean up global state + // current_file = NULL; + root = NULL; + yyin = NULL; + + return EXIT_SUCCESS; +} + +/** + * @brief Setup the environment of the target. + * @param target + * @return EXIT_SUCCESS if successful EXIT_FAILURE otherwise + */ +static int setup_target_environment(const TargetConfig *target) { + DEBUG("setting up environment for target: %s", target->name); + + assert(target->output_directory != NULL); + assert(target->archive_directory != NULL); + + int result; + + result = create_directory(target->archive_directory); + if (result != 0 && errno != EEXIST) { + const char *message = get_last_error(); + assert(message != NULL); + + print_message(Error, "Unable to create directory: %s: %s", target->archive_directory, message); + free((void *) message); + return EXIT_FAILURE; + } + + result = create_directory(target->output_directory); + if (result != 0 && errno != EEXIST) { + const char *message = get_last_error(); + assert(message != NULL); + + print_message(Error, "Unable to create directory: %s: %s", target->output_directory, message); + free((void *) message); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/** + * @brief Print the supplied AST of the specified target to a graphviz ".gv" file + * @param ast + * @param target + */ +static void print_ast_to_file(AST_NODE_PTR ast, TargetConfig *target) { + assert(ast != NULL); + assert(target != NULL); + + if (!target->print_ast) + return; + + // create file path to write graphviz to + const char *path = make_file_path(1, target->name, ".gv", target->archive_directory); + + FILE *output = fopen((const char *) path, "w"); + if (output == NULL) { + const char *message = get_last_error(); + print_message(Error, "Unable to open file for syntax tree at: %s: %s", path, message); + free((void *) message); + } else { + + AST_fprint_graphviz(output, ast); + + fclose(output); + } + + free((void *) path); +} + +/** + * @brief Build the given target + * @param unit + * @param target + */ +static void build_target(ModuleFileStack *unit, TargetConfig *target) { + print_message(Info, "Building target: %s", target->name); + + AST_NODE_PTR ast = AST_new_node(empty_location(), AST_Module, NULL); + ModuleFile *file = push_file(unit, target->root_module); + + if (compile_file_to_ast(ast, file) == EXIT_SUCCESS) { + if (setup_target_environment(target) == 0) { + + print_ast_to_file(ast, target); + + // TODO: parse AST to semantic values + // TODO: backend codegen + } + } + + AST_delete_node(ast); + + print_file_statistics(file); +} + +/** + * @brief Compile a single file. + * Creates a single target by the given command line arguments. + * @param unit + * @param argc + * @param argv + */ +static 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) { + print_message(Error, "No input file specified."); + delete_target_config(target); + return; + } + + build_target(unit, target); + + delete_target_config(target); +} + +/** + * @brief Build all project targets specified by the command line arguments. + * @param unit + * @param config + * @param argc + * @param argv + */ +static void build_project_targets(ModuleFileStack *unit, ProjectConfig *config, int argc, char *argv[]) { + if (argc == 1 && strcmp(argv[0], "all") == 0) { + // build all targets in the project + 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 { + // build all targets given in the arguments + for (int i = 0; i < argc; i++) { + char *target_name = argv[i]; + if (g_hash_table_contains(config->targets, target_name)) { + build_target(unit, g_hash_table_lookup(config->targets, target_name)); + } + } + } +} + +/** + * @brief Build targets from project. Configuration is provided by command line arguments. + * @param unit File storage + * @param argc Number of arguments + * @param argv Array of Arguments + */ +static void build_project(ModuleFileStack *unit, int argc, char *argv[]) { + if (argc <= 0) { + print_message(Error, "No targets specified."); + return; + } + + ProjectConfig *config = default_project_config(); + int err = load_project_config(config); + + if (err == PROJECT_OK) { + build_project_targets(unit, config, argc, argv); + } + + delete_project_config(config); +} + +void run_compiler(int argc, char *argv[]) { + if (argc <= 0) { + INFO("no arguments provided"); + print_help(); + return; + } + + ModuleFileStack files = new_file_stack(); + + if (strcmp(argv[0], "build") == 0) { + build_project(&files, argc - 1, &argv[1]); + } else if (strcmp(argv[0], "compile") == 0) { + compile_file(&files, argc - 1, &argv[1]); + } else { + print_message(Error, "Invalid mode of operation. Rerun with --help."); + } + + if (files.files == NULL) { + print_message(Error, "No input files, nothing to do."); + exit(1); + } + + print_unit_statistics(&files); + delete_files(&files); +} diff --git a/src/compiler.h b/src/compiler.h new file mode 100644 index 0000000..943e47b --- /dev/null +++ b/src/compiler.h @@ -0,0 +1,15 @@ +// +// Created by servostar on 6/2/24. +// + +#ifndef GEMSTONE_COMPILER_H +#define GEMSTONE_COMPILER_H + +/** + * @brief Run the gemstone compiler with the provided command arguments. + * @param argc + * @param argv + */ +void run_compiler(int argc, char *argv[]); + +#endif //GEMSTONE_COMPILER_H diff --git a/src/io/files.c b/src/io/files.c index 09ff395..86cdfdb 100644 --- a/src/io/files.c +++ b/src/io/files.c @@ -6,6 +6,28 @@ #include #include #include +#include + +#ifdef __unix__ + +#include + +#define MAX_PATH_BYTES PATH_MAX + +#elif defined(_WIN32) || defined(WIN32) + +#include + +#define MAX_PATH_BYTES _MAX_PATH + +#endif + +ModuleFileStack new_file_stack() { + ModuleFileStack stack; + stack.files = NULL; + + return stack; +} ModuleFile *push_file(ModuleFileStack *stack, const char *path) { assert(stack != NULL); @@ -92,12 +114,13 @@ void print_diagnostic(ModuleFile *file, TokenLocation *location, Message kind, c break; } - char absolute_path[PATH_MAX]; - realpath(file->path, absolute_path); + const char *absolute_path = get_absolute_path(file->path); printf("%s%s:%ld:%s %s%s:%s %s\n", BOLD, absolute_path, location->line_start, RESET, accent_color, kind_text, RESET, message); + free((void *) absolute_path); + size_t lines = location->line_end - location->line_start + 1; for (size_t l = 0; l < lines; l++) { @@ -261,7 +284,73 @@ void print_message(Message kind, const char *fmt, ...) { printf("%s%s:%s ", accent_color, kind_text, RESET); vprintf(fmt, args); - printf("\n\n"); + printf("\n"); va_end(args); } + +int create_directory(const char *path) { + assert(path != NULL); + + DEBUG("creating directory: %s", path); + + int result; +#ifdef __unix__ + result = mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +#elif defined(_WIN32) || defined(WIN32) + result = _mkdir(path); +#endif + + return result; +} + +const char *get_last_error() { + return strdup(strerror(errno)); +} + +const char *get_absolute_path(const char *path) { + assert(path != NULL); + + DEBUG("resolving absolute path of: %s", path); + +#ifdef __unix__ + // use unix specific function + char absolute_path[MAX_PATH_BYTES]; + realpath(path, absolute_path); +#elif defined(_WIN32) || defined(WIN32) + // use Windows CRT specific function + char absolute_path[MAX_PATH_BYTES]; + _fullpath(path, absolute_path, _MAX_PATH); +#endif + + return strdup(absolute_path); +} + +const char* make_file_path(int count, const char* name, const char* ext, ...) { + DEBUG("building file path..."); + + va_list args; + va_start(args, count); // Initialize the va_list with the first variadic argument + + char* path = calloc(MAX_PATH_BYTES, sizeof(char)); + + for (int i = 0; i < count; i++) { + const char* arg = va_arg(args, const char*); + assert(arg != NULL); + + strcat(path, arg); + strcat(path, PATH_SEPARATOR); + } + + va_end(args); // Clean up the va_list + + if (name != NULL) { + strcat(path, name); + } + + if (name != NULL) { + strcat(path, ext); + } + + return path; +} diff --git a/src/io/files.h b/src/io/files.h index 25d5078..7b8b166 100644 --- a/src/io/files.h +++ b/src/io/files.h @@ -8,6 +8,12 @@ #include #include +#if defined(WIN32) || defined(_WIN32) +#define PATH_SEPARATOR "\\" +#else +#define PATH_SEPARATOR "/" +#endif + typedef struct FileDiagnosticStatistics_t { size_t error_count; size_t warning_count; @@ -37,6 +43,12 @@ typedef struct TokenLocation_t { unsigned long int col_end; } TokenLocation; +/** + * @brief Create a new, empty file stack. + * @return + */ +ModuleFileStack new_file_stack(); + /** * @brief Add a new file to the file stack. * @attention The file handle returned will be invalid @@ -65,18 +77,80 @@ void delete_files(ModuleFileStack *stack); TokenLocation new_location(unsigned long int line_start, unsigned long int col_start, unsigned long int line_end, unsigned long int col_end); +/** + * @brief Create a new empty location with all of its contents set to zero + * @return + */ TokenLocation empty_location(void); +/** + * @brief Prints some diagnostic message to stdout. + * This also print the token group and the attached source as context. + * @param file + * @param location + * @param kind + * @param message + */ [[gnu::nonnull(1), gnu::nonnull(2)]] void print_diagnostic(ModuleFile *file, TokenLocation *location, Message kind, const char *message); [[gnu::nonnull(2)]] +/** + * @brief Print a general message to stdout. Provides no source context like print_diagnostic() + * @param kind + * @param fmt + * @param ... + */ void print_message(Message kind, const char *fmt, ...); +/** + * @brief Print statistics about a specific file. + * Will print the amount of infos, warning and errors emitted during compilation. + * @param file + */ [[gnu::nonnull(1)]] void print_file_statistics(ModuleFile *file); +/** + * @brief Print statistics of all files in the module stack. + * @param file_stack + */ [[gnu::nonnull(1)]] void print_unit_statistics(ModuleFileStack *file_stack); +/** + * @brief Create a new directory. Will return EEXISTS in case the directory already exists. + * @param path + * @return 0 if successful, anything else otherwise + */ +[[gnu::nonnull(1)]] +int create_directory(const char* path); + +/** + * @brief Get a string describing the last error set by errno. + * @return a string that must be freed + */ +[[nodiscard("pointer must be freed")]] +const char* get_last_error(); + +/** + * @brief Resolve the absolute path from a given relative path. + * @param path + * @return + */ +[[gnu::nonnull(1)]] +[[nodiscard("pointer must be freed")]] +const char* get_absolute_path(const char* path); + +/** + * @brief Create a file path from a base name, extension a variable amount of directory path segments. + * @param count Amount of path segments to prepend to the basename + * @param name Basename of the file + * @param ext Extension of the file + * @param ... Path segments without path separator + * @return A relative path of a file + */ +[[nodiscard("pointer must be freed")]] +const char* make_file_path(int count, const char* name, const char* ext, ...); + #endif //GEMSTONE_FILES_H diff --git a/src/main.c b/src/main.c index 397c652..a661dfb 100644 --- a/src/main.c +++ b/src/main.c @@ -1,58 +1,10 @@ #include #include #include -#include #include #include #include -#include -#include - -extern void yyrestart(FILE *); - -[[maybe_unused]] -AST_NODE_PTR root; -[[maybe_unused]] -ModuleFile *current_file; - -/** - * @brief Compile the specified file into AST - * @param ast - * @param file - * @return EXIT_SUCCESS in case the parsing was success full anything lese if not - */ -[[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); - - file->handle = fopen(file->path, "r"); - - if (file->handle == NULL) { - INFO("unable to open file: %s", file->path); - print_message(Error, "Cannot open file %s: %s", file->path, strerror(errno)); - return 1; - } - - DEBUG("parsing file: %s", file->path); - // setup global state - root = ast; - current_file = file; - yyin = file->handle; - yyrestart(yyin); - lex_reset(); - - yyparse(); - - // clean up global state - // current_file = NULL; - root = NULL; - yyin = NULL; - - return 0; -} +#include /** * @brief Log a debug message to inform about beginning exit procedures @@ -60,17 +12,6 @@ static size_t compile_file_to_ast(AST_NODE_PTR ast, ModuleFile *file) { */ void notify_exit(void) { DEBUG("Exiting gemstone..."); } -/** - * @brief Closes File after compiling. - * - */ - -void close_file(void) { - if (NULL != yyin) { - fclose(yyin); - } -} - /** * @brief Run compiler setup here * @@ -95,123 +36,12 @@ void setup(void) { DEBUG("finished starting up gemstone..."); } -static void setup_target_environment(const TargetConfig* target) { - if (target->output_directory) { - - } -} - -void build_target(ModuleFileStack *unit, TargetConfig *target) { - print_message(Info, "Compiling file: %s", target->root_module); - - TokenLocation location = new_location(0,0,0,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) { - setup_target_environment(target); - - if (target->print_ast) { - - } - - // 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) { - print_message(Error, "No input file specified."); - delete_target_config(target); - return; - } - - build_target(unit, target); - - delete_target_config(target); -} - -void build_project_targets(ModuleFileStack *unit, ProjectConfig *config, int argc, char *argv[]) { - if (argc == 1 && strcmp(argv[0], "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 { - for (int i = 0; i < argc; i++) { - char *target_name = argv[i]; - if (g_hash_table_contains(config->targets, target_name)) { - build_target(unit, g_hash_table_lookup(config->targets, target_name)); - } - } - } -} - -void build_project(ModuleFileStack *unit, int argc, char *argv[]) { - if (argc <= 0) { - print_message(Error, "No targets specified."); - return; - } - - ProjectConfig *config = default_project_config(); - int err = load_project_config(config); - - if (err == PROJECT_OK) { - build_project_targets(unit, config, argc, argv); - } - - delete_project_config(config); -} - -void configure_run_mode(int argc, char *argv[]) { - if (argc <= 0) { - INFO("no arguments provided"); - print_help(); - return; - } - - ModuleFileStack files; - files.files = NULL; - - if (strcmp(argv[0], "build") == 0) { - build_project(&files, argc - 1, &argv[1]); - } else if (strcmp(argv[0], "compile") == 0) { - compile_file(&files, argc - 1, &argv[1]); - } else { - print_message(Error, "Invalid mode of operation. Rerun with --help."); - } - - if (files.files == NULL) { - print_message(Error, "No input files, nothing to do."); - exit(1); - } - - print_unit_statistics(&files); - delete_files(&files); -} - int main(int argc, char *argv[]) { - setup(); - atexit(close_file); - print_message(Info, "running GSC version %s", GSC_VERSION); + print_message(Info, "Running GSC version %s", GSC_VERSION); - configure_run_mode(argc - 1, &argv[1]); + run_compiler(argc - 1, &argv[1]); return 0; }