1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_javascript/choc/text/choc_JSON.h

561 lines
19 KiB
C++

//
// ██████ ██  ██  ██████  ██████
// ██      ██  ██ ██    ██ ██       ** Classy Header-Only Classes **
// ██  ███████ ██  ██ ██
// ██  ██   ██ ██  ██ ██ https://github.com/Tracktion/choc
//  ██████ ██  ██  ██████   ██████
//
// CHOC is (C)2022 Tracktion Corporation, and is offered under the terms of the ISC license:
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or
// without fee is hereby granted, provided that the above copyright notice and this permission
// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#ifndef CHOC_JSON_HEADER_INCLUDED
#define CHOC_JSON_HEADER_INCLUDED
#include <limits>
#include <sstream>
#include <string_view>
#include <stdexcept>
#include "choc_UTF8.h"
#include "choc_FloatToString.h"
#include "../containers/choc_Value.h"
#undef max // It's never a smart idea to include any C headers before your C++ ones, as it
#undef min // risks polluting your namespace with all kinds of dangerous macros like these ones.
namespace choc::json
{
//==============================================================================
/// A parse exception, thrown by choc::json::parse() as needed.
struct ParseError : public std::runtime_error
{
ParseError (const char* message, choc::text::LineAndColumn lc)
: std::runtime_error (message), lineAndColumn (lc) {}
choc::text::LineAndColumn lineAndColumn;
};
/// Parses some JSON text into a choc::value::Value object, using the given pool.
/// Any errors will result in a ParseError exception being thrown.
[[nodiscard]] value::Value parse (text::UTF8Pointer);
/// Parses some JSON text into a choc::value::Value object, using the given pool.
/// Any errors will result in a ParseError exception being thrown.
[[nodiscard]] value::Value parse (std::string_view);
/// Attempts to parse a bare JSON value such as a number, string, object etc
[[nodiscard]] value::Value parseValue (std::string_view);
/// A helper function to create a JSON-friendly Value object with a set of properties.
/// The argument list must be contain pairs of names and values, e.g.
///
/// auto myObject = choc::json::create ("property1", 1234,
/// "property2", "hello",
/// "property3", 100.0f);
///
/// Essentially, this is a shorthand for calling choc::value::createObject()
/// and passing it an empty type name.
template <typename... Properties>
[[nodiscard]] value::Value create (Properties&&... propertyNamesAndValues);
//==============================================================================
/// Formats a value as a JSON string.
/// If useLineBreaks is true, it'll be formatted as multi-line JSON, if false it'll
/// just be returned as a single line.
[[nodiscard]] std::string toString (const value::ValueView&, bool useLineBreaks = false);
/// Writes a version of a string to an output stream, with any illegal or non-ascii
/// written as their equivalent JSON escape sequences.
template <typename OutputStreamType>
void writeWithEscapeCharacters (OutputStreamType&, text::UTF8Pointer sourceString);
/// Returns a version of a string with illegal or non-ascii converted into the
/// equivalent JSON escape sequences.
[[nodiscard]] std::string addEscapeCharacters (text::UTF8Pointer sourceString);
/// Returns a version of a string with illegal or non-ascii converted into the
/// equivalent JSON escape sequences.
[[nodiscard]] std::string addEscapeCharacters (std::string_view sourceString);
/// Returns a version of a string with illegal or non-ascii converted into the
/// equivalent JSON escape sequences.
[[nodiscard]] std::string getEscapedQuotedString (std::string_view sourceString);
/// Converts a double to a JSON-format string representation.
std::string doubleToString (double value);
//==============================================================================
// _ _ _ _
// __| | ___ | |_ __ _ (_)| | ___
// / _` | / _ \| __| / _` || || |/ __|
// | (_| || __/| |_ | (_| || || |\__ \ _ _ _
// \__,_| \___| \__| \__,_||_||_||___/(_)(_)(_)
//
// Code beyond this point is implementation detail...
//
//==============================================================================
template <typename OutputStreamType>
void writeWithEscapeCharacters (OutputStreamType& out, text::UTF8Pointer source)
{
auto writeUnicode = [] (OutputStreamType& o, auto digit)
{
auto hexDigit = [] (auto value) -> char { return "0123456789abcdef"[value & 15]; };
o << "\\u" << hexDigit (digit >> 12) << hexDigit (digit >> 8) << hexDigit (digit >> 4) << hexDigit (digit);
};
for (;;)
{
auto c = *source;
switch (c)
{
case 0: return;
case '\"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
case '\a': out << "\\a"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
default:
if (c > 31 && c < 127)
{
out << (char) c;
break;
}
if (c >= 0x10000)
{
auto pair = choc::text::splitCodePointIntoSurrogatePair (c);
writeUnicode (out, pair.high);
writeUnicode (out, pair.low);
break;
}
writeUnicode (out, c);
break;
}
++source;
}
}
inline std::string addEscapeCharacters (text::UTF8Pointer source)
{
std::ostringstream result (std::ios::binary);
writeWithEscapeCharacters (result, source);
return result.str();
}
inline std::string addEscapeCharacters (std::string_view source)
{
return addEscapeCharacters (text::UTF8Pointer (std::string (source).c_str()));
}
inline std::string getEscapedQuotedString (std::string_view s)
{
std::ostringstream result (std::ios::binary);
result << '"';
writeWithEscapeCharacters (result, text::UTF8Pointer (std::string (s).c_str()));
result << '"';
return result.str();
}
inline std::string doubleToString (double value)
{
if (std::isfinite (value)) return choc::text::floatToString (value, -1, true);
if (std::isnan (value)) return "\"NaN\"";
return value >= 0 ? "\"Infinity\""
: "\"-Infinity\"";
}
//==============================================================================
template <typename Stream>
struct Writer
{
Stream& out;
uint32_t indentSize, currentIndent = 0;
static constexpr const char newLine = '\n';
std::string getIndent() const { return std::string (currentIndent, ' '); }
void startIndent() { currentIndent += indentSize; out << newLine << getIndent(); }
void endIndent() { currentIndent -= indentSize; out << newLine << getIndent(); }
void dump (const value::ValueView& v)
{
if (v.isVoid()) { out << "null"; return; }
if (v.isString()) { out << getEscapedQuotedString (v.getString()); return; }
if (v.isBool()) { out << (v.getBool() ? "true" : "false"); return; }
if (v.isFloat()) { out << doubleToString (v.get<double>()); return; }
if (v.isInt()) { out << v.get<int64_t>(); return; }
if (v.isObject()) return dumpObject (v);
if (v.isArray() || v.isVector()) return dumpArrayOrVector (v);
}
void dumpArrayOrVector (const value::ValueView& v)
{
out << '[';
auto numElements = v.size();
if (indentSize != 0 && numElements != 0)
{
startIndent();
for (uint32_t i = 0; i < numElements; ++i)
{
dump (v[i]);
if (i != numElements - 1)
out << "," << newLine << getIndent();
}
endIndent();
}
else
{
for (uint32_t i = 0; i < numElements; ++i)
{
if (i != 0) out << ", ";
dump (v[i]);
}
}
out << ']';
}
void dumpObject (const value::ValueView& object)
{
out << '{';
auto numMembers = object.size();
if (indentSize != 0 && numMembers != 0)
{
startIndent();
for (uint32_t i = 0; i < numMembers; ++i)
{
auto member = object.getObjectMemberAt (i);
out << getEscapedQuotedString (member.name) << ": ";
dump (member.value);
if (i != numMembers - 1)
out << "," << newLine << getIndent();
}
endIndent();
}
else
{
for (uint32_t i = 0; i < numMembers; ++i)
{
if (i != 0) out << ", ";
auto member = object.getObjectMemberAt (i);
out << getEscapedQuotedString (member.name) << ": ";
dump (member.value);
}
}
out << '}';
}
};
template <typename Stream>
void writeAsJSON (Stream& output, const value::ValueView& value, bool useMultipleLines)
{
Writer<Stream> { output, useMultipleLines ? 2u : 0u }.dump (value);
}
inline std::string toString (const value::ValueView& v, bool useLineBreaks)
{
std::ostringstream out (std::ios::binary);
writeAsJSON (out, v, useLineBreaks);
return out.str();
}
//==============================================================================
[[noreturn]] static inline void throwParseError (const char* error, text::UTF8Pointer source, text::UTF8Pointer errorPos)
{
throw ParseError (error, text::findLineAndColumn (source, errorPos));
}
inline value::Value parse (text::UTF8Pointer text, bool parseBareValue)
{
struct Parser
{
text::UTF8Pointer source, current;
bool isEOF() const { return current.empty(); }
uint32_t peek() const { return *current; }
uint32_t pop() { return current.popFirstChar(); }
bool popIf (char c) { return current.skipIfStartsWith (c); }
bool popIf (const char* c) { return current.skipIfStartsWith (c); }
static bool isWhitespace (uint32_t c) { return c == ' ' || (c <= 13 && c >= 9); }
void skipWhitespace() { auto p = current; while (isWhitespace (p.popFirstChar())) current = p; }
[[noreturn]] void throwError (const char* error, text::UTF8Pointer errorPos) { throwParseError (error, source, errorPos); }
[[noreturn]] void throwError (const char* error) { throwError (error, current); }
value::Value parseTopLevel()
{
skipWhitespace();
if (popIf ('[')) return parseArray();
if (popIf ('{')) return parseObject();
if (! isEOF()) throwError ("Expected an object or array");
return {};
}
value::Value parseArray()
{
auto result = value::createEmptyArray();
auto arrayStart = current;
skipWhitespace();
if (popIf (']')) return result;
for (;;)
{
skipWhitespace();
if (isEOF()) throwError ("Unexpected EOF in array declaration", arrayStart);
result.addArrayElement (parseValue());
skipWhitespace();
if (popIf (',')) continue;
if (popIf (']')) break;
throwError ("Expected ',' or ']'");
}
return result;
}
value::Value parseObject()
{
auto result = value::createObject ({});
auto objectStart = current;
skipWhitespace();
if (popIf ('}')) return result;
for (;;)
{
skipWhitespace();
if (isEOF()) throwError ("Unexpected EOF in object declaration", objectStart);
if (! popIf ('"')) throwError ("Expected a name");
auto errorPos = current;
auto name = parseString();
if (name.empty())
throwError ("Property names cannot be empty", errorPos);
skipWhitespace();
errorPos = current;
if (! popIf (':')) throwError ("Expected ':'");
result.addMember (std::move (name), parseValue());
skipWhitespace();
if (popIf (',')) continue;
if (popIf ('}')) break;
throwError ("Expected ',' or '}'");
}
return result;
}
value::Value parseValue()
{
skipWhitespace();
auto startPos = current;
switch (pop())
{
case '[': return parseArray();
case '{': return parseObject();
case '"': return value::createString (parseString());
case '-': skipWhitespace(); return parseNumber (true);
case 'n': if (popIf ("ull")) return {}; break;
case 't': if (popIf ("rue")) return value::createBool (true); break;
case 'f': if (popIf ("alse")) return value::createBool (false); break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
current = startPos;
return parseNumber (false);
default: break;
}
throwError ("Syntax error", startPos);
}
value::Value parseNumber (bool negate)
{
auto startPos = current;
bool hadDot = false, hadExponent = false;
for (;;)
{
auto lastPos = current;
auto c = pop();
if (c >= '0' && c <= '9') continue;
if (c == '.' && ! hadDot) { hadDot = true; continue; }
if (! hadExponent && (c == 'e' || c == 'E'))
{
hadDot = true;
hadExponent = true;
popIf ('-');
continue;
}
if (isWhitespace (c) || c == ',' || c == '}' || c == ']' || c == 0)
{
current = lastPos;
char* endOfParsedNumber = nullptr;
if (! (hadDot || hadExponent))
{
auto v = std::strtoll (startPos.data(), &endOfParsedNumber, 10);
if (endOfParsedNumber == lastPos.data()
&& v != std::numeric_limits<long long>::max()
&& v != std::numeric_limits<long long>::min())
return value::createInt64 (static_cast<int64_t> (negate ? -v : v));
}
auto v = std::strtod (startPos.data(), &endOfParsedNumber);
if (endOfParsedNumber == lastPos.data())
return value::createFloat64 (negate ? -v : v);
}
throwError ("Syntax error in number", lastPos);
}
}
std::string parseString()
{
std::ostringstream s (std::ios::binary);
for (;;)
{
auto c = pop();
if (c == '"')
break;
if (c == '\\')
{
auto errorPos = current;
c = pop();
switch (c)
{
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'u': c = parseUnicodeCharacterNumber (false); break;
case 0: throwError ("Unexpected EOF in string constant", errorPos);
default: break;
}
}
char utf8Bytes[8];
auto numBytes = text::convertUnicodeCodepointToUTF8 (utf8Bytes, c);
for (uint32_t i = 0; i < numBytes; ++i)
s << utf8Bytes[i];
}
return s.str();
}
uint32_t parseUnicodeCharacterNumber (bool isLowSurrogate)
{
uint32_t result = 0;
for (int i = 4; --i >= 0;)
{
auto errorPos = current;
auto digit = pop();
if (digit >= '0' && digit <= '9') digit -= '0';
else if (digit >= 'a' && digit <= 'f') digit = 10 + (digit - 'a');
else if (digit >= 'A' && digit <= 'F') digit = 10 + (digit - 'A');
else throwError ("Syntax error in unicode character", errorPos);
result = (result << 4) + digit;
}
if (isLowSurrogate && ! text::isUnicodeLowSurrogate (result))
throwError ("Expected a unicode low surrogate codepoint");
if (text::isUnicodeHighSurrogate (result))
{
if (! isLowSurrogate && popIf ("\\u"))
return text::createUnicodeFromHighAndLowSurrogates ({ result, parseUnicodeCharacterNumber (true) });
throwError ("Expected a unicode low surrogate codepoint");
}
return result;
}
};
Parser p { text, text };
return parseBareValue ? p.parseValue()
: p.parseTopLevel();
}
inline value::Value parse (const char* text, size_t numbytes, bool parseBareValue)
{
if (text == nullptr)
{
text = "";
numbytes = 0;
}
if (auto error = text::findInvalidUTF8Data (text, numbytes))
throwParseError ("Illegal UTF8 data", text::UTF8Pointer (text), text::UTF8Pointer (error));
return parse (text::UTF8Pointer (text), parseBareValue);
}
inline value::Value parse (std::string_view text) { return parse (text.data(), text.length(), false); }
inline value::Value parseValue (std::string_view text) { return parse (text.data(), text.length(), true); }
template <typename... Properties>
value::Value create (Properties&&... properties)
{
static_assert ((sizeof...(properties) & 1) == 0, "The arguments must be a sequence of name, value pairs");
return choc::value::createObject ({}, std::forward<Properties> (properties)...);
}
} // namespace choc::json
#endif