#include "JsonParser.h" #include #include #include #include #include namespace Romeo { struct JsonParserState { Juliet::Arena* Arena; const char* Ptr; const char* End; }; static void SkipWhitespace(JsonParserState& state) { while (state.Ptr < state.End) { char c = *state.Ptr; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { state.Ptr++; } else { break; } } } static bool ParseString(JsonParserState& state, Juliet::String& outStr) { if (state.Ptr >= state.End || *state.Ptr != '"') { return false; } state.Ptr++; const char* start = state.Ptr; size_t len = 0; bool hasEscape = false; while (state.Ptr < state.End) { char c = *state.Ptr; if (c == '"') { break; } else if (c == '\\') { hasEscape = true; state.Ptr++; if (state.Ptr >= state.End) { return false; } } state.Ptr++; len++; } if (state.Ptr >= state.End) { return false; } const char* endQuote = state.Ptr; state.Ptr++; char* buf = Juliet::ArenaPushArray(state.Arena, len + 1 JULIET_DEBUG_PARAM("JsonParsedStr")); if (hasEscape) { size_t writeIdx = 0; const char* readPtr = start; while (readPtr < endQuote) { char c = *readPtr; if (c == '\\') { readPtr++; if (readPtr >= endQuote) { break; } char esc = *readPtr; if (esc == 'n') { buf[writeIdx++] = '\n'; } else if (esc == 'r') { buf[writeIdx++] = '\r'; } else if (esc == 't') { buf[writeIdx++] = '\t'; } else if (esc == 'b') { buf[writeIdx++] = '\b'; } else if (esc == 'f') { buf[writeIdx++] = '\f'; } else if (esc == '"') { buf[writeIdx++] = '"'; } else if (esc == '\\') { buf[writeIdx++] = '\\'; } else if (esc == '/') { buf[writeIdx++] = '/'; } else if (esc == 'u') { if (readPtr + 4 < endQuote) { buf[writeIdx++] = '?'; readPtr += 4; } } else { buf[writeIdx++] = esc; } } else { buf[writeIdx++] = c; } readPtr++; } buf[writeIdx] = '\0'; outStr.Data = buf; outStr.Size = writeIdx; } else { memcpy(buf, start, len); buf[len] = '\0'; outStr.Data = buf; outStr.Size = len; } return true; } static bool ParseValue(JsonParserState& state, JsonValue& outVal); static bool ParseObject(JsonParserState& state, JsonValue& outVal) { if (state.Ptr >= state.End || *state.Ptr != '{') { return false; } state.Ptr++; outVal.Type = JsonType::Object; outVal.ObjectVal.Create(state.Arena JULIET_DEBUG_ONLY(, "JsonObject")); while (state.Ptr < state.End) { SkipWhitespace(state); if (state.Ptr < state.End && *state.Ptr == '}') { state.Ptr++; return true; } Juliet::String key = {}; if (!ParseString(state, key)) { return false; } SkipWhitespace(state); if (state.Ptr >= state.End || *state.Ptr != ':') { return false; } state.Ptr++; JsonKeyValue kv = {}; kv.Key = key; kv.Value = Juliet::ArenaPushArray(state.Arena, 1 JULIET_DEBUG_PARAM("JsonValueRef")); new (kv.Value) JsonValue(); if (!ParseValue(state, *kv.Value)) { return false; } outVal.ObjectVal.PushBack(kv); SkipWhitespace(state); if (state.Ptr < state.End && *state.Ptr == ',') { state.Ptr++; } else if (state.Ptr < state.End && *state.Ptr == '}') { state.Ptr++; return true; } else { return false; } } return false; } static bool ParseArray(JsonParserState& state, JsonValue& outVal) { if (state.Ptr >= state.End || *state.Ptr != '[') { return false; } state.Ptr++; outVal.Type = JsonType::Array; outVal.ArrayVal.Create(state.Arena JULIET_DEBUG_ONLY(, "JsonArray")); while (state.Ptr < state.End) { SkipWhitespace(state); if (state.Ptr < state.End && *state.Ptr == ']') { state.Ptr++; return true; } JsonValue val = {}; if (!ParseValue(state, val)) { return false; } outVal.ArrayVal.PushBack(val); SkipWhitespace(state); if (state.Ptr < state.End && *state.Ptr == ',') { state.Ptr++; } else if (state.Ptr < state.End && *state.Ptr == ']') { state.Ptr++; return true; } else { return false; } } return false; } static bool ParseNumber(JsonParserState& state, JsonValue& outVal) { const char* start = state.Ptr; while (state.Ptr < state.End) { char c = *state.Ptr; if (isdigit(c) || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { state.Ptr++; } else { break; } } size_t len = static_cast(state.Ptr - start); if (len == 0) { return false; } char temp[64]; if (len >= sizeof(temp)) { return false; } memcpy(temp, start, len); temp[len] = '\0'; char* endPtr = nullptr; outVal.NumVal = strtod(temp, &endPtr); if (endPtr == temp) { return false; } outVal.Type = JsonType::Number; return true; } static bool ParseValue(JsonParserState& state, JsonValue& outVal) { SkipWhitespace(state); if (state.Ptr >= state.End) { return false; } char c = *state.Ptr; if (c == '{') { return ParseObject(state, outVal); } else if (c == '[') { return ParseArray(state, outVal); } else if (c == '"') { outVal.Type = JsonType::String; return ParseString(state, outVal.StrVal); } else if (c == 't' || c == 'f') { size_t remaining = static_cast(state.End - state.Ptr); if (c == 't' && remaining >= 4 && strncmp(state.Ptr, "true", 4) == 0) { outVal.Type = JsonType::Bool; outVal.BoolVal = true; state.Ptr += 4; return true; } else if (c == 'f' && remaining >= 5 && strncmp(state.Ptr, "false", 5) == 0) { outVal.Type = JsonType::Bool; outVal.BoolVal = false; state.Ptr += 5; return true; } } else if (c == 'n') { size_t remaining = static_cast(state.End - state.Ptr); if (remaining >= 4 && strncmp(state.Ptr, "null", 4) == 0) { outVal.Type = JsonType::Null; state.Ptr += 4; return true; } } else if (isdigit(c) || c == '-') { return ParseNumber(state, outVal); } return false; } bool Json_Parse(Juliet::Arena* arena, const Juliet::String& jsonStr, JsonValue& outValue) { Assert(arena != nullptr); if (jsonStr.Size == 0) { return false; } JsonParserState state; state.Arena = arena; state.Ptr = CStr(jsonStr); state.End = state.Ptr + jsonStr.Size; return ParseValue(state, outValue); } bool Json_Query(const JsonValue& root, const char* path, Juliet::String& outResult) { Assert(path != nullptr); const JsonValue* current = &root; const char* p = path; while (*p) { const char* start = p; while (*p && *p != '/') { p++; } size_t len = static_cast(p - start); if (len == 0) { if (*p == '/') { p++; } continue; } if (current->Type == JsonType::Object) { const JsonValue* next = nullptr; for (const JsonKeyValue& kv : current->ObjectVal) { if (kv.Key.Size == len && strncmp(CStr(kv.Key), start, len) == 0) { next = kv.Value; break; } } if (!next) { return false; } current = next; } else if (current->Type == JsonType::Array) { char tempIndex[32]; if (len >= sizeof(tempIndex)) { return false; } memcpy(tempIndex, start, len); tempIndex[len] = '\0'; int index = atoi(tempIndex); if (index < 0 || static_cast(index) >= current->ArrayVal.Size()) { return false; } current = ¤t->ArrayVal[static_cast(index)]; } else { return false; } if (*p == '/') { p++; } } if (current->Type == JsonType::String) { outResult = current->StrVal; return true; } return false; } Juliet::String Json_EscapeString(Juliet::Arena* arena, const Juliet::String& str) { Assert(arena != nullptr); size_t escLen = 0; const unsigned char* p = reinterpret_cast(CStr(str)); const unsigned char* end = p + str.Size; while (p < end) { unsigned char c = *p; if (c == '"' || c == '\\' || c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') { escLen += 2; } else if (c < 32) { escLen += 6; } else { escLen += 1; } p++; } char* buf = Juliet::ArenaPushArray(arena, escLen + 1 JULIET_DEBUG_PARAM("JsonEscapedStr")); char* dst = buf; p = reinterpret_cast(CStr(str)); while (p < end) { unsigned char c = *p; if (c == '"') { *dst++ = '\\'; *dst++ = '"'; } else if (c == '\\') { *dst++ = '\\'; *dst++ = '\\'; } else if (c == '\n') { *dst++ = '\\'; *dst++ = 'n'; } else if (c == '\r') { *dst++ = '\\'; *dst++ = 'r'; } else if (c == '\t') { *dst++ = '\\'; *dst++ = 't'; } else if (c == '\b') { *dst++ = '\\'; *dst++ = 'b'; } else if (c == '\f') { *dst++ = '\\'; *dst++ = 'f'; } else if (c < 32) { snprintf(dst, 7, "\\u00%02x", static_cast(c)); dst += 6; } else { *dst++ = static_cast(c); } p++; } *dst = '\0'; return { buf, escLen }; } }