566 lines
17 KiB
C
566 lines
17 KiB
C
//
|
|
// Created by servostar on 5/31/24.
|
|
//
|
|
|
|
#include <cfg/opt.h>
|
|
#include <string.h>
|
|
#include <sys/log.h>
|
|
#include <io/files.h>
|
|
#include <assert.h>
|
|
#include <toml.h>
|
|
#include <mem/cache.h>
|
|
#include <link/driver.h>
|
|
|
|
static GHashTable* args = NULL;
|
|
|
|
static void clean(void) {
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
g_hash_table_iter_init(&iter, args);
|
|
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
mem_free(value);
|
|
mem_free(key);
|
|
}
|
|
|
|
g_hash_table_destroy(args);
|
|
}
|
|
|
|
void parse_options(int argc, char* argv[]) {
|
|
args = g_hash_table_new(g_str_hash, g_str_equal);
|
|
|
|
atexit(clean);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
Option* option = mem_alloc(MemoryNamespaceOpt, sizeof(Option));
|
|
option->is_opt = g_str_has_prefix(argv[i], "--");
|
|
option->string = mem_strdup(MemoryNamespaceOpt, argv[i] + (option->is_opt ? 2 : 0));
|
|
option->index = i;
|
|
option->value = NULL;
|
|
|
|
char* equals = strchr(option->string, '=');
|
|
if (equals != NULL) {
|
|
option->value = equals + 1;
|
|
*equals = 0;
|
|
}
|
|
|
|
g_hash_table_insert(args, (gpointer) option->string, (gpointer) option);
|
|
}
|
|
}
|
|
|
|
bool is_option_set(const char* option) {
|
|
assert(option != NULL);
|
|
assert(args != NULL);
|
|
return g_hash_table_contains(args, option);
|
|
}
|
|
|
|
const Option* get_option(const char* option) {
|
|
if (g_hash_table_contains(args, option)) {
|
|
return g_hash_table_lookup(args, option);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GArray* get_non_options_after(const char* command) {
|
|
const Option* command_option = get_option(command);
|
|
|
|
if (command_option == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
GArray* array = mem_new_g_array(MemoryNamespaceOpt, sizeof(const char*));
|
|
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
g_hash_table_iter_init(&iter, args);
|
|
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
Option* option = (Option*) value;
|
|
if (!option->is_opt && command_option->index < option->index) {
|
|
g_array_append_val(array, option->string);
|
|
}
|
|
}
|
|
|
|
if (array->len == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
TargetConfig* default_target_config() {
|
|
DEBUG("generating default target config...");
|
|
|
|
TargetConfig* config = mem_alloc(MemoryNamespaceOpt, sizeof(TargetConfig));
|
|
|
|
config->name = mem_strdup(MemoryNamespaceOpt, "out");
|
|
config->print_ast = false;
|
|
config->print_asm = false;
|
|
config->print_ir = false;
|
|
config->driver = mem_strdup(MemoryNamespaceOpt, DEFAULT_DRIVER);
|
|
config->mode = Application;
|
|
config->archive_directory = mem_strdup(MemoryNamespaceOpt, "archive");
|
|
config->output_directory = mem_strdup(MemoryNamespaceOpt, "bin");
|
|
config->optimization_level = 1;
|
|
config->root_module = NULL;
|
|
config->link_search_paths = mem_new_g_array(MemoryNamespaceOpt, sizeof(char*));
|
|
config->lld_fatal_warnings = FALSE;
|
|
config->gsc_fatal_warnings = FALSE;
|
|
config->import_paths = mem_new_g_array(MemoryNamespaceOpt, sizeof(char*));
|
|
|
|
return config;
|
|
}
|
|
|
|
TargetConfig* default_target_config_from_args() {
|
|
DEBUG("generating default target from command line...");
|
|
|
|
TargetConfig* config = default_target_config();
|
|
|
|
gboolean fatal_warnings = is_option_set("all-fatal-warnings");
|
|
|
|
if (fatal_warnings || is_option_set("lld-fatal-warnings")) {
|
|
config->lld_fatal_warnings = true;
|
|
}
|
|
|
|
if (fatal_warnings || is_option_set("gsc-fatal-warnings")) {
|
|
config->gsc_fatal_warnings = true;
|
|
}
|
|
|
|
if (is_option_set("print-ast")) {
|
|
config->print_ast = true;
|
|
}
|
|
|
|
if (is_option_set("print-asm")) {
|
|
config->print_asm = true;
|
|
}
|
|
|
|
if (is_option_set("print-ir")) {
|
|
config->print_ir = true;
|
|
}
|
|
|
|
if (is_option_set("mode")) {
|
|
const Option* opt = get_option("mode");
|
|
|
|
if (opt->value != NULL) {
|
|
if (strcmp(opt->value, "app") == 0) {
|
|
config->mode = Application;
|
|
} else if (strcmp(opt->value, "lib") == 0) {
|
|
config->mode = Library;
|
|
} else {
|
|
print_message(Warning, "Invalid compilation mode: %s", opt->value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_option_set("output")) {
|
|
const Option* opt = get_option("output");
|
|
|
|
if (opt->value != NULL) {
|
|
config->name = mem_strdup(MemoryNamespaceOpt, (char*) opt->value);
|
|
}
|
|
}
|
|
|
|
if (is_option_set("driver")) {
|
|
const Option* opt = get_option("driver");
|
|
|
|
if (opt->value != NULL) {
|
|
config->driver = mem_strdup(MemoryNamespaceOpt, (char*) opt->value);
|
|
}
|
|
}
|
|
|
|
char* cwd = g_get_current_dir();
|
|
char* cached_cwd = mem_strdup(MemoryNamespaceOpt, cwd);
|
|
g_array_append_val(config->link_search_paths, cached_cwd);
|
|
free(cwd);
|
|
|
|
if (is_option_set("link-paths")) {
|
|
const Option* opt = get_option("link-paths");
|
|
|
|
if (opt->value != NULL) {
|
|
|
|
const char* start = opt->value;
|
|
const char* end = NULL;
|
|
while((end = strchr(start, ',')) != NULL) {
|
|
|
|
const int len = end - start;
|
|
char* link_path = mem_alloc(MemoryNamespaceOpt, len + 1);
|
|
memcpy(link_path, start, len);
|
|
link_path[len] = 0;
|
|
|
|
g_array_append_val(config->link_search_paths, link_path);
|
|
|
|
start = end;
|
|
}
|
|
|
|
const int len = strlen(start);
|
|
if (len > 0) {
|
|
char* link_path = mem_alloc(MemoryNamespaceOpt, len + 1);
|
|
memcpy(link_path, start, len);
|
|
link_path[len] = 0;
|
|
|
|
g_array_append_val(config->link_search_paths, link_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
GArray* files = get_non_options_after("compile");
|
|
|
|
if (files == NULL) {
|
|
print_message(Error, "No input file provided.");
|
|
} else {
|
|
|
|
if (files->len > 1) {
|
|
print_message(Warning, "Got more than one file to compile, using first, ignoring others.");
|
|
}
|
|
|
|
config->root_module = mem_strdup(MemoryNamespaceOpt, g_array_index(files, char*, 0));
|
|
}
|
|
|
|
char* default_import_path = mem_strdup(MemoryNamespaceOpt, ".");
|
|
g_array_append_val(config->import_paths, default_import_path);
|
|
|
|
if (is_option_set("import-paths")) {
|
|
const Option* opt = get_option("import-paths");
|
|
|
|
if (opt->value != NULL) {
|
|
|
|
const char* start = opt->value;
|
|
const char* end = NULL;
|
|
while((end = strchr(start, ',')) != NULL) {
|
|
|
|
const int len = end - start;
|
|
char* import_path = mem_alloc(MemoryNamespaceOpt, len + 1);
|
|
memcpy(import_path, start, len);
|
|
import_path[len] = 0;
|
|
|
|
g_array_append_val(config->import_paths, import_path);
|
|
|
|
start = end;
|
|
}
|
|
|
|
const int len = strlen(start);
|
|
if (len > 0) {
|
|
char* import_path = mem_alloc(MemoryNamespaceOpt, len + 1);
|
|
memcpy(import_path, start, len);
|
|
import_path[len] = 0;
|
|
|
|
g_array_append_val(config->import_paths, import_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
void print_help(void) {
|
|
DEBUG("printing help dialog...");
|
|
|
|
const char *lines[] = {
|
|
"Gemstone Compiler (c) GPL-2.0",
|
|
"Build a project target: gsc build [target]|all",
|
|
"Compile non-project file: gsc compile <target-options> [file]",
|
|
"Output information: gsc <option>",
|
|
"Target options:",
|
|
" --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",
|
|
" --mode=[app|lib] set the compilation mode to either application or library",
|
|
" --output=name name of output files without extension",
|
|
" --driver set binary driver to use",
|
|
" --link-paths=[paths,] set a list of directories to for libraries in",
|
|
" --all-fatal-warnings treat all warnings as errors",
|
|
" --lld-fatal-warnings treat linker warnings as errors",
|
|
" --gsc-fatal-warnings treat parser warnings as errors",
|
|
"Options:",
|
|
" --verbose print logs with level information or higher",
|
|
" --debug print debug logs (if not disabled at compile time)",
|
|
" --version print the version",
|
|
" --list-targets print a list of all available targets supported",
|
|
" --list-driver print a list of all available binary driver",
|
|
" --help print this help dialog",
|
|
" --color-always always colorize output",
|
|
" --print-gc-stats print statistics of the garbage collector"
|
|
};
|
|
|
|
for (unsigned int i = 0; i < sizeof(lines) / sizeof(const char *); i++) {
|
|
printf("%s\n", lines[i]);
|
|
}
|
|
}
|
|
|
|
static void get_bool(bool* boolean, const toml_table_t *table, const char* name) {
|
|
DEBUG("retrieving boolean %s", name);
|
|
|
|
const 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, const toml_table_t *table, const char* name) {
|
|
DEBUG("retrieving string %s", name);
|
|
|
|
const 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, const toml_table_t *table, const char* name) {
|
|
DEBUG("retrieving integer %s", name);
|
|
|
|
const 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 void get_array(GArray* array, const toml_table_t *table, const char* name) {
|
|
const toml_array_t* toml_array = toml_array_in(table, name);
|
|
|
|
if (toml_array) {
|
|
for (int i = 0; i < toml_array_nelem(toml_array); i++) {
|
|
toml_datum_t value = toml_string_at(toml_array, i);
|
|
|
|
if (value.ok) {
|
|
char* copy = mem_strdup(MemoryNamespaceOpt, value.u.s);
|
|
g_array_append_val(array, copy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int parse_project_table(ProjectConfig *config, const toml_table_t *project_table) {
|
|
DEBUG("parsing project table...");
|
|
|
|
// project name
|
|
get_str(&config->name, project_table, "version");
|
|
if (config->name == NULL) {
|
|
printf("Invalid project configuration: project must have a name\n\n");
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
// project version
|
|
get_str(&config->name, project_table, "name");
|
|
|
|
// author names
|
|
toml_array_t *authors = toml_array_in(project_table, "authors");
|
|
if (authors) {
|
|
config->authors = mem_new_g_array(MemoryNamespaceOpt, sizeof(char *));
|
|
|
|
for (int i = 0;; i++) {
|
|
toml_datum_t author = toml_string_at(authors, i);
|
|
if (!author.ok)
|
|
break;
|
|
|
|
g_array_append_val(config->authors, author.u.s);
|
|
}
|
|
}
|
|
|
|
// project description
|
|
get_str(&config->desc, project_table, "description");
|
|
// project license
|
|
get_str(&config->license, project_table, "license");
|
|
|
|
return PROJECT_OK;
|
|
}
|
|
|
|
static int get_mode_from_str(TargetCompilationMode* mode, const char* name) {
|
|
if (mode == NULL) {
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
if (strcmp(name, "application") == 0) {
|
|
*mode = Application;
|
|
return PROJECT_OK;
|
|
} else if (strcmp(name, "library") == 0) {
|
|
*mode = Library;
|
|
return PROJECT_OK;
|
|
}
|
|
print_message(Error, "Invalid project configuration, mode is invalid: %s", name);
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
static int parse_target(const ProjectConfig *config, const toml_table_t *target_table, const char* name) {
|
|
DEBUG("parsing target table...");
|
|
|
|
TargetConfig* target_config = default_target_config();
|
|
|
|
target_config->name = (char*) name;
|
|
|
|
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_str(&target_config->driver, target_table, "driver");
|
|
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->lld_fatal_warnings, target_table, "lld_fatal_warnings");
|
|
get_bool(&target_config->gsc_fatal_warnings, target_table, "gsc_fatal_warnings");
|
|
|
|
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);
|
|
if (err != PROJECT_OK) {
|
|
return err;
|
|
}
|
|
char* cwd = g_get_current_dir();
|
|
char* cached_cwd = mem_strdup(MemoryNamespaceOpt, cwd);
|
|
free(cwd);
|
|
|
|
g_array_append_val(target_config->link_search_paths, cached_cwd);
|
|
get_array(target_config->link_search_paths, target_table, "link-paths");
|
|
|
|
g_array_append_val(target_config->import_paths, cached_cwd);
|
|
get_array(target_config->import_paths, target_table, "import-paths");
|
|
|
|
g_hash_table_insert(config->targets, target_config->name, target_config);
|
|
|
|
return PROJECT_OK;
|
|
}
|
|
|
|
static int parse_targets(ProjectConfig *config, const toml_table_t *root) {
|
|
DEBUG("parsing targets of project \"%s\"", config->name);
|
|
|
|
toml_table_t *targets = toml_table_in(root, "target");
|
|
if (targets == NULL) {
|
|
print_message(Warning, "Project has no targets");
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
config->targets = mem_new_g_hash_table(MemoryNamespaceOpt, g_str_hash, g_str_equal);
|
|
|
|
for (int i = 0; i < toml_table_ntab(targets); i++) {
|
|
const char *key = toml_key_in(targets, i);
|
|
|
|
if (key == NULL)
|
|
break;
|
|
|
|
toml_table_t *target = toml_table_in(targets, key);
|
|
parse_target(config, target, mem_strdup(MemoryNamespaceOpt, (char*) key));
|
|
}
|
|
|
|
return PROJECT_OK;
|
|
}
|
|
|
|
int load_project_config(ProjectConfig *config) {
|
|
DEBUG("loading project config...");
|
|
|
|
FILE *config_file = fopen(PROJECT_CONFIG_FILE, "r");
|
|
if (config_file == NULL) {
|
|
print_message(Error, "Cannot open file %s: %s", PROJECT_CONFIG_FILE, strerror(errno));
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
char err_buf[TOML_ERROR_MSG_BUF];
|
|
|
|
toml_table_t *conf = toml_parse_file(config_file, err_buf, sizeof(err_buf));
|
|
fclose(config_file);
|
|
|
|
if (conf != NULL) {
|
|
int status = PROJECT_SEMANTIC_ERR;
|
|
toml_table_t *project = toml_table_in(conf, "project");
|
|
|
|
if (project != NULL) {
|
|
if (parse_project_table(config, project) == PROJECT_OK) {
|
|
status = parse_targets(config, conf);
|
|
}
|
|
} else {
|
|
print_message(Error, "Invalid project configuration: missing project table.");
|
|
}
|
|
|
|
toml_free(conf);
|
|
return status;
|
|
} else {
|
|
print_message(Error, "Invalid project configuration: %s", err_buf);
|
|
}
|
|
|
|
return PROJECT_SEMANTIC_ERR;
|
|
}
|
|
|
|
void delete_target_config(TargetConfig* config) {
|
|
if (config->root_module != NULL) {
|
|
mem_free(config->root_module);
|
|
}
|
|
if (config->archive_directory != NULL) {
|
|
mem_free(config->archive_directory);
|
|
}
|
|
if (config->name != NULL) {
|
|
mem_free(config->name);
|
|
}
|
|
if (config->output_directory != NULL) {
|
|
mem_free(config->output_directory);
|
|
}
|
|
if (config->link_search_paths) {
|
|
for (guint i = 0; i < config->link_search_paths->len; i++) {
|
|
mem_free(g_array_index(config->link_search_paths, char*, i));
|
|
}
|
|
}
|
|
mem_free(config);
|
|
}
|
|
|
|
void delete_project_config(ProjectConfig* config) {
|
|
if (config->name != NULL) {
|
|
mem_free(config->name);
|
|
}
|
|
if (config->authors != NULL) {
|
|
mem_free(config->authors);
|
|
}
|
|
if (config->desc != NULL) {
|
|
mem_free(config->desc);
|
|
}
|
|
if (config->license != NULL) {
|
|
mem_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);
|
|
}
|
|
|
|
mem_free(config->targets);
|
|
}
|
|
|
|
mem_free_from(MemoryNamespaceOpt, config);
|
|
}
|
|
|
|
ProjectConfig* default_project_config() {
|
|
ProjectConfig* config = mem_alloc(MemoryNamespaceOpt, sizeof(ProjectConfig));
|
|
|
|
config->authors = NULL;
|
|
config->name = NULL;
|
|
config->targets = NULL;
|
|
config->license = NULL;
|
|
config->version = NULL;
|
|
config->desc = NULL;
|
|
|
|
return config;
|
|
}
|
|
|
|
static void* toml_cached_malloc(size_t bytes) {
|
|
return mem_alloc(MemoryNamespaceTOML, bytes);
|
|
}
|
|
|
|
static void toml_cached_free(void* ptr) {
|
|
mem_free(ptr);
|
|
}
|
|
|
|
void init_toml() {
|
|
INFO("setting up cached memory for TOML C99...");
|
|
toml_set_memutil(toml_cached_malloc, toml_cached_free);
|
|
}
|