diff --git a/.github/workflows/build-check-sdk.yaml b/.github/workflows/build-check-sdk.yaml index 10a4811..b17b734 100644 --- a/.github/workflows/build-check-sdk.yaml +++ b/.github/workflows/build-check-sdk.yaml @@ -2,7 +2,7 @@ name: "Build check gemstone in SDK" run-name: SDK build check to ${{ inputs.deploy_target }} by @${{ github.actor }} on: [push, pull_request] env: - SDK: 0.2.2-alpine-3.19.1 + SDK: 0.2.3-alpine-3.19.1 jobs: build-check-sdk: runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index 09e34b6..a8c2f7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM servostar/gemstone:sdk-0.2.2-alpine-3.19.1 +FROM servostar/gemstone:sdk-0.2.3-alpine-3.19.1 LABEL authors="servostar" -LABEL version="0.2.2" +LABEL version="0.2.3" LABEL description="docker image for setting up the build pipeline on SDK" LABEL website="https://github.com/Servostar/gemstone" diff --git a/sdk/Dockerfile b/sdk/Dockerfile index 1a6a677..aae7438 100644 --- a/sdk/Dockerfile +++ b/sdk/Dockerfile @@ -1,11 +1,11 @@ FROM alpine:3.19.1 LABEL authors="servostar" -LABEL version="0.2.2" +LABEL version="0.2.3" LABEL description="base image for building the gemstone programming language compiler" LABEL website="https://github.com/Servostar/gemstone" # install dependencies -RUN apk add build-base gcc make cmake bison flex git python3 +RUN apk add build-base gcc make cmake bison flex git python3 graphviz # create user for build RUN adduser --disabled-password lorang diff --git a/src/ast/ast.c b/src/ast/ast.c index a9c820b..efd737b 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -3,12 +3,13 @@ #include #include #include +#include #include -struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value) { +AST_NODE_PTR AST_new_node(enum AST_SyntaxElement_t kind, const char* value) { DEBUG("creating new AST node: %d \"%s\"", kind, value); - struct AST_Node_t *node = malloc(sizeof(struct AST_Node_t)); + AST_NODE_PTR node = malloc(sizeof(struct AST_Node_t)); if (node == NULL) { PANIC("failed to allocate AST node"); @@ -18,6 +19,7 @@ struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value node->parent = NULL; node->children = NULL; node->child_count = 0; + node->child_cap = 0; node->kind = kind; node->value = value; @@ -26,11 +28,12 @@ struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value static const char* lookup_table[AST_ELEMENT_COUNT] = { "__UNINIT__" }; -void AST_init() { +void AST_init(void) { DEBUG("initializing global syntax tree..."); INFO("filling lookup table..."); + lookup_table[AST_Stmt] = "stmt"; lookup_table[AST_Expr] = "expr"; lookup_table[AST_Add] = "+"; @@ -64,6 +67,10 @@ void AST_init() { lookup_table[AST_Typedef] = "typedef"; lookup_table[AST_Box] = "box"; lookup_table[AST_Fun] = "fun"; + + lookup_table[AST_Typecast] = "cast"; + lookup_table[AST_Transmute] = "as"; + lookup_table[AST_Condition] = "condition"; } const char* AST_node_to_string(struct AST_Node_t* node) { @@ -78,6 +85,7 @@ const char* AST_node_to_string(struct AST_Node_t* node) { case AST_Ident: case AST_Macro: case AST_Import: + case AST_Call: string = node->value; break; default: @@ -87,17 +95,24 @@ const char* AST_node_to_string(struct AST_Node_t* node) { return string; } -void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child) { +#define PRE_ALLOCATION_CNT 10 + +void AST_push_node(AST_NODE_PTR owner, AST_NODE_PTR child) { DEBUG("Adding new node %p to %p", child, owner); + assert(PRE_ALLOCATION_CNT >= 1); + // if there are no children for now - if (owner->child_count == 0) { + if (owner->children == NULL) { + assert(owner->child_count == 0); DEBUG("Allocating new children array"); owner->children = malloc(sizeof(struct AST_Node_t *)); + owner->child_cap = 1; - } else { - DEBUG("Rellocating old children array"); - const size_t size = sizeof(struct AST_Node_t *) * (owner->child_count + 1); + } else if (owner->child_count >= owner->child_cap) { + DEBUG("Reallocating old children array"); + owner->child_cap += PRE_ALLOCATION_CNT; + const size_t size = sizeof(struct AST_Node_t *) * owner->child_cap; owner->children = realloc(owner->children, size); } @@ -108,8 +123,8 @@ void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child) { owner->children[owner->child_count++] = child; } -struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx) { - DEBUG("retrvieng node %d from %p", idx, owner); +AST_NODE_PTR AST_get_node(AST_NODE_PTR owner, size_t idx) { + DEBUG("retrieving node %d from %p", idx, owner); if (owner == NULL) { PANIC("AST owner node is NULL"); @@ -119,7 +134,7 @@ struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx) { PANIC("AST owner node has no children"); } - struct AST_Node_t *child = owner->children[idx]; + AST_NODE_PTR child = owner->children[idx]; if (child == NULL) { PANIC("child node is NULL"); @@ -128,20 +143,48 @@ struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx) { return child; } -void AST_delete_node(struct AST_Node_t *node) { - DEBUG("Deleting AST node: %p", node); +void AST_delete_node(AST_NODE_PTR node) { + DEBUG("Deleting AST node: %p", node); - if (node == NULL) { - PANIC("Node to free is NULL"); - } + if (node == NULL) { + PANIC("Node to free is NULL"); + } - if (node->children == NULL) { - return; - } + if (node->parent != NULL) { + AST_detach_node(node->parent, node); + } - for (size_t i = 0; i < node->child_count; i++) { - AST_delete_node(node->children[i]); - } + if (node->children == NULL) { + return; + } + + for (size_t i = 0; i < node->child_count; i++) { + if (node->children[i] != node) { + AST_delete_node(node->children[i]); + } else { + WARN("Circular dependency in AST: parent -> child -> parent -> child -> ..."); + } + } + + free(node->children); + free(node); +} + +AST_NODE_PTR AST_detach_node(AST_NODE_PTR parent, AST_NODE_PTR child) { + assert(parent != NULL); + assert(child != NULL); + + for (size_t i = 0; i < parent->child_count; i++) { + if (child == parent->children[i]) { + memcpy(&parent->children[i], &parent->children[i + 1], parent->child_count - i - 1); + parent->child_count--; + return child; + } + } + + WARN("Node not a child of parent"); + + return child; } static void __AST_visit_nodes_recurse2(struct AST_Node_t *root, diff --git a/src/ast/ast.h b/src/ast/ast.h index 486a520..13437ea 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -17,6 +17,7 @@ enum AST_SyntaxElement_t { AST_If, AST_IfElse, AST_Else, + AST_Condition, // Variable management AST_Decl, AST_Assign, @@ -68,6 +69,7 @@ struct AST_Node_t { // number of child nodes ownd by this node // length of children array size_t child_count; + size_t child_cap; // variable amount of child nodes struct AST_Node_t **children; }; @@ -89,6 +91,8 @@ void AST_delete_node(struct AST_Node_t *); // add a new child node void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child); +AST_NODE_PTR AST_detach_node(AST_NODE_PTR parent, AST_NODE_PTR child); + // get a specific child node struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx); diff --git a/tests/ast/CMakeLists.txt b/tests/ast/CMakeLists.txt index 81e7b96..455130b 100644 --- a/tests/ast/CMakeLists.txt +++ b/tests/ast/CMakeLists.txt @@ -24,12 +24,28 @@ add_test(NAME ast_build_tree add_executable(ast_print_node ${PROJECT_SOURCE_DIR}/src/ast/ast.c + ${PROJECT_SOURCE_DIR}/src/sys/log.c print_node.c) -set_target_properties(ast_build_tree +set_target_properties(ast_print_node PROPERTIES - OUTPUT_NAME "build_tree" + OUTPUT_NAME "print_node" RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/ast) add_test(NAME ast_print_node WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND python ${GEMSTONE_TEST_DIR}/ast/test_ast.py check_print_node) +# ------------------------------------------------------- # +# CTEST 3 +# test graphviz output + +add_executable(ast_graphviz + ${PROJECT_SOURCE_DIR}/src/ast/ast.c + ${PROJECT_SOURCE_DIR}/src/sys/log.c + print_graphviz.c) +set_target_properties(ast_graphviz + PROPERTIES + OUTPUT_NAME "print_graphviz" + RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/ast) +add_test(NAME ast_graphviz + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND python ${GEMSTONE_TEST_DIR}/ast/test_ast.py check_print_graphviz) diff --git a/tests/ast/print_graphviz.c b/tests/ast/print_graphviz.c new file mode 100644 index 0000000..6df64b4 --- /dev/null +++ b/tests/ast/print_graphviz.c @@ -0,0 +1,54 @@ +// +// Created by servostar on 5/8/24. +// + +#include +#include + +void generate_statement(const AST_NODE_PTR stmt) { + const AST_NODE_PTR add = AST_new_node(AST_Add, NULL); + + AST_push_node(add, AST_new_node(AST_Int, "3")); + AST_push_node(add, AST_new_node(AST_Int, "6")); + + AST_push_node(stmt, add); +} + +void generate_branch(const AST_NODE_PTR stmt) { + const AST_NODE_PTR branch = AST_new_node(AST_If, NULL); + const AST_NODE_PTR gt = AST_new_node(AST_Greater, NULL); + + AST_push_node(branch, gt); + + AST_push_node(gt, AST_new_node(AST_Float, "2.3")); + AST_push_node(gt, AST_new_node(AST_Float, "0.79")); + + AST_push_node(stmt, branch); + + generate_statement(branch); +} + +int main(void) { + + AST_init(); + + const AST_NODE_PTR root = AST_new_node(AST_Stmt, NULL); + + generate_branch(root); + + FILE* output = fopen("tmp/graph.gv", "w"); + + if (output == NULL) { + PANIC("unable to open file"); + } + + AST_fprint_graphviz(output, root); + + fflush(output); + + fclose(output); + + AST_delete_node(root); + + return 0; +} diff --git a/tests/ast/print_node.c b/tests/ast/print_node.c index 83d3d43..38e8a41 100644 --- a/tests/ast/print_node.c +++ b/tests/ast/print_node.c @@ -6,6 +6,8 @@ int main(void) { + AST_init(); + const AST_NODE_PTR node = AST_new_node(0, "value"); for (size_t i = 0; i < AST_ELEMENT_COUNT; i++) { @@ -16,4 +18,6 @@ int main(void) { } AST_delete_node(node); + + return 0; } diff --git a/tests/ast/test_ast.py b/tests/ast/test_ast.py index 111e9c3..0f9b757 100644 --- a/tests/ast/test_ast.py +++ b/tests/ast/test_ast.py @@ -28,6 +28,84 @@ def run_check_print_node(): # check exit code assert p.returncode == 0 + assert """0 stmt +1 expr +2 value +3 value +4 value +5 while +6 if +7 else if +8 else +9 condition +10 decl +11 assign +12 def +13 value +14 + +15 - +16 * +17 / +18 & +19 | +20 ^ +21 ! +22 && +23 || +24 ^^ +25 !! +26 == +27 > +28 < +29 cast +30 as +31 value +32 value +33 typedef +34 box +35 fun +36 value +""" == p.stdout + + +def run_check_print_graphviz(): + info("started check print graphviz...") + + info("creating temporary folder...") + + if not os.path.exists("tmp"): + os.mkdir("tmp") + + info("cleaning temporary folder...") + + if os.path.exists("tmp/graph.gv"): + os.remove("tmp/graph.gv") + + if os.path.exists("tmp/graph.svg"): + os.remove("tmp/graph.svg") + + p = subprocess.run(BIN_DIR + "print_graphviz", capture_output=True, text=True) + + info("checking exit code...") + + # check exit code + assert p.returncode == 0 + + info("converting gv to svg...") + + p = subprocess.run(["dot", "-Tsvg", "tmp/graph.gv", "-otmp/graph.svg"]) + + info("checking exit code...") + assert p.returncode == 0 + + info("checking svg output...") + with open("tmp/graph.svg", "r") as file: + string = "".join(file.readlines()) + assert "2.3" in string + assert "0.79" in string + assert "stmt" in string + assert "if" in string + if __name__ == "__main__": logging.basicConfig(level=logging.INFO) @@ -41,6 +119,8 @@ if __name__ == "__main__": run_check_build_tree() case "check_print_node": run_check_print_node() + case "check_print_graphviz": + run_check_print_graphviz() case _: error(f"unknown target: {target}") exit(1)