Commit 559017dd authored by Luc Everse's avatar Luc Everse

Merge branch 'devel' into 'master'

Version 1.4.0

See merge request !1
parents 729e7700 5d1a301b
Pipeline #1211 passed with stage
in 11 seconds
.idea/
cmake-build-debug/
Build/
\ No newline at end of file
Build/
build/
stages:
- test
test:freebsd:
test:
stage: test
tags:
- freebsd
- c
image: registry.wukl.net/wukl/johnson-ci-image
script:
- mkdir build
- cd build
- cmake ..
- make
- make -j16
- ./johnson-version
- ./johnson-test
This diff is collapsed.
#define EMBIGGEN_THRESHOLD .75
#define ENSMALLEN_THRESHOLD .25
#define VERSION "2.0.0j"
/* #undef FAST_COLLISION_REJECTION */
#define DISALLOW_DUPLICATE_KEYS
// Hanstable
// Copyright (c) 2014 - 2016 Luc Everse
// MIT License
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "hanstable.h"
#include "hanstable-config.h"
#ifdef FAST_COLLISION_REJECTION
#define FCR(table_, index_, hash_) ((table_)->entries[(index_)].hash == (hash_))
#else
#define FCR(table_, index_, hash_) (1)
#endif
#define FNV_1A_OFFSET_BASIS_32LEAST 2166136261UL
#define FNV_1A_PRIME_32LEAST 16777619UL
struct ht_Entry {
const char * key;
void * data;
#ifdef FAST_COLLISION_REJECTION
ht_Hash hash;
#endif
};
// FNV-1a hash
// http://www.isthe.com/chongo/tech/comp/fnv/
static ht_Hash hashs(const char * key) {
ht_Hash hash = FNV_1A_OFFSET_BASIS_32LEAST;
while(*key != '\0') {
hash ^= *(key++);
hash *= FNV_1A_PRIME_32LEAST;
}
return hash;
}
// Generate the next index mask
// Currently written for an LP64 system.
// Derived from the Bit Twiddling Hack
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static size_t nextMask(size_t x) {
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x;
}
static enum ht_Error resizeTable(struct Hanstable * table) {
size_t newMask;
// Don't resize when the current size <= minimum size
// This is to prevent auto-resizing when filling the table with the first n entries
if(table->numEntries <= table->minMask) return HT_OK;
else if(table->numEntries > (table->indexMask + 1) * EMBIGGEN_THRESHOLD) {
// Embiggen the table
// Shift everything left and paste another 1 on the end
newMask = 1 | table->indexMask << 1;
}
else if(table->numEntries < (table->indexMask + 1) * ENSMALLEN_THRESHOLD) {
// Ensmallen the table
newMask = table->indexMask >> 1;
}
else return HT_OK; // Within range, no action needed.
struct ht_Entry * newEntries = calloc((newMask + 1), sizeof(struct ht_Entry));
if(newEntries == NULL) return HT_E_MALLOC_FAILURE;
struct ht_Entry * oldEntries = table->entries;
size_t i;
for(i = 0; i <= table->indexMask; ++i) {
if(oldEntries[i].key == NULL) continue;
// Re-index
#ifdef FAST_COLLISION_REJECTION
ht_Hash hash = oldEntries[i].hash;
#else
ht_Hash hash = hashs(oldEntries[i].key);
#endif
// Find the next empty spot
size_t newIndex = hash & newMask;
while(newEntries[newIndex].key != NULL) newIndex = (newIndex + 1) & newMask;
newEntries[newIndex] = oldEntries[i];
}
table->indexMask = newMask;
table->entries = newEntries;
// Froo tho eld baffor
free(oldEntries);
return HT_OK;
}
enum ht_Error ht_create(struct Hanstable * table, size_t capacity) {
table->minMask = table->indexMask = nextMask(capacity);
table->numEntries = 0;
table->entries = calloc((table->indexMask + 1), sizeof(struct ht_Entry));
return (table->entries != NULL)? HT_OK: HT_E_MALLOC_FAILURE;
}
void ht_destroy(struct Hanstable * table) {
free(table->entries);
memset(table, 0, sizeof(struct Hanstable));
}
enum ht_Error ht_add(struct Hanstable * table, const char * key, void * data) {
ht_Hash hash = hashs(key);
// Find the next empty spot or exit on a duplicate key (if that's enabled)
size_t index = hash & table->indexMask;
while(table->entries[index].key != NULL) {
#ifdef DISALLOW_DUPLICATE_KEYS
if(FCR(table, index, hash) && strcmp(key, table->entries[index].key) == 0) {
return HT_E_KEY_ALREADY_PRESENT;
}
#endif
index = (index + 1) & table->indexMask;
}
// We're OK for adding the new item
table->entries[index].key = key;
table->entries[index].data = data;
#ifdef FAST_COLLISION_REJECTION
table->entries[index].hash = hash;
#endif
++table->numEntries;
return resizeTable(table);
}
static enum ht_Error locate(struct Hanstable * table, const char * key, size_t * index) {
ht_Hash hash = hashs(key);
*index = hash & table->indexMask;
while(table->entries[*index].key != NULL) {
if(FCR(table, index, hash) && strcmp(key, table->entries[*index].key) == 0) {
return HT_E_KEY_ALREADY_PRESENT;
}
++hash;
*index = hash & table->indexMask;
}
return HT_E_KEY_NOT_PRESENT;
}
enum ht_Error ht_get(struct Hanstable * table, const char * key, void * datap) {
// Disable all kinds of warnings
void ** data = (void **)datap;
size_t index;
if(locate(table, key, &index) == HT_E_KEY_ALREADY_PRESENT) {
*data = table->entries[index].data;
return HT_OK;
}
else {
*data = NULL;
return HT_E_KEY_NOT_PRESENT;
}
}
int ht_hasNext(struct Hanstable * table, size_t lastIndex) {
if(table == NULL) return 0;
return (ht_getNext(table, &lastIndex, NULL, NULL) == HT_OK);
}
enum ht_Error ht_getNext(struct Hanstable * table, size_t * lastIndex,
const char ** key, void * datap) {
// Disable all kinds of warnings
void ** data = (void **)datap;
while(*lastIndex <= table->indexMask) {
if(table->entries[*lastIndex].key != NULL) {
if(key != NULL) *key = table->entries[*lastIndex].key;
if(data != NULL) *data = table->entries[*lastIndex].data;
++(*lastIndex);
return HT_OK;
}
else ++(*lastIndex);
}
if(data != NULL) *data = NULL;
return HT_E_NO_ENTRIES_LEFT;
}
enum ht_Error ht_rem(struct Hanstable * table, const char * key, void * datap) {
// Disable all kinds of warnings
void ** data = (void **)datap;
size_t index;
if(locate(table, key, &index) == HT_E_KEY_ALREADY_PRESENT) {
// Save the data and remove the key.
if(data != NULL) *data = table->entries[index].data;
table->entries[index].key = NULL;
--table->numEntries;
return resizeTable(table);
}
else {
if(data != NULL) *data = NULL;
return HT_E_KEY_NOT_PRESENT;
}
}
enum ht_Error ht_remNext(struct Hanstable * table, const char ** key, void * datap) {
// Disable all kinds of warnings
void ** data = (void **)datap;
// Prevent the loop from going over the whole entries list
if(table->numEntries == 0) {
if(data != NULL) *data = NULL;
return HT_E_NO_ENTRIES_LEFT;
}
size_t index = 0;
// Keep looping over the entries until we either find an entry with a non-NULL key or we run
// out of addresses to check
while(index <= table->indexMask) {
if(table->entries[index].key != NULL) {
// Save the data and remove the key.
if(data != NULL) *data = table->entries[index].data;
if(key != NULL) *key = table->entries[index].key;
table->entries[index].key = NULL;
--table->numEntries;
return resizeTable(table);
}
index++;
}
// Gah, I've gone through ALL entries and every single one has a NULL key
// and yet the table tells me there's entries left.
table->numEntries = 0;
return HT_E_NO_ENTRIES_LEFT;
}
const char * ht_version(void) {
return VERSION;
}
const char * ht_strerror(enum ht_Error code) {
switch(code) {
case HT_E_MALLOC_FAILURE:
return "allocation failure";
case HT_E_NO_ENTRIES_LEFT:
return "the hash table is empty";
case HT_E_KEY_ALREADY_PRESENT:
return "there's already an entry with this key";
case HT_E_KEY_NOT_PRESENT:
return "entry not found";
default:
return "(unknown error code)";
}
}
// Hanstable
// Copyright (c) 2014 - 2016 Luc Everse
// MIT License
#pragma once
#ifndef HANSTABLE_H_
#define HANSTABLE_H_
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
typedef uint_fast32_t ht_Hash;
enum ht_Error {
HT_E_MALLOC_FAILURE = INT_MIN,
HT_E_KEY_NOT_PRESENT,
HT_E_KEY_ALREADY_PRESENT,
HT_E_NO_ENTRIES_LEFT,
HT_OK = 0
};
struct Hanstable {
// All entries
struct ht_Entry * entries;
// For determining the table's load
size_t numEntries;
size_t indexMask;
size_t minMask;
};
// For those wanting cleaner code
#ifndef HT_DONT_TYPEDEF_HANSTABLE_STRUCT
typedef struct Hanstable Hanstable;
#endif
// The circle of life
enum ht_Error ht_create (struct Hanstable *, size_t);
void ht_destroy(struct Hanstable *);
// Insertion
enum ht_Error ht_add(struct Hanstable *, const char *, void *);
// Retrieval
enum ht_Error ht_get (struct Hanstable *, const char *, void *);
enum ht_Error ht_getNext(struct Hanstable *, size_t *, const char **, void *);
int ht_hasNext(struct Hanstable *, size_t);
// Removal
enum ht_Error ht_rem (struct Hanstable *, const char *, void *);
enum ht_Error ht_remNext(struct Hanstable *, const char **, void *);
const char * ht_version (void);
const char * ht_strerror(enum ht_Error);
#endif
include(LibFindMacros)
libfind_pkg_detect(Hanstable hanstable FIND_PATH hanstable.h FIND_LIBRARY hanstable)
libfind_process(Hanstable)
This diff is collapsed.
function(extract_git_info)
set(SINGLE_VALUE_PARAMS WORKING_DIRECTORY)
cmake_parse_arguments(ARG "" "${SINGLE_VALUE_PARAMS}" "" ${ARGN})
execute_process(
COMMAND git describe --tags --abbrev=0
WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_DESCRIBE_STATUS
OUTPUT_VARIABLE GIT_RAW_TAG
)
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_REV_PARSE_STATUS
OUTPUT_VARIABLE GIT_RAW_COMMIT_HASH
)
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_BRANCH_STATUS
OUTPUT_VARIABLE GIT_RAW_BRANCH
)
execute_process(
COMMAND git diff-files --quiet
WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_DIFF_FILES_STATUS
)
if ((${GIT_DESCRIBE_STATUS} EQUAL 0))
string(REGEX REPLACE "\n$" "" GIT_TAG "${GIT_RAW_TAG}")
set(GIT_TAG "${GIT_TAG}" PARENT_SCOPE)
if ("${GIT_RAW_TAG}" MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+).*")
set(GIT_TAG_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
set(GIT_TAG_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE)
set(GIT_TAG_PATCH "${CMAKE_MATCH_3}" PARENT_SCOPE)
set(GIT_TAG_HAS_VERSION ON PARENT_SCOPE)
else ()
message(WARNING "Unable to extract a version from git tag ${GIT_RAW_TAG}")
endif ()
else ()
message(WARNING "Unable to extract the latest git tag")
endif ()
if (${GIT_REV_PARSE_STATUS} EQUAL 0)
string(STRIP "${GIT_RAW_COMMIT_HASH}" GIT_COMMIT)
set(GIT_COMMIT "${GIT_COMMIT}" PARENT_SCOPE)
else ()
message(WARNING "Unable to extract the latest git commit hash")
endif ()
if (${GIT_BRANCH_STATUS} EQUAL 0)
string(STRIP "${GIT_RAW_BRANCH}" GIT_BRANCH)
set(GIT_BRANCH "${GIT_BRANCH}" PARENT_SCOPE)
else ()
message(WARNING "Unable to extract the current git branch")
endif ()
if (${GIT_DIFF_FILES_STATUS} EQUAL 1)
set(GIT_DIRTY ON PARENT_SCOPE)
endif ()
endfunction()
prefix="@CMAKE_INSTALL_PREFIX@"
exec_prefix="${prefix}"
libdir="${prefix}/@CMAKE_INSTALL_LIBDIR@"
includedir="${prefix}/include"
Name: Johnson
Description: A C11 JSON library
URL: https://git.wukl.net/wukl/johnson
Version: @PROJECT_VERSION@
Requires.private: hanstable >= 3.0.0
Cflags: -I"${includedir}"
Libs: -L"${libdir}" -ljohnson
......@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <string.h>
#include "Hanstable/hanstable.h"
#include "hanstable.h"
#include "johnson.h"
char * JSON_strdup(const char *, size_t *);
......@@ -44,7 +44,7 @@ JSON_ErrorInfo info;
else { \
printf("[ OK ] [TEH] [%03d] "#fun "\n", __LINE__); \
}
/**
* @brief Assert a generic truthy value.
*
......@@ -87,7 +87,7 @@ JSON_ErrorInfo info;
*/
#define IXA(str, arg) \
printf("[INFO] [IXA] [%03d] " str "%s\n", __LINE__, arg);
/**
* @brief Output a string with an integer appended.
*
......@@ -96,7 +96,7 @@ JSON_ErrorInfo info;
*/
#define IXI(str, arg) \
printf("[INFO] [IXI] [%03d] " str "%lu\n", __LINE__, arg);
/**
* @brief Output the type of a node in both numeric and human-readable notation.
*
......@@ -112,7 +112,7 @@ static void testNode(JSON * copy, JSON * master) {
ITT(master)
ITT(copy)
TXX(copy->type == master->type)
size_t i;
switch(copy->type) {
case JSON_STRING:
......@@ -128,10 +128,10 @@ static void testNode(JSON * copy, JSON * master) {
case JSON_NULL:
break;
case JSON_OBJECT:
TXX(copy->objectChildren.numEntries == master->objectChildren.numEntries)
TXX(copy->objectChildren.num_entries == master->objectChildren.num_entries)
TXX(copy->nextIndex == 0)
TXX(master->nextIndex == 0)
for(i = 0; i < copy->objectChildren.numEntries; ++i) {
for(i = 0; i < copy->objectChildren.num_entries; ++i) {
JSON * masterChild = JSON_getNext(master, NULL);
TN(masterChild);
IXA("Key: ", masterChild->label)
......@@ -201,62 +201,62 @@ JSON_Error printNodeInfo(JSON * node, size_t level, void * payload, JSON_ErrorIn
return JSON_OK;
}
int main(void) {
IXA("Testing Johnson version ", JSON_getVersion())
JSON * mroot = JSON_createObject(NULL, NULL);
TN(mroot)
JSON_createErrorInfo(&info);
// Test generic insertion
TEJ(JSON_setString (mroot, ".\"firstName\"", "John", &info))
TEJ(JSON_setString (mroot, ".\"lastName\"", "Smith", &info))
TEJ(JSON_setBoolean(mroot, ".\"isAlive\"", 1, &info))
TEJ(JSON_setInt (mroot, ".\"age\"", JSON_INT(17179869184), &info))
TEJ(JSON_setReal (mroot, ".\"height_cm\"", JSON_REAL(167.6), &info))
// Test manual ht_Hashtable insertion
// Comes with a partial Hanstable test
JSON * jSAE = JSON_createString(NULL, "21 2nd Street");
JSON * jCiE = JSON_createString(NULL, "New York");
JSON * jStE = JSON_createString(NULL, "NY");
JSON * jPCE = JSON_createString(NULL, "10021-3100");
TN(jSAE)
TN(jCiE)
TN(jStE)
TN(jPCE)
jSAE->label = JSON_strdup("streetAddress", NULL);
jCiE->label = JSON_strdup("city", NULL);
jStE->label = JSON_strdup("state", NULL);
jPCE->label = JSON_strdup("postalCode", NULL);
TN(jSAE->label)
TN(jCiE->label)
TN(jStE->label)
TN(jPCE->label)
Hanstable hashtable;
TEH(ht_create(&hashtable, 4))
TEH(ht_add(&hashtable, jSAE->label, jSAE))
TEH(ht_add(&hashtable, jCiE->label, jCiE))
TEH(ht_add(&hashtable, jStE->label, jStE))
TEH(ht_add(&hashtable, jPCE->label, jPCE))
TEJ(JSON_setObject(mroot, ".\"address\"", &hashtable, &info))
JSON * addressNode;
TEJ(JSON_fetch(mroot, "$.\"address\"", &addressNode, &info))
jSAE->parent = jCiE->parent = jStE->parent = jPCE->parent = addressNode;
// Test manual array insertion
JSON * nodes[2];
nodes[0] = JSON_createObject(NULL, NULL);
nodes[1] = JSON_createObject(NULL, NULL);
......@@ -265,31 +265,31 @@ int main(void) {
nodes[0]->index = 0;
nodes[1]->index = 1;
TEJ(JSON_setString(nodes[0], ".\"type\"", "home", &info))
TEJ(JSON_setString(nodes[0], ".\"number\"", "212 555-1234", &info))
TEJ(JSON_setString(nodes[1], ".\"type\"", "work", &info))
TEJ(JSON_setString(nodes[1], ".\"number\"", "646 555-4567", &info))
TEJ(JSON_setArray(mroot, ".\"phoneNumbers\"", nodes, 2, &info))
JSON * numbersNode;
TEJ(JSON_fetch(mroot, "$.\"phoneNumbers\"", &numbersNode, &info));
nodes[0]->parent = nodes[1]->parent = numbersNode;
// Test array insertion/removal
TEJ(JSON_setArray(mroot, ".\"children\"", NULL, 0, &info))
TEJ(JSON_setString(mroot, "$.\"children\"[]", "Johnny", &info))
TEJ(JSON_setString(mroot, "$.\"children\"[]", "Mary", &info))
JSON * tragedyJ = JSON_removeNode(mroot, "$.\"children\"", 0, &info);
JSON * tragedyM = JSON_removeNode(mroot, "$.\"children\"", 0, &info);
TN(tragedyJ)
TN(tragedyM)
if(strcmp(tragedyJ->stringValue, "Johnny") != 0) {
fprintf(stderr, "Poor John thinks Johnny is %s!\n",
tragedyJ->stringValue);
......@@ -300,16 +300,16 @@ int main(void) {
tragedyM->stringValue);
exit(-1);
}
JSON_destroyNode(tragedyJ);
JSON_destroyNode(tragedyM);
// Test setting/overriding
TEJ(JSON_setString(mroot, ".\"spouse\"", "Marjorie", &info))
TEJ(JSON_setNull(mroot, "$.\"spouse\"", &info))
INX("I've built this tree:");
printf("TYPE LABEL/INDEX\n");
TEJ(JSON_walkNode(mroot, printNodeInfo, NULL, &info))
......@@ -322,40 +322,40 @@ int main(void) {
testNode(croot, mroot);
// Test writing to a file
FILE * outFile = fopen("out.json", "wb");
TN(outFile)
JSON_OutputStream outStream;
JSON_createOutputStream(&outStream, JSON_fileOutputInitializer, outFile);
TEJ(JSON_write(croot, &outStream, JSON_FORMAT, &info))
JSON_destroyNode(croot);