diff --git a/src/cfg/opt.c b/src/cfg/opt.c index 92577d8..b19e506 100644 --- a/src/cfg/opt.c +++ b/src/cfg/opt.c @@ -6,6 +6,84 @@ #include #include #include +#include + +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)) { + free(value); + } + + 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 = malloc(sizeof(Option)); + option->is_opt = g_str_has_prefix(argv[i], "--"); + option->string = argv[i] + (option->is_opt ? 2 : 0); + option->index = i; + option->value = NULL; + + char* equals = strchr(argv[i], '='); + if (equals != NULL) { + option->value = equals; + } + + 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 = g_array_new(FALSE, FALSE, 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) { + g_array_free(array, FALSE); + return NULL; + } + + return array; +} TargetConfig* default_target_config() { DEBUG("generating default target config..."); @@ -25,32 +103,44 @@ TargetConfig* default_target_config() { return config; } -TargetConfig* default_target_config_from_args(int argc, char *argv[]) { +TargetConfig* default_target_config_from_args() { 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 (is_option_set("print-ast")) { + config->print_ast = true; + } else if (is_option_set("print-asm")) { + config->print_asm = true; + } else if (is_option_set("print-ir")) { + config->print_ir = true; + } else if (is_option_set("mode")) { + const Option* opt = get_option("mode"); - if (strcmp(option, "--print-ast") == 0) { - config->print_ast = true; - } else if (strcmp(option, "--print-asm") == 0) { - config->print_asm = true; - } else if (strcmp(option, "--print-ir") == 0) { - config->print_ir = true; - } else if (strcmp(option, "--mode=app") == 0) { - config->mode = Application; - } else if (strcmp(option, "--mode=lib") == 0) { - config->mode = Library; - } else if (config->root_module == NULL) { - config->root_module = strdup(option); - } else { - print_message(Warning, "Got more than one file to compile, using first by ignoring others."); + 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); + } } } + 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 = strdup( ((char**) files->data) [0]); + } + return config; } @@ -66,7 +156,9 @@ void print_help(void) { " --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" + " --mode=[app|lib] set the compilation mode to either application or library", + " --verbose print logs with level information or higher", + " --debug print debug logs (if not disabled at compile time)" }; for (unsigned int i = 0; i < sizeof(lines) / sizeof(const char *); i++) { diff --git a/src/cfg/opt.h b/src/cfg/opt.h index c54bfb2..9f0b7c0 100644 --- a/src/cfg/opt.h +++ b/src/cfg/opt.h @@ -52,11 +52,18 @@ typedef struct ProjectConfig_t { GHashTable* targets; } ProjectConfig; +typedef struct Option_t { + int index; + const char* string; + const char* value; + bool is_opt; +} Option; + TargetConfig* default_target_config(); ProjectConfig* default_project_config(); -TargetConfig* default_target_config_from_args(int argc, char* argv[]); +TargetConfig* default_target_config_from_args(); int load_project_config(ProjectConfig *config); @@ -66,4 +73,16 @@ void delete_project_config(ProjectConfig* config); void delete_target_config(TargetConfig*); +void parse_options(int argc, char* argv[]); + +[[gnu::nonnull(1)]] +bool is_option_set(const char* option); + +[[gnu::nonnull(1)]] +const Option* get_option(const char* option); + +[[gnu::nonnull(1)]] +[[nodiscard("must be freed")]] +GArray* get_non_options_after(const char* command); + #endif //GEMSTONE_OPT_H diff --git a/src/compiler.c b/src/compiler.c index 1889763..79e9b20 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -160,10 +160,10 @@ static void build_target(ModuleFileStack *unit, TargetConfig *target) { * @param argc * @param argv */ -static void compile_file(ModuleFileStack *unit, int argc, char *argv[]) { +static void compile_file(ModuleFileStack *unit) { INFO("compiling basic files..."); - TargetConfig *target = default_target_config_from_args(argc, argv); + TargetConfig *target = default_target_config_from_args(); if (target->root_module == NULL) { print_message(Error, "No input file specified."); @@ -180,11 +180,9 @@ static void compile_file(ModuleFileStack *unit, int argc, char *argv[]) { * @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) { +static void build_project_targets(ModuleFileStack *unit, ProjectConfig *config) { + if (is_option_set("all")) { // build all targets in the project GHashTableIter iter; @@ -195,52 +193,51 @@ static void build_project_targets(ModuleFileStack *unit, ProjectConfig *config, 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]; + return; + } + + // build all targets given in the arguments + GArray* targets = get_non_options_after("build"); + + if (targets != NULL) { + for (guint i = 0; i < targets->len; i++) { + const char *target_name = (((Option*) targets->data) + i)->string; + if (g_hash_table_contains(config->targets, target_name)) { build_target(unit, g_hash_table_lookup(config->targets, target_name)); + } else { + print_message(Error, "Unknown target: %s", target_name); } } + + g_array_free(targets, FALSE); + } else { + print_message(Error, "No targets specified."); } } /** * @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; - } - +static void build_project(ModuleFileStack *unit) { ProjectConfig *config = default_project_config(); int err = load_project_config(config); if (err == PROJECT_OK) { - build_project_targets(unit, config, argc, argv); + build_project_targets(unit, config); } delete_project_config(config); } -void run_compiler(int argc, char *argv[]) { - if (argc <= 0) { - INFO("no arguments provided"); - print_help(); - return; - } - +void run_compiler() { 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]); + if (is_option_set("build")) { + build_project(&files); + } else if (is_option_set("compile")) { + compile_file(&files); } else { print_message(Error, "Invalid mode of operation. Rerun with --help."); } diff --git a/src/compiler.h b/src/compiler.h index 943e47b..a82a38f 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -7,9 +7,7 @@ /** * @brief Run the gemstone compiler with the provided command arguments. - * @param argc - * @param argv */ -void run_compiler(int argc, char *argv[]); +void run_compiler(); #endif //GEMSTONE_COMPILER_H diff --git a/src/main.c b/src/main.c index a661dfb..72b397a 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include /** @@ -16,8 +17,9 @@ void notify_exit(void) { DEBUG("Exiting gemstone..."); } * @brief Run compiler setup here * */ -void setup(void) { +void setup(int argc, char *argv[]) { // setup preample + parse_options(argc, argv); log_init(); DEBUG("starting gemstone..."); @@ -37,11 +39,16 @@ void setup(void) { } int main(int argc, char *argv[]) { - setup(); + if (argc <= 1) { + print_help(); + exit(1); + } + + setup(argc, argv); print_message(Info, "Running GSC version %s", GSC_VERSION); - run_compiler(argc - 1, &argv[1]); + run_compiler(); return 0; } diff --git a/src/sys/log.c b/src/sys/log.c index 0aeab5f..7dcf94e 100644 --- a/src/sys/log.c +++ b/src/sys/log.c @@ -4,14 +4,29 @@ #include #include #include +#include +#include static struct Logger_t { FILE** streams; size_t stream_count; } GlobalLogger; -void log_init(void) +int runtime_log_level = LOG_LEVEL_WARNING; + +void set_log_level(int level) { + runtime_log_level = level; +} + +void log_init() +{ + if (is_option_set("verbose")) { + set_log_level(LOG_LEVEL_INFORMATION); + } else if (is_option_set("debug")) { + set_log_level(LOG_LEVEL_DEBUG); + } + assert(LOG_DEFAULT_STREAM != NULL); log_register_stream(LOG_DEFAULT_STREAM); } @@ -30,7 +45,7 @@ void log_register_stream(FILE* restrict stream) if (GlobalLogger.streams == NULL) { PANIC("failed to allocate stream buffer"); - } + } } else { diff --git a/src/sys/log.h b/src/sys/log.h index 6b8991d..6db8fe5 100644 --- a/src/sys/log.h +++ b/src/sys/log.h @@ -53,12 +53,22 @@ will not print. #define INFO(format, ...) __LOG(LOG_STRING_INFORMATION, LOG_LEVEL_INFORMATION, format"\n", ##__VA_ARGS__) #define DEBUG(format, ...) __LOG(LOG_STRING_DEBUG, LOG_LEVEL_DEBUG, format"\n", ##__VA_ARGS__) +extern int runtime_log_level; + #define __LOG(level, priority, format, ...) \ do { \ if (LOG_LEVEL <= priority) \ - syslog_logf(level, __FILE_NAME__, __LINE__, __func__, format, ##__VA_ARGS__); \ + if (runtime_log_level <= priority) \ + syslog_logf(level, __FILE_NAME__, __LINE__, __func__, format, ##__VA_ARGS__); \ } while(0) +/** + * @brief Set the runtime log level. Must be one of: LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, + * LOG_LEVEL_INFORMATION, LOG_LEVEL_DEBUG + * @param level the new log level + */ +void set_log_level(int level); + /** * @brief Log a message into all registered streams * diff --git a/tests/ast/CMakeLists.txt b/tests/ast/CMakeLists.txt index 88007e4..5bd19b6 100644 --- a/tests/ast/CMakeLists.txt +++ b/tests/ast/CMakeLists.txt @@ -10,6 +10,12 @@ find_package(PkgConfig REQUIRED) pkg_search_module(GLIB REQUIRED IMPORTED_TARGET glib-2.0) include_directories(PRIVATE ${GLIB_INCLUDE_DIRS}) +# ------------------------------------------------ # +# Setup TOML-C99 # +# ------------------------------------------------ # + +include_directories(${PROJECT_SOURCE_DIR}/dep/tomlc99) + # ------------------------------------------------------- # # CTEST 1 # test building the syntax tree @@ -19,6 +25,8 @@ add_executable(ast_build_tree ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/io/files.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/dep/tomlc99/toml.c build_tree.c) set_target_properties(ast_build_tree PROPERTIES @@ -38,6 +46,8 @@ add_executable(ast_print_node ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/io/files.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/dep/tomlc99/toml.c print_node.c) set_target_properties(ast_print_node PROPERTIES @@ -57,6 +67,8 @@ add_executable(ast_graphviz ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/io/files.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/dep/tomlc99/toml.c print_graphviz.c) set_target_properties(ast_graphviz PROPERTIES diff --git a/tests/input_file/test_input_file.py b/tests/input_file/test_input_file.py index 3721dc0..550f3f4 100644 --- a/tests/input_file/test_input_file.py +++ b/tests/input_file/test_input_file.py @@ -12,7 +12,7 @@ def check_accept(): test_file_name = sys.argv[1] - p = subprocess.run(["./gsc", test_file_name], capture_output=True, text=True) + p = subprocess.run(["./gsc", "compile", test_file_name], capture_output=True, text=True) assert p.returncode == 0 @@ -22,7 +22,7 @@ def check_abort(): logging.basicConfig(level=logging.INFO) - p = subprocess.run("./gsc", capture_output=True, text=True) + p = subprocess.run(["./gsc", "compile"], capture_output=True, text=True) assert p.returncode == 1 diff --git a/tests/logging/CMakeLists.txt b/tests/logging/CMakeLists.txt index d5abada..2bfa385 100644 --- a/tests/logging/CMakeLists.txt +++ b/tests/logging/CMakeLists.txt @@ -8,6 +8,13 @@ include_directories(${PROJECT_SOURCE_DIR}/src) find_package(PkgConfig REQUIRED) pkg_search_module(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +include_directories(PRIVATE ${GLIB_INCLUDE_DIRS}) + +# ------------------------------------------------ # +# Setup TOML-C99 # +# ------------------------------------------------ # + +include_directories(${PROJECT_SOURCE_DIR}/dep/tomlc99) # ------------------------------------------------------- # # CTEST 1 @@ -16,11 +23,15 @@ pkg_search_module(GLIB REQUIRED IMPORTED_TARGET glib-2.0) add_executable(logging_output ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/src/io/files.c output.c) set_target_properties(logging_output PROPERTIES OUTPUT_NAME "output" RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging) +target_link_libraries(logging_output PkgConfig::GLIB) +target_link_libraries(logging_output tomlc99) add_test(NAME logging_output WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_output) @@ -32,11 +43,15 @@ add_test(NAME logging_output add_executable(logging_panic ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/src/io/files.c panic.c) set_target_properties(logging_panic PROPERTIES OUTPUT_NAME "panic" RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging) +target_link_libraries(logging_panic PkgConfig::GLIB) +target_link_libraries(logging_panic tomlc99) add_test(NAME logging_panic WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_panic) @@ -48,11 +63,15 @@ add_test(NAME logging_panic add_executable(logging_streams ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/src/io/files.c streams.c) set_target_properties(logging_streams PROPERTIES OUTPUT_NAME "stream" RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging) +target_link_libraries(logging_streams PkgConfig::GLIB) +target_link_libraries(logging_streams tomlc99) add_test(NAME logging_streams WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_stream) @@ -64,11 +83,15 @@ add_test(NAME logging_streams add_executable(logging_level ${PROJECT_SOURCE_DIR}/src/sys/log.c ${PROJECT_SOURCE_DIR}/src/sys/col.c + ${PROJECT_SOURCE_DIR}/src/cfg/opt.c + ${PROJECT_SOURCE_DIR}/src/io/files.c level.c) set_target_properties(logging_level PROPERTIES OUTPUT_NAME "level" RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging) +target_link_libraries(logging_level PkgConfig::GLIB) +target_link_libraries(logging_level tomlc99) add_test(NAME logging_level WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_level) diff --git a/tests/logging/level.c b/tests/logging/level.c index fd37cc7..ed89205 100644 --- a/tests/logging/level.c +++ b/tests/logging/level.c @@ -4,11 +4,14 @@ #include "sys/log.h" #include +#include #define LOG_LEVEL LOG_LEVEL_WARNING -int main(void) { +int main(int argc, char* argv[]) { + parse_options(argc, argv); log_init(); + set_log_level(LOG_LEVEL_DEBUG); col_init(); DEBUG("logging some debug..."); diff --git a/tests/logging/output.c b/tests/logging/output.c index dde7d90..a6cfdb2 100644 --- a/tests/logging/output.c +++ b/tests/logging/output.c @@ -4,9 +4,12 @@ #include "sys/log.h" #include +#include -int main(void) { +int main(int argc, char* argv[]) { + parse_options(argc, argv); log_init(); + set_log_level(LOG_LEVEL_DEBUG); col_init(); DEBUG("logging some debug..."); diff --git a/tests/logging/panic.c b/tests/logging/panic.c index ecd8815..f64f3a1 100644 --- a/tests/logging/panic.c +++ b/tests/logging/panic.c @@ -3,9 +3,12 @@ // #include "sys/log.h" +#include -int main(void) { +int main(int argc, char* argv[]) { + parse_options(argc, argv); log_init(); + set_log_level(LOG_LEVEL_DEBUG); // this should appear in stderr INFO("before exit"); diff --git a/tests/logging/streams.c b/tests/logging/streams.c index 97599da..5dd7cc2 100644 --- a/tests/logging/streams.c +++ b/tests/logging/streams.c @@ -4,6 +4,7 @@ #include "sys/log.h" #include +#include static FILE* file; @@ -13,8 +14,10 @@ void close_file(void) { } } -int main(void) { +int main(int argc, char* argv[]) { + parse_options(argc, argv); log_init(); + set_log_level(LOG_LEVEL_DEBUG); // this should appear in stderr INFO("should only be in stderr");