commit
8b72a9fae2
|
@ -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.1-alpine-3.19.1
|
||||
SDK: 0.2.3-alpine-3.19.1
|
||||
jobs:
|
||||
build-check-sdk:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -11,4 +11,4 @@ jobs:
|
|||
- name: Setup SDK
|
||||
run: docker pull servostar/gemstone:sdk-"$SDK" && docker build --tag gemstone:devkit-"$SDK" .
|
||||
- name: Compile
|
||||
run: docker run gemstone:devkit-"$SDK" make check
|
||||
run: docker run gemstone:devkit-"$SDK" sh run-check-test.sh
|
||||
|
|
|
@ -13,4 +13,5 @@ Makefile
|
|||
lexer.ll.c
|
||||
parser.tab.c
|
||||
parser.tab.h
|
||||
build
|
||||
build
|
||||
/Testing/
|
||||
|
|
|
@ -21,6 +21,18 @@ project(gemstone
|
|||
DESCRIPTION "programming language compiler"
|
||||
LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
set(CMAKE_C_STANDARD_REQUIRED TRUE)
|
||||
|
||||
set(GEMSTONE_TEST_DIR ${PROJECT_SOURCE_DIR}/tests)
|
||||
set(GEMSTONE_BINARY_DIR ${PROJECT_SOURCE_DIR}/bin)
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# ------------------------------------------------ #
|
||||
|
@ -79,7 +91,7 @@ add_executable(release
|
|||
set_target_properties(release
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "gsc"
|
||||
RUNTIME_OUTPUT_DIRECTORY "bin/release")
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/release)
|
||||
|
||||
# FIXME: cannot compile with /O2 because of /RTC1 flag
|
||||
if (MSVC)
|
||||
|
@ -111,7 +123,7 @@ add_executable(debug
|
|||
set_target_properties(debug
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "gsc"
|
||||
RUNTIME_OUTPUT_DIRECTORY "bin/debug")
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/debug)
|
||||
|
||||
if (MSVC)
|
||||
set(DEBUG_FLAGS /DEBUG)
|
||||
|
@ -140,7 +152,7 @@ add_executable(check
|
|||
set_target_properties(check
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "gsc"
|
||||
RUNTIME_OUTPUT_DIRECTORY "bin/check")
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/check)
|
||||
|
||||
if (MSVC)
|
||||
set(CHECK_FLAGS /DEBUG /WX)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
FROM servostar/gemstone:sdk-0.2.1-alpine-3.19.1
|
||||
FROM servostar/gemstone:sdk-0.2.3-alpine-3.19.1
|
||||
LABEL authors="servostar"
|
||||
LABEL version="0.2.1"
|
||||
LABEL version="0.2.3"
|
||||
LABEL description="docker image for setting up the build pipeline on SDK"
|
||||
LABEL website="https://github.com/Servostar/gemstone"
|
||||
|
||||
COPY --chown=lorang src /home/lorang/src
|
||||
COPY --chown=lorang tests /home/lorang/tests
|
||||
COPY --chown=lorang CMakeLists.txt /home/lorang/
|
||||
COPY --chown=lorang run-check-test.sh /home/lorang/
|
||||
|
||||
RUN cmake .
|
||||
|
|
22
README.md
22
README.md
|
@ -21,6 +21,28 @@ Requires:
|
|||
- bison
|
||||
- flex
|
||||
|
||||
## Writing Tests
|
||||
|
||||
Since the project is build and configured through CMake it makes sense to rely for tests
|
||||
on CTest. All tests are located in the subfolder `tests`. In this directory is a CMakeLists.txt which specifies which tests
|
||||
are to be run. Actual tests are located in folders within tests and contain a final CMakeLists.txt which specifies what to run
|
||||
for a single test.
|
||||
|
||||
```
|
||||
tests
|
||||
└─ test_group1
|
||||
└─ CMakeLists.txt # specify tests in this group
|
||||
└─ ... # test files of group 1
|
||||
|
||||
└─ test_group2
|
||||
└─ CMakeLists.txt # specify tests in this group
|
||||
└─ ... # test files of group 2
|
||||
|
||||
└─ CMakeLists.txt # specify test groups to run
|
||||
|
||||
CMakeLists.txt # build configuration
|
||||
```
|
||||
|
||||
## Development with VSCode/Codium
|
||||
|
||||
Recommended extensions for getting a decent experience are the following:
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Author: Sven Vogel
|
||||
# Created: 02.05.2024
|
||||
# Description: Builds the project and runs tests
|
||||
# Returns 0 on success and 1 when something went wrong
|
||||
|
||||
echo "+--------------------------------------+"
|
||||
echo "| BUILDING all TARGETS |"
|
||||
echo "+--------------------------------------+"
|
||||
|
||||
make -B
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "===> failed to build targets"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "+--------------------------------------+"
|
||||
echo "| RUNNING CODE CHECK |"
|
||||
echo "+--------------------------------------+"
|
||||
|
||||
make check
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "===> failed code check..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "+--------------------------------------+"
|
||||
echo "| RUNNING TESTS |"
|
||||
echo "+--------------------------------------+"
|
||||
|
||||
ctest -VV --output-on-failure --schedule-random -j 4
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "===> failed tests..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "+--------------------------------------+"
|
||||
echo "| COMPLETED CHECK + TESTS SUCCESSFULLY |"
|
||||
echo "+--------------------------------------+"
|
|
@ -1,11 +1,11 @@
|
|||
FROM alpine:3.19.1
|
||||
LABEL authors="servostar"
|
||||
LABEL version="0.2.1"
|
||||
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
|
||||
RUN apk add build-base gcc make cmake bison flex git python3 graphviz
|
||||
|
||||
# create user for build
|
||||
RUN adduser --disabled-password lorang
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
|
||||
#include <ast/ast.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/log.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct AST_Node_t *AST_new_node(enum AST_SyntaxElement_t kind, const char* value) {
|
||||
DEBUG("creating new AST node: %d \"%s\"", kind, value);
|
||||
assert(kind < AST_ELEMENT_COUNT);
|
||||
|
||||
struct AST_Node_t *node = malloc(sizeof(struct AST_Node_t));
|
||||
|
||||
if (node == NULL) {
|
||||
PANIC("failed to allocate AST node");
|
||||
}
|
||||
|
||||
assert(node != NULL);
|
||||
|
||||
// init to discrete state
|
||||
node->parent = NULL;
|
||||
node->children = NULL;
|
||||
node->child_count = 0;
|
||||
node->kind = kind;
|
||||
node->value = value;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static const char* lookup_table[AST_ELEMENT_COUNT] = { "__UNINIT__" };
|
||||
|
||||
void AST_init() {
|
||||
DEBUG("initializing global syntax tree...");
|
||||
|
||||
INFO("filling lookup table...");
|
||||
|
||||
lookup_table[AST_Stmt] = "stmt";
|
||||
lookup_table[AST_Expr] = "expr";
|
||||
|
||||
lookup_table[AST_Add] = "+";
|
||||
lookup_table[AST_Sub] = "-";
|
||||
lookup_table[AST_Mul] = "*";
|
||||
lookup_table[AST_Div] = "/";
|
||||
|
||||
lookup_table[AST_BitAnd] = "&";
|
||||
lookup_table[AST_BitOr] = "|";
|
||||
lookup_table[AST_BitXor] = "^";
|
||||
lookup_table[AST_BitNot] = "!";
|
||||
|
||||
lookup_table[AST_Eq] = "==";
|
||||
lookup_table[AST_Less] = "<";
|
||||
lookup_table[AST_Greater] = ">";
|
||||
|
||||
lookup_table[AST_BoolAnd] = "&&";
|
||||
lookup_table[AST_BoolOr] = "||";
|
||||
lookup_table[AST_BoolXor] = "^^";
|
||||
lookup_table[AST_BoolNot] = "!!";
|
||||
|
||||
lookup_table[AST_While] = "while";
|
||||
lookup_table[AST_If] = "if";
|
||||
lookup_table[AST_IfElse] = "else if";
|
||||
lookup_table[AST_Else] = "else";
|
||||
|
||||
lookup_table[AST_Decl] = "decl";
|
||||
lookup_table[AST_Assign] = "assign";
|
||||
lookup_table[AST_Def] = "def";
|
||||
|
||||
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(const struct AST_Node_t* node) {
|
||||
DEBUG("converting AST node to string: %p", node);
|
||||
assert(node != NULL);
|
||||
|
||||
const char* string;
|
||||
|
||||
switch(node->kind) {
|
||||
case AST_Int:
|
||||
case AST_Float:
|
||||
case AST_String:
|
||||
case AST_Ident:
|
||||
case AST_Macro:
|
||||
case AST_Import:
|
||||
case AST_Call:
|
||||
string = node->value;
|
||||
break;
|
||||
default:
|
||||
string = lookup_table[node->kind];
|
||||
}
|
||||
|
||||
assert(string != NULL);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
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);
|
||||
assert(child != NULL);
|
||||
|
||||
// if there are no children for now
|
||||
if (owner->child_count == 0) {
|
||||
DEBUG("Allocating new children array");
|
||||
owner->children = malloc(sizeof(struct AST_Node_t *));
|
||||
|
||||
} else {
|
||||
DEBUG("Rellocating old children array");
|
||||
const size_t size = sizeof(struct AST_Node_t *) * (owner->child_count + 1);
|
||||
owner->children = realloc(owner->children, size);
|
||||
}
|
||||
|
||||
if (owner->children == NULL) {
|
||||
PANIC("failed to allocate children array of AST node");
|
||||
}
|
||||
|
||||
assert(owner->children != NULL);
|
||||
|
||||
owner->children[owner->child_count++] = child;
|
||||
}
|
||||
|
||||
struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, const size_t idx) {
|
||||
DEBUG("retrvieng node %d from %p", idx, owner);
|
||||
assert(owner != NULL);
|
||||
assert(owner->children != NULL);
|
||||
assert(idx < owner->child_count);
|
||||
|
||||
if (owner->children == NULL) {
|
||||
PANIC("AST owner node has no children");
|
||||
}
|
||||
|
||||
struct AST_Node_t *child = owner->children[idx];
|
||||
|
||||
if (child == NULL) {
|
||||
PANIC("child node is NULL");
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
struct AST_Node_t* AST_remove_child(struct AST_Node_t* owner, const size_t idx) {
|
||||
assert(owner != NULL);
|
||||
assert(owner->children != NULL);
|
||||
assert(idx < owner->child_count);
|
||||
|
||||
struct AST_Node_t* child = owner->children[idx];
|
||||
|
||||
child->parent = NULL;
|
||||
|
||||
owner->child_count--;
|
||||
|
||||
// shift back every following element by one
|
||||
for (size_t i = idx; i < owner->child_count; i++) {
|
||||
owner->children[i] = owner->children[i + 1];
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
struct AST_Node_t* AST_detach_child(struct AST_Node_t* owner, const struct AST_Node_t* child) {
|
||||
assert(owner != NULL);
|
||||
assert(child != NULL);
|
||||
assert(owner->children != NULL);
|
||||
|
||||
for (size_t i = 0; i < owner->child_count; i++) {
|
||||
if (owner->children[i] == child) {
|
||||
return AST_remove_child(owner, i);
|
||||
}
|
||||
}
|
||||
|
||||
PANIC("Child to detach not a child of parent");
|
||||
}
|
||||
|
||||
void AST_delete_node(struct AST_Node_t *node) {
|
||||
assert(node != NULL);
|
||||
|
||||
DEBUG("Deleting AST node: %p", node);
|
||||
|
||||
if (node->children == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->parent != NULL) {
|
||||
const struct AST_Node_t* child = AST_detach_child(node->parent, node);
|
||||
assert(child == node);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < node->child_count; i++) {
|
||||
// prevent detach of children node
|
||||
node->children[i]->parent = NULL;
|
||||
AST_delete_node(node->children[i]);
|
||||
}
|
||||
|
||||
free(node->children);
|
||||
free(node);
|
||||
}
|
||||
|
||||
static void AST_visit_nodes_recurse2(struct AST_Node_t *root,
|
||||
void (*for_each)(struct AST_Node_t *node,
|
||||
size_t depth),
|
||||
const size_t depth) {
|
||||
DEBUG("Recursive visit of %p at %d with %p", root, depth, for_each);
|
||||
|
||||
assert(root != NULL);
|
||||
|
||||
(for_each)(root, depth);
|
||||
|
||||
for (size_t i = 0; i < root->child_count; i++) {
|
||||
AST_visit_nodes_recurse2(root->children[i], for_each, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void AST_visit_nodes_recurse(struct AST_Node_t *root,
|
||||
void (*for_each)(struct AST_Node_t *node,
|
||||
size_t depth)) {
|
||||
DEBUG("Starting recursive visit of %p with %p", root, for_each);
|
||||
|
||||
assert(root != NULL);
|
||||
assert(for_each != NULL);
|
||||
|
||||
AST_visit_nodes_recurse2(root, for_each, 0);
|
||||
}
|
||||
|
||||
static void AST_fprint_graphviz_node_definition(FILE* stream, const struct AST_Node_t* node) {
|
||||
DEBUG("Printing graphviz definition of %p", node);
|
||||
|
||||
assert(stream != NULL);
|
||||
assert(node != NULL);
|
||||
|
||||
fprintf(stream, "\tnode%p [label=\"%s\"]\n", (void*) node, AST_node_to_string(node));
|
||||
|
||||
if (node->children == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < node->child_count; i++) {
|
||||
AST_fprint_graphviz_node_definition(stream, node->children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void AST_fprint_graphviz_node_connection(FILE* stream, const struct AST_Node_t* node) {
|
||||
DEBUG("Printing graphviz connection of %p", node);
|
||||
|
||||
assert(stream != NULL);
|
||||
assert(node != NULL);
|
||||
|
||||
if (node->children == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < node->child_count; i++) {
|
||||
fprintf(stream, "\tnode%p -- node%p\n", (void*) node, (void*) node->children[i]);
|
||||
AST_fprint_graphviz_node_connection(stream, node->children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void AST_fprint_graphviz(FILE* stream, const struct AST_Node_t* root) {
|
||||
DEBUG("Starting print of graphviz graph of %p", root);
|
||||
|
||||
assert(stream != NULL);
|
||||
assert(root != NULL);
|
||||
|
||||
fprintf(stream, "graph {\n");
|
||||
|
||||
AST_fprint_graphviz_node_definition(stream, root);
|
||||
AST_fprint_graphviz_node_connection(stream, root);
|
||||
|
||||
fprintf(stream, "}\n");
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
|
||||
#ifndef _AST_H_
|
||||
#define _AST_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @brief The type of a AST node
|
||||
* @attention The last element is not to be used in the AST
|
||||
* as it is used as a lazy way to get the total number of available
|
||||
* variants of this enum.
|
||||
*/
|
||||
enum AST_SyntaxElement_t {
|
||||
AST_Stmt = 0,
|
||||
AST_Expr,
|
||||
// Literals
|
||||
AST_Int,
|
||||
AST_Float,
|
||||
AST_String,
|
||||
// Control flow
|
||||
AST_While,
|
||||
AST_If,
|
||||
AST_IfElse,
|
||||
AST_Else,
|
||||
AST_Condition,
|
||||
// Variable management
|
||||
AST_Decl,
|
||||
AST_Assign,
|
||||
AST_Def,
|
||||
AST_Ident,
|
||||
// Arithmetic operators
|
||||
AST_Add,
|
||||
AST_Sub,
|
||||
AST_Mul,
|
||||
AST_Div,
|
||||
// Bitwise operators
|
||||
AST_BitAnd,
|
||||
AST_BitOr,
|
||||
AST_BitXor,
|
||||
AST_BitNot,
|
||||
// Boolean operators
|
||||
AST_BoolAnd,
|
||||
AST_BoolOr,
|
||||
AST_BoolXor,
|
||||
AST_BoolNot,
|
||||
// Logical operators
|
||||
AST_Eq,
|
||||
AST_Greater,
|
||||
AST_Less,
|
||||
// Casts
|
||||
AST_Typecast, // type cast
|
||||
AST_Transmute, // reinterpret cast
|
||||
AST_Call, // function call
|
||||
AST_Macro, // builtin functions: lineno(), filename(), ...
|
||||
// Defintions
|
||||
AST_Typedef,
|
||||
AST_Box,
|
||||
AST_Fun,
|
||||
AST_Import,
|
||||
// amount of variants
|
||||
// in this enum
|
||||
AST_ELEMENT_COUNT
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A single node which can be joined with other nodes like a graph.
|
||||
* Every node can have one ancestor (parent) but multiple (also none) children.
|
||||
* Nodes have two properties:
|
||||
* - kind: The type of the node. Such as AST_Expr, AST_Add, ...
|
||||
* - value: A string representing an optional value. Can be a integer literal for kind AST_int
|
||||
*/
|
||||
struct AST_Node_t {
|
||||
// parent node that owns this node
|
||||
struct AST_Node_t *parent;
|
||||
|
||||
// type of AST node: if, declaration, ...
|
||||
enum AST_SyntaxElement_t kind;
|
||||
// optional value: integer literal, string literal, ...
|
||||
const char* value;
|
||||
|
||||
// number of child nodes ownd by this node
|
||||
// length of children array
|
||||
size_t child_count;
|
||||
// variable amount of child nodes
|
||||
struct AST_Node_t **children;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shorthand type for a single AST node
|
||||
*/
|
||||
typedef struct AST_Node_t* AST_NODE_PTR;
|
||||
|
||||
/**
|
||||
* @brief Initalize the global state of this module. Required for some functionality to work correctly.
|
||||
*/
|
||||
void AST_init(void);
|
||||
|
||||
/**
|
||||
* @brief Returns the string representation of the supplied node
|
||||
* @attention The retuned pointer is not to be freed as it may either be a statically stored string or
|
||||
* used by the node after this function call.
|
||||
* @param node to return string representation of
|
||||
* @return string represenation of the node
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1)]]
|
||||
const char* AST_node_to_string(const struct AST_Node_t* node);
|
||||
|
||||
/**
|
||||
* @brief Create a new node struct on the system heap. Initializes the struct with the given values.
|
||||
* All other fields are set to either NULL or 0. No allocation for children array is preformed.
|
||||
* @attention parameter value can be NULL in case no value can be provided for the node
|
||||
* @param kind the type of this node
|
||||
* @param value an optional value for this node
|
||||
* @return
|
||||
*/
|
||||
[[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);
|
||||
|
||||
/**
|
||||
* @brief Deallocate this node and all of its children.
|
||||
* @attention This function will detach this node from its parent if one is present
|
||||
* Use of the supplied node after this call is undefined behavior
|
||||
* @param node The node to deallocate
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1)]]
|
||||
void AST_delete_node(struct AST_Node_t * node);
|
||||
|
||||
/**
|
||||
* @brief Add a new child node to a parent node
|
||||
* @attention This can reallocate the children array
|
||||
* @param owner node to add a child to
|
||||
* @param child node to be added as a child
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(2)]]
|
||||
void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child);
|
||||
|
||||
/**
|
||||
* @brief Remove the specified child from the owner.
|
||||
* @attention The parent of the removed node is set to NULL.
|
||||
* The returned pointer is still valid. It must be freed at some pointer later.
|
||||
* @param owner Node to remove the child from
|
||||
* @param idx the index of the child to remove
|
||||
* @return a pointer to the child which was removed
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[nodiscard("pointer must be freed")]]
|
||||
[[gnu::nonnull(1)]]
|
||||
struct AST_Node_t* AST_remove_child(struct AST_Node_t* owner, size_t idx);
|
||||
|
||||
/**
|
||||
* @brief Detach a child from its parent. This involves removing the child from its parent
|
||||
* and marking the parent of the child as NULL.
|
||||
* @attention The returned pointer is still valid. It must be freed at some pointer later.
|
||||
* @param owner the owner to remove the child from
|
||||
* @param child the child to detach
|
||||
* @return a pointer to child detached
|
||||
*/
|
||||
[[nodiscard("pointer must be freed")]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(1)]]
|
||||
struct AST_Node_t* AST_detach_child(struct AST_Node_t* owner, const struct AST_Node_t* child);
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the n-th child of a node
|
||||
* @attention Pointer to childen nodes will never change.
|
||||
* However, the index a node is stored within a parent can change
|
||||
* if a child of lower index is removed, thus reducing the childrens index by one.
|
||||
* @param owner the parent node which owns the children
|
||||
* @param idx the index of the child to get a pointer to
|
||||
* @return a pointer to the n-th child of the owner node
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1)]]
|
||||
struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx);
|
||||
|
||||
/**
|
||||
* @brief Execute a function for every child, grandchild, ... and the supplied node as topmost ancestor
|
||||
* @param root the root to recursively execute a function for
|
||||
* @param for_each the function to execute
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(2)]]
|
||||
void AST_visit_nodes_recurse(struct AST_Node_t *root,
|
||||
void (*for_each)(struct AST_Node_t *node,
|
||||
size_t depth));
|
||||
|
||||
/**
|
||||
* @brief Prints a graphviz graph of the node and all its ancestors.
|
||||
* @param stream The stream to print to. Can be a file, stdout, ...
|
||||
* @param node the topmost ancestor
|
||||
*/
|
||||
[[maybe_unused]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(2)]]
|
||||
void AST_fprint_graphviz(FILE* stream, const struct AST_Node_t* node);
|
||||
|
||||
#endif
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
"int" {DEBUG("\"%s\" tokenized with \'KeyInt'", yytext); return(KeyInt);};
|
||||
"float" {DEBUG("\"%s\" tokenized with \'KeyFloat\'", yytext); return(KeyFloat);};
|
||||
"self" {DEBUG("\"%s\" tokenized with \'KeySelf\'", yytext); return(KeySelf);};
|
||||
"as" {DEBUG("\"%s\" tokenized with \'KeyAs'", yytext); return (KeyAs);};
|
||||
"short" {DEBUG("\"%s\" tokenized with \'KeyShort\'", yytext); return(KeyShort);};
|
||||
"long" {DEBUG("\"%s\" tokenized with \'KeyLong\'", yytext); return(KeyLong);};
|
||||
|
|
86
src/main.c
86
src/main.c
|
@ -1,77 +1,71 @@
|
|||
#include <ast/ast.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/log.h>
|
||||
#include <yacc/parser.tab.h>
|
||||
|
||||
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
||||
|
||||
extern FILE* yyin;
|
||||
extern FILE *yyin;
|
||||
|
||||
/**
|
||||
* @brief Log a debug message to inform about beginning exit procedures
|
||||
*
|
||||
*
|
||||
*/
|
||||
void notify_exit(void)
|
||||
{
|
||||
DEBUG("Exiting gemstone...");
|
||||
}
|
||||
void notify_exit(void) { DEBUG("Exiting gemstone..."); }
|
||||
|
||||
/**
|
||||
* @brief Closes File after compiling.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
void close_file(void)
|
||||
{
|
||||
if (NULL != yyin)
|
||||
{
|
||||
fclose(yyin);
|
||||
}
|
||||
void close_file(void) {
|
||||
if (NULL != yyin) {
|
||||
fclose(yyin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run compiler setup here
|
||||
*
|
||||
*
|
||||
*/
|
||||
void setup(void)
|
||||
{
|
||||
// setup preample
|
||||
void setup(void) {
|
||||
// setup preample
|
||||
|
||||
log_init();
|
||||
DEBUG("starting gemstone...");
|
||||
log_init();
|
||||
DEBUG("starting gemstone...");
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_DEBUG
|
||||
atexit(¬ify_exit);
|
||||
#endif
|
||||
#if LOG_LEVEL <= LOG_LEVEL_DEBUG
|
||||
atexit(¬ify_exit);
|
||||
#endif
|
||||
|
||||
// actual setup
|
||||
|
||||
DEBUG("finished starting up gemstone...");
|
||||
// actual setup
|
||||
AST_init();
|
||||
|
||||
DEBUG("finished starting up gemstone...");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
setup();
|
||||
atexit(close_file);
|
||||
|
||||
// Check for file input as argument
|
||||
if (2 != argc)
|
||||
{
|
||||
INFO("Usage: %s <filename>\n", argv[0]);
|
||||
PANIC("No File could be found");
|
||||
}
|
||||
|
||||
// filename as first argument
|
||||
char *filename = argv[1];
|
||||
setup();
|
||||
atexit(close_file);
|
||||
|
||||
FILE *file = fopen(filename, "r");
|
||||
// Check for file input as argument
|
||||
if (2 != argc) {
|
||||
INFO("Usage: %s <filename>\n", argv[0]);
|
||||
PANIC("No File could be found");
|
||||
}
|
||||
|
||||
if (NULL == file)
|
||||
{
|
||||
PANIC("File couldn't be opened!");
|
||||
}
|
||||
yyin = file;
|
||||
|
||||
yyparse();
|
||||
// filename as first argument
|
||||
char *filename = argv[1];
|
||||
|
||||
return 0;
|
||||
FILE *file = fopen(filename, "r");
|
||||
|
||||
if (NULL == file) {
|
||||
PANIC("File couldn't be opened!");
|
||||
}
|
||||
yyin = file;
|
||||
|
||||
yyparse();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/log.h>
|
||||
#include <assert.h>
|
||||
|
||||
static struct Logger_t {
|
||||
FILE** streams;
|
||||
|
@ -11,13 +12,15 @@ static struct Logger_t {
|
|||
|
||||
void log_init(void)
|
||||
{
|
||||
assert(LOG_DEFAULT_STREAM != NULL);
|
||||
log_register_stream(LOG_DEFAULT_STREAM);
|
||||
}
|
||||
|
||||
void log_register_stream(FILE* restrict stream)
|
||||
{
|
||||
if (stream == NULL)
|
||||
PANIC("stream to register is NULL");
|
||||
// replace runtime check with assertion
|
||||
// only to be used in debug target
|
||||
assert(stream != NULL);
|
||||
|
||||
if (GlobalLogger.stream_count == 0)
|
||||
{
|
||||
|
@ -57,7 +60,7 @@ static void vflogf(
|
|||
vfprintf(stream, format, args);
|
||||
}
|
||||
|
||||
void __logf(
|
||||
void syslog_logf(
|
||||
const char* restrict level,
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
|
@ -78,7 +81,7 @@ void __logf(
|
|||
va_end(args);
|
||||
}
|
||||
|
||||
void __panicf(
|
||||
void syslog_panicf(
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
const char* restrict func,
|
||||
|
@ -95,7 +98,7 @@ void __panicf(
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void __fatalf(
|
||||
void syslog_fatalf(
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
const char* restrict func,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define _SYS_ERR_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LOG_DEFAULT_STREAM stderr
|
||||
|
||||
|
@ -35,14 +34,14 @@
|
|||
* This macro will print debug information to stderr and call abort() to
|
||||
* performa a ungracefull exit. No clean up possible.
|
||||
*/
|
||||
#define PANIC(format, ...) __panicf(__FILE_NAME__, __LINE__, __func__, format"\n", ##__VA_ARGS__)
|
||||
#define PANIC(format, ...) syslog_panicf(__FILE_NAME__, __LINE__, __func__, format"\n", ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @brief Panic is used in cases where the process is in an invalid or undefined state.
|
||||
* This macro will print debug information to stderr and call exit() to
|
||||
* initiate a gracefull exit, giving the process the opportunity to clean up.
|
||||
*/
|
||||
#define FATAL(format, ...) __fatalf(__FILE_NAME__, __LINE__, __func__, format"\n", ##__VA_ARGS__)
|
||||
#define FATAL(format, ...) syslog_fatalf(__FILE_NAME__, __LINE__, __func__, format"\n", ##__VA_ARGS__)
|
||||
|
||||
/*
|
||||
Standard log macros. These will not terminate the application.
|
||||
|
@ -57,7 +56,7 @@ will not print.
|
|||
#define __LOG(level, priority, format, ...) \
|
||||
do { \
|
||||
if (LOG_LEVEL <= priority) \
|
||||
__logf(level, __FILE_NAME__, __LINE__, __func__, format, ##__VA_ARGS__); \
|
||||
syslog_logf(level, __FILE_NAME__, __LINE__, __func__, format, ##__VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
|
@ -70,10 +69,11 @@ will not print.
|
|||
* @param format the format to print following args in
|
||||
* @param ...
|
||||
*/
|
||||
void __logf(
|
||||
[[gnu::nonnull(1), gnu::nonnull(2), gnu::nonnull(4)]]
|
||||
void syslog_logf(
|
||||
const char* restrict level,
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
unsigned long line,
|
||||
const char* restrict func,
|
||||
const char* restrict format,
|
||||
...);
|
||||
|
@ -87,9 +87,11 @@ void __logf(
|
|||
* @param format the format to print following args in
|
||||
* @param ...
|
||||
*/
|
||||
void __panicf(
|
||||
[[noreturn]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(3), gnu::nonnull(4)]]
|
||||
void syslog_panicf(
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
unsigned long line,
|
||||
const char* restrict func,
|
||||
const char* restrict format,
|
||||
...);
|
||||
|
@ -103,9 +105,11 @@ void __panicf(
|
|||
* @param format the format to print following args in
|
||||
* @param ...
|
||||
*/
|
||||
void __fatalf(
|
||||
[[noreturn]]
|
||||
[[gnu::nonnull(1), gnu::nonnull(3), gnu::nonnull(4)]]
|
||||
void syslog_fatalf(
|
||||
const char* restrict file,
|
||||
const unsigned long line,
|
||||
unsigned long line,
|
||||
const char* restrict func,
|
||||
const char* restrict format,
|
||||
...);
|
||||
|
@ -121,6 +125,7 @@ void log_init(void);
|
|||
*
|
||||
* @param stream
|
||||
*/
|
||||
[[gnu::nonnull(1)]]
|
||||
void log_register_stream(FILE* restrict stream);
|
||||
|
||||
#endif /* _SYS_ERR_H_ */
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
%token KeyInt
|
||||
%token KeyFloat
|
||||
%token KeySelf
|
||||
%token KeyAs
|
||||
%token <string> ValInt
|
||||
%token <string> Ident
|
||||
|
@ -63,15 +64,26 @@
|
|||
%left OpBitand OpBitor OpBitxor OpBitnot
|
||||
|
||||
%%
|
||||
program: statementlist
|
||||
| fundef;
|
||||
program: program programbody
|
||||
| programbody;
|
||||
|
||||
programbody: moduleimport
|
||||
| fundef
|
||||
| box
|
||||
| definition
|
||||
| decl
|
||||
| typedef;
|
||||
|
||||
|
||||
|
||||
expr: ValFloat
|
||||
| ValInt
|
||||
| ValMultistr
|
||||
| ValStr
|
||||
| Ident
|
||||
| operation;
|
||||
| operation
|
||||
| boxaccess
|
||||
| boxselfaccess;
|
||||
|
||||
exprlist: expr ',' exprlist
|
||||
| expr;
|
||||
|
@ -98,9 +110,26 @@ IOqualifyier: KeyIn
|
|||
|
||||
paramdecl: type ':' Ident { DEBUG("Param-Declaration"); };
|
||||
|
||||
funcall: Ident argumentlist { DEBUG("Function call"); };
|
||||
box: KeyType KeyBox ':' Ident '{' boxbody '}' { DEBUG("Box"); }
|
||||
| KeyType KeyBox ':' Ident '{' '}';
|
||||
|
||||
assign: Ident '=' expr { DEBUG("Assignment"); };
|
||||
boxbody: boxbody boxcontent
|
||||
| boxcontent;
|
||||
|
||||
boxcontent: decl { DEBUG("Box decl Content"); }
|
||||
| definition { DEBUG("Box def Content"); }
|
||||
| fundef { DEBUG("Box fun Content"); };
|
||||
|
||||
boxselfaccess: KeySelf '.' Ident
|
||||
| KeySelf '.' boxaccess;
|
||||
|
||||
boxaccess: Ident '.' Ident
|
||||
| Ident '.' boxaccess;
|
||||
|
||||
boxcall: boxaccess argumentlist
|
||||
| boxselfaccess argumentlist;
|
||||
|
||||
funcall: Ident argumentlist { DEBUG("Function call"); };
|
||||
|
||||
moduleimport: KeyImport ValStr { DEBUG("Module-Import"); };
|
||||
|
||||
|
@ -112,7 +141,8 @@ statement: assign
|
|||
| definition
|
||||
| while
|
||||
| branch
|
||||
| funcall;
|
||||
| funcall
|
||||
| boxcall;
|
||||
|
||||
branchif: KeyIf expr '{' statementlist '}' { DEBUG("if"); };
|
||||
branchelse: KeyElse '{' statementlist '}' { DEBUG("if-else"); };
|
||||
|
@ -129,15 +159,25 @@ while: KeyWhile expr '{' statementlist '}' { DEBUG("while"); };
|
|||
identlist: Ident ',' identlist
|
||||
| Ident;
|
||||
|
||||
decl: type ':' identlist;
|
||||
decl: type ':' identlist
|
||||
| storagequalifier type ':' identlist
|
||||
|
||||
|
||||
definition: decl '=' expr { DEBUG("Definition"); };
|
||||
|
||||
assign: Ident '=' expr { DEBUG("Assignment"); };
|
||||
storagequalifier: KeyGlobal
|
||||
| KeyStatic
|
||||
| KeyLocal;
|
||||
|
||||
assign: Ident '=' expr { DEBUG("Assignment"); }
|
||||
| boxaccess '=' expr
|
||||
| boxselfaccess '=' expr ;
|
||||
|
||||
sign: KeySigned
|
||||
| KeyUnsigned;
|
||||
|
||||
typedef: KeyType type':' Ident;
|
||||
|
||||
scale: scale KeyShort
|
||||
| scale KeyHalf
|
||||
| scale KeyLong
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
include(CTest)
|
||||
|
||||
set(PROJECT_BINARY_DIR bin)
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/tests)
|
||||
set(CTEST_BINARY_DIRECTORY ${PROJECT_BINARY_DIR}/tests)
|
||||
|
||||
# Provide test to run here or include another CMakeLists.txt
|
||||
|
||||
add_subdirectory(logging)
|
||||
add_subdirectory(input_file)
|
||||
add_subdirectory(ast)
|
|
@ -0,0 +1,51 @@
|
|||
include(CTest)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 1
|
||||
# test building the syntax tree
|
||||
|
||||
add_executable(ast_build_tree
|
||||
${PROJECT_SOURCE_DIR}/src/ast/ast.c
|
||||
${PROJECT_SOURCE_DIR}/src/sys/log.c
|
||||
build_tree.c)
|
||||
set_target_properties(ast_build_tree
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "build_tree"
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/ast)
|
||||
add_test(NAME ast_build_tree
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/ast/test_ast.py check_build_tree)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 2
|
||||
# test node to string output
|
||||
|
||||
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_print_node
|
||||
PROPERTIES
|
||||
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)
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Created by servostar on 5/7/24.
|
||||
//
|
||||
|
||||
#include <ast/ast.h>
|
||||
#include <sys/log.h>
|
||||
|
||||
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) {
|
||||
|
||||
const AST_NODE_PTR root = AST_new_node(AST_Stmt, NULL);
|
||||
|
||||
generate_branch(root);
|
||||
|
||||
AST_delete_node(root);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Created by servostar on 5/7/24.
|
||||
//
|
||||
|
||||
#include <ast/ast.h>
|
||||
#include <sys/log.h>
|
||||
|
||||
int main(void) {
|
||||
|
||||
struct AST_Node_t* node = AST_new_node(AST_If, NULL);
|
||||
|
||||
struct AST_Node_t* child = AST_new_node(AST_Add, NULL);
|
||||
AST_push_node(child, AST_new_node(AST_Int, "43"));
|
||||
AST_push_node(child, AST_new_node(AST_Int, "9"));
|
||||
|
||||
AST_push_node(node, child);
|
||||
AST_push_node(node, AST_new_node(AST_Expr, NULL));
|
||||
AST_push_node(node, AST_new_node(AST_Expr, NULL));
|
||||
|
||||
FILE* out = fopen("ast.gv", "w+");
|
||||
// convert this file ^^^^^^
|
||||
// to an svg with: `dot -Tsvg ast.gv > graph.svg`
|
||||
|
||||
AST_fprint_graphviz(out, node);
|
||||
|
||||
AST_delete_node(node);
|
||||
|
||||
fflush(out);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Created by servostar on 5/8/24.
|
||||
//
|
||||
|
||||
#include <ast/ast.h>
|
||||
#include <sys/log.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Created by servostar on 5/7/24.
|
||||
//
|
||||
|
||||
#include <ast/ast.h>
|
||||
|
||||
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++) {
|
||||
// set kind
|
||||
node->kind = i;
|
||||
// print symbol
|
||||
printf("%ld %s\n", i, AST_node_to_string(node));
|
||||
}
|
||||
|
||||
AST_delete_node(node);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import logging
|
||||
from logging import info, error
|
||||
import os
|
||||
|
||||
BIN_DIR = "bin/tests/ast/"
|
||||
|
||||
|
||||
def run_check_build_tree():
|
||||
info("started check tree build...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "build_tree", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# check exit code
|
||||
assert p.returncode == 0
|
||||
|
||||
|
||||
def run_check_print_node():
|
||||
info("started check node print...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "print_node", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# 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)
|
||||
|
||||
target = sys.argv[1]
|
||||
|
||||
info(f"starting ast test suite with target: {target}")
|
||||
|
||||
match target:
|
||||
case "check_build_tree":
|
||||
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)
|
|
@ -0,0 +1,9 @@
|
|||
include(CTest)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 1
|
||||
# test if the program accepts a file as input
|
||||
|
||||
add_test(NAME input_file_check
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/check
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/input_file/test_input_file.py ${GEMSTONE_TEST_DIR}/input_file/test.gem)
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
import "std.io"
|
||||
|
||||
fun main {
|
||||
print("Hello, World!!!")
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
import logging
|
||||
from logging import info
|
||||
|
||||
|
||||
def check_accept():
|
||||
info("testing handling of input file...")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
test_file_name = sys.argv[1]
|
||||
|
||||
p = subprocess.run(["./gsc", test_file_name], capture_output=True, text=True)
|
||||
|
||||
assert p.returncode == 0
|
||||
|
||||
|
||||
def check_abort():
|
||||
info("testing handling of missing input file...")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
p = subprocess.run("./gsc", capture_output=True, text=True)
|
||||
|
||||
assert p.returncode == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
info("check if binary exists...")
|
||||
assert os.path.exists("./gsc")
|
||||
|
||||
check_accept()
|
||||
check_abort()
|
|
@ -0,0 +1,63 @@
|
|||
include(CTest)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 1
|
||||
# test the default output of the logger
|
||||
|
||||
add_executable(logging_output
|
||||
${PROJECT_SOURCE_DIR}/src/sys/log.c
|
||||
output.c)
|
||||
set_target_properties(logging_output
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "output"
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging)
|
||||
add_test(NAME logging_output
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_output)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 2
|
||||
# test the panic functionality of the logger
|
||||
|
||||
add_executable(logging_panic
|
||||
${PROJECT_SOURCE_DIR}/src/sys/log.c
|
||||
panic.c)
|
||||
set_target_properties(logging_panic
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "panic"
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging)
|
||||
add_test(NAME logging_panic
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_panic)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 3
|
||||
# test the ability to write to multiple output streams
|
||||
|
||||
add_executable(logging_streams
|
||||
${PROJECT_SOURCE_DIR}/src/sys/log.c
|
||||
streams.c)
|
||||
set_target_properties(logging_streams
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "stream"
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging)
|
||||
add_test(NAME logging_streams
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_stream)
|
||||
|
||||
# ------------------------------------------------------- #
|
||||
# CTEST 4
|
||||
# test compile time log level switch
|
||||
|
||||
add_executable(logging_level
|
||||
${PROJECT_SOURCE_DIR}/src/sys/log.c
|
||||
level.c)
|
||||
set_target_properties(logging_level
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "level"
|
||||
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/tests/logging)
|
||||
add_test(NAME logging_level
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMAND python ${GEMSTONE_TEST_DIR}/logging/test_logging.py check_level)
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Created by servostar on 5/2/24.
|
||||
//
|
||||
|
||||
#include "sys/log.h"
|
||||
|
||||
#define LOG_LEVEL LOG_LEVEL_WARNING
|
||||
|
||||
int main(void) {
|
||||
log_init();
|
||||
|
||||
DEBUG("logging some debug...");
|
||||
INFO("logging some info...");
|
||||
WARN("logging some warning...");
|
||||
ERROR("logging some error...");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Created by servostar on 5/1/24.
|
||||
//
|
||||
|
||||
#include "sys/log.h"
|
||||
|
||||
int main(void) {
|
||||
log_init();
|
||||
|
||||
DEBUG("logging some debug...");
|
||||
INFO("logging some info...");
|
||||
WARN("logging some warning...");
|
||||
ERROR("logging some error...");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Created by servostar on 5/2/24.
|
||||
//
|
||||
|
||||
#include "sys/log.h"
|
||||
|
||||
int main(void) {
|
||||
log_init();
|
||||
|
||||
// this should appear in stderr
|
||||
INFO("before exit");
|
||||
|
||||
PANIC("oooops something happened");
|
||||
|
||||
// this should NOT appear in stderr
|
||||
// ^^^
|
||||
ERROR("after exit");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// Created by servostar on 5/2/24.
|
||||
//
|
||||
|
||||
#include "sys/log.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static FILE* file;
|
||||
|
||||
void close_file(void) {
|
||||
if (file != NULL) {
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
log_init();
|
||||
|
||||
// this should appear in stderr
|
||||
INFO("should only be in stderr");
|
||||
|
||||
file = fopen("tmp/test.log", "w");
|
||||
if (file == NULL) {
|
||||
PANIC("could not open file");
|
||||
}
|
||||
atexit(close_file);
|
||||
|
||||
log_register_stream(file);
|
||||
|
||||
INFO("should be in both");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import logging
|
||||
from logging import info, error
|
||||
import os
|
||||
|
||||
BIN_DIR = "bin/tests/logging/"
|
||||
|
||||
|
||||
def run_check_output():
|
||||
info("started check output...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "output", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# check exit code
|
||||
assert p.returncode == 0
|
||||
|
||||
output = p.stderr
|
||||
|
||||
# check if logs appear in default log output (stderr)
|
||||
info("checking stderr...")
|
||||
|
||||
assert "logging some debug..." in output
|
||||
assert "logging some info..." in output
|
||||
assert "logging some warning..." in output
|
||||
assert "logging some error..." in output
|
||||
|
||||
|
||||
def run_check_level():
|
||||
info("started check level...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "level", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# check exit code
|
||||
assert p.returncode == 0
|
||||
|
||||
output = p.stderr
|
||||
|
||||
# check if logs appear in default log output (stderr)
|
||||
info("checking stderr...")
|
||||
|
||||
assert "logging some debug..." not in output
|
||||
assert "logging some info..." not in output
|
||||
assert "logging some warning..." in output
|
||||
assert "logging some error..." in output
|
||||
|
||||
|
||||
def run_check_panic():
|
||||
info("started check panic...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "panic", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# check exit code
|
||||
assert p.returncode == 1
|
||||
|
||||
output = p.stderr
|
||||
|
||||
# check if logs appear (not) in default log output (stderr)
|
||||
info("checking stderr...")
|
||||
|
||||
assert "before exit" in output
|
||||
assert "oooops something happened" in output
|
||||
assert "after exit" not in output
|
||||
|
||||
|
||||
def run_check_stream():
|
||||
info("started check panic...")
|
||||
|
||||
info("creating temporary folder...")
|
||||
|
||||
if not os.path.exists("tmp"):
|
||||
os.mkdir("tmp")
|
||||
|
||||
info("cleaning temporary folder...")
|
||||
|
||||
if os.path.exists("tmp/test.log"):
|
||||
os.remove("tmp/test.log")
|
||||
|
||||
info("launching test binary...")
|
||||
|
||||
p = subprocess.run(BIN_DIR + "stream", capture_output=True, text=True)
|
||||
|
||||
info("checking exit code...")
|
||||
|
||||
# check exit code
|
||||
assert p.returncode == 0
|
||||
|
||||
with open("tmp/test.log", "r") as file:
|
||||
assert "should be in both" in "".join(file.readlines())
|
||||
|
||||
output = p.stderr
|
||||
|
||||
# check if logs appear (not) in default log output (stderr)
|
||||
info("checking stderr...")
|
||||
|
||||
assert "should only be in stderr" in output
|
||||
assert "should be in both" in output
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
target = sys.argv[1]
|
||||
|
||||
info(f"starting logging test suite with target: {target}")
|
||||
|
||||
match target:
|
||||
case "check_output":
|
||||
run_check_output()
|
||||
case "check_panic":
|
||||
run_check_panic()
|
||||
case "check_stream":
|
||||
run_check_stream()
|
||||
case "check_level":
|
||||
run_check_level()
|
||||
case _:
|
||||
error(f"unknown target: {target}")
|
||||
exit(1)
|
Loading…
Reference in New Issue