300 lines
10 KiB
C
300 lines
10 KiB
C
#include "dn_json_builder.h"
|
|
|
|
static void DN_JSB_Indent_(DN_JSBBuilder *builder, char *buf, size_t buf_size, size_t depth)
|
|
{
|
|
for (size_t i = 0; i < depth; i++) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, " ");
|
|
}
|
|
}
|
|
|
|
DN_JSBDepth *DN_JSB_PeekDepth_(DN_JSBBuilder *builder, size_t offset)
|
|
{
|
|
DN_JSBDepth *result = 0;
|
|
if (builder->depth_count > offset)
|
|
result = &builder->depth[builder->depth_count - (1 + offset)];
|
|
return result;
|
|
}
|
|
|
|
bool DN_JSB_PushDepth_(DN_JSBBuilder *builder, DN_JSBDepthType type)
|
|
{
|
|
bool result = false;
|
|
if (builder->depth_count < builder->depth_max) {
|
|
DN_JSBDepth *depth = &builder->depth[builder->depth_count++];
|
|
depth->last_action = DN_JSBAction_Nil;
|
|
depth->type = type;
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_PushObject(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
if (top_depth && top_depth->type != DN_JSBDepthType_Array) {
|
|
if ((top_depth->last_action == DN_JSBAction_Value || top_depth->last_action == DN_JSBAction_PopArray || top_depth->last_action == DN_JSBAction_PushObject))
|
|
return DN_JSBResult_BadAPIUsage;
|
|
}
|
|
|
|
if (!DN_JSB_PushDepth_(builder, DN_JSBDepthType_Object))
|
|
return DN_JSBResult_DepthArrayOutOfMem;
|
|
|
|
bool leading_comma = top_depth && (top_depth->last_action != DN_JSBAction_Nil && top_depth->last_action != DN_JSBAction_Key);
|
|
if (builder->pretty && top_depth && top_depth->type == DN_JSBDepthType_Array) {
|
|
if (leading_comma) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, ",\n");
|
|
} else {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
}
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count - 1);
|
|
leading_comma = false;
|
|
}
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "%s{", leading_comma ? "," : "");
|
|
if (top_depth)
|
|
top_depth->last_action = DN_JSBAction_PushObject; // Record the push on the now, previous depth
|
|
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_PopObject(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
if (!top_depth || top_depth->type != DN_JSBDepthType_Object)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
bool had_children = top_depth->last_action != DN_JSBAction_Nil;
|
|
builder->depth_count--; // Remove the depth entry from the stack
|
|
|
|
DN_JSBDepth *new_top = DN_JSB_PeekDepth_(builder, 0);
|
|
if (new_top)
|
|
new_top->last_action = DN_JSBAction_PopObject;
|
|
|
|
if (builder->pretty && had_children) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count);
|
|
}
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "}");
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_PushArray(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
if (top_depth && top_depth->type != DN_JSBDepthType_Array) {
|
|
if (top_depth->last_action == DN_JSBAction_Value || top_depth->last_action == DN_JSBAction_PopArray || top_depth->last_action == DN_JSBAction_PushObject || top_depth->last_action == DN_JSBAction_PopObject)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
}
|
|
|
|
if (!DN_JSB_PushDepth_(builder, DN_JSBDepthType_Array))
|
|
return DN_JSBResult_DepthArrayOutOfMem;
|
|
|
|
bool leading_comma = top_depth && (top_depth->last_action != DN_JSBAction_Nil && top_depth->last_action != DN_JSBAction_Key);
|
|
if (builder->pretty && top_depth && top_depth->type == DN_JSBDepthType_Array) {
|
|
if (leading_comma) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, ",\n");
|
|
} else {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
}
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count - 1);
|
|
leading_comma = false;
|
|
}
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "%s[", leading_comma ? "," : "");
|
|
if (top_depth)
|
|
top_depth->last_action = DN_JSBAction_PushArray; // Record the push on the now, previous depth
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_PopArray(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
if (!top_depth || top_depth->type != DN_JSBDepthType_Array)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
bool had_children = top_depth->last_action != DN_JSBAction_Nil;
|
|
builder->depth_count--; // Remove the depth entry from the stack
|
|
DN_JSBDepth *new_top = DN_JSB_PeekDepth_(builder, 0);
|
|
if (new_top)
|
|
new_top->last_action = DN_JSBAction_PopArray;
|
|
|
|
if (builder->pretty && had_children) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count);
|
|
}
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "]");
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_KeyF(DN_JSBBuilder *builder, char *buf, size_t buf_size, char const *fmt, ...)
|
|
{
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
if (!top_depth || top_depth->last_action == DN_JSBAction_Key || top_depth->last_action == DN_JSBAction_PushArray)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
if (builder->pretty) {
|
|
if (top_depth->last_action != DN_JSBAction_Nil) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, ",\n");
|
|
} else {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
}
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count);
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\"");
|
|
DN_JSB_AppendFV_(builder, buf, buf_size, fmt, args);
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "\": ");
|
|
va_end(args);
|
|
if (result == DN_JSBResult_Success)
|
|
top_depth->last_action = DN_JSBAction_Key;
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "%s\"", top_depth->last_action != DN_JSBAction_Nil ? "," : "");
|
|
DN_JSB_AppendFV_(builder, buf, buf_size, fmt, args);
|
|
DN_JSBResult result = DN_JSB_AppendF_(builder, buf, buf_size, "\":");
|
|
va_end(args);
|
|
|
|
if (result == DN_JSBResult_Success)
|
|
top_depth->last_action = DN_JSBAction_Key;
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_ValueStringF(DN_JSBBuilder *builder, char *buf, size_t buf_size, char const *fmt, ...)
|
|
{
|
|
DN_JSBResult result = DN_JSB_BeginWriteValue_(builder, buf, buf_size);
|
|
if (result == DN_JSBResult_Success) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\"");
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_JSB_AppendFV_(builder, buf, buf_size, fmt, args);
|
|
va_end(args);
|
|
result = DN_JSB_AppendF_(builder, buf, buf_size, "\"");
|
|
DN_JSB_FinishWriteValue_(builder, result);
|
|
}
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_ValueNull(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBResult result = DN_JSB_BeginWriteValue_(builder, buf, buf_size);
|
|
if (result == DN_JSBResult_Success) {
|
|
result = DN_JSB_AppendF_(builder, buf, buf_size, "null");
|
|
DN_JSB_FinishWriteValue_(builder, result);
|
|
}
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_ValueBool(DN_JSBBuilder *builder, char *buf, size_t buf_size, bool flag)
|
|
{
|
|
DN_JSBResult result = DN_JSB_BeginWriteValue_(builder, buf, buf_size);
|
|
if (result == DN_JSBResult_Success) {
|
|
result = DN_JSB_AppendF_(builder, buf, buf_size, flag ? "true" : "false");
|
|
DN_JSB_FinishWriteValue_(builder, result);
|
|
}
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_ValueNumber(DN_JSBBuilder *builder, char *buf, size_t buf_size, double number)
|
|
{
|
|
DN_JSBResult result = DN_JSB_BeginWriteValue_(builder, buf, buf_size);
|
|
if (result == DN_JSBResult_Success) {
|
|
result = DN_JSB_AppendF_(builder, buf, buf_size, "%.17G", number);
|
|
DN_JSB_FinishWriteValue_(builder, result);
|
|
}
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_BeginWriteValue_(DN_JSBBuilder *builder, char *buf, size_t buf_size)
|
|
{
|
|
DN_JSBDepth *top = DN_JSB_PeekDepth_(builder, 0);
|
|
if (!top)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
bool is_key = top->last_action == DN_JSBAction_Key;
|
|
bool is_array = top->type == DN_JSBDepthType_Array;
|
|
if (!is_key && !is_array)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
DN_JSBResult result = DN_JSBResult_Success;
|
|
if (top->type == DN_JSBDepthType_Array) {
|
|
if (builder->pretty) {
|
|
if (top->last_action != DN_JSBAction_Nil) {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, ",\n");
|
|
} else {
|
|
DN_JSB_AppendF_(builder, buf, buf_size, "\n");
|
|
}
|
|
DN_JSB_Indent_(builder, buf, buf_size, builder->depth_count);
|
|
} else if (top->last_action != DN_JSBAction_Nil) {
|
|
result = DN_JSB_AppendF_(builder, buf, buf_size, ",");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void DN_JSB_FinishWriteValue_(DN_JSBBuilder *builder, DN_JSBResult last_value_raw_str_result)
|
|
{
|
|
if (last_value_raw_str_result == DN_JSBResult_Success) {
|
|
DN_JSBDepth *top_depth = DN_JSB_PeekDepth_(builder, 0);
|
|
top_depth->last_action = DN_JSBAction_Value;
|
|
}
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_AppendFV_(DN_JSBBuilder *builder, char *buf, size_t buf_size, char const *fmt, va_list args)
|
|
{
|
|
// NOTE: Calc size required
|
|
size_t size = 0;
|
|
{
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
size = DN_JSB_VSNPrintF(0, 0, fmt, args_copy);
|
|
va_end(args_copy);
|
|
}
|
|
|
|
// NOTE: Do the write
|
|
DN_JSBResult result = DN_JSBResult_Success;
|
|
if (buf) {
|
|
if (builder->p > buf_size)
|
|
return DN_JSBResult_BadAPIUsage;
|
|
|
|
char *dest = buf + builder->p;
|
|
size_t dest_size = (buf + buf_size) - dest;
|
|
if ((size + 1) <= dest_size) { // NOTE: +1 because vsnprintf always null-terminates the stream
|
|
DN_JSB_VSNPrintF(dest, dest_size, fmt, args);
|
|
} else {
|
|
result = DN_JSBResult_BufOutOfMem;
|
|
}
|
|
}
|
|
|
|
if (result == DN_JSBResult_Success)
|
|
builder->p += size;
|
|
if (builder->error == DN_JSBResult_Success)
|
|
builder->error = result;
|
|
return result;
|
|
}
|
|
|
|
DN_JSBResult DN_JSB_AppendF_(DN_JSBBuilder *builder, char *buf, size_t buf_size, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_JSBResult result = DN_JSB_AppendFV_(builder, buf, buf_size, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|