Merge pull request #66 from Servostar/concept/ast-design

Concept/ast design
This commit is contained in:
Filleo 2024-05-12 21:28:53 +02:00 committed by GitHub
commit 730248595e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 873 additions and 67 deletions

View File

@ -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.2-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

View File

@ -21,6 +21,9 @@ 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_TEST_DIR ${PROJECT_SOURCE_DIR}/tests)
set(GEMSTONE_BINARY_DIR ${PROJECT_SOURCE_DIR}/bin) set(GEMSTONE_BINARY_DIR ${PROJECT_SOURCE_DIR}/bin)

View File

@ -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 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 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"

View File

@ -1,11 +1,11 @@
FROM alpine:3.19.1 FROM alpine:3.19.1
LABEL authors="servostar" 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 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 python3 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

274
src/ast/ast.c Normal file
View File

@ -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");
}

200
src/ast/ast.h Normal file
View File

@ -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

View File

@ -1,77 +1,71 @@
#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);
}
} }
/** /**
* @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(&notify_exit); atexit(&notify_exit);
#endif #endif
// actual setup // actual setup
AST_init();
DEBUG("finished starting up gemstone...");
DEBUG("finished starting up gemstone...");
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
setup(); setup();
atexit(close_file); atexit(close_file);
// Check for file input as argument
if (2 != argc)
{
INFO("Usage: %s <filename>\n", argv[0]);
PANIC("No File could be found");
}
// filename as first argument
char *filename = argv[1];
FILE *file = fopen(filename, "r"); // Check for file input as argument
if (2 != argc) {
INFO("Usage: %s <filename>\n", argv[0]);
PANIC("No File could be found");
}
if (NULL == file) // filename as first argument
{ char *filename = argv[1];
PANIC("File couldn't be opened!");
}
yyin = file;
yyparse();
return 0; FILE *file = fopen(filename, "r");
if (NULL == file) {
PANIC("File couldn't be opened!");
}
yyin = file;
yyparse();
return 0;
} }

View 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,

View File

@ -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_ */

View File

@ -7,4 +7,5 @@ set(CTEST_BINARY_DIRECTORY ${PROJECT_BINARY_DIR}/tests)
# Provide test to run here or include another CMakeLists.txt # Provide test to run here or include another CMakeLists.txt
add_subdirectory(logging) add_subdirectory(logging)
add_subdirectory(input_file) add_subdirectory(input_file)
add_subdirectory(ast)

51
tests/ast/CMakeLists.txt Normal file
View File

@ -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)

40
tests/ast/build_tree.c Normal file
View File

@ -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;
}

32
tests/ast/gen_graph.c Normal file
View File

@ -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;
}

View File

@ -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;
}

23
tests/ast/print_node.c Normal file
View File

@ -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;
}

126
tests/ast/test_ast.py Normal file
View File

@ -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)