diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..be66b92 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dep/tomlc99"] + path = dep/tomlc99 + url = https://github.com/cktan/tomlc99.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b304447..b5a44cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,6 @@ cmake_minimum_required(VERSION 3.15...3.25) # Header must be included this way: #include # # ├─ res -# ├─ dep -# │ └─ klib # ├─ src # │ ├─ lex # │ │ └─ lexer.l @@ -29,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) @@ -63,10 +63,8 @@ set(YACC_SOURCE_FILE ${PROJECT_SOURCE_DIR}/src/yacc/parser.y) set(YACC_GENERATED_SOURCE_FILE ${PROJECT_SOURCE_DIR}/src/yacc/parser.tab.c) add_custom_command(OUTPUT ${YACC_GENERATED_SOURCE_FILE} - COMMAND bison ARGS -Wno-yacc -Wcounterexamples -d -o ${YACC_GENERATED_SOURCE_FILE} ${YACC_SOURCE_FILE} - COMMENT "generate C source file for parser" VERBATIM) @@ -76,7 +74,28 @@ add_custom_command(OUTPUT ${YACC_GENERATED_SOURCE_FILE} find_package(PkgConfig REQUIRED) pkg_search_module(GLIB REQUIRED IMPORTED_TARGET glib-2.0) - + +# ------------------------------------------------ # +# TOML-C99 # +# ------------------------------------------------ # + +execute_process(COMMAND git submodule init -- dep/tomlc99) + +add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/dep/tomlc99/toml.c + COMMAND git + ARGS submodule update --init --recursive --checkout + COMMENT "checkout dependency TOML-C99" + VERBATIM) + +add_library(tomlc99 ${PROJECT_SOURCE_DIR}/dep/tomlc99/toml.c) +set_target_properties(tomlc99 + PROPERTIES + OUTPUT_NAME "toml" + ARCHIVE_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/dep) + +include_directories(${PROJECT_SOURCE_DIR}/dep/tomlc99) + + # ------------------------------------------------ # # Source # # ------------------------------------------------ # @@ -109,6 +128,8 @@ set_target_properties(release target_link_libraries(release PkgConfig::GLIB) +target_link_libraries(release tomlc99) + # FIXME: cannot compile with /O2 because of /RTC1 flag if (MSVC) set(RELEASE_FLAGS) @@ -127,6 +148,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 # # ------------------------------------------------ # @@ -143,6 +167,8 @@ set_target_properties(debug target_link_libraries(debug PkgConfig::GLIB) +target_link_libraries(debug tomlc99) + if (MSVC) set(DEBUG_FLAGS /DEBUG) else() @@ -174,6 +200,8 @@ set_target_properties(check target_link_libraries(check PkgConfig::GLIB) +target_link_libraries(check tomlc99) + if (MSVC) set(CHECK_FLAGS /DEBUG /WX) else() diff --git a/Dockerfile b/Dockerfile index 68265d8..bb287f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,7 @@ COPY --chown=lorang CMakeLists.txt /home/lorang/ COPY --chown=lorang run-check-test.sh /home/lorang/ COPY --chown=lorang .env /home/lorang/ COPY --chown=lorang run-docker-build.sh /home/lorang/ +COPY --chown=lorang dep /home/lorang/dep +COPY --chown=lorang .git /home/lorang/.git RUN cmake . diff --git a/dep/tomlc99 b/dep/tomlc99 new file mode 160000 index 0000000..5221b3d --- /dev/null +++ b/dep/tomlc99 @@ -0,0 +1 @@ +Subproject commit 5221b3d3d66c25a1dc6f0372b4f824f1202fe398 diff --git a/src/ast/ast.c b/src/ast/ast.c index 892af1f..4e3ba6f 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -5,7 +5,7 @@ #include #include -struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value) { +struct AST_Node_t *AST_new_node(TokenLocation location, enum AST_SyntaxElement_t kind, const char* value) { DEBUG("creating new AST node: %d \"%s\"", kind, value); assert(kind < AST_ELEMENT_COUNT); @@ -23,6 +23,7 @@ struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value node->child_count = 0; node->kind = kind; node->value = value; + node->location = location; return node; } @@ -83,6 +84,9 @@ void AST_init() { lookup_table[AST_Negate] = "-"; lookup_table[AST_Parameter] = "parameter"; lookup_table[AST_ParamDecl] = "parameter-declaration"; + lookup_table[AST_AddressOf] = "address of"; + lookup_table[AST_Dereference] = "deref"; + lookup_table[AST_Reference] = "ref"; } const char* AST_node_to_string(const struct AST_Node_t* node) { @@ -114,6 +118,14 @@ const char* AST_node_to_string(const struct AST_Node_t* node) { return string; } +static inline unsigned long int min(unsigned long int a, unsigned long int b) { + return a > b ? b : a; +} + +static inline unsigned long int max(unsigned long int a, unsigned long int b) { + return a > b ? a : b; +} + void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child) { DEBUG("Adding new node %p to %p", child, owner); assert(owner != NULL); @@ -134,6 +146,12 @@ void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child) { PANIC("failed to allocate children array of AST node"); } + owner->location.col_end = max(owner->location.col_end, child->location.col_end); + owner->location.line_end = max(owner->location.line_end, child->location.line_end); + + owner->location.col_start = min(owner->location.col_start, child->location.col_start); + owner->location.line_start = min(owner->location.line_start, child->location.line_start); + assert(owner->children != NULL); owner->children[owner->child_count++] = child; diff --git a/src/ast/ast.h b/src/ast/ast.h index ff8297b..519f10a 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -3,6 +3,7 @@ #define _AST_H_ #include +#include /** * @brief The type of a AST node @@ -75,6 +76,9 @@ enum AST_SyntaxElement_t { AST_Parameter, AST_Qualifyier, AST_ParamDecl, + AST_AddressOf, + AST_Dereference, + AST_Reference, AST_ELEMENT_COUNT }; @@ -94,6 +98,8 @@ struct AST_Node_t { // optional value: integer literal, string literal, ... const char* value; + TokenLocation location; + // number of child nodes ownd by this node // length of children array size_t child_count; @@ -133,7 +139,7 @@ const char* AST_node_to_string(const struct AST_Node_t* node); [[maybe_unused]] [[nodiscard("pointer must be freed")]] [[gnu::returns_nonnull]] -struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value); +struct AST_Node_t *AST_new_node(TokenLocation location, enum AST_SyntaxElement_t kind, const char* value); /** * @brief Deallocate this node and all of its children. diff --git a/src/cfg/opt.c b/src/cfg/opt.c new file mode 100644 index 0000000..6de694e --- /dev/null +++ b/src/cfg/opt.c @@ -0,0 +1,419 @@ +// +// Created by servostar on 5/31/24. +// + +#include +#include +#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); + 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 = malloc(sizeof(Option)); + option->is_opt = g_str_has_prefix(argv[i], "--"); + option->string = strdup(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 = 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..."); + + TargetConfig* config = malloc(sizeof(TargetConfig)); + + config->name = strdup("out"); + config->print_ast = false; + config->print_asm = false; + config->print_ir = false; + config->mode = Application; + config->archive_directory = strdup("archive"); + config->output_directory = strdup("bin"); + config->optimization_level = 1; + config->root_module = NULL; + + return config; +} + +TargetConfig* default_target_config_from_args() { + DEBUG("generating default target from command line..."); + + TargetConfig* config = default_target_config(); + + 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 = strdup(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]); + + g_array_free(files, TRUE); + } + + 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 [file]", + "Output information: gsc