commit
8b72a9fae2
|
@ -2,7 +2,7 @@ name: "Build check gemstone in SDK"
|
||||||
run-name: SDK build check to ${{ inputs.deploy_target }} by @${{ github.actor }}
|
run-name: SDK build check to ${{ inputs.deploy_target }} by @${{ github.actor }}
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
env:
|
env:
|
||||||
SDK: 0.2.1-alpine-3.19.1
|
SDK: 0.2.3-alpine-3.19.1
|
||||||
jobs:
|
jobs:
|
||||||
build-check-sdk:
|
build-check-sdk:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -11,4 +11,4 @@ jobs:
|
||||||
- name: Setup SDK
|
- name: Setup SDK
|
||||||
run: docker pull servostar/gemstone:sdk-"$SDK" && docker build --tag gemstone:devkit-"$SDK" .
|
run: docker pull servostar/gemstone:sdk-"$SDK" && docker build --tag gemstone:devkit-"$SDK" .
|
||||||
- name: Compile
|
- name: Compile
|
||||||
run: docker run gemstone:devkit-"$SDK" make check
|
run: docker run gemstone:devkit-"$SDK" sh run-check-test.sh
|
||||||
|
|
|
@ -14,3 +14,4 @@ lexer.ll.c
|
||||||
parser.tab.c
|
parser.tab.c
|
||||||
parser.tab.h
|
parser.tab.h
|
||||||
build
|
build
|
||||||
|
/Testing/
|
||||||
|
|
|
@ -21,6 +21,18 @@ project(gemstone
|
||||||
DESCRIPTION "programming language compiler"
|
DESCRIPTION "programming language compiler"
|
||||||
LANGUAGES C)
|
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)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# ------------------------------------------------ #
|
# ------------------------------------------------ #
|
||||||
|
@ -79,7 +91,7 @@ add_executable(release
|
||||||
set_target_properties(release
|
set_target_properties(release
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
OUTPUT_NAME "gsc"
|
OUTPUT_NAME "gsc"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "bin/release")
|
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/release)
|
||||||
|
|
||||||
# FIXME: cannot compile with /O2 because of /RTC1 flag
|
# FIXME: cannot compile with /O2 because of /RTC1 flag
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
|
@ -111,7 +123,7 @@ add_executable(debug
|
||||||
set_target_properties(debug
|
set_target_properties(debug
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
OUTPUT_NAME "gsc"
|
OUTPUT_NAME "gsc"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "bin/debug")
|
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/debug)
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(DEBUG_FLAGS /DEBUG)
|
set(DEBUG_FLAGS /DEBUG)
|
||||||
|
@ -140,7 +152,7 @@ add_executable(check
|
||||||
set_target_properties(check
|
set_target_properties(check
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
OUTPUT_NAME "gsc"
|
OUTPUT_NAME "gsc"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "bin/check")
|
RUNTIME_OUTPUT_DIRECTORY ${GEMSTONE_BINARY_DIR}/check)
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(CHECK_FLAGS /DEBUG /WX)
|
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 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 description="docker image for setting up the build pipeline on SDK"
|
||||||
LABEL website="https://github.com/Servostar/gemstone"
|
LABEL website="https://github.com/Servostar/gemstone"
|
||||||
|
|
||||||
COPY --chown=lorang src /home/lorang/src
|
COPY --chown=lorang src /home/lorang/src
|
||||||
|
COPY --chown=lorang tests /home/lorang/tests
|
||||||
COPY --chown=lorang CMakeLists.txt /home/lorang/
|
COPY --chown=lorang CMakeLists.txt /home/lorang/
|
||||||
|
COPY --chown=lorang run-check-test.sh /home/lorang/
|
||||||
|
|
||||||
RUN cmake .
|
RUN cmake .
|
||||||
|
|
22
README.md
22
README.md
|
@ -21,6 +21,28 @@ Requires:
|
||||||
- bison
|
- bison
|
||||||
- flex
|
- 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
|
## Development with VSCode/Codium
|
||||||
|
|
||||||
Recommended extensions for getting a decent experience are the following:
|
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
|
FROM alpine:3.19.1
|
||||||
LABEL authors="servostar"
|
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 description="base image for building the gemstone programming language compiler"
|
||||||
LABEL website="https://github.com/Servostar/gemstone"
|
LABEL website="https://github.com/Servostar/gemstone"
|
||||||
|
|
||||||
# install dependencies
|
# 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
|
# create user for build
|
||||||
RUN adduser --disabled-password lorang
|
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);};
|
"int" {DEBUG("\"%s\" tokenized with \'KeyInt'", yytext); return(KeyInt);};
|
||||||
"float" {DEBUG("\"%s\" tokenized with \'KeyFloat\'", yytext); return(KeyFloat);};
|
"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);};
|
"as" {DEBUG("\"%s\" tokenized with \'KeyAs'", yytext); return (KeyAs);};
|
||||||
"short" {DEBUG("\"%s\" tokenized with \'KeyShort\'", yytext); return(KeyShort);};
|
"short" {DEBUG("\"%s\" tokenized with \'KeyShort\'", yytext); return(KeyShort);};
|
||||||
"long" {DEBUG("\"%s\" tokenized with \'KeyLong\'", yytext); return(KeyLong);};
|
"long" {DEBUG("\"%s\" tokenized with \'KeyLong\'", yytext); return(KeyLong);};
|
||||||
|
|
28
src/main.c
28
src/main.c
|
@ -1,29 +1,25 @@
|
||||||
|
#include <ast/ast.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/log.h>
|
#include <sys/log.h>
|
||||||
#include <yacc/parser.tab.h>
|
#include <yacc/parser.tab.h>
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
||||||
|
|
||||||
extern FILE* yyin;
|
extern FILE *yyin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Log a debug message to inform about beginning exit procedures
|
* @brief Log a debug message to inform about beginning exit procedures
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void notify_exit(void)
|
void notify_exit(void) { DEBUG("Exiting gemstone..."); }
|
||||||
{
|
|
||||||
DEBUG("Exiting gemstone...");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Closes File after compiling.
|
* @brief Closes File after compiling.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void close_file(void)
|
void close_file(void) {
|
||||||
{
|
if (NULL != yyin) {
|
||||||
if (NULL != yyin)
|
|
||||||
{
|
|
||||||
fclose(yyin);
|
fclose(yyin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,18 +28,18 @@ void close_file(void)
|
||||||
* @brief Run compiler setup here
|
* @brief Run compiler setup here
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void setup(void)
|
void setup(void) {
|
||||||
{
|
|
||||||
// setup preample
|
// setup preample
|
||||||
|
|
||||||
log_init();
|
log_init();
|
||||||
DEBUG("starting gemstone...");
|
DEBUG("starting gemstone...");
|
||||||
|
|
||||||
#if LOG_LEVEL <= LOG_LEVEL_DEBUG
|
#if LOG_LEVEL <= LOG_LEVEL_DEBUG
|
||||||
atexit(¬ify_exit);
|
atexit(¬ify_exit);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// actual setup
|
// actual setup
|
||||||
|
AST_init();
|
||||||
|
|
||||||
DEBUG("finished starting up gemstone...");
|
DEBUG("finished starting up gemstone...");
|
||||||
}
|
}
|
||||||
|
@ -54,8 +50,7 @@ int main(int argc, char *argv[]) {
|
||||||
atexit(close_file);
|
atexit(close_file);
|
||||||
|
|
||||||
// Check for file input as argument
|
// Check for file input as argument
|
||||||
if (2 != argc)
|
if (2 != argc) {
|
||||||
{
|
|
||||||
INFO("Usage: %s <filename>\n", argv[0]);
|
INFO("Usage: %s <filename>\n", argv[0]);
|
||||||
PANIC("No File could be found");
|
PANIC("No File could be found");
|
||||||
}
|
}
|
||||||
|
@ -65,8 +60,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
FILE *file = fopen(filename, "r");
|
FILE *file = fopen(filename, "r");
|
||||||
|
|
||||||
if (NULL == file)
|
if (NULL == file) {
|
||||||
{
|
|
||||||
PANIC("File couldn't be opened!");
|
PANIC("File couldn't be opened!");
|
||||||
}
|
}
|
||||||
yyin = file;
|
yyin = file;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/log.h>
|
#include <sys/log.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
static struct Logger_t {
|
static struct Logger_t {
|
||||||
FILE** streams;
|
FILE** streams;
|
||||||
|
@ -11,13 +12,15 @@ static struct Logger_t {
|
||||||
|
|
||||||
void log_init(void)
|
void log_init(void)
|
||||||
{
|
{
|
||||||
|
assert(LOG_DEFAULT_STREAM != NULL);
|
||||||
log_register_stream(LOG_DEFAULT_STREAM);
|
log_register_stream(LOG_DEFAULT_STREAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_register_stream(FILE* restrict stream)
|
void log_register_stream(FILE* restrict stream)
|
||||||
{
|
{
|
||||||
if (stream == NULL)
|
// replace runtime check with assertion
|
||||||
PANIC("stream to register is NULL");
|
// only to be used in debug target
|
||||||
|
assert(stream != NULL);
|
||||||
|
|
||||||
if (GlobalLogger.stream_count == 0)
|
if (GlobalLogger.stream_count == 0)
|
||||||
{
|
{
|
||||||
|
@ -57,7 +60,7 @@ static void vflogf(
|
||||||
vfprintf(stream, format, args);
|
vfprintf(stream, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __logf(
|
void syslog_logf(
|
||||||
const char* restrict level,
|
const char* restrict level,
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
const unsigned long line,
|
||||||
|
@ -78,7 +81,7 @@ void __logf(
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __panicf(
|
void syslog_panicf(
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
const unsigned long line,
|
||||||
const char* restrict func,
|
const char* restrict func,
|
||||||
|
@ -95,7 +98,7 @@ void __panicf(
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fatalf(
|
void syslog_fatalf(
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
const unsigned long line,
|
||||||
const char* restrict func,
|
const char* restrict func,
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define _SYS_ERR_H_
|
#define _SYS_ERR_H_
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define LOG_DEFAULT_STREAM stderr
|
#define LOG_DEFAULT_STREAM stderr
|
||||||
|
|
||||||
|
@ -35,14 +34,14 @@
|
||||||
* This macro will print debug information to stderr and call abort() to
|
* This macro will print debug information to stderr and call abort() to
|
||||||
* performa a ungracefull exit. No clean up possible.
|
* 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.
|
* @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
|
* This macro will print debug information to stderr and call exit() to
|
||||||
* initiate a gracefull exit, giving the process the opportunity to clean up.
|
* 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.
|
Standard log macros. These will not terminate the application.
|
||||||
|
@ -57,7 +56,7 @@ will not print.
|
||||||
#define __LOG(level, priority, format, ...) \
|
#define __LOG(level, priority, format, ...) \
|
||||||
do { \
|
do { \
|
||||||
if (LOG_LEVEL <= priority) \
|
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)
|
} while(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,10 +69,11 @@ will not print.
|
||||||
* @param format the format to print following args in
|
* @param format the format to print following args in
|
||||||
* @param ...
|
* @param ...
|
||||||
*/
|
*/
|
||||||
void __logf(
|
[[gnu::nonnull(1), gnu::nonnull(2), gnu::nonnull(4)]]
|
||||||
|
void syslog_logf(
|
||||||
const char* restrict level,
|
const char* restrict level,
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
unsigned long line,
|
||||||
const char* restrict func,
|
const char* restrict func,
|
||||||
const char* restrict format,
|
const char* restrict format,
|
||||||
...);
|
...);
|
||||||
|
@ -87,9 +87,11 @@ void __logf(
|
||||||
* @param format the format to print following args in
|
* @param format the format to print following args in
|
||||||
* @param ...
|
* @param ...
|
||||||
*/
|
*/
|
||||||
void __panicf(
|
[[noreturn]]
|
||||||
|
[[gnu::nonnull(1), gnu::nonnull(3), gnu::nonnull(4)]]
|
||||||
|
void syslog_panicf(
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
unsigned long line,
|
||||||
const char* restrict func,
|
const char* restrict func,
|
||||||
const char* restrict format,
|
const char* restrict format,
|
||||||
...);
|
...);
|
||||||
|
@ -103,9 +105,11 @@ void __panicf(
|
||||||
* @param format the format to print following args in
|
* @param format the format to print following args in
|
||||||
* @param ...
|
* @param ...
|
||||||
*/
|
*/
|
||||||
void __fatalf(
|
[[noreturn]]
|
||||||
|
[[gnu::nonnull(1), gnu::nonnull(3), gnu::nonnull(4)]]
|
||||||
|
void syslog_fatalf(
|
||||||
const char* restrict file,
|
const char* restrict file,
|
||||||
const unsigned long line,
|
unsigned long line,
|
||||||
const char* restrict func,
|
const char* restrict func,
|
||||||
const char* restrict format,
|
const char* restrict format,
|
||||||
...);
|
...);
|
||||||
|
@ -121,6 +125,7 @@ void log_init(void);
|
||||||
*
|
*
|
||||||
* @param stream
|
* @param stream
|
||||||
*/
|
*/
|
||||||
|
[[gnu::nonnull(1)]]
|
||||||
void log_register_stream(FILE* restrict stream);
|
void log_register_stream(FILE* restrict stream);
|
||||||
|
|
||||||
#endif /* _SYS_ERR_H_ */
|
#endif /* _SYS_ERR_H_ */
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
%token KeyInt
|
%token KeyInt
|
||||||
%token KeyFloat
|
%token KeyFloat
|
||||||
|
%token KeySelf
|
||||||
%token KeyAs
|
%token KeyAs
|
||||||
%token <string> ValInt
|
%token <string> ValInt
|
||||||
%token <string> Ident
|
%token <string> Ident
|
||||||
|
@ -63,15 +64,26 @@
|
||||||
%left OpBitand OpBitor OpBitxor OpBitnot
|
%left OpBitand OpBitor OpBitxor OpBitnot
|
||||||
|
|
||||||
%%
|
%%
|
||||||
program: statementlist
|
program: program programbody
|
||||||
| fundef;
|
| programbody;
|
||||||
|
|
||||||
|
programbody: moduleimport
|
||||||
|
| fundef
|
||||||
|
| box
|
||||||
|
| definition
|
||||||
|
| decl
|
||||||
|
| typedef;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
expr: ValFloat
|
expr: ValFloat
|
||||||
| ValInt
|
| ValInt
|
||||||
| ValMultistr
|
| ValMultistr
|
||||||
| ValStr
|
| ValStr
|
||||||
| Ident
|
| Ident
|
||||||
| operation;
|
| operation
|
||||||
|
| boxaccess
|
||||||
|
| boxselfaccess;
|
||||||
|
|
||||||
exprlist: expr ',' exprlist
|
exprlist: expr ',' exprlist
|
||||||
| expr;
|
| expr;
|
||||||
|
@ -98,9 +110,26 @@ IOqualifyier: KeyIn
|
||||||
|
|
||||||
paramdecl: type ':' Ident { DEBUG("Param-Declaration"); };
|
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"); };
|
moduleimport: KeyImport ValStr { DEBUG("Module-Import"); };
|
||||||
|
|
||||||
|
@ -112,7 +141,8 @@ statement: assign
|
||||||
| definition
|
| definition
|
||||||
| while
|
| while
|
||||||
| branch
|
| branch
|
||||||
| funcall;
|
| funcall
|
||||||
|
| boxcall;
|
||||||
|
|
||||||
branchif: KeyIf expr '{' statementlist '}' { DEBUG("if"); };
|
branchif: KeyIf expr '{' statementlist '}' { DEBUG("if"); };
|
||||||
branchelse: KeyElse '{' statementlist '}' { DEBUG("if-else"); };
|
branchelse: KeyElse '{' statementlist '}' { DEBUG("if-else"); };
|
||||||
|
@ -129,15 +159,25 @@ while: KeyWhile expr '{' statementlist '}' { DEBUG("while"); };
|
||||||
identlist: Ident ',' identlist
|
identlist: Ident ',' identlist
|
||||||
| Ident;
|
| Ident;
|
||||||
|
|
||||||
decl: type ':' identlist;
|
decl: type ':' identlist
|
||||||
|
| storagequalifier type ':' identlist
|
||||||
|
|
||||||
|
|
||||||
definition: decl '=' expr { DEBUG("Definition"); };
|
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
|
sign: KeySigned
|
||||||
| KeyUnsigned;
|
| KeyUnsigned;
|
||||||
|
|
||||||
|
typedef: KeyType type':' Ident;
|
||||||
|
|
||||||
scale: scale KeyShort
|
scale: scale KeyShort
|
||||||
| scale KeyHalf
|
| scale KeyHalf
|
||||||
| scale KeyLong
|
| 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