Add some more helper libraries
This commit is contained in:
parent
07499b1004
commit
5db8a60014
1833
Code/Dqn.h
1833
Code/Dqn.h
File diff suppressed because it is too large
Load Diff
80
Code/Dqn_Curl.h
Normal file
80
Code/Dqn_Curl.h
Normal 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
684
Code/Dqn_Jsmn.h
Normal 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
948
Code/Dqn_MetaDesk.h
Normal 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
95
Code/Dqn_Money.h
Normal 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
|
@ -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,43 +322,34 @@ 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_Array<int> array = {};
|
||||
if (array.allocator.type == Dqn_AllocatorType::Null)
|
||||
array.allocator = Dqn_Allocator_InitWithHeap();
|
||||
DQN_TEST_START_SCOPE(testing_state, "Dynamic Memory: Reserve and check over commit reallocates");
|
||||
Dqn_ArenaAllocator arena = {};
|
||||
Dqn_Array<int> array = {};
|
||||
array.arena = &arena;
|
||||
|
||||
DQN_DEFER { Dqn_Array_Free(&array); };
|
||||
Dqn_Array_Reserve(&array, 4);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.size == 0, "array.size: %d", array.size);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max);
|
||||
|
||||
Dqn_Array_Reserve(&array, 4);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.size == 0, "array.size: %d", array.size);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max);
|
||||
int DATA[] = {1, 2, 3, 4};
|
||||
Dqn_Array_AddArray(&array, DATA, Dqn_ArrayCount(DATA));
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[0] == 1, "array.data: %d", array.data[0]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[1] == 2, "array.data: %d", array.data[1]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[2] == 3, "array.data: %d", array.data[2]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[3] == 4, "array.data: %d", array.data[3]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.size == 4, "array.size: %d", array.size);
|
||||
|
||||
int DATA[] = {1, 2, 3, 4};
|
||||
Dqn_Array_AddArray(&array, DATA, Dqn_ArrayCount(DATA));
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[0] == 1, "array.data: %d", array.data[0]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[1] == 2, "array.data: %d", array.data[1]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[2] == 3, "array.data: %d", array.data[2]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.data[3] == 4, "array.data: %d", array.data[3]);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, array.size == 4, "array.size: %d", array.size);
|
||||
int *added_item = Dqn_Array_Add(&array, 5);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, *added_item == 5, "added_item: %d", *added_item);
|
||||
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);
|
||||
|
||||
int *added_item = Dqn_Array_Add(&array, 5);
|
||||
DQN_TEST_EXPECT_MSG(testing_state, *added_item == 5, "added_item: %d", *added_item);
|
||||
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
1039
Code/Dqn_U128.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user