Merge pull request #66 from Servostar/concept/ast-design
Concept/ast design
This commit is contained in:
commit
730248595e
|
@ -2,7 +2,7 @@ name: "Build check gemstone in SDK"
|
|||
run-name: SDK build check to ${{ inputs.deploy_target }} by @${{ github.actor }}
|
||||
on: [push, pull_request]
|
||||
env:
|
||||
SDK: 0.2.2-alpine-3.19.1
|
||||
SDK: 0.2.3-alpine-3.19.1
|
||||
jobs:
|
||||
build-check-sdk:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -21,6 +21,9 @@ 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)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
FROM servostar/gemstone:sdk-0.2.2-alpine-3.19.1
|
||||
FROM servostar/gemstone:sdk-0.2.3-alpine-3.19.1
|
||||
LABEL authors="servostar"
|
||||
LABEL version="0.2.2"
|
||||
LABEL version="0.2.3"
|
||||
LABEL description="docker image for setting up the build pipeline on SDK"
|
||||
LABEL website="https://github.com/Servostar/gemstone"
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
FROM alpine:3.19.1
|
||||
LABEL authors="servostar"
|
||||
LABEL version="0.2.2"
|
||||
LABEL version="0.2.3"
|
||||
LABEL description="base image for building the gemstone programming language compiler"
|
||||
LABEL website="https://github.com/Servostar/gemstone"
|
||||
|
||||
# install dependencies
|
||||
RUN apk add build-base gcc make cmake bison flex git python3
|
||||
RUN apk add build-base gcc make cmake bison flex git python3 graphviz
|
||||
|
||||
# create user for build
|
||||
RUN adduser --disabled-password lorang
|
||||
|
|
|
@ -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
|
72
src/main.c
72
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
|
||||
// actual setup
|
||||
AST_init();
|
||||
|
||||
DEBUG("finished starting up gemstone...");
|
||||
DEBUG("finished starting up gemstone...");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
setup();
|
||||
atexit(close_file);
|
||||
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");
|
||||
}
|
||||
// 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];
|
||||
// filename as first argument
|
||||
char *filename = argv[1];
|
||||
|
||||
FILE *file = fopen(filename, "r");
|
||||
FILE *file = fopen(filename, "r");
|
||||
|
||||
if (NULL == file)
|
||||
{
|
||||
PANIC("File couldn't be opened!");
|
||||
}
|
||||
yyin = file;
|
||||
if (NULL == file) {
|
||||
PANIC("File couldn't be opened!");
|
||||
}
|
||||
yyin = file;
|
||||
|
||||
yyparse();
|
||||
yyparse();
|
||||
|
||||
return 0;
|
||||
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_ */
|
||||
|
|
|
@ -8,3 +8,4 @@ set(CTEST_BINARY_DIRECTORY ${PROJECT_BINARY_DIR}/tests)
|
|||
|
||||
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)
|
Loading…
Reference in New Issue