added doxygen and added new compiler module

This commit is contained in:
Sven Vogel 2024-06-02 20:57:59 +02:00
parent 01f5ef953d
commit fcbfb548c3
5 changed files with 439 additions and 176 deletions

255
src/compiler.c Normal file
View File

@ -0,0 +1,255 @@
//
// Created by servostar on 6/2/24.
//
#include <compiler.h>
#include <ast/ast.h>
#include <stdlib.h>
#include <sys/log.h>
#include <yacc/parser.tab.h>
#include <lex/util.h>
#include <io/files.h>
#include <assert.h>
#include <cfg/opt.h>
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);
}

15
src/compiler.h Normal file
View File

@ -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

View File

@ -6,6 +6,28 @@
#include <sys/log.h>
#include <assert.h>
#include <sys/col.h>
#include <stdarg.h>
#ifdef __unix__
#include <sys/stat.h>
#define MAX_PATH_BYTES PATH_MAX
#elif defined(_WIN32) || defined(WIN32)
#include <Windows.h>
#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;
}

View File

@ -8,6 +8,12 @@
#include <stdio.h>
#include <glib.h>
#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

View File

@ -1,58 +1,10 @@
#include <ast/ast.h>
#include <stdlib.h>
#include <sys/log.h>
#include <yacc/parser.tab.h>
#include <sys/col.h>
#include <lex/util.h>
#include <io/files.h>
#include <assert.h>
#include <cfg/opt.h>
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 <compiler.h>
/**
* @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;
}