initial commit

This commit is contained in:
2024-08-03 07:54:28 +01:00
commit 7eb0fc5581
26 changed files with 3418 additions and 0 deletions

58
.clang-format Normal file
View File

@@ -0,0 +1,58 @@
BasedOnStyle: WebKit
IndentWidth: 8
---
Language: Cpp
DerivePointerAlignment: false
PointerAlignment: Right
ColumnLimit: 80
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignEscapedNewlines: Right
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
ExperimentalAutoDetectBinPacking: false
BitFieldColonSpacing: Both
BreakBeforeBraces: Linux
BreakBeforeBinaryOperators: All
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
BreakStringLiterals: true
ContinuationIndentWidth: 8
Cpp11BracedListStyle: true
IncludeBlocks: Regroup
SortIncludes: true
IndentRequires: true
NamespaceIndentation: Inner
ReflowComments: true
SpacesBeforeTrailingComments: 3
TabWidth: 8
UseTab: AlignWithSpaces
PenaltyReturnTypeOnItsOwnLine: 1000000
PenaltyExcessCharacter: 5
PenaltyBreakOpenParenthesis: 5
PenaltyBreakBeforeFirstCallParameter: 5
PenaltyIndentedWhitespace: 0
AttributeMacros:
- BLUELIB_API
ForEachMacros:
- b_btree_foreach
- b_queue_foreach

169
.gitignore vendored Normal file
View File

@@ -0,0 +1,169 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,c,vim,linux,windows,git
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,c,vim,linux,windows,git
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/macos,c,vim,linux,windows,git
build/

22
CMakeLists.txt Normal file
View File

@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.25)
project(bluelib C)
set(b_modules core)
set(b_system_name ${CMAKE_SYSTEM_NAME})
string(TOLOWER ${b_system_name} b_system_name)
foreach (module ${b_modules})
add_subdirectory(${module})
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${module}-test)
message(STATUS "Building unit tests for module ${module}")
add_executable(blue-${module}-test
${module}-test/${module}-test.c
misc/AllTests.c
misc/CuTest.c
misc/CuTest.h)
target_link_libraries(blue-${module}-test blue-${module})
target_include_directories(blue-${module}-test PRIVATE misc/)
endif ()
endforeach (module)

32
LICENSE Normal file
View File

@@ -0,0 +1,32 @@
The Clear BSD License
Copyright (c) 2023 Max Wash
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the disclaimer
below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

0
README Normal file
View File

30
cmake/Templates.cmake Normal file
View File

@@ -0,0 +1,30 @@
macro(add_bluelib_module module_name)
file(GLOB sources *.c *.h)
file(GLOB sys_sources sys/${b_system_name}/*.c sys/${b_system_name}/*.h)
set(root_header include/blue/${module_name}.h)
file(GLOB headers include/blue/${module_name}/*.h)
add_library(blue-${module_name}-obj OBJECT ${sources} ${root_header} ${headers})
set_target_properties(blue-${module_name}-obj
PROPERTIES POSITION_INDEPENDENT_CODE ON)
string(REPLACE "-" "_" module_preproc_token ${module_name})
string(TOUPPER ${module_preproc_token} module_preproc_token)
set(module_preproc_token BLUELIB_${module_preproc_token})
target_include_directories(blue-${module_name}-obj PUBLIC include/)
target_compile_definitions(blue-${module_name}-obj PUBLIC ${module_preproc_token})
message(STATUS "Building module ${module_name} (shared)")
add_library(blue-${module_name} SHARED $<TARGET_OBJECTS:blue-${module_name}-obj>)
message(STATUS "Building module ${module_name} (static)")
add_library(blue-${module_name}-s STATIC $<TARGET_OBJECTS:blue-${module_name}-obj>)
target_include_directories(blue-${module_name} PUBLIC include/)
target_include_directories(blue-${module_name}-s PUBLIC include/)
install(TARGETS blue-${module_name} blue-${module_name}-s)
install(FILES ${root_header} DESTINATION include/blue)
install(FILES ${headers} DESTINATION include/blue/${module_name})
endmacro(add_bluelib_module)

183
core-test/core-test.c Normal file
View File

@@ -0,0 +1,183 @@
#include "blue/core/misc.h"
#include <CuTest.h>
#include <blue/core/btree.h>
#include <blue/core/queue.h>
#include <blue/core/stringstream.h>
#include <stdlib.h>
#include <time.h>
struct test_tree_node {
int value;
b_btree_node node;
};
struct test_queue_entry {
int value;
b_queue_entry entry;
};
B_BTREE_DEFINE_SIMPLE_INSERT(struct test_tree_node, node, value, test_tree_insert);
void test_btree_insert(CuTest *tc)
{
b_btree tree = {0};
struct test_tree_node nodes[3] = {0};
for (int i = 0; i < sizeof nodes / sizeof *nodes; i++) {
nodes[i].value = i;
}
test_tree_insert(&tree, &nodes[0]);
CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left);
CuAssertPtrEquals(tc, NULL, nodes[0].node.b_right);
CuAssertIntEquals(tc, 1, nodes[0].node.b_height);
test_tree_insert(&tree, &nodes[1]);
CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left);
CuAssertPtrEquals(tc, &nodes[1].node, nodes[0].node.b_right);
CuAssertIntEquals(tc, 2, nodes[0].node.b_height);
CuAssertPtrEquals(tc, NULL, nodes[1].node.b_left);
CuAssertPtrEquals(tc, NULL, nodes[1].node.b_right);
CuAssertIntEquals(tc, 1, nodes[1].node.b_height);
test_tree_insert(&tree, &nodes[2]);
CuAssertPtrEquals(tc, &nodes[0].node, nodes[1].node.b_left);
CuAssertPtrEquals(tc, &nodes[2].node, nodes[1].node.b_right);
CuAssertIntEquals(tc, 2, nodes[1].node.b_height);
CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left);
CuAssertPtrEquals(tc, NULL, nodes[0].node.b_right);
CuAssertIntEquals(tc, 1, nodes[0].node.b_height);
CuAssertPtrEquals(tc, NULL, nodes[2].node.b_left);
CuAssertPtrEquals(tc, NULL, nodes[2].node.b_right);
CuAssertIntEquals(tc, 1, nodes[2].node.b_height);
}
void test_btree_iterate(CuTest *tc)
{
static const size_t nr_nodes = 256;
srand(time(NULL));
b_btree tree = {0};
struct test_tree_node *nodes = calloc(nr_nodes, sizeof *nodes);
for (int i = 0; i < nr_nodes; i++) {
nodes[i].value = rand();
test_tree_insert(&tree, &nodes[i]);
}
int prev = -1;
b_btree_iterator it;
b_btree_foreach (&it, &tree) {
struct test_tree_node *node
= b_unbox(struct test_tree_node, it.node, node);
CuAssertPtrNotNull(tc, node);
if (prev == -1) {
prev = node->value;
continue;
}
CuAssertTrue(tc, prev < node->value);
prev = node->value;
}
free(nodes);
}
void test_queue_insert(CuTest *tc)
{
struct test_queue_entry entries[5] = {0};
for (int i = 0; i < sizeof entries / sizeof *entries; i++) {
entries[i].value = i;
}
b_queue q = B_QUEUE_INIT;
b_queue_push_back(&q, &entries[0].entry);
b_queue_push_back(&q, &entries[2].entry);
b_queue_push_back(&q, &entries[4].entry);
b_queue_insert_after(&q, &entries[3].entry, &entries[2].entry);
b_queue_insert_before(&q, &entries[1].entry, &entries[2].entry);
CuAssertPtrEquals(tc, NULL, entries[0].entry.qe_prev);
CuAssertPtrEquals(tc, &entries[1].entry, entries[0].entry.qe_next);
CuAssertPtrEquals(tc, &entries[0].entry, entries[1].entry.qe_prev);
CuAssertPtrEquals(tc, &entries[2].entry, entries[1].entry.qe_next);
CuAssertPtrEquals(tc, &entries[1].entry, entries[2].entry.qe_prev);
CuAssertPtrEquals(tc, &entries[3].entry, entries[2].entry.qe_next);
CuAssertPtrEquals(tc, &entries[2].entry, entries[3].entry.qe_prev);
CuAssertPtrEquals(tc, &entries[4].entry, entries[3].entry.qe_next);
CuAssertPtrEquals(tc, &entries[3].entry, entries[4].entry.qe_prev);
CuAssertPtrEquals(tc, NULL, entries[4].entry.qe_next);
}
void test_queue_iterate(CuTest *tc)
{
b_queue q = B_QUEUE_INIT;
struct test_queue_entry entries[32] = {0};
for (int i = 0; i < sizeof entries / sizeof *entries; i++) {
entries[i].value = i;
b_queue_push_back(&q, &entries[i].entry);
}
int prev = -1;
b_queue_iterator it;
b_queue_foreach (&it, &q) {
struct test_queue_entry *e
= b_unbox(struct test_queue_entry, it.entry, entry);
CuAssertPtrNotNull(tc, e);
if (prev == -1) {
prev = e->value;
continue;
}
CuAssertTrue(tc, prev < e->value);
prev = e->value;
}
}
void test_stringstream_1(CuTest *tc)
{
char buf[1024];
b_stringstream s;
b_stringstream_begin(&s, buf, sizeof buf);
b_stringstream_add(&s, "hello");
b_stringstream_addf(&s, "(%d + %.1f)", 32, 2.3);
const char *x[] = {"ABC", "DEF", NULL};
b_stringstream_addv(&s, x);
b_stringstream_addvl(&s, x, 2);
b_stringstream_add_many(&s, "more", "more", "more", NULL);
const char *end = b_stringstream_end(&s);
CuAssertStrEquals(tc, "hello(32 + 2.3)ABCDEFABCDEFmoremoremore", end);
}
CuSuite *get_all_tests(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_btree_insert);
SUITE_ADD_TEST(suite, test_btree_iterate);
SUITE_ADD_TEST(suite, test_queue_insert);
SUITE_ADD_TEST(suite, test_queue_iterate);
SUITE_ADD_TEST(suite, test_stringstream_1);
return suite;
}

3
core/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
include(../cmake/Templates.cmake)
add_bluelib_module(core)

821
core/btree.c Normal file
View File

@@ -0,0 +1,821 @@
/*
The Clear BSD License
Copyright (c) 2023 Max Wash
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the disclaimer
below) provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
*/
/* templated AVL binary tree implementation
this file implements an extensible AVL binary tree data structure.
the primary rule of an AVL binary tree is that for a given node N,
the heights of N's left and right subtrees can differ by at most 1.
the height of a subtree is the length of the longest path between
the root of the subtree and a leaf node, including the root node itself.
the height of a leaf node is 1.
when a node is inserted into or deleted from the tree, this rule may
be broken, in which the tree must be rotated to restore the balance.
no more than one rotation is required for any insert operations,
while multiple rotations may be required for a delete operation.
there are four types of rotations that can be applied to a tree:
- left rotation
- right rotation
- double left rotations
- double right rotations
by enforcing the balance rule, for a tree with n nodes, the worst-case
performance for insert, delete, and search operations is guaranteed
to be O(log n).
this file intentionally excludes any kind of search function implementation.
it is up to the programmer to implement their own tree node type
using b_btree_node, and their own search function using b_btree.
this allows the programmer to define their own node types with complex
non-integer key types. btree.h contains a number of macros to help
define these functions. the macros do all the work, you just have to
provide a comparator function.
*/
#include <blue/core/btree.h>
#include <stddef.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define IS_LEFT_CHILD(p, c) ((p) && (c) && ((p)->b_left == (c)))
#define IS_RIGHT_CHILD(p, c) ((p) && (c) && ((p)->b_right == (c)))
#define HAS_LEFT_CHILD(x) ((x) && ((x)->b_left))
#define HAS_RIGHT_CHILD(x) ((x) && ((x)->b_right))
#define HAS_NO_CHILDREN(x) ((x) && (!(x)->b_left) && (!(x)->b_right))
#define HAS_ONE_CHILD(x) \
((HAS_LEFT_CHILD(x) && !HAS_RIGHT_CHILD(x)) \
|| (!HAS_LEFT_CHILD(x) && HAS_RIGHT_CHILD(x)))
#define HAS_TWO_CHILDREN(x) (HAS_LEFT_CHILD(x) && HAS_RIGHT_CHILD(x))
#define HEIGHT(x) ((x) ? (x)->b_height : 0)
static inline void update_height(struct b_btree_node *x)
{
x->b_height = MAX(HEIGHT(x->b_left), HEIGHT((x->b_right))) + 1;
}
static inline int bf(struct b_btree_node *x)
{
int bf = 0;
if (!x) {
return bf;
}
if (x->b_right) {
bf += x->b_right->b_height;
}
if (x->b_left) {
bf -= x->b_left->b_height;
}
return bf;
}
/* perform a left rotation on a subtree
if you have a tree like this:
Z
/ \
X .
/ \
. Y
/ \
. .
and you perform a left rotation on node X,
you will get the following tree:
Z
/ \
Y .
/ \
X .
/ \
. .
note that this function does NOT update b_height for the rotated
nodes. it is up to you to call update_height_to_root().
*/
static void rotate_left(struct b_btree *tree, struct b_btree_node *x)
{
struct b_btree_node *y = x->b_right;
struct b_btree_node *p = x->b_parent;
if (y->b_left) {
y->b_left->b_parent = x;
}
x->b_right = y->b_left;
if (!p) {
tree->b_root = y;
} else if (x == p->b_left) {
p->b_left = y;
} else {
p->b_right = y;
}
x->b_parent = y;
y->b_left = x;
y->b_parent = p;
}
static void update_height_to_root(struct b_btree_node *x)
{
while (x) {
update_height(x);
x = x->b_parent;
}
}
/* perform a right rotation on a subtree
if you have a tree like this:
Z
/ \
. X
/ \
Y .
/ \
. .
and you perform a right rotation on node X,
you will get the following tree:
Z
/ \
. Y
/ \
. X
/ \
. .
note that this function does NOT update b_height for the rotated
nodes. it is up to you to call update_height_to_root().
*/
static void rotate_right(struct b_btree *tree, struct b_btree_node *y)
{
struct b_btree_node *x = y->b_left;
struct b_btree_node *p = y->b_parent;
if (x->b_right) {
x->b_right->b_parent = y;
}
y->b_left = x->b_right;
if (!p) {
tree->b_root = x;
} else if (y == p->b_left) {
p->b_left = x;
} else {
p->b_right = x;
}
y->b_parent = x;
x->b_right = y;
x->b_parent = p;
}
/* for a given node Z, perform a right rotation on Z's right child,
followed by a left rotation on Z itself.
if you have a tree like this:
Z
/ \
. X
/ \
Y .
/ \
. .
and you perform a double-left rotation on node Z,
you will get the following tree:
Y
/ \
/ \
Z X
/ \ / \
. . . .
note that, unlike rotate_left and rotate_right, this function
DOES update b_height for the rotated nodes (since it needs to be
done in a certain order).
*/
static void rotate_double_left(struct b_btree *tree, struct b_btree_node *z)
{
struct b_btree_node *x = z->b_right;
struct b_btree_node *y = x->b_left;
rotate_right(tree, x);
rotate_left(tree, z);
update_height(z);
update_height(x);
while (y) {
update_height(y);
y = y->b_parent;
}
}
/* for a given node Z, perform a left rotation on Z's left child,
followed by a right rotation on Z itself.
if you have a tree like this:
Z
/ \
X .
/ \
. Y
/ \
. .
and you perform a double-right rotation on node Z,
you will get the following tree:
Y
/ \
/ \
X Z
/ \ / \
. . . .
note that, unlike rotate_left and rotate_right, this function
DOES update b_height for the rotated nodes (since it needs to be
done in a certain order).
*/
static void rotate_double_right(struct b_btree *tree, struct b_btree_node *z)
{
struct b_btree_node *x = z->b_left;
struct b_btree_node *y = x->b_right;
rotate_left(tree, x);
rotate_right(tree, z);
update_height(z);
update_height(x);
while (y) {
update_height(y);
y = y->b_parent;
}
}
/* run after an insert operation. checks that the balance factor
of the local subtree is within the range -1 <= BF <= 1. if it
is not, rotate the subtree to restore balance.
note that at most one rotation should be required after a node
is inserted into the tree.
this function depends on all nodes in the tree having
correct b_height values.
@param w the node that was just inserted into the tree
*/
static void insert_fixup(struct b_btree *tree, struct b_btree_node *w)
{
struct b_btree_node *z = NULL, *y = NULL, *x = NULL;
z = w;
while (z) {
if (bf(z) >= -1 && bf(z) <= 1) {
goto next_ancestor;
}
if (IS_LEFT_CHILD(z, y)) {
if (IS_LEFT_CHILD(y, x)) {
rotate_right(tree, z);
update_height_to_root(z);
} else {
rotate_double_right(tree, z);
}
} else {
if (IS_LEFT_CHILD(y, x)) {
rotate_double_left(tree, z);
} else {
rotate_left(tree, z);
update_height_to_root(z);
}
}
next_ancestor:
x = y;
y = z;
z = z->b_parent;
}
}
/* run after a delete operation. checks that the balance factor
of the local subtree is within the range -1 <= BF <= 1. if it
is not, rotate the subtree to restore balance.
note that, unlike insert_fixup, multiple rotations may be required
to restore balance after a node is deleted.
this function depends on all nodes in the tree having
correct b_height values.
@param w one of the following:
- the parent of the node that was deleted if the node
had no children.
- the parent of the node that replaced the deleted node
if the deleted node had two children.
- the node that replaced the node that was deleted, if
the node that was deleted had one child.
*/
static void delete_fixup(struct b_btree *tree, struct b_btree_node *w)
{
struct b_btree_node *z = w;
while (z) {
if (bf(z) > 1) {
if (bf(z->b_right) >= 0) {
rotate_left(tree, z);
update_height_to_root(z);
} else {
rotate_double_left(tree, z);
}
} else if (bf(z) < -1) {
if (bf(z->b_left) <= 0) {
rotate_right(tree, z);
update_height_to_root(z);
} else {
rotate_double_right(tree, z);
}
}
z = z->b_parent;
}
}
/* updates b_height for all nodes between the inserted node and the root
of the tree, and calls insert_fixup.
@param node the node that was just inserted into the tree.
*/
void b_btree_insert_fixup(struct b_btree *tree, struct b_btree_node *node)
{
node->b_height = 0;
struct b_btree_node *cur = node;
while (cur) {
update_height(cur);
cur = cur->b_parent;
}
insert_fixup(tree, node);
}
/* remove a node from a tree.
this function assumes that `node` has no children, and therefore
doesn't need to be replaced.
updates b_height for all nodes between `node` and the tree root.
@param node the node to delete.
*/
static struct b_btree_node *remove_node_with_no_children(
struct b_btree *tree,
struct b_btree_node *node)
{
struct b_btree_node *w = node->b_parent;
struct b_btree_node *p = node->b_parent;
node->b_parent = NULL;
if (!p) {
tree->b_root = NULL;
} else if (IS_LEFT_CHILD(p, node)) {
p->b_left = NULL;
} else {
p->b_right = NULL;
}
while (p) {
update_height(p);
p = p->b_parent;
}
return w;
}
/* remove a node from a tree.
this function assumes that `node` has one child.
the child of `node` is inherited by `node`'s parent, and `node` is removed.
updates b_height for all nodes between the node that replaced
`node` and the tree root.
@param node the node to delete.
*/
static struct b_btree_node *replace_node_with_one_subtree(
struct b_btree *tree,
struct b_btree_node *node)
{
struct b_btree_node *p = node->b_parent;
struct b_btree_node *z = NULL;
if (HAS_LEFT_CHILD(node)) {
z = node->b_left;
} else {
z = node->b_right;
}
struct b_btree_node *w = z;
if (!p) {
tree->b_root = z;
} else if (IS_LEFT_CHILD(p, node)) {
p->b_left = z;
} else if (IS_RIGHT_CHILD(p, node)) {
p->b_right = z;
}
z->b_parent = p;
node->b_parent = NULL;
node->b_left = node->b_right = NULL;
while (z) {
update_height(z);
z = z->b_parent;
}
return w;
}
/* remove a node from a tree.
this function assumes that `node` has two children.
find the in-order successor Y of `node` (the largest node in `node`'s left
sub-tree), removes `node` from the tree and moves Y to where `node` used to
be.
if Y has a child (it will never have more than one), have Y's parent inherit
Y's child.
updates b_height for all nodes between the deepest node that was modified
and the tree root.
@param z the node to delete.
*/
static struct b_btree_node *replace_node_with_two_subtrees(
struct b_btree *tree,
struct b_btree_node *z)
{
/* x will replace z */
struct b_btree_node *x = z->b_left;
while (x->b_right) {
x = x->b_right;
}
/* y is the node that will replace x (if x has a left child) */
struct b_btree_node *y = x->b_left;
/* w is the starting point for the height update and fixup */
struct b_btree_node *w = x;
if (w->b_parent != z) {
w = w->b_parent;
}
if (y) {
w = y;
}
if (IS_LEFT_CHILD(x->b_parent, x)) {
x->b_parent->b_left = y;
} else if (IS_RIGHT_CHILD(x->b_parent, x)) {
x->b_parent->b_right = y;
}
if (y) {
y->b_parent = x->b_parent;
}
if (IS_LEFT_CHILD(z->b_parent, z)) {
z->b_parent->b_left = x;
} else if (IS_RIGHT_CHILD(z->b_parent, z)) {
z->b_parent->b_right = x;
}
x->b_parent = z->b_parent;
x->b_left = z->b_left;
x->b_right = z->b_right;
if (x->b_left) {
x->b_left->b_parent = x;
}
if (x->b_right) {
x->b_right->b_parent = x;
}
if (!x->b_parent) {
tree->b_root = x;
}
struct b_btree_node *cur = w;
while (cur) {
update_height(cur);
cur = cur->b_parent;
}
return w;
}
/* delete a node from the tree and re-balance it afterwards */
void b_btree_delete(struct b_btree *tree, struct b_btree_node *node)
{
struct b_btree_node *w = NULL;
if (HAS_NO_CHILDREN(node)) {
w = remove_node_with_no_children(tree, node);
} else if (HAS_ONE_CHILD(node)) {
w = replace_node_with_one_subtree(tree, node);
} else if (HAS_TWO_CHILDREN(node)) {
w = replace_node_with_two_subtrees(tree, node);
}
if (w) {
delete_fixup(tree, w);
}
node->b_left = node->b_right = node->b_parent = NULL;
}
static struct b_btree_node *first_node(const struct b_btree *tree, int *depth)
{
/* the first node in the tree is the node with the smallest key.
we keep moving left until we can't go any further */
struct b_btree_node *cur = tree->b_root;
int d = 0;
if (!cur) {
*depth = 0;
return NULL;
}
while (cur->b_left) {
d++;
cur = cur->b_left;
}
*depth = d;
return cur;
}
struct b_btree_node *b_btree_first(const struct b_btree *tree)
{
int d;
return first_node(tree, &d);
}
static struct b_btree_node *last_node(const struct b_btree *tree, int *depth)
{
/* the first node in the tree is the node with the largest key.
we keep moving right until we can't go any further */
struct b_btree_node *cur = tree->b_root;
int d = 0;
if (!cur) {
return NULL;
}
while (cur->b_right) {
d++;
cur = cur->b_right;
}
*depth = d;
return cur;
}
b_btree_node *b_btree_last(const struct b_btree *tree)
{
int d;
return last_node(tree, &d);
}
static b_btree_node *next_node(const struct b_btree_node *node, int *depth_diff)
{
if (!node) {
return NULL;
}
int depth = 0;
/* there are two possibilities for the next node:
1. if `node` has a right sub-tree, every node in this sub-tree is
bigger than node. the in-order successor of `node` is the smallest
node in this subtree.
2. if `node` has no right sub-tree, we've reached the largest node in
the sub-tree rooted at `node`. we need to go back to our parent
and continue the search elsewhere.
*/
if (node->b_right) {
/* case 1: step into `node`'s right sub-tree and keep going
left to find the smallest node */
struct b_btree_node *cur = node->b_right;
depth++;
while (cur->b_left) {
cur = cur->b_left;
depth++;
}
*depth_diff = depth;
return cur;
}
/* case 2: keep stepping back up towards the root of the tree.
if we encounter a step where we are our parent's left child,
we've found a parent with a value larger than us. this parent
is the in-order successor of `node` */
while (node->b_parent && node->b_parent->b_left != node) {
node = node->b_parent;
depth--;
}
*depth_diff = depth - 1;
return node->b_parent;
}
static b_btree_node *prev_node(const struct b_btree_node *node, int *depth_diff)
{
if (!node) {
return NULL;
}
int depth = 0;
/* there are two possibilities for the previous node:
1. if `node` has a left sub-tree, every node in this sub-tree is
smaller than `node`. the in-order predecessor of `node` is the
largest node in this subtree.
2. if `node` has no left sub-tree, we've reached the smallest node in
the sub-tree rooted at `node`. we need to go back to our parent
and continue the search elsewhere.
*/
if (node->b_left) {
/* case 1: step into `node`'s left sub-tree and keep going
right to find the largest node */
b_btree_node *cur = node->b_left;
depth++;
while (cur->b_right) {
cur = cur->b_right;
depth++;
}
*depth_diff = depth;
return cur;
}
/* case 2: keep stepping back up towards the root of the tree.
if we encounter a step where we are our parent's right child,
we've found a parent with a value smaller than us. this parent
is the in-order predecessor of `node`. */
while (node->b_parent && node->b_parent->b_right != node) {
node = node->b_parent;
depth--;
}
*depth_diff = depth - 1;
return node->b_parent;
}
b_btree_node *b_btree_next(const struct b_btree_node *node)
{
int d;
return next_node(node, &d);
}
b_btree_node *b_btree_prev(const struct b_btree_node *node)
{
int d;
return prev_node(node, &d);
}
static bool btree_iterator_next(struct b_iterator *it)
{
return b_btree_iterator_next((struct b_btree_iterator *)it);
}
static b_status btree_iterator_erase(struct b_iterator *it)
{
return b_btree_iterator_erase((struct b_btree_iterator *)it);
}
static bool btree_iterator_is_valid(const struct b_iterator *it)
{
return b_btree_iterator_is_valid((struct b_btree_iterator *)it);
}
static const b_iterator_ops btree_iterator_ops = {
.it_next = btree_iterator_next,
.it_erase = btree_iterator_erase,
.it_close = NULL,
.it_is_valid = btree_iterator_is_valid,
};
int b_btree_iterator_begin(
const struct b_btree *tree,
struct b_btree_iterator *it)
{
int depth = 0;
it->_b = (struct b_btree *)tree;
it->i = 0;
it->node = first_node(tree, &depth);
it->depth = depth;
it->_base.it_ops = &btree_iterator_ops;
return 0;
}
bool b_btree_iterator_next(struct b_btree_iterator *it)
{
int depth_diff = 0;
struct b_btree_node *next = next_node(it->node, &depth_diff);
if (!next) {
it->node = NULL;
it->depth = 0;
it->i++;
return false;
}
it->node = next;
it->i++;
it->depth += depth_diff;
return true;
}
b_status b_btree_iterator_erase(struct b_btree_iterator *it)
{
if (!it->node) {
return B_ERR_OUT_OF_BOUNDS;
}
int depth_diff = 0;
struct b_btree_node *next = next_node(it->node, &depth_diff);
b_btree_delete(it->_b, it->node);
if (!next) {
it->node = NULL;
it->depth = 0;
} else {
it->node = next;
it->depth = 0;
struct b_btree_node *cur = next->b_parent;
while (cur) {
it->depth++;
cur = cur->b_parent;
}
}
return B_SUCCESS;
}
bool b_btree_iterator_is_valid(const struct b_btree_iterator *it)
{
return it->node != NULL;
}

0
core/include/blue/core.h Normal file
View File

View File

@@ -0,0 +1,357 @@
#ifndef BLUELIB_CORE_BTREE_H_
#define BLUELIB_CORE_BTREE_H_
#include <blue/core/iterator.h>
#include <blue/core/misc.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* defines a simple node insertion function.
this function assumes that your nodes have simple integer keys that can be
compared with the usual operators.
EXAMPLE:
if you have a tree node type like this:
struct my_tree_node {
int key;
b_btree_node base;
}
You would use the following call to generate an insert function for a tree
with this node type:
BTREE_DEFINE_SIMPLE_INSERT(
struct my_tree_node,
base,
key,
my_tree_node_insert);
Which would emit a function defined like:
static void my_tree_node_insert(b_btree *tree, struct my_tree_node *node);
@param node_type your custom tree node type. usually a structure that
contains a b_btree_node member.
@param container_node_member the name of the b_btree_node member variable
within your custom type.
@param container_key_member the name of the key member variable within your
custom type.
@param function_name the name of the function to generate.
*/
#define B_BTREE_DEFINE_SIMPLE_INSERT( \
node_type, container_node_member, container_key_member, function_name) \
void function_name(b_btree *tree, node_type *node) \
{ \
if (!tree->b_root) { \
tree->b_root = &node->container_node_member; \
b_btree_insert_fixup(tree, &node->container_node_member); \
return; \
} \
\
b_btree_node *cur = tree->b_root; \
while (1) { \
node_type *cur_node = b_unbox( \
node_type, cur, container_node_member); \
b_btree_node *next = NULL; \
\
if (node->container_key_member \
>= cur_node->container_key_member) { \
next = b_btree_right(cur); \
\
if (!next) { \
b_btree_put_right( \
cur, \
&node->container_node_member); \
break; \
} \
} else if ( \
node->container_key_member \
< cur_node->container_key_member) { \
next = b_btree_left(cur); \
\
if (!next) { \
b_btree_put_left( \
cur, \
&node->container_node_member); \
break; \
} \
} \
\
cur = next; \
} \
\
b_btree_insert_fixup(tree, &node->container_node_member); \
}
/* defines a node insertion function.
this function should be used for trees with complex node keys that cannot be
directly compared. a comparator for your keys must be supplied.
EXAMPLE:
if you have a tree node type like this:
struct my_tree_node {
complex_key_t key;
b_btree_node base;
}
You would need to define a comparator function or macro with the following
signature:
int my_comparator(struct my_tree_node *a, struct my_tree_node *b);
Which implements the following:
return -1 if a < b
return 0 if a == b
return 1 if a > b
You would use the following call to generate an insert function for a tree
with this node type:
BTREE_DEFINE_INSERT(struct my_tree_node, base, key, my_tree_node_insert,
my_comparator);
Which would emit a function defined like:
static void my_tree_node_insert(b_btree *tree, struct my_tree_node *node);
@param node_type your custom tree node type. usually a structure that
contains a b_btree_node member.
@param container_node_member the name of the b_btree_node member variable
within your custom type.
@param container_key_member the name of the key member variable within your
custom type.
@param function_name the name of the function to generate.
@param comparator the name of a comparator function or functional-macro that
conforms to the requirements listed above.
*/
#define B_BTREE_DEFINE_INSERT( \
node_type, container_node_member, container_key_member, function_name, \
comparator) \
void function_name(b_btree *tree, node_type *node) \
{ \
if (!tree->b_root) { \
tree->b_root = &node->container_node_member; \
b_btree_insert_fixup(tree, &node->container_node_member); \
return; \
} \
\
b_btree_node *cur = tree->b_root; \
while (1) { \
node_type *cur_node = b_unbox( \
node_type, cur, container_node_member); \
b_btree_node *next = NULL; \
int cmp = comparator(node, cur_node); \
\
if (cmp >= 0) { \
next = b_btree_right(cur); \
\
if (!next) { \
b_btree_put_right( \
cur, \
&node->container_node_member); \
break; \
} \
} else if (cmp == -1) { \
next = b_btree_left(cur); \
\
if (!next) { \
b_btree_put_left( \
cur, \
&node->container_node_member); \
break; \
} \
} else { \
return; \
} \
\
cur = next; \
} \
\
b_btree_insert_fixup(tree, &node->container_node_member); \
}
/* defines a simple tree search function.
this function assumes that your nodes have simple integer keys that can be
compared with the usual operators.
EXAMPLE:
if you have a tree node type like this:
struct my_tree_node {
int key;
b_btree_node base;
}
You would use the following call to generate a search function for a tree
with this node type:
BTREE_DEFINE_SIMPLE_GET(struct my_tree_node, int, base, key,
my_tree_node_get);
Which would emit a function defined like:
static struct my_tree_node *my_tree_node_get(b_btree *tree, int key);
@param node_type your custom tree node type. usually a structure that
contains a b_btree_node member.
@param key_type the type name of the key embedded in your custom tree node
type. this type must be compatible with the builtin comparison operators.
@param container_node_member the name of the b_btree_node member variable
within your custom type.
@param container_key_member the name of the key member variable within your
custom type.
@param function_name the name of the function to generate.
*/
#define B_BTREE_DEFINE_SIMPLE_GET( \
node_type, key_type, container_node_member, container_key_member, \
function_name) \
node_type *function_name(const b_btree *tree, key_type key) \
{ \
b_btree_node *cur = tree->b_root; \
while (cur) { \
node_type *cur_node = b_unbox( \
node_type, cur, container_node_member); \
if (key > cur_node->container_key_member) { \
cur = b_btree_right(cur); \
} else if (key < cur_node->container_key_member) { \
cur = b_btree_left(cur); \
} else { \
return cur_node; \
} \
} \
\
return NULL; \
}
#define b_btree_foreach(it, btree) \
for (int z__b_unique_name() = b_btree_iterator_begin(btree, it); \
(it)->node != NULL; b_btree_iterator_next(it))
/* binary tree nodes. this *cannot* be used directly. you need to define a
custom node type that contains a member variable of type b_btree_node.
you would then use the supplied macros to define functions to manipulate your
custom binary tree.
*/
typedef struct b_btree_node {
struct b_btree_node *b_parent, *b_left, *b_right;
unsigned short b_height;
} b_btree_node;
/* binary tree. unlike b_btree_node, you can define variables of type b_btree.
*/
typedef struct b_btree {
b_btree_node *b_root;
} b_btree;
typedef struct b_btree_iterator {
b_iterator _base;
size_t i, depth;
b_btree_node *node;
b_btree *_b;
} b_btree_iterator;
/* re-balance a binary tree after an insertion operation.
NOTE that, if you define an insertion function using BTREE_DEFINE_INSERT or
similar, this function will automatically called for you.
@param tree the tree to re-balance.
@param node the node that was just inserted into the tree.
*/
extern void b_btree_insert_fixup(b_btree *tree, b_btree_node *node);
/* delete a node from a binary tree and re-balance the tree afterwards.
@param tree the tree to delete from
@param node the node to delete.
*/
extern void b_btree_delete(b_btree *tree, b_btree_node *node);
/* get the first node in a binary tree.
this will be the node with the smallest key (i.e. the node that is
furthest-left from the root)
*/
extern b_btree_node *b_btree_first(const b_btree *tree);
/* get the last node in a binary tree.
this will be the node with the largest key (i.e. the node that is
furthest-right from the root)
*/
extern b_btree_node *b_btree_last(const b_btree *tree);
/* for any binary tree node, this function returns the node with the
* next-largest key value */
extern b_btree_node *b_btree_next(const b_btree_node *node);
/* for any binary tree node, this function returns the node with the
* next-smallest key value */
extern b_btree_node *b_btree_prev(const b_btree_node *node);
/* return true if the btree is empty, false otherwise */
static inline bool b_btree_empty(const b_btree *tree)
{
return tree->b_root == NULL;
}
/* sets `child` as the immediate left-child of `parent` */
static inline void b_btree_put_left(b_btree_node *parent, b_btree_node *child)
{
parent->b_left = child;
child->b_parent = parent;
}
/* sets `child` as the immediate right-child of `parent` */
static inline void b_btree_put_right(b_btree_node *parent, b_btree_node *child)
{
parent->b_right = child;
child->b_parent = parent;
}
/* get the immediate left-child of `node` */
static inline b_btree_node *b_btree_left(b_btree_node *node)
{
return node->b_left;
}
/* get the immediate right-child of `node` */
static inline b_btree_node *b_btree_right(b_btree_node *node)
{
return node->b_right;
}
/* get the immediate parent of `node` */
static inline b_btree_node *b_btree_parent(b_btree_node *node)
{
return node->b_parent;
}
/* get the height of `node`.
the height of a node is defined as the length of the longest path
between the node and a leaf node.
this count includes the node itself, so the height of a leaf node will be 1.
*/
static inline unsigned short b_btree_height(b_btree_node *node)
{
return node->b_height;
}
extern int b_btree_iterator_begin(const b_btree *tree, b_btree_iterator *it);
extern bool b_btree_iterator_next(b_btree_iterator *it);
extern b_status b_btree_iterator_erase(b_btree_iterator *it);
extern bool b_btree_iterator_is_valid(const b_btree_iterator *it);
#ifdef __cplusplus
}
#endif
#endif

View File

View File

@@ -0,0 +1,25 @@
#ifndef BLUELIB_CORE_ITERATOR_H_
#define BLUELIB_CORE_ITERATOR_H_
#include <blue/core/status.h>
#include <stdbool.h>
struct b_iterator;
typedef struct b_iterator_ops {
b_status (*it_close)(struct b_iterator *);
bool (*it_next)(struct b_iterator *);
b_status (*it_erase)(struct b_iterator *);
bool (*it_is_valid)(const struct b_iterator *);
} b_iterator_ops;
typedef struct b_iterator {
const b_iterator_ops *it_ops;
} b_iterator;
extern b_status b_iterator_close(b_iterator *it);
extern bool b_iterator_next(b_iterator *it);
extern b_status b_iterator_erase(b_iterator *it);
extern bool b_iterator_is_valid(const b_iterator *it);
#endif

View File

@@ -0,0 +1,13 @@
#ifndef BLUELIB_MISC_H_
#define BLUELIB_MISC_H_
#define b_unbox(type, box, member) \
((type *_Nonnull)((box) ? (uintptr_t)(box) - (offsetof(type, member)) : 0))
#define z__b_merge_(a, b) a##b
#define z__b_label_(a) z__b_merge_(__unique_name_, a)
#define z__b_unique_name() z__b_label_(__LINE__)
#define z__b_numargs(arg_type, ...) \
(sizeof((arg_type[]) {__VA_ARGS__}) / sizeof(arg_type))
#endif // C_MISC_H_

View File

@@ -0,0 +1,88 @@
#ifndef BLUELIB_CORE_QUEUE_H_
#define BLUELIB_CORE_QUEUE_H_
#include <blue/core/iterator.h>
#include <blue/core/status.h>
#include <stdbool.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
#define B_QUEUE_INIT ((b_queue) {.q_first = NULL, .q_last = NULL})
#define B_QUEUE_ENTRY_INIT ((b_queue_entry) {.qe_next = NULL, .qe_prev = NULL})
#define b_queue_foreach(it, q) \
for (int z__b_unique_name() = b_queue_iterator_begin(q, it); \
(it)->entry != NULL; b_queue_iterator_next(it))
typedef struct b_queue_entry {
struct b_queue_entry *qe_next;
struct b_queue_entry *qe_prev;
} b_queue_entry;
typedef struct b_queue {
b_queue_entry *q_first;
b_queue_entry *q_last;
} b_queue;
typedef struct b_queue_iterator {
b_iterator _base;
size_t i;
b_queue_entry *entry;
b_queue *_q;
} b_queue_iterator;
static inline void b_queue_init(b_queue *q)
{
memset(q, 0x00, sizeof *q);
}
static inline bool b_queue_empty(b_queue *q)
{
return q->q_first == NULL;
}
static inline b_queue_entry *b_queue_first(const b_queue *q)
{
return q->q_first;
}
static inline b_queue_entry *b_queue_last(const b_queue *q)
{
return q->q_last;
}
static inline b_queue_entry *b_queue_next(const b_queue_entry *entry)
{
return entry->qe_next;
}
static inline b_queue_entry *b_queue_prev(const b_queue_entry *entry)
{
return entry->qe_prev;
}
extern size_t b_queue_length(b_queue *q);
extern void b_queue_insert_before(
b_queue *q, b_queue_entry *entry, b_queue_entry *before);
extern void b_queue_insert_after(
b_queue *q, b_queue_entry *entry, b_queue_entry *after);
extern void b_queue_push_front(b_queue *q, b_queue_entry *entry);
extern void b_queue_push_back(b_queue *q, b_queue_entry *entry);
extern b_queue_entry *b_queue_pop_front(b_queue *q);
extern b_queue_entry *b_queue_pop_back(b_queue *q);
extern void b_queue_delete(b_queue *q, b_queue_entry *entry);
extern void b_queue_delete_all(b_queue *q);
extern int b_queue_iterator_begin(const b_queue *q, b_queue_iterator *it);
extern bool b_queue_iterator_next(b_queue_iterator *it);
extern b_status b_queue_iterator_erase(b_queue_iterator *it);
extern bool b_queue_iterator_is_valid(const b_queue_iterator *it);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,23 @@
#ifndef BLUELIB_CORE_STATUS_H_
#define BLUELIB_CORE_STATUS_H_
#define B_OK(status) ((status) == B_SUCCESS)
#define B_ERR(status) ((status) != B_SUCCESS)
typedef enum {
B_SUCCESS = 0x00u,
B_ERR_NO_MEMORY,
B_ERR_OUT_OF_BOUNDS,
B_ERR_INVALID_ARGUMENT,
B_ERR_NAME_EXISTS,
B_ERR_NOT_SUPPORTED,
B_ERR_BAD_STATE,
B_ERR_NO_ENTRY,
B_ERR_NO_DATA,
B_ERR_UNKNOWN_FUNCTION,
B_ERR_BAD_FORMAT,
} b_status;
extern const char *b_status_to_string(b_status status);
#endif

View File

@@ -0,0 +1,30 @@
#ifndef BLUELIB_CORE_STRINGSTREAM_H_
#define BLUELIB_CORE_STRINGSTREAM_H_
#include <blue/core/status.h>
#include <stddef.h>
typedef struct b_stringstream {
char *ss_buf;
size_t ss_len;
size_t ss_max;
unsigned char ss_alloc;
} b_stringstream;
extern void b_stringstream_begin(b_stringstream *strv, char *buf, size_t max);
extern void b_stringstream_begin_dynamic(b_stringstream *strv);
extern b_status b_stringstream_add(b_stringstream *strv, const char *str);
extern b_status b_stringstream_addf(
b_stringstream *strv,
const char *format,
...);
extern b_status b_stringstream_addv(b_stringstream *strv, const char **strs);
extern b_status b_stringstream_addvl(
b_stringstream *strv,
const char **strs,
size_t count);
extern b_status b_stringstream_add_many(b_stringstream *strv, ...);
extern char *b_stringstream_end(b_stringstream *strv);
#endif

37
core/iterator.c Normal file
View File

@@ -0,0 +1,37 @@
#include <blue/core/iterator.h>
b_status b_iterator_cleanup(struct b_iterator *it)
{
if (it->it_ops && it->it_ops->it_close) {
return it->it_ops->it_close(it);
}
return B_SUCCESS;
}
bool b_iterator_next(struct b_iterator *it)
{
if (it->it_ops && it->it_ops->it_next) {
return it->it_ops->it_next(it);
}
return false;
}
b_status b_iterator_erase(struct b_iterator *it)
{
if (it->it_ops && it->it_ops->it_erase) {
return it->it_ops->it_erase(it);
}
return B_ERR_NOT_SUPPORTED;
}
bool b_iterator_is_valid(const struct b_iterator *it)
{
if (it->it_ops && it->it_ops->it_is_valid) {
return it->it_ops->it_is_valid(it);
}
return false;
}

196
core/queue.c Normal file
View File

@@ -0,0 +1,196 @@
#include <blue/core/queue.h>
size_t b_queue_length(struct b_queue *q)
{
size_t i = 0;
struct b_queue_entry *x = q->q_first;
while (x) {
i++;
x = x->qe_next;
}
return i;
}
void b_queue_insert_before(
struct b_queue *q, struct b_queue_entry *entry, struct b_queue_entry *before)
{
struct b_queue_entry *x = before->qe_prev;
if (x) {
x->qe_next = entry;
} else {
q->q_first = entry;
}
entry->qe_prev = x;
before->qe_prev = entry;
entry->qe_next = before;
}
void b_queue_insert_after(
struct b_queue *q, struct b_queue_entry *entry, struct b_queue_entry *after)
{
struct b_queue_entry *x = after->qe_next;
if (x) {
x->qe_prev = entry;
} else {
q->q_last = entry;
}
entry->qe_next = x;
after->qe_next = entry;
entry->qe_prev = after;
}
void b_queue_push_front(struct b_queue *q, struct b_queue_entry *entry)
{
if (q->q_first) {
q->q_first->qe_prev = entry;
}
entry->qe_next = q->q_first;
entry->qe_prev = NULL;
q->q_first = entry;
if (!q->q_last) {
q->q_last = entry;
}
}
void b_queue_push_back(struct b_queue *q, struct b_queue_entry *entry)
{
if (q->q_last) {
q->q_last->qe_next = entry;
}
entry->qe_prev = q->q_last;
entry->qe_next = NULL;
q->q_last = entry;
if (!q->q_first) {
q->q_first = entry;
}
}
struct b_queue_entry *b_queue_pop_front(struct b_queue *q)
{
struct b_queue_entry *x = q->q_first;
if (x) {
b_queue_delete(q, x);
}
return x;
}
struct b_queue_entry *b_queue_pop_back(struct b_queue *q)
{
struct b_queue_entry *x = q->q_last;
if (x) {
b_queue_delete(q, x);
}
return x;
}
void b_queue_delete(struct b_queue *q, struct b_queue_entry *entry)
{
if (!entry) {
return;
}
if (entry == q->q_first) {
q->q_first = q->q_first->qe_next;
}
if (entry == q->q_last) {
q->q_last = q->q_last->qe_prev;
}
if (entry->qe_next) {
entry->qe_next->qe_prev = entry->qe_prev;
}
if (entry->qe_prev) {
entry->qe_prev->qe_next = entry->qe_next;
}
entry->qe_next = entry->qe_prev = NULL;
}
void b_queue_delete_all(struct b_queue *q)
{
struct b_queue_entry *x = q->q_first;
while (x) {
struct b_queue_entry *next = x->qe_next;
x->qe_next = x->qe_prev = NULL;
x = next;
}
q->q_first = q->q_last = NULL;
}
static bool queue_iterator_next(struct b_iterator *it)
{
return b_queue_iterator_next((struct b_queue_iterator *)it);
}
static b_status queue_iterator_erase(struct b_iterator *it)
{
return b_queue_iterator_erase((struct b_queue_iterator *)it);
}
static bool queue_iterator_is_valid(const struct b_iterator *it)
{
return b_queue_iterator_is_valid((const struct b_queue_iterator *)it);
}
static const b_iterator_ops queue_iterator_ops = {
.it_next = queue_iterator_next,
.it_erase = queue_iterator_erase,
.it_close = NULL,
.it_is_valid = queue_iterator_is_valid,
};
int b_queue_iterator_begin(const struct b_queue *q, struct b_queue_iterator *it)
{
it->_q = (struct b_queue *)q;
it->_base.it_ops = &queue_iterator_ops;
it->entry = q->q_first;
it->i = 0;
return 0;
}
bool b_queue_iterator_next(struct b_queue_iterator *it)
{
if (!it->entry) {
return false;
}
it->entry = it->entry->qe_next;
it->i++;
return it->entry != NULL;
}
b_status b_queue_iterator_erase(struct b_queue_iterator *it)
{
if (!it->entry) {
return B_ERR_OUT_OF_BOUNDS;
}
struct b_queue_entry *next = it->entry->qe_next;
b_queue_delete(it->_q, it->entry);
it->entry = next;
return B_SUCCESS;
}
bool b_queue_iterator_is_valid(const struct b_queue_iterator *it)
{
return it->entry != NULL;
}

15
core/status.c Normal file
View File

@@ -0,0 +1,15 @@
#include <blue/core/status.h>
#include <stddef.h>
#define ENUM_STR(s) \
case s: \
return #s;
const char *b_status_to_string(b_status status)
{
switch (status) {
ENUM_STR(B_SUCCESS);
default:
return NULL;
}
}

118
core/stringstream.c Normal file
View File

@@ -0,0 +1,118 @@
#include <blue/core/stringstream.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void b_stringstream_begin(b_stringstream *ss, char *buf, size_t max)
{
ss->ss_buf = buf;
ss->ss_max = max;
ss->ss_len = 0;
ss->ss_alloc = 0;
}
void b_stringstream_begin_dynamic(b_stringstream *ss)
{
ss->ss_buf = NULL;
ss->ss_max = 0;
ss->ss_len = 0;
ss->ss_alloc = 1;
}
static b_status ss_builder_push_string(b_stringstream *ss, const char *s, size_t len)
{
if (ss->ss_len + len >= ss->ss_max && ss->ss_alloc == 1) {
char *new_buf = realloc(ss->ss_buf, ss->ss_len + len + 1);
if (!new_buf) {
return B_ERR_NO_MEMORY;
}
ss->ss_buf = new_buf;
ss->ss_max = ss->ss_len + len + 1;
}
for (size_t i = 0; i < len; i++) {
if (ss->ss_len < ss->ss_max) {
ss->ss_buf[ss->ss_len++] = s[i];
ss->ss_buf[ss->ss_len] = 0;
}
}
return B_SUCCESS;
}
b_status b_stringstream_add(struct b_stringstream *ss, const char *str)
{
return ss_builder_push_string(ss, str, strlen(str));
}
b_status b_stringstream_addf(struct b_stringstream *ss, const char *format, ...)
{
char str[1024];
va_list arg;
va_start(arg, format);
size_t len = vsnprintf(str, sizeof str, format, arg);
va_end(arg);
return ss_builder_push_string(ss, str, len);
}
b_status b_stringstream_addv(b_stringstream *ss, const char **strs)
{
for (size_t i = 0; strs[i]; i++) {
size_t len = strlen(strs[i]);
b_status status = ss_builder_push_string(ss, strs[i], len);
if (B_ERR(status)) {
return status;
}
}
return B_SUCCESS;
}
b_status b_stringstream_addvl(b_stringstream *ss, const char **strs, size_t count)
{
for (size_t i = 0; i < count; i++) {
if (!strs[i]) {
continue;
}
size_t len = strlen(strs[i]);
b_status status = ss_builder_push_string(ss, strs[i], len);
if (B_ERR(status)) {
return status;
}
}
return B_SUCCESS;
}
b_status b_stringstream_add_many(b_stringstream *ss, ...)
{
va_list arg;
va_start(arg, ss);
const char *s = NULL;
while ((s = va_arg(arg, const char *))) {
size_t len = strlen(s);
b_status status = ss_builder_push_string(ss, s, len);
if (B_ERR(status)) {
return status;
}
}
return B_SUCCESS;
}
char *b_stringstream_end(b_stringstream *ss)
{
char *out = ss->ss_buf;
ss->ss_alloc = 0;
ss->ss_len = 0;
ss->ss_max = 0;
ss->ss_buf = NULL;
return out;
}

0
include/blue.h Normal file
View File

24
misc/AllTests.c Normal file
View File

@@ -0,0 +1,24 @@
#include "CuTest.h"
#include <stdio.h>
CuSuite *get_all_tests(void);
int RunAllTests(void)
{
CuString *output = CuStringNew();
CuSuite *suite = CuSuiteNew();
CuSuiteAddSuite(suite, get_all_tests());
CuSuiteRun(suite);
CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output);
printf("%s\n", output->buffer);
return suite->failCount;
}
int main(void)
{
return RunAllTests();
}

345
misc/CuTest.c Normal file
View File

@@ -0,0 +1,345 @@
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "CuTest.h"
/*-------------------------------------------------------------------------*
* CuStr
*-------------------------------------------------------------------------*/
char* CuStrAlloc(size_t size)
{
char* newStr = (char*) malloc( sizeof(char) * (size) );
return newStr;
}
char* CuStrCopy(const char* old)
{
size_t len = strlen(old);
char* newStr = CuStrAlloc(len + 1);
strcpy(newStr, old);
return newStr;
}
/*-------------------------------------------------------------------------*
* CuString
*-------------------------------------------------------------------------*/
void CuStringInit(CuString* str)
{
str->length = 0;
str->size = STRING_MAX;
str->buffer = (char*) malloc(sizeof(char) * str->size);
str->buffer[0] = '\0';
}
CuString* CuStringNew(void)
{
CuString* str = (CuString*) malloc(sizeof(CuString));
str->length = 0;
str->size = STRING_MAX;
str->buffer = (char*) malloc(sizeof(char) * str->size);
str->buffer[0] = '\0';
return str;
}
void CuStringDelete(CuString *str)
{
if (!str) return;
free(str->buffer);
free(str);
}
void CuStringResize(CuString* str, size_t newSize)
{
str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize);
str->size = newSize;
}
void CuStringAppend(CuString* str, const char* text)
{
size_t length;
if (text == NULL) {
text = "NULL";
}
length = strlen(text);
if (str->length + length + 1 >= str->size)
CuStringResize(str, str->length + length + 1 + STRING_INC);
str->length += length;
strcat(str->buffer, text);
}
void CuStringAppendChar(CuString* str, char ch)
{
char text[2];
text[0] = ch;
text[1] = '\0';
CuStringAppend(str, text);
}
void CuStringAppendFormat(CuString* str, const char* format, ...)
{
va_list argp;
char buf[HUGE_STRING_LEN];
va_start(argp, format);
vsprintf(buf, format, argp);
va_end(argp);
CuStringAppend(str, buf);
}
void CuStringInsert(CuString* str, const char* text, size_t pos)
{
size_t length = strlen(text);
if (pos > str->length)
pos = str->length;
if (str->length + length + 1 >= str->size)
CuStringResize(str, str->length + length + 1 + STRING_INC);
memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1);
str->length += length;
memcpy(str->buffer + pos, text, length);
}
/*-------------------------------------------------------------------------*
* CuTest
*-------------------------------------------------------------------------*/
void CuTestInit(CuTest* t, const char* name, TestFunction function)
{
t->name = CuStrCopy(name);
t->failed = 0;
t->ran = 0;
t->message = NULL;
t->function = function;
t->jumpBuf = NULL;
}
CuTest* CuTestNew(const char* name, TestFunction function)
{
CuTest* tc = CU_ALLOC(CuTest);
CuTestInit(tc, name, function);
return tc;
}
void CuTestDelete(CuTest *t)
{
if (!t) return;
CuStringDelete(t->message);
free(t->name);
free(t);
}
void CuTestRun(CuTest* tc)
{
jmp_buf buf;
tc->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
tc->ran = 1;
(tc->function)(tc);
}
tc->jumpBuf = 0;
}
static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
{
char buf[HUGE_STRING_LEN];
sprintf(buf, "%s:%d: ", file, line);
CuStringInsert(string, buf, 0);
tc->failed = 1;
free(tc->message);
tc->message = CuStringNew();
CuStringAppend(tc->message, string->buffer);
if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
}
void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
{
CuString string;
CuStringInit(&string);
if (message2 != NULL)
{
CuStringAppend(&string, message2);
CuStringAppend(&string, ": ");
}
CuStringAppend(&string, message);
CuFailInternal(tc, file, line, &string);
}
void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition)
{
if (condition) return;
CuFail_Line(tc, file, line, NULL, message);
}
void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
const char* expected, const char* actual)
{
CuString string;
if ((expected == NULL && actual == NULL) ||
(expected != NULL && actual != NULL &&
strcmp(expected, actual) == 0))
{
return;
}
CuStringInit(&string);
if (message != NULL)
{
CuStringAppend(&string, message);
CuStringAppend(&string, ": ");
}
CuStringAppend(&string, "expected <");
CuStringAppend(&string, expected);
CuStringAppend(&string, "> but was <");
CuStringAppend(&string, actual);
CuStringAppend(&string, ">");
CuFailInternal(tc, file, line, &string);
}
void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
int expected, int actual)
{
char buf[STRING_MAX];
if (expected == actual) return;
sprintf(buf, "expected <%d> but was <%d>", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}
void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
double expected, double actual, double delta)
{
char buf[STRING_MAX];
if (fabs(expected - actual) <= delta) return;
sprintf(buf, "expected <%f> but was <%f>", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}
void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
void* expected, void* actual)
{
char buf[STRING_MAX];
if (expected == actual) return;
sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}
/*-------------------------------------------------------------------------*
* CuSuite
*-------------------------------------------------------------------------*/
void CuSuiteInit(CuSuite* testSuite)
{
testSuite->count = 0;
testSuite->failCount = 0;
memset(testSuite->list, 0, sizeof(testSuite->list));
}
CuSuite* CuSuiteNew(void)
{
CuSuite* testSuite = CU_ALLOC(CuSuite);
CuSuiteInit(testSuite);
return testSuite;
}
void CuSuiteDelete(CuSuite *testSuite)
{
unsigned int n;
for (n=0; n < MAX_TEST_CASES; n++)
{
if (testSuite->list[n])
{
CuTestDelete(testSuite->list[n]);
}
}
free(testSuite);
}
void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
{
assert(testSuite->count < MAX_TEST_CASES);
testSuite->list[testSuite->count] = testCase;
testSuite->count++;
}
void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2)
{
int i;
for (i = 0 ; i < testSuite2->count ; ++i)
{
CuTest* testCase = testSuite2->list[i];
CuSuiteAdd(testSuite, testCase);
}
}
void CuSuiteRun(CuSuite* testSuite)
{
int i;
for (i = 0 ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
CuTestRun(testCase);
if (testCase->failed) { testSuite->failCount += 1; }
}
}
void CuSuiteSummary(CuSuite* testSuite, CuString* summary)
{
int i;
for (i = 0 ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
CuStringAppend(summary, testCase->failed ? "F" : ".");
}
CuStringAppend(summary, "\n\n");
}
void CuSuiteDetails(CuSuite* testSuite, CuString* details)
{
int i;
int failCount = 0;
if (testSuite->failCount == 0)
{
int passCount = testSuite->count - testSuite->failCount;
const char* testWord = passCount == 1 ? "test" : "tests";
CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord);
}
else
{
if (testSuite->failCount == 1)
CuStringAppend(details, "There was 1 failure:\n");
else
CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount);
for (i = 0 ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
if (testCase->failed)
{
failCount++;
CuStringAppendFormat(details, "%d) %s: %s\n",
failCount, testCase->name, testCase->message->buffer);
}
}
CuStringAppend(details, "\n!!!FAILURES!!!\n");
CuStringAppendFormat(details, "Runs: %d ", testSuite->count);
CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount);
CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount);
}
}

117
misc/CuTest.h Normal file
View File

@@ -0,0 +1,117 @@
#ifndef CU_TEST_H
#define CU_TEST_H
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#define CUTEST_VERSION "CuTest 1.5c"
/* CuString */
char* CuStrAlloc(size_t size);
char* CuStrCopy(const char* old);
#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
#define HUGE_STRING_LEN 8192
#define STRING_MAX 256
#define STRING_INC 256
typedef struct
{
size_t length;
size_t size;
char* buffer;
} CuString;
void CuStringInit(CuString* str);
CuString* CuStringNew(void);
void CuStringRead(CuString* str, const char* path);
void CuStringAppend(CuString* str, const char* text);
void CuStringAppendChar(CuString* str, char ch);
void CuStringAppendFormat(CuString* str, const char* format, ...);
void CuStringInsert(CuString* str, const char* text, size_t pos);
void CuStringResize(CuString* str, size_t newSize);
void CuStringDelete(CuString* str);
/* CuTest */
typedef struct CuTest CuTest;
typedef void (*TestFunction)(CuTest *);
struct CuTest
{
char* name;
TestFunction function;
int failed;
int ran;
CuString *message;
jmp_buf *jumpBuf;
};
void CuTestInit(CuTest* t, const char* name, TestFunction function);
CuTest* CuTestNew(const char* name, TestFunction function);
void CuTestRun(CuTest* tc);
void CuTestDelete(CuTest *t);
/* Internal versions of assert functions -- use the public versions */
void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message);
void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition);
void CuAssertStrEquals_LineMsg(CuTest* tc,
const char* file, int line, const char* message,
const char* expected, const char* actual);
void CuAssertIntEquals_LineMsg(CuTest* tc,
const char* file, int line, const char* message,
int expected, int actual);
void CuAssertDblEquals_LineMsg(CuTest* tc,
const char* file, int line, const char* message,
double expected, double actual, double delta);
void CuAssertPtrEquals_LineMsg(CuTest* tc,
const char* file, int line, const char* message,
void* expected, void* actual);
/* public assert functions */
#define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms))
#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond))
#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond))
#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",((p) != NULL))
#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),((p) != NULL))
/* CuSuite */
#define MAX_TEST_CASES 1024
#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))
typedef struct
{
int count;
CuTest* list[MAX_TEST_CASES];
int failCount;
} CuSuite;
void CuSuiteInit(CuSuite* testSuite);
CuSuite* CuSuiteNew(void);
void CuSuiteDelete(CuSuite *testSuite);
void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase);
void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2);
void CuSuiteRun(CuSuite* testSuite);
void CuSuiteSummary(CuSuite* testSuite, CuString* summary);
void CuSuiteDetails(CuSuite* testSuite, CuString* details);
#endif /* CU_TEST_H */

712
misc/CuTestTest.c Normal file
View File

@@ -0,0 +1,712 @@
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "CuTest.h"
/*-------------------------------------------------------------------------*
* Helper functions
*-------------------------------------------------------------------------*/
#define CompareAsserts(tc, message, expected, actual) X_CompareAsserts((tc), __FILE__, __LINE__, (message), (expected), (actual))
static void X_CompareAsserts(CuTest* tc, const char *file, int line, const char* message, const char* expected, CuString *actual)
{
int mismatch;
if (expected == NULL || actual == NULL || actual==NULL) {
mismatch = (expected != NULL || actual != NULL);
} else {
const char *front = __FILE__ ":";
const size_t frontLen = strlen(front);
const size_t expectedLen = strlen(expected);
const char *matchStr = actual->buffer;
mismatch = (strncmp(matchStr, front, frontLen) != 0);
if (!mismatch) {
matchStr = strchr(matchStr + frontLen, ':');
mismatch |= (matchStr == NULL || strncmp(matchStr, ": ", 2));
if (!mismatch) {
matchStr += 2;
mismatch |= (strncmp(matchStr, expected, expectedLen) != 0);
}
}
}
CuAssert_Line(tc, file, line, message, !mismatch);
}
/*-------------------------------------------------------------------------*
* CuString Test
*-------------------------------------------------------------------------*/
void TestCuStringNew(CuTest* tc)
{
CuString* str = CuStringNew();
CuAssertTrue(tc, 0 == str->length);
CuAssertTrue(tc, 0 != str->size);
CuAssertStrEquals(tc, "", str->buffer);
}
void TestCuStringAppend(CuTest* tc)
{
CuString* str = CuStringNew();
CuStringAppend(str, "hello");
CuAssertIntEquals(tc, 5, (int)str->length);
CuAssertStrEquals(tc, "hello", str->buffer);
CuStringAppend(str, " world");
CuAssertIntEquals(tc, 11, (int)str->length);
CuAssertStrEquals(tc, "hello world", str->buffer);
}
void TestCuStringAppendNULL(CuTest* tc)
{
CuString* str = CuStringNew();
CuStringAppend(str, NULL);
CuAssertIntEquals(tc, 4, (int)str->length);
CuAssertStrEquals(tc, "NULL", str->buffer);
}
void TestCuStringAppendChar(CuTest* tc)
{
CuString* str = CuStringNew();
CuStringAppendChar(str, 'a');
CuStringAppendChar(str, 'b');
CuStringAppendChar(str, 'c');
CuStringAppendChar(str, 'd');
CuAssertIntEquals(tc, 4, (int)str->length);
CuAssertStrEquals(tc, "abcd", str->buffer);
}
void TestCuStringInserts(CuTest* tc)
{
CuString* str = CuStringNew();
CuStringAppend(str, "world");
CuAssertIntEquals(tc, 5, (int)str->length);
CuAssertStrEquals(tc, "world", str->buffer);
CuStringInsert(str, "hell", 0);
CuAssertIntEquals(tc, 9, (int)str->length);
CuAssertStrEquals(tc, "hellworld", str->buffer);
CuStringInsert(str, "o ", 4);
CuAssertIntEquals(tc, 11, (int)str->length);
CuAssertStrEquals(tc, "hello world", str->buffer);
CuStringInsert(str, "!", 11);
CuAssertIntEquals(tc, 12, (int)str->length);
CuAssertStrEquals(tc, "hello world!", str->buffer);
}
void TestCuStringResizes(CuTest* tc)
{
CuString* str = CuStringNew();
int i;
for(i = 0 ; i < STRING_MAX ; ++i)
{
CuStringAppend(str, "aa");
}
CuAssertTrue(tc, STRING_MAX * 2 == str->length);
CuAssertTrue(tc, STRING_MAX * 2 <= str->size);
}
CuSuite* CuStringGetSuite(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestCuStringNew);
SUITE_ADD_TEST(suite, TestCuStringAppend);
SUITE_ADD_TEST(suite, TestCuStringAppendNULL);
SUITE_ADD_TEST(suite, TestCuStringAppendChar);
SUITE_ADD_TEST(suite, TestCuStringInserts);
SUITE_ADD_TEST(suite, TestCuStringResizes);
return suite;
}
/*-------------------------------------------------------------------------*
* CuTest Test
*-------------------------------------------------------------------------*/
void TestPasses(CuTest* tc)
{
CuAssert(tc, "test should pass", 1 == 0 + 1);
}
void zTestFails(CuTest* tc)
{
CuAssert(tc, "test should fail", 1 == 1 + 1);
}
void TestCuTestNew(CuTest* tc)
{
CuTest* tc2 = CuTestNew("MyTest", TestPasses);
CuAssertStrEquals(tc, "MyTest", tc2->name);
CuAssertTrue(tc, !tc2->failed);
CuAssertTrue(tc, tc2->message == NULL);
CuAssertTrue(tc, tc2->function == TestPasses);
CuAssertTrue(tc, tc2->ran == 0);
CuAssertTrue(tc, tc2->jumpBuf == NULL);
}
void TestCuTestInit(CuTest *tc)
{
CuTest tc2;
CuTestInit(&tc2, "MyTest", TestPasses);
CuAssertStrEquals(tc, "MyTest", tc2.name);
CuAssertTrue(tc, !tc2.failed);
CuAssertTrue(tc, tc2.message == NULL);
CuAssertTrue(tc, tc2.function == TestPasses);
CuAssertTrue(tc, tc2.ran == 0);
CuAssertTrue(tc, tc2.jumpBuf == NULL);
}
void TestCuAssert(CuTest* tc)
{
CuTest tc2;
CuTestInit(&tc2, "MyTest", TestPasses);
CuAssert(&tc2, "test 1", 5 == 4 + 1);
CuAssertTrue(tc, !tc2.failed);
CuAssertTrue(tc, tc2.message == NULL);
CuAssert(&tc2, "test 2", 0);
CuAssertTrue(tc, tc2.failed);
CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message);
CuAssert(&tc2, "test 3", 1);
CuAssertTrue(tc, tc2.failed);
CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message);
CuAssert(&tc2, "test 4", 0);
CuAssertTrue(tc, tc2.failed);
CompareAsserts(tc, "CuAssert didn't fail", "test 4", tc2.message);
}
void TestCuAssertPtrEquals_Success(CuTest* tc)
{
CuTest tc2;
int x;
CuTestInit(&tc2, "MyTest", TestPasses);
/* test success case */
CuAssertPtrEquals(&tc2, &x, &x);
CuAssertTrue(tc, ! tc2.failed);
CuAssertTrue(tc, tc2.message == NULL);
}
void TestCuAssertPtrEquals_Failure(CuTest* tc)
{
CuTest tc2;
int x;
int* nullPtr = NULL;
char expected_message[STRING_MAX];
CuTestInit(&tc2, "MyTest", TestPasses);
/* test failing case */
sprintf(expected_message, "expected pointer <0x%p> but was <0x%p>", (void*)nullPtr, (void*)&x);
CuAssertPtrEquals(&tc2, NULL, &x);
CuAssertTrue(tc, tc2.failed);
CompareAsserts(tc, "CuAssertPtrEquals failed", expected_message, tc2.message);
}
void TestCuAssertPtrNotNull_Success(CuTest* tc)
{
CuTest tc2;
int x;
CuTestInit(&tc2, "MyTest", TestPasses);
/* test success case */
CuAssertPtrNotNull(&tc2, &x);
CuAssertTrue(tc, ! tc2.failed);
CuAssertTrue(tc, tc2.message == NULL);
}
void TestCuAssertPtrNotNull_Failure(CuTest* tc)
{
CuTest tc2;
CuTestInit(&tc2, "MyTest", TestPasses);
/* test failing case */
CuAssertPtrNotNull(&tc2, NULL);
CuAssertTrue(tc, tc2.failed);
CompareAsserts(tc, "CuAssertPtrNotNull failed", "null pointer unexpected", tc2.message);
}
void TestCuTestRun(CuTest* tc)
{
CuTest tc2;
CuTestInit(&tc2, "MyTest", zTestFails);
CuTestRun(&tc2);
CuAssertStrEquals(tc, "MyTest", tc2.name);
CuAssertTrue(tc, tc2.failed);
CuAssertTrue(tc, tc2.ran);
CompareAsserts(tc, "TestRun failed", "test should fail", tc2.message);
}
/*-------------------------------------------------------------------------*
* CuSuite Test
*-------------------------------------------------------------------------*/
void TestCuSuiteInit(CuTest* tc)
{
CuSuite ts;
CuSuiteInit(&ts);
CuAssertTrue(tc, ts.count == 0);
CuAssertTrue(tc, ts.failCount == 0);
}
void TestCuSuiteNew(CuTest* tc)
{
CuSuite* ts = CuSuiteNew();
CuAssertTrue(tc, ts->count == 0);
CuAssertTrue(tc, ts->failCount == 0);
}
void TestCuSuiteAddTest(CuTest* tc)
{
CuSuite ts;
CuTest tc2;
CuSuiteInit(&ts);
CuTestInit(&tc2, "MyTest", zTestFails);
CuSuiteAdd(&ts, &tc2);
CuAssertTrue(tc, ts.count == 1);
CuAssertStrEquals(tc, "MyTest", ts.list[0]->name);
}
void TestCuSuiteAddSuite(CuTest* tc)
{
CuSuite* ts1 = CuSuiteNew();
CuSuite* ts2 = CuSuiteNew();
CuSuiteAdd(ts1, CuTestNew("TestFails1", zTestFails));
CuSuiteAdd(ts1, CuTestNew("TestFails2", zTestFails));
CuSuiteAdd(ts2, CuTestNew("TestFails3", zTestFails));
CuSuiteAdd(ts2, CuTestNew("TestFails4", zTestFails));
CuSuiteAddSuite(ts1, ts2);
CuAssertIntEquals(tc, 4, ts1->count);
CuAssertStrEquals(tc, "TestFails1", ts1->list[0]->name);
CuAssertStrEquals(tc, "TestFails2", ts1->list[1]->name);
CuAssertStrEquals(tc, "TestFails3", ts1->list[2]->name);
CuAssertStrEquals(tc, "TestFails4", ts1->list[3]->name);
}
void TestCuSuiteRun(CuTest* tc)
{
CuSuite ts;
CuTest tc1, tc2, tc3, tc4;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestPasses", TestPasses);
CuTestInit(&tc2, "TestPasses", TestPasses);
CuTestInit(&tc3, "TestFails", zTestFails);
CuTestInit(&tc4, "TestFails", zTestFails);
CuSuiteAdd(&ts, &tc1);
CuSuiteAdd(&ts, &tc2);
CuSuiteAdd(&ts, &tc3);
CuSuiteAdd(&ts, &tc4);
CuAssertTrue(tc, ts.count == 4);
CuSuiteRun(&ts);
CuAssertTrue(tc, ts.count - ts.failCount == 2);
CuAssertTrue(tc, ts.failCount == 2);
}
void TestCuSuiteSummary(CuTest* tc)
{
CuSuite ts;
CuTest tc1, tc2;
CuString summary;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestPasses", TestPasses);
CuTestInit(&tc2, "TestFails", zTestFails);
CuStringInit(&summary);
CuSuiteAdd(&ts, &tc1);
CuSuiteAdd(&ts, &tc2);
CuSuiteRun(&ts);
CuSuiteSummary(&ts, &summary);
CuAssertTrue(tc, ts.count == 2);
CuAssertTrue(tc, ts.failCount == 1);
CuAssertStrEquals(tc, ".F\n\n", summary.buffer);
}
void TestCuSuiteDetails_SingleFail(CuTest* tc)
{
CuSuite ts;
CuTest tc1, tc2;
CuString details;
const char* front;
const char* back;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestPasses", TestPasses);
CuTestInit(&tc2, "TestFails", zTestFails);
CuStringInit(&details);
CuSuiteAdd(&ts, &tc1);
CuSuiteAdd(&ts, &tc2);
CuSuiteRun(&ts);
CuSuiteDetails(&ts, &details);
CuAssertTrue(tc, ts.count == 2);
CuAssertTrue(tc, ts.failCount == 1);
front = "There was 1 failure:\n"
"1) TestFails: ";
back = "test should fail\n"
"\n!!!FAILURES!!!\n"
"Runs: 2 Passes: 1 Fails: 1\n";
CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back));
details.buffer[strlen(front)] = 0;
CuAssertStrEquals(tc, front, details.buffer);
}
void TestCuSuiteDetails_SinglePass(CuTest* tc)
{
CuSuite ts;
CuTest tc1;
CuString details;
const char* expected;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestPasses", TestPasses);
CuStringInit(&details);
CuSuiteAdd(&ts, &tc1);
CuSuiteRun(&ts);
CuSuiteDetails(&ts, &details);
CuAssertTrue(tc, ts.count == 1);
CuAssertTrue(tc, ts.failCount == 0);
expected =
"OK (1 test)\n";
CuAssertStrEquals(tc, expected, details.buffer);
}
void TestCuSuiteDetails_MultiplePasses(CuTest* tc)
{
CuSuite ts;
CuTest tc1, tc2;
CuString details;
const char* expected;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestPasses", TestPasses);
CuTestInit(&tc2, "TestPasses", TestPasses);
CuStringInit(&details);
CuSuiteAdd(&ts, &tc1);
CuSuiteAdd(&ts, &tc2);
CuSuiteRun(&ts);
CuSuiteDetails(&ts, &details);
CuAssertTrue(tc, ts.count == 2);
CuAssertTrue(tc, ts.failCount == 0);
expected =
"OK (2 tests)\n";
CuAssertStrEquals(tc, expected, details.buffer);
}
void TestCuSuiteDetails_MultipleFails(CuTest* tc)
{
CuSuite ts;
CuTest tc1, tc2;
CuString details;
const char* front;
const char* mid;
const char* back;
CuSuiteInit(&ts);
CuTestInit(&tc1, "TestFails1", zTestFails);
CuTestInit(&tc2, "TestFails2", zTestFails);
CuStringInit(&details);
CuSuiteAdd(&ts, &tc1);
CuSuiteAdd(&ts, &tc2);
CuSuiteRun(&ts);
CuSuiteDetails(&ts, &details);
CuAssertTrue(tc, ts.count == 2);
CuAssertTrue(tc, ts.failCount == 2);
front =
"There were 2 failures:\n"
"1) TestFails1: ";
mid = "test should fail\n"
"2) TestFails2: ";
back = "test should fail\n"
"\n!!!FAILURES!!!\n"
"Runs: 2 Passes: 0 Fails: 2\n";
CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back));
CuAssert(tc, "Couldn't find middle", strstr(details.buffer, mid) != NULL);
details.buffer[strlen(front)] = 0;
CuAssertStrEquals(tc, front, details.buffer);
}
/*-------------------------------------------------------------------------*
* Misc Test
*-------------------------------------------------------------------------*/
void TestCuStrCopy(CuTest* tc)
{
const char* old = "hello world";
const char* newStr = CuStrCopy(old);
CuAssert(tc, "old is new", strcmp(old, newStr) == 0);
}
void TestCuStringAppendFormat(CuTest* tc)
{
int i;
char* text = CuStrAlloc(301); /* long string */
CuString* str = CuStringNew();
for (i = 0 ; i < 300 ; ++i)
text[i] = 'a';
text[300] = '\0';
CuStringAppendFormat(str, "%s", text);
/* buffer limit raised to HUGE_STRING_LEN so no overflow */
CuAssert(tc, "length of str->buffer is 300", 300 == strlen(str->buffer));
}
void TestFail(CuTest* tc)
{
jmp_buf buf;
int pointReached = 0;
CuTest* tc2 = CuTestNew("TestFails", zTestFails);
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuFail(tc2, "hello world");
pointReached = 1;
}
CuAssert(tc, "point was not reached", pointReached == 0);
}
void TestAssertStrEquals(CuTest* tc)
{
jmp_buf buf;
CuTest *tc2 = CuTestNew("TestAssertStrEquals", zTestFails);
const char* expected = "expected <hello> but was <world>";
const char *expectedMsg = "some text: expected <hello> but was <world>";
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertStrEquals(tc2, "hello", "world");
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals failed", expected, tc2->message);
if (setjmp(buf) == 0)
{
CuAssertStrEquals_Msg(tc2, "some text", "hello", "world");
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message);
}
void TestAssertStrEquals_NULL(CuTest* tc)
{
jmp_buf buf;
CuTest *tc2 = CuTestNew("TestAssertStrEquals_NULL", zTestFails);
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertStrEquals(tc2, NULL, NULL);
}
CuAssertTrue(tc, !tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message);
if (setjmp(buf) == 0)
{
CuAssertStrEquals_Msg(tc2, "some text", NULL, NULL);
}
CuAssertTrue(tc, !tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message);
}
void TestAssertStrEquals_FailNULLStr(CuTest* tc)
{
jmp_buf buf;
CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails);
const char* expected = "expected <hello> but was <NULL>";
const char *expectedMsg = "some text: expected <hello> but was <NULL>";
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertStrEquals(tc2, "hello", NULL);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expected, tc2->message);
if (setjmp(buf) == 0)
{
CuAssertStrEquals_Msg(tc2, "some text", "hello", NULL);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expectedMsg, tc2->message);
}
void TestAssertStrEquals_FailStrNULL(CuTest* tc)
{
jmp_buf buf;
CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails);
const char* expected = "expected <NULL> but was <hello>";
const char *expectedMsg = "some text: expected <NULL> but was <hello>";
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertStrEquals(tc2, NULL, "hello");
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expected, tc2->message);
if (setjmp(buf) == 0)
{
CuAssertStrEquals_Msg(tc2, "some text", NULL, "hello");
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expectedMsg, tc2->message);
}
void TestAssertIntEquals(CuTest* tc)
{
jmp_buf buf;
CuTest *tc2 = CuTestNew("TestAssertIntEquals", zTestFails);
const char* expected = "expected <42> but was <32>";
const char* expectedMsg = "some text: expected <42> but was <32>";
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertIntEquals(tc2, 42, 32);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertIntEquals failed", expected, tc2->message);
if (setjmp(buf) == 0)
{
CuAssertIntEquals_Msg(tc2, "some text", 42, 32);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message);
}
void TestAssertDblEquals(CuTest* tc)
{
jmp_buf buf;
double x = 3.33;
double y = 10.0 / 3.0;
CuTest *tc2 = CuTestNew("TestAssertDblEquals", zTestFails);
char expected[STRING_MAX];
char expectedMsg[STRING_MAX];
sprintf(expected, "expected <%lf> but was <%lf>", x, y);
sprintf(expectedMsg, "some text: expected <%lf> but was <%lf>", x, y);
CuTestInit(tc2, "TestAssertDblEquals", TestPasses);
CuAssertDblEquals(tc2, x, x, 0.0);
CuAssertTrue(tc, ! tc2->failed);
CuAssertTrue(tc, tc2->message == NULL);
CuAssertDblEquals(tc2, x, y, 0.01);
CuAssertTrue(tc, ! tc2->failed);
CuAssertTrue(tc, tc2->message == NULL);
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertDblEquals(tc2, x, y, 0.001);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertDblEquals failed", expected, tc2->message);
tc2->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
CuAssertDblEquals_Msg(tc2, "some text", x, y, 0.001);
}
CuAssertTrue(tc, tc2->failed);
CompareAsserts(tc, "CuAssertDblEquals failed", expectedMsg, tc2->message);
}
/*-------------------------------------------------------------------------*
* main
*-------------------------------------------------------------------------*/
CuSuite* CuGetSuite(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestCuStringAppendFormat);
SUITE_ADD_TEST(suite, TestCuStrCopy);
SUITE_ADD_TEST(suite, TestFail);
SUITE_ADD_TEST(suite, TestAssertStrEquals);
SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL);
SUITE_ADD_TEST(suite, TestAssertStrEquals_FailStrNULL);
SUITE_ADD_TEST(suite, TestAssertStrEquals_FailNULLStr);
SUITE_ADD_TEST(suite, TestAssertIntEquals);
SUITE_ADD_TEST(suite, TestAssertDblEquals);
SUITE_ADD_TEST(suite, TestCuTestNew);
SUITE_ADD_TEST(suite, TestCuTestInit);
SUITE_ADD_TEST(suite, TestCuAssert);
SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Success);
SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Failure);
SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Success);
SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Failure);
SUITE_ADD_TEST(suite, TestCuTestRun);
SUITE_ADD_TEST(suite, TestCuSuiteInit);
SUITE_ADD_TEST(suite, TestCuSuiteNew);
SUITE_ADD_TEST(suite, TestCuSuiteAddTest);
SUITE_ADD_TEST(suite, TestCuSuiteAddSuite);
SUITE_ADD_TEST(suite, TestCuSuiteRun);
SUITE_ADD_TEST(suite, TestCuSuiteSummary);
SUITE_ADD_TEST(suite, TestCuSuiteDetails_SingleFail);
SUITE_ADD_TEST(suite, TestCuSuiteDetails_SinglePass);
SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultiplePasses);
SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultipleFails);
return suite;
}