| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    The code included in this file is provided under the terms of the ISC license
 -    http://www.isc.org/downloads/software-support-policy/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.
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - 
 - struct JSONParser
 - {
 -     JSONParser (String::CharPointerType text) : startLocation (text), currentLocation (text) {}
 - 
 -     String::CharPointerType startLocation, currentLocation;
 - 
 -     struct ErrorException
 -     {
 -         String message;
 -         int line = 1, column = 1;
 - 
 -         String getDescription() const   { return String (line) + ":" + String (column) + ": error: " + message; }
 -         Result getResult() const        { return Result::fail (getDescription()); }
 -     };
 - 
 -     [[noreturn]] void throwError (juce::String message, String::CharPointerType location)
 -     {
 -         ErrorException e;
 -         e.message = std::move (message);
 - 
 -         for (auto i = startLocation; i < location && ! i.isEmpty(); ++i)
 -         {
 -             ++e.column;
 -             if (*i == '\n')  { e.column = 1; e.line++; }
 -         }
 - 
 -         throw e;
 -     }
 - 
 -     void skipWhitespace()             { currentLocation = currentLocation.findEndOfWhitespace(); }
 -     juce_wchar readChar()             { return currentLocation.getAndAdvance(); }
 -     juce_wchar peekChar() const       { return *currentLocation; }
 -     bool matchIf (char c)             { if (peekChar() == (juce_wchar) c) { ++currentLocation; return true; } return false; }
 -     bool isEOF() const                { return peekChar() == 0; }
 - 
 -     bool matchString (const char* t)
 -     {
 -         while (*t != 0)
 -             if (! matchIf (*t++))
 -                 return false;
 - 
 -         return true;
 -     }
 - 
 -     var parseObjectOrArray()
 -     {
 -         skipWhitespace();
 - 
 -         if (matchIf ('{')) return parseObject();
 -         if (matchIf ('[')) return parseArray();
 - 
 -         if (! isEOF())
 -             throwError ("Expected '{' or '['", currentLocation);
 - 
 -         return {};
 -     }
 - 
 -     String parseString (const juce_wchar quoteChar)
 -     {
 -         MemoryOutputStream buffer (256);
 - 
 -         for (;;)
 -         {
 -             auto c = readChar();
 - 
 -             if (c == quoteChar)
 -                 break;
 - 
 -             if (c == '\\')
 -             {
 -                 auto errorLocation = currentLocation;
 -                 c = readChar();
 - 
 -                 switch (c)
 -                 {
 -                     case '"':
 -                     case '\'':
 -                     case '\\':
 -                     case '/':  break;
 - 
 -                     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 = 0;
 - 
 -                         for (int i = 4; --i >= 0;)
 -                         {
 -                             auto digitValue = CharacterFunctions::getHexDigitValue (readChar());
 - 
 -                             if (digitValue < 0)
 -                                 throwError ("Syntax error in unicode escape sequence", errorLocation);
 - 
 -                             c = (juce_wchar) ((c << 4) + static_cast<juce_wchar> (digitValue));
 -                         }
 - 
 -                         break;
 -                     }
 - 
 -                     default:  break;
 -                 }
 -             }
 - 
 -             if (c == 0)
 -                 throwError ("Unexpected EOF in string constant", currentLocation);
 - 
 -             buffer.appendUTF8Char (c);
 -         }
 - 
 -         return buffer.toUTF8();
 -     }
 - 
 -     var parseAny()
 -     {
 -         skipWhitespace();
 -         auto originalLocation = currentLocation;
 - 
 -         switch (readChar())
 -         {
 -             case '{':    return parseObject();
 -             case '[':    return parseArray();
 -             case '"':    return parseString ('"');
 -             case '\'':   return parseString ('\'');
 - 
 -             case '-':
 -                 skipWhitespace();
 -                 return parseNumber (true);
 - 
 -             case '0': case '1': case '2': case '3': case '4':
 -             case '5': case '6': case '7': case '8': case '9':
 -                 currentLocation = originalLocation;
 -                 return parseNumber (false);
 - 
 -             case 't':   // "true"
 -                 if (matchString ("rue"))
 -                     return var (true);
 - 
 -                 break;
 - 
 -             case 'f':   // "false"
 -                 if (matchString ("alse"))
 -                     return var (false);
 - 
 -                 break;
 - 
 -             case 'n':   // "null"
 -                 if (matchString ("ull"))
 -                     return {};
 - 
 -                 break;
 - 
 -             default:
 -                 break;
 -         }
 - 
 -         throwError ("Syntax error", originalLocation);
 -     }
 - 
 -     var parseNumber (bool isNegative)
 -     {
 -         auto originalPos = currentLocation;
 - 
 -         int64 intValue = readChar() - '0';
 -         jassert (intValue >= 0 && intValue < 10);
 - 
 -         for (;;)
 -         {
 -             auto lastPos = currentLocation;
 -             auto c = readChar();
 -             auto digit = ((int) c) - '0';
 - 
 -             if (isPositiveAndBelow (digit, 10))
 -             {
 -                 intValue = intValue * 10 + digit;
 -                 continue;
 -             }
 - 
 -             if (c == 'e' || c == 'E' || c == '.')
 -             {
 -                 currentLocation = originalPos;
 -                 auto asDouble = CharacterFunctions::readDoubleValue (currentLocation);
 -                 return var (isNegative ? -asDouble : asDouble);
 -             }
 - 
 -             if (CharacterFunctions::isWhitespace (c)
 -                  || c == ',' || c == '}' || c == ']' || c == 0)
 -             {
 -                 currentLocation = lastPos;
 -                 break;
 -             }
 - 
 -             throwError ("Syntax error in number", lastPos);
 -         }
 - 
 -         auto correctedValue = isNegative ? -intValue : intValue;
 - 
 -         return (intValue >> 31) != 0 ? var (correctedValue)
 -                                      : var ((int) correctedValue);
 -     }
 - 
 -     var parseObject()
 -     {
 -         auto resultObject = new DynamicObject();
 -         var result (resultObject);
 -         auto& resultProperties = resultObject->getProperties();
 -         auto startOfObjectDecl = currentLocation;
 - 
 -         for (;;)
 -         {
 -             skipWhitespace();
 -             auto errorLocation = currentLocation;
 -             auto c = readChar();
 - 
 -             if (c == '}')
 -                 break;
 - 
 -             if (c == 0)
 -                 throwError ("Unexpected EOF in object declaration", startOfObjectDecl);
 - 
 -             if (c != '"')
 -                 throwError ("Expected a property name in double-quotes", errorLocation);
 - 
 -             errorLocation = currentLocation;
 -             Identifier propertyName (parseString ('"'));
 - 
 -             if (! propertyName.isValid())
 -                 throwError ("Invalid property name", errorLocation);
 - 
 -             skipWhitespace();
 -             errorLocation = currentLocation;
 - 
 -             if (readChar() != ':')
 -                 throwError ("Expected ':'", errorLocation);
 - 
 -             resultProperties.set (propertyName, parseAny());
 - 
 -             skipWhitespace();
 -             if (matchIf (',')) continue;
 -             if (matchIf ('}')) break;
 - 
 -             throwError ("Expected ',' or '}'", currentLocation);
 -         }
 - 
 -         return result;
 -     }
 - 
 -     var parseArray()
 -     {
 -         auto result = var (Array<var>());
 -         auto destArray = result.getArray();
 -         auto startOfArrayDecl = currentLocation;
 - 
 -         for (;;)
 -         {
 -             skipWhitespace();
 - 
 -             if (matchIf (']'))
 -                 break;
 - 
 -             if (isEOF())
 -                 throwError ("Unexpected EOF in array declaration", startOfArrayDecl);
 - 
 -             destArray->add (parseAny());
 -             skipWhitespace();
 - 
 -             if (matchIf (',')) continue;
 -             if (matchIf (']')) break;
 - 
 -             throwError ("Expected ',' or ']'", currentLocation);
 -         }
 - 
 -         return result;
 -     }
 - };
 - 
 - //==============================================================================
 - struct JSONFormatter
 - {
 -     static void writeEscapedChar (OutputStream& out, const unsigned short value)
 -     {
 -         out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
 -     }
 - 
 -     static void writeString (OutputStream& out, String::CharPointerType t)
 -     {
 -         for (;;)
 -         {
 -             auto c = t.getAndAdvance();
 - 
 -             switch (c)
 -             {
 -                 case 0:  return;
 - 
 -                 case '\"':  out << "\\\""; break;
 -                 case '\\':  out << "\\\\"; break;
 -                 case '\a':  out << "\\a";  break;
 -                 case '\b':  out << "\\b";  break;
 -                 case '\f':  out << "\\f";  break;
 -                 case '\t':  out << "\\t";  break;
 -                 case '\r':  out << "\\r";  break;
 -                 case '\n':  out << "\\n";  break;
 - 
 -                 default:
 -                     if (c >= 32 && c < 127)
 -                     {
 -                         out << (char) c;
 -                     }
 -                     else
 -                     {
 -                         if (CharPointer_UTF16::getBytesRequiredFor (c) > 2)
 -                         {
 -                             CharPointer_UTF16::CharType chars[2];
 -                             CharPointer_UTF16 utf16 (chars);
 -                             utf16.write (c);
 - 
 -                             for (int i = 0; i < 2; ++i)
 -                                 writeEscapedChar (out, (unsigned short) chars[i]);
 -                         }
 -                         else
 -                         {
 -                             writeEscapedChar (out, (unsigned short) c);
 -                         }
 -                     }
 - 
 -                     break;
 -             }
 -         }
 -     }
 - 
 -     static void writeSpaces (OutputStream& out, int numSpaces)
 -     {
 -         out.writeRepeatedByte (' ', (size_t) numSpaces);
 -     }
 - 
 -     static void writeArray (OutputStream& out, const Array<var>& array, const JSON::FormatOptions& format)
 -     {
 -         out << '[';
 - 
 -         if (! array.isEmpty())
 -         {
 -             if (format.getSpacing() == JSON::Spacing::multiLine)
 -                 out << newLine;
 - 
 -             for (int i = 0; i < array.size(); ++i)
 -             {
 -                 if (format.getSpacing() == JSON::Spacing::multiLine)
 -                     writeSpaces (out, format.getIndentLevel() + indentSize);
 - 
 -                 JSON::writeToStream (out, array.getReference (i), format.withIndentLevel (format.getIndentLevel() + indentSize));
 - 
 -                 if (i < array.size() - 1)
 -                 {
 -                     out << ",";
 - 
 -                     switch (format.getSpacing())
 -                     {
 -                         case JSON::Spacing::none: break;
 -                         case JSON::Spacing::singleLine: out << ' '; break;
 -                         case JSON::Spacing::multiLine: out << newLine; break;
 -                     }
 -                 }
 -                 else if (format.getSpacing() == JSON::Spacing::multiLine)
 -                     out << newLine;
 -             }
 - 
 -             if (format.getSpacing() == JSON::Spacing::multiLine)
 -                 writeSpaces (out, format.getIndentLevel());
 -         }
 - 
 -         out << ']';
 -     }
 - 
 -     enum { indentSize = 2 };
 - };
 - 
 - 
 - void JSON::writeToStream (OutputStream& out, const var& v, const FormatOptions& opt)
 - {
 -     if (v.isString())
 -     {
 -         out << '"';
 -         JSONFormatter::writeString (out, v.toString().getCharPointer());
 -         out << '"';
 -     }
 -     else if (v.isVoid())
 -     {
 -         out << "null";
 -     }
 -     else if (v.isUndefined())
 -     {
 -         out << "undefined";
 -     }
 -     else if (v.isBool())
 -     {
 -         out << (static_cast<bool> (v) ? "true" : "false");
 -     }
 -     else if (v.isDouble())
 -     {
 -         auto d = static_cast<double> (v);
 - 
 -         if (juce_isfinite (d))
 -         {
 -             out << serialiseDouble (d);
 -         }
 -         else
 -         {
 -             out << "null";
 -         }
 -     }
 -     else if (v.isArray())
 -     {
 -         JSONFormatter::writeArray (out, *v.getArray(), opt);
 -     }
 -     else if (v.isObject())
 -     {
 -         if (auto* object = v.getDynamicObject())
 -             object->writeAsJSON (out, opt);
 -         else
 -             jassertfalse; // Only DynamicObjects can be converted to JSON!
 -     }
 -     else
 -     {
 -         // Can't convert these other types of object to JSON!
 -         jassert (! (v.isMethod() || v.isBinaryData()));
 - 
 -         out << v.toString();
 -     }
 - }
 - 
 - String JSON::toString (const var& v, const FormatOptions& opt)
 - {
 -     MemoryOutputStream mo { 1024 };
 -     writeToStream (mo, v, opt);
 -     return mo.toUTF8();
 - }
 - 
 - //==============================================================================
 - var JSON::parse (const String& text)
 - {
 -     var result;
 - 
 -     if (parse (text, result))
 -         return result;
 - 
 -     return {};
 - }
 - 
 - var JSON::fromString (StringRef text)
 - {
 -     try
 -     {
 -         return JSONParser (text.text).parseAny();
 -     }
 -     catch (const JSONParser::ErrorException&) {}
 - 
 -     return {};
 - }
 - 
 - var JSON::parse (InputStream& input)
 - {
 -     return parse (input.readEntireStreamAsString());
 - }
 - 
 - var JSON::parse (const File& file)
 - {
 -     return parse (file.loadFileAsString());
 - }
 - 
 - Result JSON::parse (const String& text, var& result)
 - {
 -     try
 -     {
 -         result = JSONParser (text.getCharPointer()).parseObjectOrArray();
 -     }
 -     catch (const JSONParser::ErrorException& error)
 -     {
 -         return error.getResult();
 -     }
 - 
 -     return Result::ok();
 - }
 - 
 - String JSON::toString (const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
 - {
 -     return toString (data, FormatOptions{}.withSpacing (allOnOneLine ? Spacing::singleLine : Spacing::multiLine)
 -                                           .withMaxDecimalPlaces (maximumDecimalPlaces));
 - }
 - 
 - void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
 - {
 -     writeToStream (output, data, FormatOptions{}.withSpacing (allOnOneLine ? Spacing::singleLine : Spacing::multiLine)
 -                                                 .withMaxDecimalPlaces (maximumDecimalPlaces));
 - }
 - 
 - String JSON::escapeString (StringRef s)
 - {
 -     MemoryOutputStream mo;
 -     JSONFormatter::writeString (mo, s.text);
 -     return mo.toString();
 - }
 - 
 - Result JSON::parseQuotedString (String::CharPointerType& t, var& result)
 - {
 -     try
 -     {
 -         JSONParser parser (t);
 -         auto quote = parser.readChar();
 - 
 -         if (quote != '"' && quote != '\'')
 -             return Result::fail ("Not a quoted string!");
 - 
 -         result = parser.parseString (quote);
 -         t = parser.currentLocation;
 -     }
 -     catch (const JSONParser::ErrorException& error)
 -     {
 -         return error.getResult();
 -     }
 - 
 -     return Result::ok();
 - }
 - 
 - 
 - //==============================================================================
 - //==============================================================================
 - #if JUCE_UNIT_TESTS
 - 
 - class JSONTests final : public UnitTest
 - {
 - public:
 -     JSONTests()
 -         : UnitTest ("JSON", UnitTestCategories::json)
 -     {}
 - 
 -     static String createRandomWideCharString (Random& r)
 -     {
 -         juce_wchar buffer[40] = { 0 };
 - 
 -         for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
 -         {
 -             if (r.nextBool())
 -             {
 -                 do
 -                 {
 -                     buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
 -                 }
 -                 while (! CharPointer_UTF16::canRepresent (buffer[i]));
 -             }
 -             else
 -                 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
 -         }
 - 
 -         return CharPointer_UTF32 (buffer);
 -     }
 - 
 -     static String createRandomIdentifier (Random& r)
 -     {
 -         char buffer[30] = { 0 };
 - 
 -         for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
 -         {
 -             static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
 -             buffer[i] = chars [r.nextInt (sizeof (chars) - 1)];
 -         }
 - 
 -         return CharPointer_ASCII (buffer);
 -     }
 - 
 -     // Creates a random double that can be easily stringified, to avoid
 -     // false failures when decimal places are rounded or truncated slightly
 -     static var createRandomDouble (Random& r)
 -     {
 -         return var ((r.nextDouble() * 1000.0) + 0.1);
 -     }
 - 
 -     static var createRandomVar (Random& r, int depth)
 -     {
 -         switch (r.nextInt (depth > 3 ? 6 : 8))
 -         {
 -             case 0:     return {};
 -             case 1:     return r.nextInt();
 -             case 2:     return r.nextInt64();
 -             case 3:     return r.nextBool();
 -             case 4:     return createRandomDouble (r);
 -             case 5:     return createRandomWideCharString (r);
 - 
 -             case 6:
 -             {
 -                 var v (createRandomVar (r, depth + 1));
 - 
 -                 for (int i = 1 + r.nextInt (30); --i >= 0;)
 -                     v.append (createRandomVar (r, depth + 1));
 - 
 -                 return v;
 -             }
 - 
 -             case 7:
 -             {
 -                 auto o = new DynamicObject();
 - 
 -                 for (int i = r.nextInt (30); --i >= 0;)
 -                     o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
 - 
 -                 return o;
 -             }
 - 
 -             default:
 -                 return {};
 -         }
 -     }
 - 
 -     void runTest() override
 -     {
 -         {
 -             beginTest ("JSON");
 - 
 -             auto r = getRandom();
 - 
 -             expect (JSON::parse (String()) == var());
 -             expect (JSON::parse ("{}").isObject());
 -             expect (JSON::parse ("[]").isArray());
 -             expect (JSON::parse ("[ 1234 ]")[0].isInt());
 -             expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64());
 -             expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble());
 -             expect (JSON::parse ("[ -1234]")[0].isInt());
 -             expect (JSON::parse ("[-12345678901234]")[0].isInt64());
 -             expect (JSON::parse ("[-1.123e3]")[0].isDouble());
 - 
 -             for (int i = 100; --i >= 0;)
 -             {
 -                 var v;
 - 
 -                 if (i > 0)
 -                     v = createRandomVar (r, 0);
 - 
 -                 const auto oneLine = r.nextBool();
 -                 const auto asString = JSON::toString (v, oneLine);
 -                 const auto parsed = JSON::parse ("[" + asString + "]")[0];
 -                 const auto parsedString = JSON::toString (parsed, oneLine);
 -                 expect (asString.isNotEmpty() && parsedString == asString);
 -             }
 -         }
 - 
 -         {
 -             beginTest ("Float formatting");
 - 
 -             std::map<double, String> tests;
 -             tests[1] = "1.0";
 -             tests[1.1] = "1.1";
 -             tests[1.01] = "1.01";
 -             tests[0.76378] = "0.76378";
 -             tests[-10] = "-10.0";
 -             tests[10.01] = "10.01";
 -             tests[0.0123] = "0.0123";
 -             tests[-3.7e-27] = "-3.7e-27";
 -             tests[1e+40] = "1.0e40";
 -             tests[-12345678901234567.0] = "-1.234567890123457e16";
 -             tests[192000] = "192000.0";
 -             tests[1234567] = "1.234567e6";
 -             tests[0.00006] = "0.00006";
 -             tests[0.000006] = "6.0e-6";
 - 
 -             for (auto& test : tests)
 -                 expectEquals (JSON::toString (test.first), test.second);
 -         }
 -     }
 - };
 - 
 - static JSONTests JSONUnitTests;
 - 
 - #endif
 - 
 - } // namespace juce
 
 
  |