Add some more helper libraries

This commit is contained in:
doyle 2021-07-28 21:10:25 +10:00
parent 07499b1004
commit 5db8a60014
7 changed files with 3904 additions and 1145 deletions

1775
Code/Dqn.h

File diff suppressed because it is too large Load Diff

80
Code/Dqn_Curl.h Normal file
View File

@ -0,0 +1,80 @@
#ifndef DQN_CURL_H
#define DQN_CURL_H
// -----------------------------------------------------------------------------
// NOTE: Dqn_Curl
// -----------------------------------------------------------------------------
// A wrapper over CURL that is primarily used for hot-code reloading by
// packaging the CURL api into a struct that can be passed across DLL
// boundaries.
//
// curl_global_init(CURL_GLOBAL_ALL) must return CURLE_OK before anything
// in this file is used. You may cleanup curl on exit via curl_global_cleanup()
// if desired.
//
// An easy way to generate the curl commands required to query a url is to use
// CURL itself and the option to dump the command to a C-compatible file, i.e.
//
// curl --libcurl RequestToCCode.c -X POST -H "Content-Type: application/json" --data-binary "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_service_nodes\", \"params\": []}" oxen.observer:22023/json_rpc
//
// -----------------------------------------------------------------------------
// NOTE: Configuration
// -----------------------------------------------------------------------------
// #define DQN_CURL_IMPLEMENTATION
// Define this in one and only one C++ file to enable the implementation
// code of the header file.
#define CURL_STATICLIB
#define NOMINMAX
#include <curl-7.72.0/include/curl/curl.h>
struct Dqn_CurlProcs
{
CURL *(*curl_easy_init)(void);
CURLcode (*curl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
CURLcode (*curl_easy_setopt)(CURL *handle, CURLoption option, ...);
const char *(*curl_easy_strerror)(CURLcode);
void (*curl_easy_cleanup)(CURL *curl);
CURLM *(*curl_multi_init)(void);
CURLMcode (*curl_multi_perform)(CURLM *multi_handle, int *running_handles);
CURLMsg *(*curl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
CURLMcode (*curl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*curl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
const char *(*curl_multi_strerror)(CURLMcode);
struct curl_slist *(*curl_slist_append)(struct curl_slist *, const char *);
void (*curl_slist_free_all)(struct curl_slist *);
};
Dqn_CurlProcs Dqn_CurlProcs_Init();
#endif // DQN_CURL_H
#if defined(DQN_CURL_IMPLEMENTATION)
#pragma comment (lib, "Advapi32")
#pragma comment (lib, "Crypt32")
#pragma comment (lib, "Ws2_32")
#pragma comment (lib, "Wldap32")
#pragma comment (lib, "Normaliz")
Dqn_CurlProcs Dqn_CurlProcs_Init()
{
Dqn_CurlProcs result = {};
result.curl_easy_init = curl_easy_init;
result.curl_easy_getinfo = curl_easy_getinfo;
result.curl_easy_setopt = curl_easy_setopt;
result.curl_easy_strerror = curl_easy_strerror;
result.curl_easy_cleanup = curl_easy_cleanup;
result.curl_multi_init = curl_multi_init;
result.curl_multi_perform = curl_multi_perform;
result.curl_multi_info_read = curl_multi_info_read;
result.curl_multi_remove_handle = curl_multi_remove_handle;
result.curl_multi_add_handle = curl_multi_add_handle;
result.curl_multi_strerror = curl_multi_strerror;
result.curl_slist_append = curl_slist_append;
result.curl_slist_free_all = curl_slist_free_all;
return result;
}
#endif // DQN_CURL_IMPLEMENTATION

684
Code/Dqn_Jsmn.h Normal file
View File

@ -0,0 +1,684 @@
#ifndef DQN_JSMN_H
#define DQN_JSMN_H
// -----------------------------------------------------------------------------
// NOTE: Dqn_Jsmn
// -----------------------------------------------------------------------------
// Some helper functions ontop of the jsmn API to make it more ergonomic to use.
// JSMN is embedded in this source file for convenience.
//
// -----------------------------------------------------------------------------
// NOTE: Configuration
// -----------------------------------------------------------------------------
// #define DQN_JSMN_IMPLEMENTATION
// Define this in one and only one C++ file to enable the implementation
// code of the header file. This will also automatically enable the JSMN
// implementation.
//
#if !defined(DQN_H)
#error You must include "Dqn.h" before including "Dqn_Jsmn.h"
#endif // DQN_H
// -----------------------------------------------------------------------------
// NOTE: JSMN Configuration
// -----------------------------------------------------------------------------
// JSMN has its own set of #defines that are definable by the user. See the JSMN
// header file below for more information.
//
// -----------------------------------------------------------------------------
// NOTE: JSMN Header File: commit 053d3cd29200edb1bfd181d917d140c16c1f8834
// -----------------------------------------------------------------------------
/*
* MIT License
*
* Copyright (c) 2010 Serge Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef JSMN_H
#define JSMN_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef JSMN_STATIC
#define JSMN_API static
#else
#define JSMN_API extern
#endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
typedef struct jsmntok {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
JSMN_API void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each
* describing
* a single JSON object.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
#ifdef __cplusplus
}
#endif
#endif // JSMN_H
// -----------------------------------------------------------------------------
// Header File
// -----------------------------------------------------------------------------
struct Dqn_JsmnError
{
jsmntok_t token;
char const *cpp_func;
char const *cpp_file; // The file of the .cpp/h source code that triggered the error
int cpp_line; // The line of the .cpp/h source code that triggered the error
};
struct Dqn_JsmnErrorHandle
{
Dqn_ArenaAllocator *arena;
Dqn_List<Dqn_JsmnError> list;
};
void Dqn_JsmnErrorHandle_AddError (Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line);
Dqn_String Dqn_JsmnToken_String (Dqn_String json, jsmntok_t token);
Dqn_b32 Dqn_JsmnToken_Bool (Dqn_String json, jsmntok_t token);
Dqn_u64 Dqn_JsmnToken_U64 (Dqn_String json, jsmntok_t token);
jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json);
jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray (jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json);
#define Dqn_JsmnToken_ExpectBool(json, token, err_handle) Dqn_JsmnToken__ExpectBool(json, token, err_handle, __FILE__, __LINE__)
#define Dqn_JsmnToken_ExpectNumber(json, token, err_handle) Dqn_JsmnToken__ExpectNumber(json, token, err_handle, __FILE__, __LINE__)
#define Dqn_JsmnToken_ExpectString(token, err_handle) Dqn_JsmnToken__ExpectString(token, err_handle, __FILE__, __LINE__)
#define Dqn_JsmnToken_ExpectArray(token, err_handle) Dqn_JsmnToken__ExpectArray(token, err_handle, __FILE__, __LINE__)
#define Dqn_JsmnToken_ExpectObject(token, err_handle) Dqn_JsmnToken__ExpectObject(token, err_handle, __FILE__, __LINE__)
#define DQN_JSMN_ERROR_HANDLE_DUMP(handle) \
for (Dqn_ListChunk<Dqn_JsmnError> *chunk = handle.list.head; chunk; chunk = chunk->next) \
{ \
for (auto *error = chunk->data; error != (chunk->data + chunk->count); error++) \
{ \
DQN_LOG_E("Json parsing error at %s from %s:%d", \
error->cpp_func, \
Dqn_Str_FileNameFromPath(error->cpp_file), \
error->cpp_line); \
} \
}
#endif // DQN_JSMN_H
#if defined(DQN_JSMN_IMPLEMENTATION)
// -----------------------------------------------------------------------------
// Implementation
// -----------------------------------------------------------------------------
void Dqn_JsmnErrorHandle_AddError(Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line)
{
if (!err_handle)
return;
if (!err_handle->list.head)
err_handle->list = Dqn_List_InitWithArena<Dqn_JsmnError>(err_handle->arena, 16);
Dqn_JsmnError *error = Dqn_List_Make(&err_handle->list, 1);
if (error)
{
error->cpp_func = func;
error->cpp_file = file;
error->cpp_line = line;
}
}
Dqn_b32 Dqn_JsmnToken__ExpectBool(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line)
{
DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size);
char ch = json.str[token.start];
Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == 't' || ch == 'f');
if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line);
return result;
}
Dqn_b32 Dqn_JsmnToken__ExpectNumber(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line)
{
DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size);
char ch = json.str[token.start];
Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == '-' || Dqn_Char_IsDigit(ch));
if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line);
return result;
}
Dqn_b32 Dqn_JsmnToken__ExpectString(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line)
{
Dqn_b32 result = token.type == JSMN_STRING;
if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line);
return result;
}
Dqn_b32 Dqn_JsmnToken__ExpectArray(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line)
{
Dqn_b32 result = token.type == JSMN_ARRAY;
if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line);
return result;
}
Dqn_b32 Dqn_JsmnToken__ExpectObject(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line)
{
Dqn_b32 result = token.type == JSMN_OBJECT;
if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line);
return result;
}
Dqn_String Dqn_JsmnToken_String(Dqn_String json, jsmntok_t token)
{
Dqn_String result = Dqn_String_Init(json.str + token.start, token.end - token.start);
return result;
}
Dqn_b32 Dqn_JsmnToken_Bool(Dqn_String json, jsmntok_t token)
{
DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size);
char ch = json.str[token.start];
Dqn_b32 result = ch == 't';
if (!result) { DQN_ASSERT(ch == 'f'); }
return result;
}
Dqn_u64 Dqn_JsmnToken_U64(Dqn_String json, jsmntok_t token)
{
DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size);
Dqn_String string = Dqn_JsmnToken_String(json, token);
Dqn_u64 result = Dqn_String_ToU64(string);
return result;
}
jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json)
{
jsmntok_t *result = start_it;
if (!Dqn_JsmnToken_ExpectObject(*result, err_handle)) return result;
jsmntok_t *object = result++;
for (int index = 0; index < object->size; index++)
{
jsmntok_t *key = result++;
jsmntok_t *value = result++;
Dqn_String key_str = Dqn_JsmnToken_String(json, *key);
Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str;
if (value->type == JSMN_OBJECT)
{
result = Dqn_JsmnToken_AdvanceItPastObject(value, err_handle, json);
}
else if (value->type == JSMN_ARRAY)
{
result = Dqn_JsmnToken_AdvanceItPastArray(value, err_handle, json);
}
}
return result;
}
jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json)
{
jsmntok_t *result = start_it;
if (!Dqn_JsmnToken_ExpectArray(*result, err_handle)) return result;
jsmntok_t *array = result++;
for (int index = 0; index < array->size; index++)
{
jsmntok_t *value = result++;
Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str;
if (value->type == JSMN_OBJECT)
{
Dqn_JsmnToken_AdvanceItPastObject(start_it, err_handle, json);
}
else if (value->type == JSMN_ARRAY)
{
Dqn_JsmnToken_AdvanceItPastArray(start_it, err_handle, json);
}
}
return result;
}
// -----------------------------------------------------------------------------
// JSMN Implementation
// -----------------------------------------------------------------------------
#ifdef __cplusplus
extern "C" {
#endif
/**
* Allocates a fresh unused token from the token pool.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
const int start, const int end) {
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t':
case '\r':
case '\n':
case ' ':
case ',':
case ']':
case '}':
goto found;
default:
/* to quiet a warning from gcc*/
break;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"':
case '/':
case '\\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
i++) {
/* If it isn't a hex character we have an error */
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens) {
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{':
case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
return JSMN_ERROR_NOMEM;
}
if (parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
/* In strict mode an object or array can't become a key */
if (t->type == JSMN_OBJECT) {
return JSMN_ERROR_INVAL;
}
#endif
t->size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}':
case ']':
if (tokens == NULL) {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
if (token->type != type || parser->toksuper == -1) {
return JSMN_ERROR_INVAL;
}
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) {
return JSMN_ERROR_INVAL;
}
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
parser->toksuper = tokens[parser->toksuper].parent;
#else
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if (tokens[i].start != -1 && tokens[i].end == -1) {
parser->toksuper = i;
break;
}
}
}
#endif
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 't':
case 'f':
case 'n':
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
const jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
JSMN_API void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
#ifdef __cplusplus
}
#endif
#endif // DQN_JSMN_IMPLEMENTATION

948
Code/Dqn_MetaDesk.h Normal file
View File

@ -0,0 +1,948 @@
#ifndef DQN_META_DESK_H
#define DQN_META_DESK_H
// -----------------------------------------------------------------------------
// NOTE: Dqn_MetaDesk
// -----------------------------------------------------------------------------
// Contains helpers functions to code generate formatted CPP files using
// Dion-System's MetaDesk library.
//
// MetaDesk must be available under the include path at
// "metadesk/source/md.[c|h]" when this file is compiled.
//
// -----------------------------------------------------------------------------
// NOTE: Configuration
// -----------------------------------------------------------------------------
// #define DQN_META_DESK_IMPLEMENTATION
// Define this in one and only one C++ file to enable the implementation
// code of the header file.
//
// #define DQN_META_DESK_STANDALONE_PROGRAM
// Define this to enable a "main" function allowing this translation unit to
// be compiled into an executable, by default this program will generate all
// the possible code generation outputs. This file's implementation must
// also be available to this translation unit.
//
// -----------------------------------------------------------------------------
// NOTE: Dqn_MetaDesk Header File
// -----------------------------------------------------------------------------
#include <stdio.h>
#include <stdarg.h>
#include "metadesk/source/md.h"
#define DQN_MD_INVALID_CODE_PATH MD_Assert(0)
#define DQN_MD_CAST(Type) (Type)
// -----------------------------------------------------------------------------
// NOTE: Dqn_MDCppFile: Helper functions to generate formatted CPP files
// -----------------------------------------------------------------------------
struct Dqn_MDCppFile
{
FILE *file;
int indent;
int space_per_indent;
bool append_extra_new_line;
};
int Dqn_MDCppFile_SpacePerIndent(Dqn_MDCppFile *cpp);
void Dqn_MDCppFile_LineBeginV (Dqn_MDCppFile *cpp, char const *fmt, va_list args);
void Dqn_MDCppFile_LineBegin (Dqn_MDCppFile *cpp, char const *fmt, ...);
void Dqn_MDCppFile_LineEnd (Dqn_MDCppFile *cpp, char const *fmt, ...);
void Dqn_MDCppFile_LineAdd (Dqn_MDCppFile *cpp, char const *fmt, ...);
void Dqn_MDCppFile_LineV (Dqn_MDCppFile *cpp, char const *fmt, va_list args);
void Dqn_MDCppFile_Line (Dqn_MDCppFile *cpp, char const *fmt, ...);
void Dqn_MDCppFile_NewLine (Dqn_MDCppFile *cpp);
void Dqn_MDCppFile_Indent (Dqn_MDCppFile *cpp);
void Dqn_MDCppFile_Unindent (Dqn_MDCppFile *cpp);
void Dqn_MDCppFile_BeginBlock (Dqn_MDCppFile *cpp, char const *fmt, ...);
void Dqn_MDCppFile_EndBlock (Dqn_MDCppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block);
#define Dqn_MDCppFile_EndEnumBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/)
#define Dqn_MDCppFile_EndForBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/)
#define Dqn_MDCppFile_EndIfBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/)
#define Dqn_MDCppFile_EndFuncBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, true /*new_line_on_next_block*/)
#define Dqn_MDCppFile_EndStructBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/)
// -----------------------------------------------------------------------------
// NOTE: Dqn_MD: Code generating helper
// -----------------------------------------------------------------------------
enum Dqn_MD_CodeGenFlag
{
Dqn_MD_CodeGenFlag_CEnum = (1 << 0),
Dqn_MD_CodeGenFlag_CppStruct = (1 << 1),
Dqn_MD_CodeGenFlag_ImGuiFunction = (1 << 2),
Dqn_MD_CodeGenFlag_JsonParsingFunction = (1 << 3),
Dqn_MD_CodeGenFlag_CurlQueryFunction = (1 << 4),
Dqn_MD_CodeGenFlag_JsonEndpointURLFunction = (1 << 5),
Dqn_MD_CodeGenFlag_All = (0xFFFFFFFF),
};
// file_path: The path of the file to feed into the code generator to parse and spit out code.
// output_name: The name to assign the .cpp/.h file that is generated from the
// function, i.e. <output_name>.h and <output_name>.cpp
void Dqn_MD_CodeGen(char const *file_path, char const *output_name, unsigned int flags);
// -----------------------------------------------------------------------------
// NOTE: Dqn_MD: Code generating functions
// -----------------------------------------------------------------------------
#if 0
Generate a CPP struct for the given 'struct_node'. Children nodes of the
'struct_node' must have a 'type' child associated with it.
Input
INS_CoinGecko_Coin:
{
id: {type: string},
symbol: {type: string},
name: {type: string},
market_cap_rank: {type: u16},
market_data: {type: INS_CoinGecko_CoinMarketData},
}
Output
struct CoinGecko_CoinMarketDataPrice
{
Dqn_f32 aud;
Dqn_f32 btc;
Dqn_f32 eth;
Dqn_f32 usd;
};
#endif
void Dqn_MD_GenerateCppStruct(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool *requires_arena);
#if 0
Generate for any root MetaDesk node tagged with the 'required_tag'
required_tag: The tag that must be attached to the MetaDesk node for an enum to be generated for it
name: The name of the enum to generate
Input: (With name = "INS_QueryType", required_tag = "json_endpoint")
char const EXAMPLE[] = R"(@json_endpoint Etherscan_TXListForAddress: {}
@json_endpoint CoinGecko_Coin: {}");
Output
enum INS_QueryType
{
INS_QueryType_Etherscan_TXListForAddress,
INS_QueryType_CoinGecko_Coin,
INS_QueryType_Count
};
#endif
void Dqn_MD_GenerateCEnum(Dqn_MDCppFile *cpp, MD_ParseResult *parse, MD_String8 required_tag, char const *name);
#if 0
Generate ImGui widgets for visualising the given 'struct_node'. If a custom type
is provided, we assume that an ImGui function has been previously generated for
the type already.
Input
INS_CoinGecko_Coin:
{
id: {type: string, json_type: string},
symbol: {type: string, json_type: string},
name: {type: string, json_type: string},
market_cap_rank: {type: u16, json_type: number},
market_data: {type: INS_CoinGecko_CoinMarketData, json_type: object},
}
Output
void INS_CoinGecko_CoinImGui(INS_CoinGecko_Coin const *val){
ImGui::Text("id: %.*s", DQN_STRING_FMT(val->id));
ImGui::Text("symbol: %.*s", DQN_STRING_FMT(val->symbol));
ImGui::Text("name: %.*s", DQN_STRING_FMT(val->name));
ImGui::Text("market_cap_rank: %I64u", val->market_cap_rank);
INS_CoinGecko_CoinMarketDataImGui(&val->market_data);
}
#endif
void Dqn_MD_GenerateImGuiFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool header_file);
#if 0
Generate for any root MetaDesk node tagged with 'json_endpoint'. The MetaDesk
node containing the 'json_endpoint' can specify fields that the JSON parsing
function will attempt to pull out from the provided JSON string, powered by the
library jsmn.h. This function requires "Dqn_Jsmn.h/cpp" to be present in the
project using this generated code.
Input
char const EXAMPLE[] = R"(
Etherscan_TXListForAddressEntry:
{
block_number: {type: u64, json_key: "blockNumber", json_type: string},
nonce: {type: u64, json_type: string},
block_hash: {type: string, json_key: "blockHash", json_type: string},
}
@json_endpoint(url: "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s"
params: {eth_address:string, api_key: string})
Etherscan_TXListForAddress:
{
status: {type: bool, json_type: string},
message: {type: string, json_type: string},
result: {type: Etherscan_TXListForAddressEntry[], json_type: array},
}");
Output: (Truncated for brevity)
Etherscan_TXListForAddress Etherscan_TXListForAddressParseTokens(jsmntok_t **start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json, Dqn_ArenaAllocator *arena)
{
INS_Etherscan_TXListForAddress result = {};
jsmntok_t *it = (*start_it);
if (!Dqn_JsmnToken_ExpectObject(*it, err_handle)) return result;
jsmntok_t *object = it++;
for (int index = 0; index < object->size; index++)
{
jsmntok_t *key = it++;
jsmntok_t *value = it++;
Dqn_String key_str = Dqn_JsmnToken_String(json, *key);
Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str;
if (key_str == DQN_STRING("status"))
{
if (Dqn_JsmnToken_ExpectString(*value, err_handle))
{
result.status = DQN_CAST(Dqn_b32)Dqn_String_ToU64(value_str);
}
}
...
else if (key_str == DQN_STRING("result"))
{
if (Dqn_JsmnToken_ExpectArray(*value, err_handle))
{
result.result = Dqn_Array_InitWithArenaNoGrow(arena, INS_Etherscan_TXListForAddressEntry, value->size, 0, Dqn_ZeroMem::Yes);
for (int array_index = 0; array_index < value->size; array_index++)
{
auto *result_entry = Dqn_Array_Make(&result.result, 1);
*result_entry = INS_Etherscan_TXListForAddressEntryParseTokens(&it, err_handle, json, arena);
}
}
}
...
}
*start_it = it;
return result;
}
#endif
void Dqn_MD_GenerateJsonParsingFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool requires_arena, bool header_file);
#if 0
Generate for any root MetaDesk node with the following tags defined
@json_endpoint(url: <...>, params: {<...>})
Input
char const EXAMPLE[] = R"(
@json_endpoint(url: "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s"
params: {eth_address:string, api_key: string})
Etherscan_TXListForAddress:
{
}");
Output
Dqn_String Etherscan_TXListForAddressURL(Dqn_ArenaAllocator *arena, Dqn_String eth_address, Dqn_String api_key)
{
Dqn_String result = Dqn_String_InitArenaFmt(arena, "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s", DQN_STRING_FMT(eth_address), DQN_STRING_FMT(api_key));
DQN_HARD_ASSERT(result.str);
return result;
}
#endif
void Dqn_MD_GenerateJsonEndpointURLFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file);
void Dqn_MD_GenerateCurlQueryFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file);
#endif // DQN_META_DESK_H
#if defined(DQN_META_DESK_IMPLEMENTATION)
// -----------------------------------------------------------------------------
// NOTE: Implementation
// -----------------------------------------------------------------------------
#pragma warning(push)
#pragma warning(disable: 4244) // '=': conversion from 'MD_u64' to 'unsigned int', possible loss of data
#pragma warning(disable: 4101) // 'consume': unreferenced local variable
#include "metadesk/source/md.c"
#pragma warning(pop)
// -----------------------------------------------------------------------------
// NOTE: Dqn_MDCppFile Implementation
// -----------------------------------------------------------------------------
int Dqn_MDCppFile_SpacePerIndent(Dqn_MDCppFile *cpp)
{
int result = cpp->space_per_indent == 0 ? 4 : cpp->space_per_indent;
return result;
}
void Dqn_MDCppFile_LineBeginV(Dqn_MDCppFile *cpp, char const *fmt, va_list args)
{
int spaces = cpp->indent * Dqn_MDCppFile_SpacePerIndent(cpp);
fprintf(cpp->file, "%*s", spaces, "");
vfprintf(cpp->file, fmt, args);
}
void Dqn_MDCppFile_LineBegin(Dqn_MDCppFile *cpp, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_MDCppFile_LineBeginV(cpp, fmt, args);
va_end(args);
}
void Dqn_MDCppFile_LineEnd(Dqn_MDCppFile *cpp, char const *fmt, ...)
{
if (fmt)
{
va_list args;
va_start(args, fmt);
vfprintf(cpp->file, fmt, args);
va_end(args);
}
fputc('\n', cpp->file);
}
void Dqn_MDCppFile_LineAdd(Dqn_MDCppFile *cpp, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(cpp->file, fmt, args);
va_end(args);
}
void Dqn_MDCppFile_LineV(Dqn_MDCppFile *cpp, char const *fmt, va_list args)
{
Dqn_MDCppFile_LineBeginV(cpp, fmt, args);
Dqn_MDCppFile_LineEnd(cpp, nullptr);
}
void Dqn_MDCppFile_Line(Dqn_MDCppFile *cpp, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_MDCppFile_LineBeginV(cpp, fmt, args);
Dqn_MDCppFile_LineEnd(cpp, nullptr);
va_end(args);
}
void Dqn_MDCppFile_NewLine(Dqn_MDCppFile *cpp)
{
fputc('\n', cpp->file);
}
void Dqn_MDCppFile_Indent(Dqn_MDCppFile *cpp)
{
cpp->indent++;
}
void Dqn_MDCppFile_Unindent(Dqn_MDCppFile *cpp)
{
cpp->indent--;
MD_Assert(cpp->indent >= 0);
}
void Dqn_MDCppFile_BeginBlock(Dqn_MDCppFile *cpp, char const *fmt, ...)
{
if (fmt)
{
va_list args;
va_start(args, fmt);
Dqn_MDCppFile_LineV(cpp, fmt, args);
va_end(args);
}
Dqn_MDCppFile_Line(cpp, "{");
Dqn_MDCppFile_Indent(cpp);
}
void Dqn_MDCppFile_EndBlock(Dqn_MDCppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block)
{
Dqn_MDCppFile_Unindent(cpp);
Dqn_MDCppFile_Line(cpp, trailing_semicolon ? "};" : "}");
if (new_line_on_next_block) fputc('\n', cpp->file);
}
// -----------------------------------------------------------------------------
// MD Helpers
// -----------------------------------------------------------------------------
static bool MD_NodeHasArrayNotation(MD_Node const *node)
{
unsigned node_brackets = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasBracketRight;
bool result = (node->flags & node_brackets) == node_brackets;
return result;
}
// -----------------------------------------------------------------------------
// Code
// -----------------------------------------------------------------------------
void Dqn_MD_CodeGen(char const *file_path, char const *output_name, unsigned int flags)
{
MD_String8 h_file_name = MD_S8Fmt("%s.h", output_name);
MD_String8 cpp_file_name = MD_S8Fmt("%s.cpp", output_name);
Dqn_MDCppFile cpp = {};
Dqn_MDCppFile header = {};
cpp.file = fopen(DQN_MD_CAST(char const *)cpp_file_name.str, "w+b");
header.file = fopen(DQN_MD_CAST(char const *)h_file_name.str, "w+b");
fprintf(stdout, "Generating meta file from %s to %.*s and %.*s\n", file_path, MD_S8VArg(cpp_file_name), MD_S8VArg(h_file_name));
if (cpp.file && header.file)
{
MD_ParseResult parse = MD_ParseWholeFile(MD_S8CString(DQN_MD_CAST(char *)file_path));
if (flags & Dqn_MD_CodeGenFlag_CEnum)
Dqn_MD_GenerateCEnum(&header, &parse, MD_S8Lit("json_endpoint"), "INS_QueryType");
for (MD_EachNode(struct_node, parse.node->first_child))
{
bool requires_arena = false;
if (flags & Dqn_MD_CodeGenFlag_CppStruct)
Dqn_MD_GenerateCppStruct(&header, struct_node, &requires_arena);
if (flags & Dqn_MD_CodeGenFlag_ImGuiFunction)
Dqn_MD_GenerateImGuiFunction(&header, struct_node, true /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_JsonParsingFunction)
Dqn_MD_GenerateJsonParsingFunction(&header, struct_node, requires_arena, true /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_CurlQueryFunction)
Dqn_MD_GenerateCurlQueryFunction(&header, struct_node, true /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_JsonEndpointURLFunction)
Dqn_MD_GenerateJsonEndpointURLFunction(&header, struct_node, true /*header_file*/);
Dqn_MDCppFile_NewLine(&header);
if (flags & Dqn_MD_CodeGenFlag_ImGuiFunction)
Dqn_MD_GenerateImGuiFunction(&cpp, struct_node, false /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_JsonParsingFunction)
Dqn_MD_GenerateJsonParsingFunction(&cpp, struct_node, requires_arena, false /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_CurlQueryFunction)
Dqn_MD_GenerateCurlQueryFunction(&cpp, struct_node, false /*header_file*/);
if (flags & Dqn_MD_CodeGenFlag_JsonEndpointURLFunction)
Dqn_MD_GenerateJsonEndpointURLFunction(&cpp, struct_node, false /*header_file*/);
}
}
if (cpp.file) fclose(cpp.file);
if (header.file) fclose(header.file);
}
MD_String8 Dqn_MD__ConvertTypeToCppType(MD_String8 type)
{
MD_String8 result = type;
if (MD_S8Match(type, MD_S8Lit("u8"), 0)) { result = MD_S8Lit("Dqn_u8"); }
else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) { result = MD_S8Lit("Dqn_u16"); }
else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) { result = MD_S8Lit("Dqn_u32"); }
else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) { result = MD_S8Lit("Dqn_u64"); }
else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) { result = MD_S8Lit("intx::uint128"); }
else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) { result = MD_S8Lit("Dqn_i8"); }
else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) { result = MD_S8Lit("Dqn_i16"); }
else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) { result = MD_S8Lit("Dqn_i32"); }
else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) { result = MD_S8Lit("Dqn_i64"); }
else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) { result = MD_S8Lit("Dqn_f32"); }
else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) { result = MD_S8Lit("Dqn_f64"); }
else if (MD_S8Match(type, MD_S8Lit("string"), 0)) { result = MD_S8Lit("Dqn_String"); }
else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) { result = MD_S8Lit("Dqn_b32"); }
return result;
}
bool Dqn_MD__TypeIsUnsignedInt(MD_String8 type)
{
bool result = MD_S8Match(type, MD_S8Lit("u8"), 0) ||
MD_S8Match(type, MD_S8Lit("u16"), 0) ||
MD_S8Match(type, MD_S8Lit("u32"), 0) ||
MD_S8Match(type, MD_S8Lit("u64"), 0) ||
MD_S8Match(type, MD_S8Lit("u128"), 0);
return result;
}
bool Dqn_MD__TypeIsSignedInt(MD_String8 type)
{
bool result = MD_S8Match(type, MD_S8Lit("i8"), 0) ||
MD_S8Match(type, MD_S8Lit("i16"), 0) ||
MD_S8Match(type, MD_S8Lit("i32"), 0) ||
MD_S8Match(type, MD_S8Lit("i64"), 0) ||
MD_S8Match(type, MD_S8Lit("i128"), 0);
return result;
}
MD_String8 Dqn_MD__ConvertTypeToFmtString(MD_String8 type)
{
MD_String8 result = type;
if (MD_S8Match(type, MD_S8Lit("u8"), 0)) { result = MD_S8Lit("%I64u"); }
else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) { result = MD_S8Lit("%I64u"); }
else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) { result = MD_S8Lit("%I64u"); }
else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) { result = MD_S8Lit("%I64u"); }
else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) { result = MD_S8Lit("%I64d"); }
else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) { result = MD_S8Lit("%I64d"); }
else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) { result = MD_S8Lit("%I64d"); }
else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) { result = MD_S8Lit("%I64d"); }
else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) { result = MD_S8Lit("%f"); }
else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) { result = MD_S8Lit("%f"); }
else if (MD_S8Match(type, MD_S8Lit("string"), 0)) { result = MD_S8Lit("DQN_STRING_FMT(%.*s)"); }
else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) { result = MD_S8Lit("%s"); }
return result;
}
void Dqn_MD_GenerateCppStruct(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool *requires_arena)
{
Dqn_MDCppFile_BeginBlock(cpp, "struct %.*s", MD_S8VArg(struct_node->string));
for (MD_EachNode(field, struct_node->first_child))
{
MD_Node *type_node = MD_NodeFromString(field->first_child, field->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0);
MD_String8 type = type_node->first_child->string;
MD_Assert(!MD_NodeIsNil(type_node));
MD_String8 cpp_type = Dqn_MD__ConvertTypeToCppType(type);
if (MD_S8Match(type, MD_S8Lit("string"), 0)) *requires_arena = true;
bool const is_array = MD_NodeHasArrayNotation(type_node->first_child->next);
if (is_array)
{
*requires_arena = true;
Dqn_MDCppFile_Line(cpp, "Dqn_Array<%.*s> %.*s;", MD_S8VArg(cpp_type), MD_S8VArg(field->string));
}
else
{
Dqn_MDCppFile_Line(cpp, "%.*s %.*s;", MD_S8VArg(cpp_type), MD_S8VArg(field->string));
}
}
Dqn_MDCppFile_EndStructBlock(cpp);
}
void Dqn_MD_GenerateCEnum(Dqn_MDCppFile *cpp, MD_ParseResult *parse, MD_String8 required_tag, char const *name)
{
Dqn_MDCppFile_BeginBlock(cpp, "enum %s", name);
for (MD_EachNode(struct_node, parse->node->first_child))
{
if (MD_NodeHasTag(struct_node, required_tag, DQN_MD_CAST(MD_MatchFlags)0))
Dqn_MDCppFile_Line(cpp, "%s_%.*s,", name, MD_S8VArg(struct_node->string));
}
Dqn_MDCppFile_Line(cpp, "%s_Count", name);
Dqn_MDCppFile_EndEnumBlock(cpp);
}
void Dqn_MD_GenerateImGuiFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool header_file)
{
const MD_String8 VAL_STR = MD_S8Lit("val");
Dqn_MDCppFile_LineBegin(cpp,
"void %.*sImGui(%.*s const *%.*s)",
MD_S8VArg(struct_node->string),
MD_S8VArg(struct_node->string),
MD_S8VArg(VAL_STR));
if (header_file)
{
Dqn_MDCppFile_LineEnd(cpp, ";");
return;
}
Dqn_MDCppFile_BeginBlock(cpp, nullptr);
for (MD_EachNode(field_node, struct_node->first_child))
{
MD_Node *type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0);
MD_String8 type = type_node->first_child->string;
MD_String8 field = field_node->string;
MD_Assert(!MD_NodeIsNil(type_node));
bool is_array = MD_NodeHasArrayNotation(type_node->first_child->next);
MD_String8 param = VAL_STR;
if (is_array)
{
Dqn_MDCppFile_BeginBlock(cpp, "for (%.*s const &it : val->%.*s)", MD_S8VArg(type), MD_S8VArg(field));
}
if (is_array)
{
if (MD_S8Match(type, MD_S8Lit("u8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", *it);)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("string"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%.*s", DQN_STRING_FMT(*it));)", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%s", *it ? "true" : "false");)", MD_S8VArg(field));
else Dqn_MDCppFile_Line(cpp, R"(%.*sImGui(&it);)", MD_S8VArg(type));
}
else
{
if (MD_S8Match(type, MD_S8Lit("u8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("string"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%.*s", DQN_STRING_FMT(%.*s->%.*s));)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%s", %.*s->%.*s ? "true" : "false");)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field));
else Dqn_MDCppFile_Line(cpp, R"(%.*sImGui(&%.*s->%.*s);)", MD_S8VArg(type), MD_S8VArg(param), MD_S8VArg(field));
}
if (is_array)
Dqn_MDCppFile_EndForBlock(cpp);
}
Dqn_MDCppFile_EndFuncBlock(cpp);
}
void Dqn_MD_GenerateJsonParsingFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool requires_arena, bool header_file)
{
Dqn_MDCppFile_LineBegin(cpp, "%.*s %.*sParse(Dqn_String json, Dqn_JsmnErrorHandle *err_handle, Dqn_ArenaAllocator *temp_arena", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string));
if (requires_arena)
Dqn_MDCppFile_LineAdd(cpp, ", Dqn_ArenaAllocator *arena");
if (header_file)
{
Dqn_MDCppFile_LineEnd(cpp, ");");
}
else
{
Dqn_MDCppFile_LineEnd(cpp, ")");
Dqn_MDCppFile_BeginBlock(cpp, nullptr);
Dqn_MDCppFile_Line(cpp, "jsmn_parser parser = {};");
Dqn_MDCppFile_Line(cpp, "jsmn_init(&parser);");
Dqn_MDCppFile_NewLine(cpp);
Dqn_MDCppFile_Line(cpp, "int tokens_required = jsmn_parse(&parser, json.str, json.size, nullptr, 0);");
Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(tokens_required > 0);");
Dqn_MDCppFile_Line(cpp, "auto *tokens = Dqn_ArenaAllocator_NewArray(temp_arena, jsmntok_t, tokens_required, Dqn_ZeroMem::No);");
Dqn_MDCppFile_NewLine(cpp);
Dqn_MDCppFile_Line(cpp, "jsmn_init(&parser);");
Dqn_MDCppFile_Line(cpp, "jsmn_parse(&parser, json.str, json.size, tokens, tokens_required);");
Dqn_MDCppFile_Line(cpp, "jsmntok_t *it = tokens + 0;");
Dqn_MDCppFile_NewLine(cpp);
Dqn_MDCppFile_Line(cpp, "%.*s result = %.*sParseTokens(&it, err_handle, json, arena);", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string));
Dqn_MDCppFile_Line(cpp, "return result;");
Dqn_MDCppFile_EndFuncBlock(cpp);
}
Dqn_MDCppFile_LineBegin(cpp, "%.*s %.*sParseTokens(jsmntok_t **start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string));
if (requires_arena)
Dqn_MDCppFile_LineAdd(cpp, ", Dqn_ArenaAllocator *arena");
if (header_file)
{
Dqn_MDCppFile_LineEnd(cpp, ");");
return;
}
Dqn_MDCppFile_LineEnd(cpp, ")");
Dqn_MDCppFile_BeginBlock(cpp, nullptr);
Dqn_MDCppFile_Line(cpp, "%.*s result = {};", MD_S8VArg(struct_node->string));
Dqn_MDCppFile_Line(cpp, "jsmntok_t *it = (*start_it);");
Dqn_MDCppFile_Line(cpp, "if (!Dqn_JsmnToken_ExpectObject(*it, err_handle)) return result;");
Dqn_MDCppFile_Line(cpp, "jsmntok_t *object = it++;");
Dqn_MDCppFile_NewLine(cpp);
Dqn_MDCppFile_BeginBlock(cpp, "for (int index = 0; index < object->size; index++)");
Dqn_MDCppFile_Line(cpp, "jsmntok_t *key = it++;");
Dqn_MDCppFile_Line(cpp, "jsmntok_t *value = it++;");
Dqn_MDCppFile_Line(cpp, "Dqn_String key_str = Dqn_JsmnToken_String(json, *key);");
Dqn_MDCppFile_Line(cpp, "Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str;");
Dqn_MDCppFile_NewLine(cpp);
MD_String8 const IF_BRANCH = MD_S8Lit("if");
MD_String8 const ELSE_IF_BRANCH = MD_S8Lit("else if");
MD_String8 branch_str = IF_BRANCH;
for (MD_EachNode(field_node, struct_node->first_child))
{
MD_Node *type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0);
MD_Node *json_type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("json_type"), (MD_MatchFlags)0);
MD_Node *json_key_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("json_key"), (MD_MatchFlags)0);
MD_String8 field = field_node->string;
MD_String8 type = type_node->first_child->string;
MD_String8 json_type = json_type_node->first_child->string;
MD_String8 json_key = MD_NodeIsNil(json_key_node) ? field : json_key_node->first_child->string;
MD_Assert(!MD_NodeIsNil(type_node));
//------------------------------------------------------------------
// Convert the type's in our .mdesk struct into JSON parsing code
//------------------------------------------------------------------
Dqn_MDCppFile_BeginBlock(
cpp, "%.*s (key_str == DQN_STRING(\"%.*s\"))", MD_S8VArg(branch_str), MD_S8VArg(json_key));
branch_str = ELSE_IF_BRANCH;
if (MD_S8Match(json_type, MD_S8Lit("array"), (MD_MatchFlags)0))
{
Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectArray(*value, err_handle))");
Dqn_MDCppFile_Line(cpp, R"(result.%.*s = Dqn_Array_InitWithArenaNoGrow(arena, %.*s, value->size, 0, Dqn_ZeroMem::Yes);)", MD_S8VArg(field), MD_S8VArg(type));
Dqn_MDCppFile_BeginBlock(cpp, "for (int array_index = 0; array_index < value->size; array_index++)");
Dqn_MDCppFile_Line(cpp,
R"(auto *%.*s_entry = Dqn_Array_Make(&result.%.*s, 1);)",
MD_S8VArg(field),
MD_S8VArg(field));
// TODO(dqn): This doesn't work if the type is not a custom type ...
Dqn_MDCppFile_Line(cpp,
R"(*%.*s_entry = %.*sParseTokens(&it, err_handle, json, arena);)",
MD_S8VArg(field),
MD_S8VArg(type));
Dqn_MDCppFile_EndIfBlock(cpp);
Dqn_MDCppFile_EndForBlock(cpp);
}
else if (MD_S8Match(json_type, MD_S8Lit("object"), (MD_MatchFlags)0))
{
Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectObject(*value, err_handle))");
Dqn_MDCppFile_Line(cpp, "it = value; // Rewind the iterator to the object");
Dqn_MDCppFile_Line(cpp, "result.%.*s = %.*sParseTokens(&it, err_handle, json);", MD_S8VArg(field), MD_S8VArg(type));
Dqn_MDCppFile_EndIfBlock(cpp);
}
else
{
//--------------------------------------------------------------
// Enforce that the JSON token matches the expected type
//--------------------------------------------------------------
if (MD_S8Match(json_type, MD_S8Lit("number"), (MD_MatchFlags)0))
Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectNumber(json, *value, err_handle))");
else if (MD_S8Match(json_type, MD_S8Lit("string"), (MD_MatchFlags)0))
Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectString(*value, err_handle))");
else
DQN_MD_INVALID_CODE_PATH;
//--------------------------------------------------------------
// Convert JSON token to our struct's expecting type
//--------------------------------------------------------------
if (MD_S8Match(type, MD_S8Lit("f32"), (MD_MatchFlags)0))
Dqn_MDCppFile_Line(cpp, "result.%.*s = DQN_CAST(Dqn_f32)atof(value_str.str);", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("f64"), (MD_MatchFlags)0))
Dqn_MDCppFile_Line(cpp, "result.%.*s = atof(value_str.str);", MD_S8VArg(field));
else if (MD_S8Match(type, MD_S8Lit("u8"), (MD_MatchFlags)0) ||
MD_S8Match(type, MD_S8Lit("u16"), (MD_MatchFlags)0) ||
MD_S8Match(type, MD_S8Lit("u32"), (MD_MatchFlags)0) ||
MD_S8Match(type, MD_S8Lit("u64"), (MD_MatchFlags)0))
{
if (MD_S8Match(type, MD_S8Lit("u64"), (MD_MatchFlags)0))
{
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = Dqn_String_ToU64(value_str);)",
MD_S8VArg(field));
}
else
{
// We need to truncate the deserialized type to write it
// to our struct.
// NOTE: This bit capitalises the types, i.e. turns u16
// -> U16, which matches our function naming convention
// for Dqn_Safe_TruncateU64To<Type> and co.
char type_all_caps[4] = {};
memcpy(type_all_caps, type.str, type.size);
type_all_caps[0] = MD_CharToUpper(type_all_caps[0]);
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = Dqn_Safe_TruncateU64To%s(Dqn_String_ToU64(value_str));)",
MD_S8VArg(field),
type_all_caps);
}
}
else if (MD_S8Match(type, MD_S8Lit("u128"), 0))
{
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = Dqn_U128_FromString(value_str.str, Dqn_Safe_TruncateU64ToInt(value_str.size));)",
MD_S8VArg(field));
}
else if (MD_S8Match(type, MD_S8Lit("i8"), 0) ||
MD_S8Match(type, MD_S8Lit("i16"), 0) ||
MD_S8Match(type, MD_S8Lit("i32"), 0) ||
MD_S8Match(type, MD_S8Lit("i64"), 0))
{
char type_all_caps[4] = {};
memcpy(type_all_caps, type.str, type.size);
type_all_caps[0] = MD_CharToUpper(type_all_caps[0]);
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = Dqn_Safe_TruncateU64To%s(Dqn_String_ToU64(value_str));)",
MD_S8VArg(field),
type_all_caps);
}
else if (MD_S8Match(type, MD_S8Lit("string"), 0))
{
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = Dqn_String_Copy(value_str, arena);)",
MD_S8VArg(field));
}
else if (MD_S8Match(type, MD_S8Lit("bool"), 0))
{
Dqn_MDCppFile_Line(
cpp,
R"(result.%.*s = DQN_CAST(Dqn_b32)Dqn_String_ToU64(value_str);)",
MD_S8VArg(field));
}
else
{
// NOTE: Unhandled 'json_type' specified in the .mdesk file
DQN_MD_INVALID_CODE_PATH;
}
Dqn_MDCppFile_EndIfBlock(cpp);
}
Dqn_MDCppFile_EndIfBlock(cpp);
}
if (struct_node->first_child)
{
Dqn_MDCppFile_BeginBlock(cpp, "else");
Dqn_MDCppFile_Line (cpp, "// Potential unhandled json object and value, advance the iterator accordingly");
Dqn_MDCppFile_Line (cpp, "DQN_ASSERT(key->type == JSMN_STRING);");
Dqn_MDCppFile_BeginBlock(cpp, "if (value->type == JSMN_OBJECT)");
Dqn_MDCppFile_Line (cpp, "it = Dqn_JsmnToken_AdvanceItPastObject(value, err_handle, json);");
Dqn_MDCppFile_EndIfBlock(cpp);
Dqn_MDCppFile_BeginBlock(cpp, "else if (value->type == JSMN_ARRAY)");
Dqn_MDCppFile_Line (cpp, "it = Dqn_JsmnToken_AdvanceItPastArray(value, err_handle, json);");
Dqn_MDCppFile_EndIfBlock(cpp);
Dqn_MDCppFile_EndIfBlock(cpp);
}
Dqn_MDCppFile_EndForBlock(cpp);
Dqn_MDCppFile_Line(cpp, "*start_it = it;");
Dqn_MDCppFile_Line(cpp, "return result;");
Dqn_MDCppFile_EndFuncBlock(cpp);
}
void Dqn_MD_GenerateJsonEndpointURLFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file)
{
MD_Node *json_endpoint_node = MD_NodeFromString(struct_node->first_tag, struct_node->last_tag + 1, MD_S8Lit("json_endpoint"), (MD_MatchFlags)0);
if (MD_NodeIsNil(json_endpoint_node))
return;
if (MD_NodeIsNil(json_endpoint_node->first_child))
{
DQN_MD_INVALID_CODE_PATH;
return;
}
MD_Node *url_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("url"), (MD_MatchFlags)0);
MD_Node *params_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("params"), (MD_MatchFlags)0);
MD_String8 url = url_node->first_child->string;
if (MD_NodeIsNil(url_node) || MD_NodeIsNil(params_node))
{
DQN_MD_INVALID_CODE_PATH;
return;
}
Dqn_MDCppFile_LineBegin(cpp, "Dqn_String %.*sURL(Dqn_ArenaAllocator *arena", MD_S8VArg(struct_node->string));
if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", ");
for (MD_EachNode(param_node, params_node->first_child))
{
MD_String8 name = param_node->string;
MD_String8 type = param_node->first_child->string;
MD_Assert(!MD_NodeIsNil(param_node->first_child));
Dqn_MDCppFile_LineAdd(cpp, "%.*s %.*s", MD_S8VArg(Dqn_MD__ConvertTypeToCppType(type)), MD_S8VArg(name));
if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", ");
}
if (header_file)
{
Dqn_MDCppFile_LineEnd(cpp, ");");
return;
}
Dqn_MDCppFile_LineEnd(cpp, ")");
Dqn_MDCppFile_BeginBlock(cpp, nullptr);
Dqn_MDCppFile_LineBegin(cpp, R"(Dqn_String result = Dqn_String_Fmt(arena, "%.*s")", MD_S8VArg(url));
if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", ");
for (MD_EachNode(param_node, params_node->first_child))
{
MD_String8 name = param_node->string;
MD_String8 type = param_node->first_child->string;
Dqn_MD__ConvertTypeToFmtString(type);
if (MD_S8Match(type, MD_S8Lit("string"), 0))
Dqn_MDCppFile_LineAdd(cpp, "DQN_STRING_FMT(%.*s)", MD_S8VArg(name));
else
Dqn_MDCppFile_LineAdd(cpp, "%.*s", MD_S8VArg(name));
if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", ");
}
Dqn_MDCppFile_LineEnd(cpp, ");");
Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(result.str);");
Dqn_MDCppFile_Line(cpp, "return result;");
Dqn_MDCppFile_EndFuncBlock(cpp);
}
void Dqn_MD_GenerateCurlQueryFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file)
{
MD_Node *json_endpoint_node = MD_NodeFromString(struct_node->first_tag, struct_node->last_tag + 1, MD_S8Lit("json_endpoint"), (MD_MatchFlags)0);
if (MD_NodeIsNil(json_endpoint_node))
return;
if (MD_NodeIsNil(json_endpoint_node->first_child))
{
DQN_MD_INVALID_CODE_PATH;
return;
}
MD_Node *url_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("url"), (MD_MatchFlags)0);
MD_Node *params_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("params"), (MD_MatchFlags)0);
MD_String8 url = url_node->first_child->string;
if (MD_NodeIsNil(url_node) || MD_NodeIsNil(params_node))
{
DQN_MD_INVALID_CODE_PATH;
return;
}
Dqn_MDCppFile_LineBegin(cpp, "Dqn_b32 %.*sQuery(struct INS_Core *insight", MD_S8VArg(struct_node->string));
if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", ");
for (MD_EachNode(param_node, params_node->first_child))
{
MD_String8 name = param_node->string;
MD_String8 type = param_node->first_child->string;
MD_Assert(!MD_NodeIsNil(param_node->first_child));
Dqn_MDCppFile_LineAdd(cpp, "%.*s %.*s", MD_S8VArg(Dqn_MD__ConvertTypeToCppType(type)), MD_S8VArg(name));
if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", ");
}
if (header_file)
{
Dqn_MDCppFile_LineEnd(cpp, ");");
return;
}
Dqn_MDCppFile_LineEnd(cpp, ")");
Dqn_MDCppFile_BeginBlock(cpp, nullptr);
Dqn_MDCppFile_Line(cpp, "Dqn_FixedString<1024> url = {};");
Dqn_MDCppFile_LineBegin(cpp, R"(Dqn_FixedString_AppendFmt(&url, "%.*s")", MD_S8VArg(url));
if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", ");
for (MD_EachNode(param_node, params_node->first_child))
{
MD_String8 name = param_node->string;
Dqn_MDCppFile_LineAdd(cpp, "DQN_STRING_FMT(%.*s)", MD_S8VArg(name));
if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", ");
}
Dqn_MDCppFile_LineEnd(cpp, ");");
Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(url.size);");
Dqn_MDCppFile_Line(cpp, "INS_CurlQuery *query = INS_Curl_QueueQuery(insight, INS_QueryType_%.*s, Dqn_FixedString_ToString(&url));", MD_S8VArg(struct_node->string));
Dqn_MDCppFile_Line(cpp, "Dqn_b32 result = query != nullptr;");
Dqn_MDCppFile_Line(cpp, "return result;");
Dqn_MDCppFile_EndFuncBlock(cpp);
}
#endif // DQN_META_DESK_IMPLEMENTATION
#if defined(DQN_META_DESK_STANDALONE_PROGRAM)
int main(int argc, char **argv)
{
if (argc != 3)
{
fprintf(stdout, "Please pass a file name and output name\nUsage: metadesk <file> <output_name>\n");
return -1;
}
char *file = argv[1];
char *output_name = argv[2];
Dqn_MD_CodeGen(file, output_name, Dqn_MD_CodeGenFlag_All);
return 0;
}
#endif // DQN_META_DESK_STANDALONE_PROGRAM

95
Code/Dqn_Money.h Normal file
View File

@ -0,0 +1,95 @@
#if !defined(DQN_MONEY_H)
#define DQN_MONEY_H
#if !defined(DQN_U128_H)
#error You must include "Dqn_U128.h" before including "Dqn_Money.h"
#endif // DQN_U128_H
struct Dqn_MoneyString
{
char str[64];
int size;
};
int const DQN_MONEY_BTC_DECIMAL_PLACES = 8;
int const DQN_MONEY_ETH_DECIMAL_PLACES = 18;
int const DQN_MONEY_BNB_DECIMAL_PLACES = DQN_MONEY_ETH_DECIMAL_PLACES;
int const DQN_MONEY_DOLLAR_DECIMAL_PLACES = 2;
int const DQN_MONEY_OXEN_DECIMAL_PLACES = 9;
Dqn_MoneyString Dqn_MoneyU128ToString(intx::uint128 atomic_amount, intx::uint128 decimal_places, Dqn_b32 comma_sep = true);
#endif // DQN_MONEY_H
#if defined(DQN_MONEY_IMPLEMENTATION)
Dqn_MoneyString Dqn_MoneyU128ToString(intx::uint128 atomic_amount, intx::uint128 decimal_places, Dqn_b32 comma_sep)
{
DQN_HARD_ASSERT_MSG(decimal_places < 32, "// TODO(dqn): Verify what our limits for this are");
intx::uint128 atomic_units_per_currency = 1;
for (int place = 0; place < decimal_places; place++)
atomic_units_per_currency *= 10;
intx::uint128 atomic_part = atomic_amount % atomic_units_per_currency;
intx::uint128 whole_part = atomic_amount / atomic_units_per_currency;
Dqn_MoneyString string = {};
if (atomic_amount == 0)
{
string.str[string.size++] = '0';
string.str[string.size++] = '.';
string.str[string.size++] = '0';
}
else
{
int atomic_part_size = 0;
intx::uint128 atomic_copy = atomic_part;
// Skip any trailing 0s in the fractional part
for (; atomic_copy > 0; atomic_copy /= 10, atomic_part_size++)
{
char digit = (char)(atomic_copy % 10);
if (digit != 0) break;
}
// Write the fraction part into the string
for (; atomic_copy > 0; atomic_copy /= 10, atomic_part_size++)
{
char digit = (char)(atomic_copy % 10);
string.str[string.size++] = '0' + digit;
}
// If the string has a size we have a fractional number
if (string.size)
{
// Add zeros until the fractional part is complete
for (int digits = atomic_part_size; digits < decimal_places; digits++)
string.str[string.size++] = '0';
string.str[string.size++] = '.';
}
// Write the whole part into the string
if (whole_part == 0)
{
string.str[string.size++] = '0';
}
else
{
for (int digit_count = 0; whole_part > 0; digit_count++)
{
if (comma_sep && (digit_count != 0) && (digit_count % 3 == 0))
string.str[string.size++] = ',';
char digit = (char)(whole_part % 10);
string.str[string.size++] = '0' + digit;
whole_part /= 10;
}
}
}
Dqn_MoneyString result = {};
for (int i = string.size - 1; i >= 0; i--)
result.str[result.size++] = string.str[i];
return result;
}
#endif // DQN_MONEY_IMPLEMENTATION

View File

@ -31,6 +31,9 @@ struct Dqn_TestingState
Dqn_ArenaAllocator arena;
};
static int g_dqn_test_total_good_tests;
static int g_dqn_test_total_tests;
#if defined(DQN_TEST_NO_ANSI_COLORS)
#define DQN_TEST_ANSI_COLOR_RED
#define DQN_TEST_ANSI_COLOR_GREEN
@ -61,20 +64,12 @@ struct Dqn_TestingState
testing_state.test.scope_started = true; \
testing_state.num_tests_in_group++
// NOTE: Zero initialised allocators can be a null allocator if #define
// DQN_ALLOCATOR_DEFAULT_TO_NULL is defined, so handle this case specially
// by defaulting to the heap allocator which is the behaviour it would have
// used if the hash define was not used.
// In the macro below we ensure that the allocator is not null, this idiom is
// repeated whereever we zero initialise an allocator.
#define DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, name) \
fprintf(stdout, name "\n"); \
if (testing_state.arena.backup_allocator.type == Dqn_AllocatorType::Null) \
testing_state.arena.backup_allocator = Dqn_Allocator_InitWithHeap(); \
DQN_DEFER \
{ \
g_dqn_test_total_good_tests += testing_state.num_tests_ok_in_group; \
g_dqn_test_total_tests += testing_state.num_tests_in_group; \
Dqn_TestingState_PrintGroupResult(&testing_state); \
testing_state = {}; \
fprintf(stdout, "\n\n"); \
@ -85,7 +80,7 @@ struct Dqn_TestingState
if (!(expr)) \
{ \
testing_state.test.fail_expr = DQN_STRING(#expr); \
testing_state.test.fail_msg = Dqn_String_InitArenaFmt(&testing_state.arena, msg, ##__VA_ARGS__); \
testing_state.test.fail_msg = Dqn_String_Fmt(&testing_state.arena, msg, ##__VA_ARGS__); \
}
#define DQN_TEST_EXPECT(testing_state, expr) DQN_TEST_EXPECT_MSG(testing_state, expr, "")
@ -138,6 +133,7 @@ void Dqn_TestState_PrintResult(Dqn_TestState const *result)
void Dqn_Test_Allocator()
{
#if 0
Dqn_TestingState testing_state = {};
DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_Allocator");
@ -235,6 +231,7 @@ void Dqn_Test_Allocator()
DQN_TEST_EXPECT_MSG(testing_state, metadata.offset <= MAX_OFFSET, "metadata.offset: %u, MAX_OFFSET: %u", metadata.offset, MAX_OFFSET);
}
}
#endif
}
void Dqn_Test_Array()
@ -325,24 +322,14 @@ void Dqn_Test_Array()
DQN_TEST_EXPECT_MSG(testing_state, array.size == 1, "array.size: %d", array.size);
DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max);
}
{
DQN_TEST_START_SCOPE(testing_state, "Fixed Memory: Test free on fixed memory array does nothing");
int memory[4] = {};
Dqn_Array<int> array = Dqn_Array_InitWithMemory(memory, Dqn_ArrayCount(memory), 0 /*size*/);
DQN_DEFER { Dqn_Array_Free(&array); };
}
}
// NOTE: Dynamic Memory: Dqn_Array
{
{
DQN_TEST_START_SCOPE(testing_state, "Dynamic Memory: Reserve and check over commit reallocates");
Dqn_ArenaAllocator arena = {};
Dqn_Array<int> array = {};
if (array.allocator.type == Dqn_AllocatorType::Null)
array.allocator = Dqn_Allocator_InitWithHeap();
DQN_DEFER { Dqn_Array_Free(&array); };
array.arena = &arena;
Dqn_Array_Reserve(&array, 4);
DQN_TEST_EXPECT_MSG(testing_state, array.size == 0, "array.size: %d", array.size);
@ -361,7 +348,8 @@ void Dqn_Test_Array()
DQN_TEST_EXPECT_MSG(testing_state, array.data[4] == 5, "array.data: %d", array.data[4]);
DQN_TEST_EXPECT_MSG(testing_state, array.size == 5, "array.size: %d", array.size);
DQN_TEST_EXPECT_MSG(testing_state, array.max >= 5, "array.max: %d", array.max);
}
Dqn_ArenaAllocator_Free(&arena);
}
}
@ -468,9 +456,160 @@ void Dqn_Test_M4()
}
}
void Dqn_Test_Map()
{
Dqn_TestingState testing_state = {};
DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_Map");
Dqn_ArenaAllocator arena = {};
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item to map");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_MapEntry<int> *entry = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5 /*value*/, Dqn_MapCollideRule::Overwrite);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64d", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 3, "hash: %I64u", entry->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry->value == 5, "value: %d", entry->value);
DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add l-value item to map");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
int value = 5;
Dqn_MapEntry<int> *entry = Dqn_Map_Add(&map, 3 /*hash*/, value, Dqn_MapCollideRule::Overwrite);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64d", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 3, "hash: %I64u", entry->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry->value == 5, "value: %d", entry->value);
DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and overwrite on collision");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Overwrite);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, entry_a == entry_b, "Expected entry to be overwritten");
DQN_TEST_EXPECT_MSG(testing_state, entry_b->hash == 4, "hash: %I64u", entry_b->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry_b->value == 6, "value: %d", entry_b->value);
DQN_TEST_EXPECT_MSG(testing_state, entry_b->next == nullptr, "next: %p", entry_b->next);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and fail on collision");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Fail);
DQN_TEST_EXPECT_MSG(testing_state, entry_b == nullptr, "Expected entry to be overwritten");
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and chain on collision");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 1, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, entry_a != entry_b, "Expected colliding entry to be chained");
DQN_TEST_EXPECT_MSG(testing_state, entry_a->next == entry_b, "Expected chained entry to be next to our first map entry");
DQN_TEST_EXPECT_MSG(testing_state, entry_b->hash == 4, "hash: %I64u", entry_b->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry_b->value == 6, "value: %d", entry_b->value);
DQN_TEST_EXPECT_MSG(testing_state, entry_b->next == nullptr, "next: %p", entry_b->next);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and get them back out again");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
Dqn_MapEntry<int> *entry_a_copy = Dqn_Map_Get(&map, 3 /*hash*/);
Dqn_MapEntry<int> *entry_b_copy = Dqn_Map_Get(&map, 4 /*hash*/);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 1, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT(testing_state, entry_a_copy == entry_a);
DQN_TEST_EXPECT(testing_state, entry_b_copy == entry_b);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and erase it");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
Dqn_Map_Get(&map, 3 /*hash*/);
Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem::No);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list != nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->hash == 3, "Entry should not be zeroed out on erase");
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->value == 5, "Entry should not be zeroed out on erase");
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->next == nullptr, "This should be the first and only entry in the free list");
Dqn_MapEntry<int> *entry = Dqn_Map_Get(&map, 4 /*hash*/);
DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 4, "hash: %I64u", entry->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry->value == 6, "value: %d", entry->value);
DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Add r-value item and erase it, zeroing the memory out");
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(&arena, 1);
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
Dqn_Map_Get(&map, 3 /*hash*/);
Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem::Yes);
DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size);
DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count);
DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list != nullptr, "free_list: %p", map.free_list);
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->hash == 0, "Entry should be zeroed out on erase");
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->value == 0, "Entry should be zeroed out on erase");
DQN_TEST_EXPECT_MSG(testing_state, map.free_list->next == nullptr, "This should be the first and only entry in the free list");
Dqn_MapEntry<int> *entry = Dqn_Map_Get(&map, 4 /*hash*/);
DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 4, "hash: %I64u", entry->hash);
DQN_TEST_EXPECT_MSG(testing_state, entry->value == 6, "value: %d", entry->value);
DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next);
Dqn_ArenaAllocator_Free(&arena);
}
// TODO(dqn): Test free list is chained correctly
// TODO(dqn): Test deleting 'b' from the list in the situation [map] - [a]->[b], we currently only test deleting a
}
void Dqn_Test_Intrinsics()
{
// TODO(doyle): We don't have meaningful tests here, but since
// TODO(dqn): We don't have meaningful tests here, but since
// atomics/intrinsics are implemented using macros we ensure the macro was
// written properly with these tests.
@ -812,24 +951,92 @@ void Dqn_Test_Str()
}
}
void Dqn_Test_String()
{
Dqn_TestingState testing_state = {};
DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_String");
{
DQN_TEST_START_SCOPE(testing_state, "Initialise with string literal w/ macro");
Dqn_String string = DQN_STRING("AB");
DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size);
DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap);
DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]);
}
{
DQN_TEST_START_SCOPE(testing_state, "Initialise with format string");
Dqn_ArenaAllocator arena = {};
Dqn_String string = Dqn_String_Fmt(&arena, "%s", "AB");
DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size);
DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap);
DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == 0, "string[2]: %c", string.str[2]);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Copy string");
Dqn_ArenaAllocator arena = {};
Dqn_String string = DQN_STRING("AB");
Dqn_String copy = Dqn_String_Copy(string, &arena);
DQN_TEST_EXPECT_MSG(testing_state, copy.size == 2, "size: %I64d", copy.size);
DQN_TEST_EXPECT_MSG(testing_state, copy.cap == 2, "cap: %I64d", copy.cap);
DQN_TEST_EXPECT_MSG(testing_state, copy.str[0] == 'A', "copy[0]: %c", copy.str[0]);
DQN_TEST_EXPECT_MSG(testing_state, copy.str[1] == 'B', "copy[1]: %c", copy.str[1]);
DQN_TEST_EXPECT_MSG(testing_state, copy.str[2] == 0, "copy[2]: %c", copy.str[2]);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Trim whitespace around string");
Dqn_String string = Dqn_String_TrimWhitespaceAround(DQN_STRING(" AB "));
DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size);
DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap);
DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == ' ', "string[1]: %c", string.str[1]);
}
{
DQN_TEST_START_SCOPE(testing_state, "Allocate string from arena");
Dqn_ArenaAllocator arena = {};
Dqn_String string = Dqn_String_Allocate(&arena, 2, Dqn_ZeroMem::No);
DQN_TEST_EXPECT_MSG(testing_state, string.size == 0, "size: %I64d", string.size);
DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap);
Dqn_ArenaAllocator_Free(&arena);
}
{
DQN_TEST_START_SCOPE(testing_state, "Append to allocated string");
Dqn_ArenaAllocator arena = {};
Dqn_String string = Dqn_String_Allocate(&arena, 2, Dqn_ZeroMem::No);
Dqn_String_AppendFmt(&string, "%c", 'A');
Dqn_String_AppendFmt(&string, "%c", 'B');
DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size);
DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap);
DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]);
DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == 0, "string[2]: %c", string.str[2]);
}
}
void Dqn_Test_StringBuilder()
{
Dqn_TestingState testing_state = {};
DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_StringBuilder");
Dqn_Allocator allocator = Dqn_Allocator_InitWithHeap();
Dqn_ArenaAllocator arena = {};
// NOTE: Dqn_StringBuilder_Append
{
{
DQN_TEST_START_SCOPE(testing_state, "Append variable size strings and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append variable size strings and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_Append(&builder, "Abc", 1);
Dqn_StringBuilder_Append(&builder, "cd");
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "Acd";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -837,16 +1044,13 @@ void Dqn_Test_StringBuilder()
}
{
DQN_TEST_START_SCOPE(testing_state, "Append empty string and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append empty string and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_Append(&builder, "");
Dqn_StringBuilder_Append(&builder, "");
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -854,16 +1058,13 @@ void Dqn_Test_StringBuilder()
}
{
DQN_TEST_START_SCOPE(testing_state, "Append empty string onto string and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append empty string onto string and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_Append(&builder, "Acd");
Dqn_StringBuilder_Append(&builder, "");
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "Acd";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -871,15 +1072,12 @@ void Dqn_Test_StringBuilder()
}
{
DQN_TEST_START_SCOPE(testing_state, "Append nullptr and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append nullptr and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_Append(&builder, nullptr, 5);
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -887,17 +1085,15 @@ void Dqn_Test_StringBuilder()
}
{
DQN_TEST_START_SCOPE(testing_state, "Append and require new linked buffer and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append and require new linked buffer and build using heap arena");
Dqn_StringBuilder<2> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_InitWithArena(&builder, &arena);
Dqn_StringBuilder_Append(&builder, "A");
Dqn_StringBuilder_Append(&builder, "z"); // Should force a new memory block
Dqn_StringBuilder_Append(&builder, "tec");
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "Aztec";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -907,16 +1103,13 @@ void Dqn_Test_StringBuilder()
// NOTE: Dqn_StringBuilder_AppendChar
{
DQN_TEST_START_SCOPE(testing_state, "Append char and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append char and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_AppendChar(&builder, 'a');
Dqn_StringBuilder_AppendChar(&builder, 'b');
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "ab";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -926,16 +1119,13 @@ void Dqn_Test_StringBuilder()
// NOTE: Dqn_StringBuilder_AppendFmt
{
{
DQN_TEST_START_SCOPE(testing_state, "Append format string and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append format string and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_AppendFmt(&builder, "Number: %d, String: %s, ", 4, "Hello Sailor");
Dqn_StringBuilder_AppendFmt(&builder, "Extra Stuff");
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "Number: 4, String: Hello Sailor, Extra Stuff";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -943,15 +1133,12 @@ void Dqn_Test_StringBuilder()
}
{
DQN_TEST_START_SCOPE(testing_state, "Append nullptr format string and build using heap allocator");
DQN_TEST_START_SCOPE(testing_state, "Append nullptr format string and build using heap arena");
Dqn_StringBuilder<> builder = {};
if (builder.backup_allocator.type == Dqn_AllocatorType::Null)
builder.backup_allocator = Dqn_Allocator_InitWithHeap();
Dqn_StringBuilder_AppendFmt(&builder, nullptr);
Dqn_isize size = 0;
char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size);
DQN_DEFER { Dqn_Allocator_Free(&allocator, result); };
char *result = Dqn_StringBuilder_Build(&builder, &arena, &size);
DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); };
char const EXPECT_STR[] = "";
DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size);
@ -1001,10 +1188,13 @@ void Dqn_Test_RunSuite()
Dqn_Test_FixedString();
Dqn_Test_Intrinsics();
Dqn_Test_M4();
Dqn_Test_Map();
Dqn_Test_Rect();
Dqn_Test_Str();
Dqn_Test_String();
Dqn_Test_StringBuilder();
Dqn_Test_TicketMutex();
fprintf(stdout, "Summary: %d/%d tests succeeded\n", g_dqn_test_total_good_tests, g_dqn_test_total_tests);
}
#if defined(DQN_TEST_WITH_MAIN)

1039
Code/Dqn_U128.h Normal file

File diff suppressed because it is too large Load Diff