mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2025-12-06 16:57:04 +08:00
I initially tried to use the existing .clang-format file, but it does not match the code style (at least with clang-format 11) and the formatting is not consistent across files. Therefore, I decided to rewrite the .clang-format with some personal preferences. Used command find . -iname "*.hpp" -o -iname "*.cpp" | xargs clang-format -i -style=file
599 lines
18 KiB
C++
599 lines
18 KiB
C++
// From github.com/nbsdx/SimpleJSON.
|
|
// Released under the DWTFYW PL
|
|
//
|
|
|
|
#ifndef SIMPLEJSON_HPP
|
|
#define SIMPLEJSON_HPP
|
|
|
|
#include "../chaiscript_defines.hpp"
|
|
#include "quick_flat_map.hpp"
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
namespace json {
|
|
using std::enable_if;
|
|
using std::initializer_list;
|
|
using std::is_convertible;
|
|
using std::is_floating_point;
|
|
using std::is_integral;
|
|
using std::is_same;
|
|
|
|
class JSON {
|
|
public:
|
|
enum class Class {
|
|
Null = 0,
|
|
Object,
|
|
Array,
|
|
String,
|
|
Floating,
|
|
Integral,
|
|
Boolean
|
|
};
|
|
|
|
private:
|
|
using Data
|
|
= std::variant<std::nullptr_t, chaiscript::utility::QuickFlatMap<std::string, JSON>, std::vector<JSON>, std::string, double, std::int64_t, bool>;
|
|
|
|
struct Internal {
|
|
Internal(std::nullptr_t)
|
|
: d(nullptr) {
|
|
}
|
|
Internal()
|
|
: d(nullptr) {
|
|
}
|
|
Internal(Class c)
|
|
: d(make_type(c)) {
|
|
}
|
|
template<typename T>
|
|
Internal(T t)
|
|
: d(std::move(t)) {
|
|
}
|
|
|
|
static Data make_type(Class c) {
|
|
switch (c) {
|
|
case Class::Null:
|
|
return nullptr;
|
|
case Class::Object:
|
|
return chaiscript::utility::QuickFlatMap<std::string, JSON>{};
|
|
case Class::Array:
|
|
return std::vector<JSON>{};
|
|
case Class::String:
|
|
return std::string{};
|
|
case Class::Floating:
|
|
return double{};
|
|
case Class::Integral:
|
|
return std::int64_t{};
|
|
case Class::Boolean:
|
|
return bool{};
|
|
}
|
|
throw std::runtime_error("unknown type");
|
|
}
|
|
|
|
void set_type(Class c) {
|
|
if (type() != c) {
|
|
d = make_type(c);
|
|
}
|
|
}
|
|
|
|
Class type() const noexcept { return Class(d.index()); }
|
|
|
|
template<auto ClassValue, typename Visitor, typename Or>
|
|
decltype(auto) visit_or(Visitor &&visitor, Or &&other) const {
|
|
if (type() == Class(ClassValue)) {
|
|
return visitor(std::get<static_cast<std::size_t>(ClassValue)>(d));
|
|
} else {
|
|
return other();
|
|
}
|
|
}
|
|
|
|
template<auto ClassValue>
|
|
auto &get_set_type() {
|
|
set_type(ClassValue);
|
|
return (std::get<static_cast<std::size_t>(ClassValue)>(d));
|
|
}
|
|
|
|
auto &Map() { return get_set_type<Class::Object>(); }
|
|
auto &Vector() { return get_set_type<Class::Array>(); }
|
|
auto &String() { return get_set_type<Class::String>(); }
|
|
auto &Int() { return get_set_type<Class::Integral>(); }
|
|
auto &Float() { return get_set_type<Class::Floating>(); }
|
|
auto &Bool() { return get_set_type<Class::Boolean>(); }
|
|
|
|
auto Map() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Object)>(&d); }
|
|
auto Vector() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Array)>(&d); }
|
|
auto String() const noexcept { return std::get_if<static_cast<std::size_t>(Class::String)>(&d); }
|
|
auto Int() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d); }
|
|
auto Float() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d); }
|
|
auto Bool() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d); }
|
|
|
|
Data d;
|
|
};
|
|
|
|
Internal internal;
|
|
|
|
public:
|
|
template<typename Container>
|
|
class JSONWrapper {
|
|
Container *object = nullptr;
|
|
|
|
public:
|
|
JSONWrapper(Container *val)
|
|
: object(val) {
|
|
}
|
|
JSONWrapper(std::nullptr_t) {}
|
|
|
|
typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); }
|
|
typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); }
|
|
typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); }
|
|
typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); }
|
|
};
|
|
|
|
template<typename Container>
|
|
class JSONConstWrapper {
|
|
const Container *object = nullptr;
|
|
|
|
public:
|
|
JSONConstWrapper(const Container *val)
|
|
: object(val) {
|
|
}
|
|
JSONConstWrapper(std::nullptr_t) {}
|
|
|
|
typename Container::const_iterator begin() const noexcept {
|
|
return object ? object->begin() : typename Container::const_iterator();
|
|
}
|
|
typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); }
|
|
};
|
|
|
|
JSON() = default;
|
|
JSON(std::nullptr_t) {}
|
|
|
|
explicit JSON(Class type)
|
|
: internal(type) {
|
|
}
|
|
|
|
JSON(initializer_list<JSON> list)
|
|
: internal(Class::Object) {
|
|
for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i) {
|
|
operator[](i->to_string()) = *std::next(i);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
explicit JSON(T b, typename enable_if<is_same<T, bool>::value>::type * = nullptr) noexcept
|
|
: internal(static_cast<bool>(b)) {
|
|
}
|
|
|
|
template<typename T>
|
|
explicit JSON(T i, typename enable_if<is_integral<T>::value && !is_same<T, bool>::value>::type * = nullptr) noexcept
|
|
: internal(static_cast<std::int64_t>(i)) {
|
|
}
|
|
|
|
template<typename T>
|
|
explicit JSON(T f, typename enable_if<is_floating_point<T>::value>::type * = nullptr) noexcept
|
|
: internal(static_cast<double>(f)) {
|
|
}
|
|
|
|
template<typename T>
|
|
explicit JSON(T s, typename enable_if<is_convertible<T, std::string>::value>::type * = nullptr)
|
|
: internal(static_cast<std::string>(s)) {
|
|
}
|
|
|
|
static JSON Load(const std::string &);
|
|
|
|
JSON &operator[](const std::string &key) { return internal.Map().operator[](key); }
|
|
|
|
JSON &operator[](const size_t index) {
|
|
auto &vec = internal.Vector();
|
|
if (index >= vec.size()) {
|
|
vec.resize(index + 1);
|
|
}
|
|
|
|
return vec.operator[](index);
|
|
}
|
|
|
|
JSON &at(const std::string &key) { return operator[](key); }
|
|
|
|
const JSON &at(const std::string &key) const {
|
|
return internal.visit_or<Class::Object>([&](const auto &m) -> const JSON & { return m.at(key); },
|
|
[]() -> const JSON & { throw std::range_error("Not an object, no keys"); });
|
|
}
|
|
|
|
JSON &at(size_t index) { return operator[](index); }
|
|
|
|
const JSON &at(size_t index) const {
|
|
return internal.visit_or<Class::Array>([&](const auto &m) -> const JSON & { return m.at(index); },
|
|
[]() -> const JSON & { throw std::range_error("Not an array, no indexes"); });
|
|
}
|
|
|
|
auto length() const noexcept {
|
|
return internal.visit_or<Class::Array>([&](const auto &m) { return static_cast<int>(m.size()); }, []() { return -1; });
|
|
}
|
|
|
|
bool has_key(const std::string &key) const noexcept {
|
|
return internal.visit_or<Class::Object>([&](const auto &m) { return m.count(key) != 0; }, []() { return false; });
|
|
}
|
|
|
|
int size() const noexcept {
|
|
if (auto m = internal.Map(); m != nullptr) {
|
|
return static_cast<int>(m->size());
|
|
}
|
|
if (auto v = internal.Vector(); v != nullptr) {
|
|
return static_cast<int>(v->size());
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
Class JSONType() const noexcept { return internal.type(); }
|
|
|
|
/// Functions for getting primitives from the JSON object.
|
|
bool is_null() const noexcept { return internal.type() == Class::Null; }
|
|
|
|
std::string to_string() const noexcept {
|
|
return internal.visit_or<Class::String>([](const auto &o) { return o; }, []() { return std::string{}; });
|
|
}
|
|
double to_float() const noexcept {
|
|
return internal.visit_or<Class::Floating>([](const auto &o) { return o; }, []() { return double{}; });
|
|
}
|
|
std::int64_t to_int() const noexcept {
|
|
return internal.visit_or<Class::Integral>([](const auto &o) { return o; }, []() { return std::int64_t{}; });
|
|
}
|
|
bool to_bool() const noexcept {
|
|
return internal.visit_or<Class::Boolean>([](const auto &o) { return o; }, []() { return false; });
|
|
}
|
|
|
|
JSONWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() {
|
|
return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
|
|
}
|
|
|
|
JSONWrapper<std::vector<JSON>> array_range() { return std::get_if<static_cast<std::size_t>(Class::Array)>(&internal.d); }
|
|
|
|
JSONConstWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() const {
|
|
return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
|
|
}
|
|
|
|
JSONConstWrapper<std::vector<JSON>> array_range() const { return std::get_if<static_cast<std::size_t>(Class::Array)>(&internal.d); }
|
|
|
|
std::string dump(long depth = 1, std::string tab = " ") const {
|
|
switch (internal.type()) {
|
|
case Class::Null:
|
|
return "null";
|
|
case Class::Object: {
|
|
std::string pad = "";
|
|
for (long i = 0; i < depth; ++i, pad += tab) {
|
|
}
|
|
|
|
std::string s = "{\n";
|
|
bool skip = true;
|
|
for (auto &p : *internal.Map()) {
|
|
if (!skip) {
|
|
s += ",\n";
|
|
}
|
|
s += (pad + "\"" + json_escape(p.first) + "\" : " + p.second.dump(depth + 1, tab));
|
|
skip = false;
|
|
}
|
|
s += ("\n" + pad.erase(0, 2) + "}");
|
|
return s;
|
|
}
|
|
case Class::Array: {
|
|
std::string s = "[";
|
|
bool skip = true;
|
|
for (auto &p : *internal.Vector()) {
|
|
if (!skip) {
|
|
s += ", ";
|
|
}
|
|
s += p.dump(depth + 1, tab);
|
|
skip = false;
|
|
}
|
|
s += "]";
|
|
return s;
|
|
}
|
|
case Class::String:
|
|
return "\"" + json_escape(*internal.String()) + "\"";
|
|
case Class::Floating:
|
|
return std::to_string(*internal.Float());
|
|
case Class::Integral:
|
|
return std::to_string(*internal.Int());
|
|
case Class::Boolean:
|
|
return *internal.Bool() ? "true" : "false";
|
|
}
|
|
|
|
throw std::runtime_error("Unhandled JSON type");
|
|
}
|
|
|
|
private:
|
|
static std::string json_escape(const std::string &str) {
|
|
std::string output;
|
|
for (char i : str) {
|
|
switch (i) {
|
|
case '\"':
|
|
output += "\\\"";
|
|
break;
|
|
case '\\':
|
|
output += "\\\\";
|
|
break;
|
|
case '\b':
|
|
output += "\\b";
|
|
break;
|
|
case '\f':
|
|
output += "\\f";
|
|
break;
|
|
case '\n':
|
|
output += "\\n";
|
|
break;
|
|
case '\r':
|
|
output += "\\r";
|
|
break;
|
|
case '\t':
|
|
output += "\\t";
|
|
break;
|
|
default:
|
|
output += i;
|
|
break;
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
private:
|
|
};
|
|
|
|
struct JSONParser {
|
|
static bool isspace(const char c) noexcept {
|
|
#ifdef CHAISCRIPT_MSVC
|
|
// MSVC warns on these line in some circumstances
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 6330)
|
|
#endif
|
|
return ::isspace(c) != 0;
|
|
#ifdef CHAISCRIPT_MSVC
|
|
#pragma warning(pop)
|
|
#endif
|
|
}
|
|
|
|
static void consume_ws(const std::string &str, size_t &offset) {
|
|
while (isspace(str.at(offset)) && offset <= str.size()) {
|
|
++offset;
|
|
}
|
|
}
|
|
|
|
static JSON parse_object(const std::string &str, size_t &offset) {
|
|
JSON Object(JSON::Class::Object);
|
|
|
|
++offset;
|
|
consume_ws(str, offset);
|
|
if (str.at(offset) == '}') {
|
|
++offset;
|
|
return Object;
|
|
}
|
|
|
|
for (; offset < str.size();) {
|
|
JSON Key = parse_next(str, offset);
|
|
consume_ws(str, offset);
|
|
if (str.at(offset) != ':') {
|
|
throw std::runtime_error(std::string("JSON ERROR: Object: Expected colon, found '") + str.at(offset) + "'\n");
|
|
}
|
|
consume_ws(str, ++offset);
|
|
JSON Value = parse_next(str, offset);
|
|
Object[Key.to_string()] = Value;
|
|
|
|
consume_ws(str, offset);
|
|
if (str.at(offset) == ',') {
|
|
++offset;
|
|
continue;
|
|
} else if (str.at(offset) == '}') {
|
|
++offset;
|
|
break;
|
|
} else {
|
|
throw std::runtime_error(std::string("JSON ERROR: Object: Expected comma, found '") + str.at(offset) + "'\n");
|
|
}
|
|
}
|
|
|
|
return Object;
|
|
}
|
|
|
|
static JSON parse_array(const std::string &str, size_t &offset) {
|
|
JSON Array(JSON::Class::Array);
|
|
size_t index = 0;
|
|
|
|
++offset;
|
|
consume_ws(str, offset);
|
|
if (str.at(offset) == ']') {
|
|
++offset;
|
|
return Array;
|
|
}
|
|
|
|
for (; offset < str.size();) {
|
|
Array[index++] = parse_next(str, offset);
|
|
consume_ws(str, offset);
|
|
|
|
if (str.at(offset) == ',') {
|
|
++offset;
|
|
continue;
|
|
} else if (str.at(offset) == ']') {
|
|
++offset;
|
|
break;
|
|
} else {
|
|
throw std::runtime_error(std::string("JSON ERROR: Array: Expected ',' or ']', found '") + str.at(offset) + "'\n");
|
|
}
|
|
}
|
|
|
|
return Array;
|
|
}
|
|
|
|
static JSON parse_string(const std::string &str, size_t &offset) {
|
|
std::string val;
|
|
for (char c = str.at(++offset); c != '\"'; c = str.at(++offset)) {
|
|
if (c == '\\') {
|
|
switch (str.at(++offset)) {
|
|
case '\"':
|
|
val += '\"';
|
|
break;
|
|
case '\\':
|
|
val += '\\';
|
|
break;
|
|
case '/':
|
|
val += '/';
|
|
break;
|
|
case 'b':
|
|
val += '\b';
|
|
break;
|
|
case 'f':
|
|
val += '\f';
|
|
break;
|
|
case 'n':
|
|
val += '\n';
|
|
break;
|
|
case 'r':
|
|
val += '\r';
|
|
break;
|
|
case 't':
|
|
val += '\t';
|
|
break;
|
|
case 'u': {
|
|
val += "\\u";
|
|
for (size_t i = 1; i <= 4; ++i) {
|
|
c = str.at(offset + i);
|
|
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
|
val += c;
|
|
} else {
|
|
throw std::runtime_error(
|
|
std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'");
|
|
}
|
|
}
|
|
offset += 4;
|
|
} break;
|
|
default:
|
|
val += '\\';
|
|
break;
|
|
}
|
|
} else {
|
|
val += c;
|
|
}
|
|
}
|
|
++offset;
|
|
return JSON(val);
|
|
}
|
|
|
|
static JSON parse_number(const std::string &str, size_t &offset) {
|
|
std::string val, exp_str;
|
|
char c = '\0';
|
|
bool isDouble = false;
|
|
bool isNegative = false;
|
|
std::int64_t exp = 0;
|
|
bool isExpNegative = false;
|
|
if (offset < str.size() && str.at(offset) == '-') {
|
|
isNegative = true;
|
|
++offset;
|
|
}
|
|
for (; offset < str.size();) {
|
|
c = str.at(offset++);
|
|
if (c >= '0' && c <= '9') {
|
|
val += c;
|
|
} else if (c == '.' && !isDouble) {
|
|
val += c;
|
|
isDouble = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (offset < str.size() && (c == 'E' || c == 'e')) {
|
|
c = str.at(offset++);
|
|
if (c == '-') {
|
|
isExpNegative = true;
|
|
} else if (c == '+') {
|
|
// do nothing
|
|
} else {
|
|
--offset;
|
|
}
|
|
|
|
for (; offset < str.size();) {
|
|
c = str.at(offset++);
|
|
if (c >= '0' && c <= '9') {
|
|
exp_str += c;
|
|
} else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
|
|
throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
exp = chaiscript::parse_num<std::int64_t>(exp_str) * (isExpNegative ? -1 : 1);
|
|
} else if (offset < str.size() && (!isspace(c) && c != ',' && c != ']' && c != '}')) {
|
|
throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'");
|
|
}
|
|
--offset;
|
|
|
|
if (isDouble) {
|
|
return JSON((isNegative ? -1 : 1) * chaiscript::parse_num<double>(val) * std::pow(10, exp));
|
|
} else {
|
|
if (!exp_str.empty()) {
|
|
return JSON((isNegative ? -1 : 1) * static_cast<double>(chaiscript::parse_num<std::int64_t>(val)) * std::pow(10, exp));
|
|
} else {
|
|
return JSON((isNegative ? -1 : 1) * chaiscript::parse_num<std::int64_t>(val));
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSON parse_bool(const std::string &str, size_t &offset) {
|
|
if (str.substr(offset, 4) == "true") {
|
|
offset += 4;
|
|
return JSON(true);
|
|
} else if (str.substr(offset, 5) == "false") {
|
|
offset += 5;
|
|
return JSON(false);
|
|
} else {
|
|
throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr(offset, 5) + "'");
|
|
}
|
|
}
|
|
|
|
static JSON parse_null(const std::string &str, size_t &offset) {
|
|
if (str.substr(offset, 4) != "null") {
|
|
throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr(offset, 4) + "'");
|
|
}
|
|
offset += 4;
|
|
return JSON();
|
|
}
|
|
|
|
static JSON parse_next(const std::string &str, size_t &offset) {
|
|
char value;
|
|
consume_ws(str, offset);
|
|
value = str.at(offset);
|
|
switch (value) {
|
|
case '[':
|
|
return parse_array(str, offset);
|
|
case '{':
|
|
return parse_object(str, offset);
|
|
case '\"':
|
|
return parse_string(str, offset);
|
|
case 't':
|
|
case 'f':
|
|
return parse_bool(str, offset);
|
|
case 'n':
|
|
return parse_null(str, offset);
|
|
default:
|
|
if ((value <= '9' && value >= '0') || value == '-') {
|
|
return parse_number(str, offset);
|
|
}
|
|
}
|
|
throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'");
|
|
}
|
|
};
|
|
|
|
inline JSON JSON::Load(const std::string &str) {
|
|
size_t offset = 0;
|
|
return JSONParser::parse_next(str, offset);
|
|
}
|
|
|
|
} // End Namespace json
|
|
|
|
#endif
|