mysqlshdk/scripting/types.cc (2,083 lines of code) (raw):
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "scripting/types.h"
#include <rapidjson/prettywriter.h>
#include <cfloat>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <limits>
#include <locale>
#include <sstream>
#include <stdexcept>
#include "mysqlshdk/libs/utils/dtoa.h"
#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_json.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#ifdef WIN32
#ifdef max
#undef max
#endif
#endif
namespace shcore {
namespace {
// These is* functions have undefined behavior if the passed value
// is out of the -1-255 range
#define IS_ALPHA(x) (isalpha(static_cast<unsigned char>(x)))
#define IS_DIGIT(x) (isdigit(static_cast<unsigned char>(x)))
// kTypeConvertible[from_type][to_type] = is_convertible
// from_type = row, to_type = column
#define T true
#define F false
const bool kTypeConvertible[13][13] = {
// Undf, Null,Bool,Str, Int, UInt,Flot,Obj, Arr, Map, MapR,Fun, Binary
{T, F, F, F, F, F, F, F, F, F, F, F, F}, // Undefined
{T, T, F, F, F, F, F, T, T, T, T, T, F}, // Null
{T, F, T, F, T, T, T, F, F, F, F, F, F}, // Bool
{T, F, T, T, T, T, T, F, F, F, F, F, T}, // String
{T, F, T, F, T, T, T, F, F, F, F, F, F}, // Integer
{T, F, T, F, T, T, T, F, F, F, F, F, F}, // UInteger
{T, F, T, F, T, T, T, F, F, F, F, F, F}, // Float
{T, F, F, F, F, F, F, T, F, F, F, F, F}, // Object
{T, F, F, F, F, F, F, F, T, F, F, F, F}, // Array
{T, F, F, F, F, F, F, F, F, T, T, F, F}, // Map
{T, F, F, F, F, F, F, F, F, T, T, F, F}, // MapRef
{T, F, F, F, F, F, F, F, F, F, F, T, F}, // Function
{T, F, F, T, F, F, F, F, F, F, F, F, T}, // Binary
};
#undef T
#undef F
// Note: Null can be cast to Object/Array/Map, but a valid Object/Array/Map
// pointer is not NULL, so they can't be cast to it.
/**
* Translate hex value from printable ascii character ([0-9a-zA-Z]) to decimal
* value
*/
static const uint32_t ascii_to_hex[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
void skip_whitespace(const char **pc) {
while (**pc == ' ' || **pc == '\t' || **pc == '\r' || **pc == '\n' ||
**pc == '\v' || **pc == '\f')
++*pc;
}
} // namespace
// --
Exception::Exception(const std::string &message, int code,
const std::shared_ptr<Value::Map_type> &e)
: shcore::Error(message, code), _error(e) {}
Exception::Exception(const std::string &message, int code)
: shcore::Error(message, code) {
_error = shcore::make_dict("type", Value("MYSQLSH"), "message",
Value(message), "code", Value(code));
}
Exception::Exception(const std::string &type, const std::string &message,
int code)
: shcore::Error(message, code) {
_error = shcore::make_dict("type", Value(type), "message", Value(message),
"code", Value(code));
}
Exception Exception::argument_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("ArgumentError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::attrib_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("AttributeError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::type_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("TypeError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::value_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("ValueError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::logic_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("LogicError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::runtime_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("RuntimeError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::scripting_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("ScriptingError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::metadata_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("MetadataError");
(*error)["message"] = Value(message);
Exception e(message, -1, error);
return e;
}
Exception Exception::error_with_code(const std::string &type,
const std::string &message, int code) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value(type);
(*error)["message"] = Value(message);
(*error)["code"] = Value(code);
Exception e(message, code, error);
return e;
}
Exception Exception::error_with_code_and_state(const std::string &type,
const std::string &message,
int code, const char *sqlstate) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value(type);
(*error)["message"] = Value(message);
(*error)["code"] = Value(code);
if (sqlstate && *sqlstate) (*error)["state"] = Value(std::string(sqlstate));
Exception e(message, code, error);
return e;
}
Exception Exception::parser_error(const std::string &message) {
std::shared_ptr<Value::Map_type> error(new Value::Map_type());
(*error)["type"] = Value("ParserError");
(*error)["message"] = Value(message);
Exception e(message, 0, error);
return e;
}
void Exception::set_file_context(const std::string &file, size_t line) {
if (!file.empty()) (*_error)["file"] = Value(file);
if (line > 0) (*_error)["line"] = Value(static_cast<uint64_t>(line));
}
const char *Exception::type() const noexcept {
if ((*_error)["type"].type == String)
return (*_error)["type"].value.s->c_str();
return "Exception";
}
bool Exception::is_argument() const {
return strcmp(type(), "ArgumentError") == 0;
}
bool Exception::is_attribute() const {
return strcmp(type(), "AttributeError") == 0;
}
bool Exception::is_value() const { return strcmp(type(), "ValueError") == 0; }
bool Exception::is_type() const { return strcmp(type(), "TypeError") == 0; }
bool Exception::is_runtime() const {
return strcmp(type(), "RuntimeError") == 0;
}
bool Exception::is_metadata() const {
return strcmp(type(), "MetadataError") == 0;
}
bool Exception::is_mysql() const { return strcmp(type(), "MySQL Error") == 0; }
bool Exception::is_mysqlsh() const {
return strcmp(type(), "MySQL Shell Error") == 0;
}
bool Exception::is_parser() const { return strcmp(type(), "ParserError") == 0; }
std::string Exception::format() const {
std::string error_message;
std::string type = _error->get_string("type", "");
std::string message = what();
std::string state = _error->get_string("state", "");
std::string error_location = _error->get_string("location", "");
if (!message.empty()) {
if (!type.empty()) error_message += type;
if (code() != -1 &&
!is_mysqlsh()) { // don't show shell error codes for now
error_message += " " + std::to_string(code());
if (!state.empty()) error_message += " (" + state + ")";
}
if (!error_message.empty()) error_message += ": ";
error_message += message;
if (!error_location.empty()) error_message += " at " + error_location;
}
return error_message;
}
// --
std::string type_name(Value_type type) {
switch (type) {
case Undefined:
return "Undefined";
case shcore::Null:
return "Null";
case Bool:
return "Bool";
case Integer:
return "Integer";
case UInteger:
return "UInteger";
case Float:
return "Float";
case String:
return "String";
case Object:
return "Object";
case Array:
return "Array";
case Map:
return "Map";
case MapRef:
return "MapRef";
case Function:
return "Function";
case Binary:
return "Binary";
default:
return "";
}
}
std::string type_description(Value_type type) {
switch (type) {
case Undefined:
return "an undefined";
case shcore::Null:
return "a null";
case Bool:
return "a bool";
case Integer:
return "an integer";
case UInteger:
return "an unsigned integer";
case Float:
return "a float";
case String:
return "a string";
case Object:
return "an object";
case Array:
return "an array";
case Map:
case MapRef:
return "a map";
case Function:
return "a function";
case Binary:
return "a binary string";
default:
return "";
}
}
// --
Value_type Value::Map_type::get_type(const std::string &k) const {
const_iterator iter = find(k);
if (iter == end()) return Undefined;
return iter->second.type;
}
std::string Value::Map_type::get_string(const std::string &k,
const std::string &def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(String);
return iter->second.get_string();
}
bool Value::Map_type::get_bool(const std::string &k, bool def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(Bool);
return iter->second.as_bool();
}
int64_t Value::Map_type::get_int(const std::string &k, int64_t def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(Integer);
return iter->second.as_int();
}
uint64_t Value::Map_type::get_uint(const std::string &k, uint64_t def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(UInteger);
return iter->second.as_uint();
}
double Value::Map_type::get_double(const std::string &k, double def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(Float);
return iter->second.as_double();
}
std::shared_ptr<Value::Map_type> Value::Map_type::get_map(
const std::string &k, std::shared_ptr<Map_type> def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(Map);
return iter->second.as_map();
}
std::shared_ptr<Value::Array_type> Value::Map_type::get_array(
const std::string &k, std::shared_ptr<Array_type> def) const {
const_iterator iter = find(k);
if (iter == end()) return def;
iter->second.check_type(Array);
return iter->second.as_array();
}
void Value::Map_type::merge_contents(std::shared_ptr<Map_type> source,
bool overwrite) {
Value::Map_type::const_iterator iter;
for (iter = source->begin(); iter != source->end(); ++iter) {
std::string k = iter->first;
Value v = iter->second;
if (!overwrite && this->has_key(k)) continue;
(*this)[k] = v;
}
}
Value::Value(const std::string &s, bool binary)
: type(binary ? Binary : String) {
value.s = new std::string(s);
}
Value::Value(std::string &&s, bool binary) : type(binary ? Binary : String) {
value.s = new std::string(std::move(s));
}
Value::Value(const char *s) {
if (s) {
type = String;
value.s = new std::string(s);
} else {
type = shcore::Null;
}
}
Value::Value(const char *s, size_t n, bool binary) {
if (s) {
type = binary ? Binary : String;
value.s = new std::string(s, n);
} else {
type = shcore::Null;
}
}
Value::Value(std::string_view s, bool binary) : type(binary ? Binary : String) {
value.s = new std::string(s);
}
Value::Value(std::wstring_view s)
: Value(shcore::wide_to_utf8(s.data(), s.length())) {}
Value::Value(std::nullptr_t) : type(shcore::Null) {}
Value::Value(int i) : type(Integer) { value.i = i; }
Value::Value(unsigned int ui) : type(UInteger) { value.ui = ui; }
Value::Value(int64_t i) : type(Integer) { value.i = i; }
Value::Value(uint64_t ui) : type(UInteger) { value.ui = ui; }
Value::Value(float f) : type(Float) {
// direct typecast from float to double works by just appending 0s to the
// binary IEEE representation, which will result in a different number
// So we convert through decimal instead
value.d = std::stod(shcore::ftoa(f));
}
Value::Value(double d) : type(Float) { value.d = d; }
Value::Value(bool b) : type(Bool) { value.b = b; }
Value::Value(const std::shared_ptr<Function_base> &f) : type(Function) {
if (f) {
value.func = new std::shared_ptr<Function_base>(f);
} else {
type = shcore::Null;
}
}
Value::Value(std::shared_ptr<Function_base> &&f) : type(Function) {
if (f) {
value.func = new std::shared_ptr<Function_base>(std::move(f));
} else {
type = shcore::Null;
}
}
Value::Value(const std::shared_ptr<Object_bridge> &n) : type(Object) {
if (n) {
value.o = new std::shared_ptr<Object_bridge>(n);
} else {
type = shcore::Null;
}
}
Value::Value(std::shared_ptr<Object_bridge> &&n) : type(Object) {
if (n) {
value.o = new std::shared_ptr<Object_bridge>(std::move(n));
} else {
type = shcore::Null;
}
}
Value::Value(const Map_type_ref &n) : type(Map) {
if (n) {
value.map = new std::shared_ptr<Map_type>(n);
} else {
type = shcore::Null;
}
}
Value::Value(Map_type_ref &&n) : type(Map) {
if (n) {
value.map = new std::shared_ptr<Map_type>(std::move(n));
} else {
type = shcore::Null;
}
}
Value::Value(const std::weak_ptr<Map_type> &n) : type(MapRef) {
value.mapref = new std::weak_ptr<Map_type>(n);
}
Value::Value(std::weak_ptr<Map_type> &&n) : type(MapRef) {
value.mapref = new std::weak_ptr<Map_type>(std::move(n));
}
Value::Value(const Array_type_ref &n) : type(Array) {
if (n) {
value.array = new std::shared_ptr<Array_type>(n);
} else {
type = shcore::Null;
}
}
Value::Value(Array_type_ref &&n) : type(Array) {
if (n) {
value.array = new std::shared_ptr<Array_type>(std::move(n));
} else {
type = shcore::Null;
}
}
Value &Value::operator=(const Value &other) {
if (this == &other) return *this;
if (type == other.type) {
switch (type) {
case Undefined:
break;
case shcore::Null:
break;
case Bool:
value.b = other.value.b;
break;
case Integer:
value.i = other.value.i;
break;
case UInteger:
value.ui = other.value.ui;
break;
case Float:
value.d = other.value.d;
break;
case Binary:
case String:
*value.s = *other.value.s;
break;
case Object:
*value.o = *other.value.o;
break;
case Array:
*value.array = *other.value.array;
break;
case Map:
*value.map = *other.value.map;
break;
case MapRef:
*value.mapref = *other.value.mapref;
break;
case Function:
*value.func = *other.value.func;
break;
}
} else {
switch (type) {
case Undefined:
case shcore::Null:
case Bool:
case Integer:
case UInteger:
case Float:
break;
case Binary:
case String:
delete value.s;
break;
case Object:
delete value.o;
break;
case Array:
delete value.array;
break;
case Map:
delete value.map;
break;
case MapRef:
delete value.mapref;
break;
case Function:
delete value.func;
break;
}
type = other.type;
switch (type) {
case Undefined:
case shcore::Null:
break;
case Bool:
value.b = other.value.b;
break;
case Integer:
value.i = other.value.i;
break;
case UInteger:
value.ui = other.value.ui;
break;
case Float:
value.d = other.value.d;
break;
case Binary:
case String:
value.s = new std::string(*other.value.s);
break;
case Object:
value.o = new std::shared_ptr<Object_bridge>(*other.value.o);
break;
case Array:
value.array = new std::shared_ptr<Array_type>(*other.value.array);
break;
case Map:
value.map = new std::shared_ptr<Map_type>(*other.value.map);
break;
case MapRef:
value.mapref = new std::weak_ptr<Map_type>(*other.value.mapref);
break;
case Function:
value.func = new std::shared_ptr<Function_base>(*other.value.func);
break;
}
}
return *this;
}
Value &Value::operator=(Value &&other) noexcept {
if (this == &other) return *this;
switch (type) {
case Undefined:
case shcore::Null:
case Bool:
case Integer:
case UInteger:
case Float:
break;
case Binary:
case String:
delete value.s;
value.s = nullptr;
break;
case Object:
delete value.o;
value.s = nullptr;
break;
case Array:
delete value.array;
value.s = nullptr;
break;
case Map:
delete value.map;
value.s = nullptr;
break;
case MapRef:
delete value.mapref;
value.s = nullptr;
break;
case Function:
delete value.func;
value.s = nullptr;
break;
}
type = Undefined;
std::swap(type, other.type);
switch (type) {
case Undefined:
case shcore::Null:
break;
case Bool:
std::swap(value.b, other.value.b);
break;
case Integer:
std::swap(value.i, other.value.i);
break;
case UInteger:
std::swap(value.ui, other.value.ui);
break;
case Float:
std::swap(value.d, other.value.d);
break;
case Binary:
case String:
std::swap(value.s, other.value.s);
break;
case Object:
std::swap(value.o, other.value.o);
break;
case Array:
std::swap(value.array, other.value.array);
break;
case Map:
std::swap(value.map, other.value.map);
break;
case MapRef:
std::swap(value.mapref, other.value.mapref);
break;
case Function:
std::swap(value.func, other.value.func);
break;
}
return *this;
}
Value Value::parse_map(const char **pc) {
Map_type_ref map(new Map_type());
// Skips the opening {
++*pc;
bool done = false;
while (!done) {
skip_whitespace(pc);
if (**pc == '}') {
++*pc;
break;
}
Value key, value;
if (**pc != '"' && **pc != '\'')
throw Exception::parser_error(
"Error parsing map, unexpected character reading key.");
else {
key = parse_string(pc, **pc);
skip_whitespace(pc);
if (**pc != ':')
throw Exception::parser_error(
"Error parsing map, unexpected item value separator.");
// skips the :
++*pc;
skip_whitespace(pc);
value = parse(pc);
(*map)[key.get_string()] = value;
skip_whitespace(pc);
if (**pc == '}') {
done = true;
// Skips the }
++*pc;
} else if (**pc == ',')
++*pc;
else
throw Exception::parser_error(
"Error parsing map, unexpected item separator.");
skip_whitespace(pc);
}
}
return Value(map);
}
Value Value::parse_array(const char **pc) {
Array_type_ref array(new Array_type());
// Skips the opening [
++*pc;
skip_whitespace(pc);
bool done = false;
while (!done) {
skip_whitespace(pc);
if (**pc != ']') array->push_back(parse(pc));
skip_whitespace(pc);
if (**pc == ']') {
done = true;
// Skips the ]
++*pc;
} else if (**pc == ',')
++*pc;
else
throw Exception::parser_error(
"Error parsing array, unexpected value separator.");
skip_whitespace(pc);
}
return Value(array);
}
inline std::string unicode_codepoint_to_utf8(uint32_t uni) {
std::string s;
if (uni <= 0x7f) {
s.push_back(static_cast<char>(uni & 0xff)); // 0xxxxxxx
} else if (uni <= 0x7ff) {
s.push_back(static_cast<char>(((uni >> 6) & 0xff) | 0xc0)); // 110xxxxxx
s.push_back(static_cast<char>((uni & 0x3f) | 0x80)); // 10xxxxxx
} else if (uni <= 0xffff) {
s.push_back(static_cast<char>(((uni >> 12) & 0xff) | 0xe0)); // 1110xxxx
s.push_back(static_cast<char>(((uni >> 6) & 0x3f) | 0x80)); // 110xxxxxx
s.push_back(static_cast<char>((uni & 0x3f) | 0x80)); // 10xxxxxx
} else {
if (uni >= 0x10ffff) {
throw Exception::parser_error("Invalid unicode codepoint");
}
s.push_back(static_cast<char>(((uni >> 18) & 0xff) | 0xf0)); // 11110xxx
s.push_back(static_cast<char>(((uni >> 12) & 0x3f) | 0x80)); // 1110xxxx
s.push_back(static_cast<char>(((uni >> 6) & 0x3f) | 0x80)); // 110xxxxxx
s.push_back(static_cast<char>((uni & 0x3f) | 0x80)); // 10xxxxxx
}
return s;
}
Value Value::parse_string(const char **pc, char quote) {
const char *p = *pc;
// calculate length
while (*p && *++p != quote) {
// escaped char
if (*p == '\\') ++p;
}
int32_t len = p - *pc;
if (*p != quote) {
std::string msg = "missing closing ";
msg.push_back(quote);
throw Exception::parser_error(msg);
}
std::string s;
p = *pc;
++*pc;
while (**pc != '\0' && (*pc - p < len)) {
const char *pc_i = *pc;
if (*pc_i == '\\') {
switch (*(pc_i + 1)) {
case 'n':
s.append("\n");
break;
case '"':
s.append("\"");
break;
case '\'':
s.append("\'");
break;
case 'a':
s.append("\a");
break;
case 'b':
s.append("\b");
break;
case 'f':
s.append("\f");
break;
case 'r':
s.append("\r");
break;
case 't':
s.append("\t");
break;
case 'v':
s.append("\v");
break;
case '\\':
s.append("\\");
break;
case 'x': {
if (*pc - (pc_i + 1 + 2) < len && isxdigit(pc_i[2]) &&
isxdigit(pc_i[3])) {
const char c =
(ascii_to_hex[static_cast<unsigned char>(pc_i[2])] << 4) |
ascii_to_hex[static_cast<unsigned char>(pc_i[3])];
s.append(std::string{c});
pc_i += 2;
} else {
throw Exception::parser_error("Invalid \\xXX hex escape");
}
} break;
case 'u':
if (*pc - (pc_i + 1 + 4) < len && isxdigit(pc_i[2]) &&
isxdigit(pc_i[3]) && isxdigit(pc_i[4]) && isxdigit(pc_i[5])) {
uint32_t unich =
(ascii_to_hex[static_cast<unsigned>(pc_i[2])] << 12) |
(ascii_to_hex[static_cast<unsigned>(pc_i[3])] << 8) |
(ascii_to_hex[static_cast<unsigned>(pc_i[4])] << 4) |
ascii_to_hex[static_cast<unsigned>(pc_i[5])];
s.append(unicode_codepoint_to_utf8(unich));
pc_i += 4;
} else {
throw Exception::parser_error("Invalid \\uXXXX unicode escape");
}
break;
case '\0':
s.append("\0");
break;
}
*pc = pc_i + 2;
} else {
s.append(*pc, 1);
++*pc;
}
}
// Skips the closing quote
++*pc;
return Value(s);
}
Value Value::parse_single_quoted_string(const char **pc) {
return parse_string(pc, '\'');
}
Value Value::parse_double_quoted_string(const char **pc) {
return parse_string(pc, '"');
}
Value Value::parse_number(const char **pcc) {
Value ret_val;
const char *pc = *pcc;
// Parsing based on the following state table
// Valid integer: state == 1
// Valid float: state == 2,4,6
// 0 1 2 3 4 5 6
// + # . # e + d
constexpr unsigned char STATE_VALIDITY[] = {0, 1, 0, 1, 0, 0, 1};
enum States {
FRONT_SIGN,
INT_DIGITS,
DOT,
FLOAT_DIGITS,
EXP,
EXP_SIGN,
EXP_DIGITS
};
// State starts at 1
int state = INT_DIGITS;
// Sign can appear at the beggining
if (*pc == '-' || *pc == '+') {
++pc;
state = FRONT_SIGN;
}
// Continues while there are digits
if (*pc && IS_DIGIT(*pc)) state = INT_DIGITS;
while (*pc && IS_DIGIT(*++pc))
;
if (STATE_VALIDITY[state] == 1 && tolower(*pc) == '.') {
state = DOT;
// Skips the .
++pc;
// Continues while there are digits
if (*pc && IS_DIGIT(*pc)) state = FLOAT_DIGITS;
while (*pc && IS_DIGIT(*++pc))
;
}
// exponential
if (STATE_VALIDITY[state] == 1 && tolower(*pc) == 'e') {
state = EXP;
// Skips the e
++pc;
// Sign can appear for exponential numbers
if (*pc == '-' || *pc == '+') {
++pc;
state = EXP_SIGN;
}
// Continues while there are digits
if (*pc && IS_DIGIT(*pc)) state = FLOAT_DIGITS;
while (*pc && IS_DIGIT(*++pc))
;
}
size_t len = pc - *pcc;
std::string number(*pcc, len);
if (STATE_VALIDITY[state] == 1) {
if (state == INT_DIGITS) {
int64_t ll = 0;
try {
ll = std::atoll(number.c_str());
} catch (const std::invalid_argument &e) {
std::string s = "Error parsing int: ";
s += e.what();
throw Exception::parser_error(s);
}
ret_val = Value(static_cast<int64_t>(ll));
} else {
double d = 0;
try {
d = std::stod(number.c_str());
} catch (const std::invalid_argument &e) {
std::string s = "Error parsing float: ";
s += e.what();
throw Exception::parser_error(s);
}
ret_val = Value(d);
}
} else {
std::string parsed(*pcc, pc - *pcc);
throw Exception::parser_error("Error parsing number from: '" + parsed +
"'");
}
*pcc = pc;
return ret_val;
}
Value Value::parse(const std::string &s) {
const char *begin = s.c_str();
const char *pc = begin;
// ignore any white-space at the beginning of string
skip_whitespace(&pc);
Value tmp(parse(&pc));
size_t parsed_length = pc - begin;
if (parsed_length < s.size()) {
// ensure any leftover chars are just whitespaces
skip_whitespace(&pc);
parsed_length = pc - begin;
if (parsed_length < s.size())
throw Exception::parser_error(
"Unexpected characters left at the end of document: ..." +
std::string(pc));
}
return tmp;
}
Value Value::parse(const char *s, size_t size) {
const char *begin = s;
const char *pc = begin;
// ignore any white-space at the beginning of string
skip_whitespace(&pc);
Value tmp(parse(&pc));
size_t parsed_length = pc - begin;
if (parsed_length < size) {
// ensure any leftover chars are just whitespaces
skip_whitespace(&pc);
parsed_length = pc - begin;
if (parsed_length < size)
throw Exception::parser_error(
"Unexpected characters left at the end of document: ..." +
std::string(pc));
}
return tmp;
}
Value Value::parse(const char **pc) {
if (**pc == '{') return parse_map(pc);
if (**pc == '[') return parse_array(pc);
if (**pc == '"') return parse_double_quoted_string(pc);
if (**pc == '\'') return parse_single_quoted_string(pc);
if (IS_DIGIT(**pc) || **pc == '-' || **pc == '+') // a number
return parse_number(pc);
// a constant between true, false, null
const char *pi = *pc;
while (**pc && IS_ALPHA(**pc)) ++*pc;
std::string_view tok{pi, static_cast<size_t>(*pc - pi)};
if (tok.size() == 9 && str_caseeq(tok, "undefined")) return Value();
if (tok.size() == 4 && str_caseeq(tok, "true")) return Value(true);
if (tok.size() == 4 && str_caseeq(tok, "null")) return Value::Null();
if (tok.size() == 5 && str_caseeq(tok, "false")) return Value(false);
throw Exception::parser_error("Can't parse '" + std::string{tok} + "'");
}
bool Value::operator==(const Value &other) const {
if (type == other.type) {
switch (type) {
case Undefined:
return true; // undefined == undefined is true
case shcore::Null:
return true;
case Bool:
return value.b == other.value.b;
case Integer:
return value.i == other.value.i;
case UInteger:
return value.ui == other.value.ui;
case Float:
return value.d == other.value.d;
case Binary:
case String:
return *value.s == *other.value.s;
case Object:
return **value.o == **other.value.o;
case Array:
return **value.array == **other.value.array;
case Map:
return **value.map == **other.value.map;
case MapRef:
return *value.mapref->lock() == *other.value.mapref->lock();
case Function:
return **value.func == **other.value.func;
}
} else {
// with type conversion
switch (type) {
case Undefined:
return false;
case shcore::Null:
return false;
case Bool:
switch (other.type) {
case Integer:
if (other.value.i == 1)
return value.b == true;
else if (other.value.i == 0)
return value.b == false;
return false;
case UInteger:
if (other.value.ui == 1)
return value.b == true;
else if (other.value.ui == 0)
return value.b == false;
return false;
case Float:
if (other.value.d == 1.0)
return value.b == true;
else if (other.value.d == 0.0)
return value.b == false;
return false;
default:
return false;
}
case Integer:
switch (other.type) {
case Bool:
return other.operator==(*this);
case Float:
return value.i == (int64_t)other.value.d &&
((other.value.d - (int64_t)other.value.d) == 0.0);
default:
return false;
}
case UInteger:
switch (other.type) {
case Bool:
return other.operator==(*this);
case Float:
return value.ui == (uint64_t)other.value.d &&
((other.value.d - (uint64_t)other.value.d) == 0.0);
default:
return false;
}
case Float:
switch (other.type) {
case Bool:
return other.operator==(*this);
case Integer:
case UInteger:
return other.operator==(*this);
default:
return false;
}
default:
return false;
}
}
return false;
}
bool Value::operator<(const Value &other) const {
if (type == other.type) {
switch (type) {
case Undefined:
return false;
case shcore::Null:
return false;
case Bool:
return value.b < other.value.b;
case Integer:
return value.i < other.value.i;
case UInteger:
return value.ui < other.value.ui;
case Float:
return value.d < other.value.d;
case Binary:
case String:
return *value.s < *other.value.s;
case Object:
return **value.o < **other.value.o;
case Array:
return **value.array < **other.value.array;
case Map:
return **value.map < **other.value.map;
case MapRef:
return *value.mapref->lock() < *other.value.mapref->lock();
case Function:
// NOTE: not implemented, it's not possible to order functions
return false;
}
} else {
// with type conversion
switch (type) {
case Undefined:
return false;
case shcore::Null:
return false;
case Bool:
switch (other.type) {
case Integer:
if (other.value.i == 1)
return value.b < true;
else if (other.value.i == 0)
return false;
return false;
case UInteger:
if (other.value.ui == 1)
return value.b < true;
else if (other.value.ui == 0)
return false;
return false;
case Float:
if (other.value.d == 1.0)
return value.b < true;
else if (other.value.d == 0.0)
return false;
return false;
default:
return false;
}
case Integer:
switch (other.type) {
case Bool:
return other.operator<(*this);
case Float:
return value.i < (int64_t)other.value.d ||
(value.i < (int64_t)other.value.d &&
((other.value.d - (int64_t)other.value.d) > 0.0));
default:
return false;
}
case UInteger:
switch (other.type) {
case Bool:
return other.operator<(*this);
case Float:
return value.ui < (uint64_t)other.value.d ||
(value.ui == (uint64_t)other.value.d &&
((other.value.d - (uint64_t)other.value.d) > 0.0));
default:
return false;
}
case Float:
switch (other.type) {
case Bool:
return other.operator<(*this);
case Integer:
case UInteger:
return other.operator<(*this);
default:
return false;
}
default:
return false;
}
}
return false;
}
bool Value::operator<=(const Value &other) const {
if (type == other.type) {
switch (type) {
case Undefined:
return true; // undefined == undefined is true
case shcore::Null:
return true;
case Bool:
return value.b <= other.value.b;
case Integer:
return value.i <= other.value.i;
case UInteger:
return value.ui <= other.value.ui;
case Float:
return value.d <= other.value.d;
case Binary:
case String:
return *value.s <= *other.value.s;
case Object:
return **value.o <= **other.value.o;
case Array:
return **value.array <= **other.value.array;
case Map:
return **value.map <= **other.value.map;
case MapRef:
return *value.mapref->lock() <= *other.value.mapref->lock();
case Function:
// NOTE: not implemented, it's not possible to order functions
return **value.func == **other.value.func;
}
} else {
// with type conversion
switch (type) {
case Undefined:
return false;
case shcore::Null:
return false;
case Bool:
switch (other.type) {
case Integer:
if (other.value.i == 1)
return true;
else if (other.value.i == 0)
return value.b <= false;
return false;
case UInteger:
if (other.value.ui == 1)
return true;
else if (other.value.ui == 0)
return value.b <= false;
return false;
case Float:
if (other.value.d == 1.0)
return true;
else if (other.value.d == 0.0)
return value.b <= false;
return false;
default:
return false;
}
case Integer:
switch (other.type) {
case Bool:
return other.operator<=(*this);
case Float:
return value.i < (int64_t)other.value.d ||
(value.i == (int64_t)other.value.d &&
((other.value.d - (int64_t)other.value.d) >= 0.0));
default:
return false;
}
case UInteger:
switch (other.type) {
case Bool:
return other.operator<=(*this);
case Float:
return value.ui < (uint64_t)other.value.d ||
(value.ui == (uint64_t)other.value.d &&
((other.value.d - (uint64_t)other.value.d) >= 0.0));
default:
return false;
}
case Float:
switch (other.type) {
case Bool:
return other.operator<=(*this);
case Integer:
case UInteger:
return other.operator<=(*this);
default:
return false;
}
default:
return false;
}
}
return false;
}
std::string Value::json(bool pprint) const {
std::stringstream s;
JSON_dumper dumper(pprint);
dumper.append_value(*this);
return dumper.str();
}
std::string Value::descr(bool pprint) const {
std::string s;
append_descr(s, pprint ? 0 : -1, false); // top level strings are not quoted
return s;
}
std::string Value::repr() const {
std::string s;
append_repr(s);
return s;
}
std::string &Value::append_descr(std::string &s_out, int indent,
char quote_strings) const {
std::string nl = (indent >= 0) ? "\n" : "";
switch (type) {
case Undefined:
s_out.append("undefined");
break;
case shcore::Null:
s_out.append("null");
break;
case Bool:
if (value.b)
s_out += "true";
else
s_out += "false";
break;
case Integer:
s_out += std::to_string(value.i);
break;
case UInteger:
s_out += std::to_string(value.ui);
break;
case Float:
s_out += shcore::dtoa(value.d);
break;
case String:
if (quote_strings) {
s_out += quote_string(*value.s, quote_strings);
} else {
s_out += *value.s;
}
break;
case Object:
if (!value.o || !*value.o)
throw Exception::value_error("Invalid object value encountered");
as_object()->append_descr(s_out, indent, quote_strings);
break;
case Array: {
if (!value.array || !*value.array)
throw Exception::value_error("Invalid array value encountered");
Array_type *vec = value.array->get();
Array_type::iterator myend = vec->end(), mybegin = vec->begin();
s_out += "[";
for (Array_type::iterator iter = mybegin; iter != myend; ++iter) {
if (iter != mybegin) s_out += ", ";
s_out += nl;
if (indent >= 0) s_out.append((indent + 1) * 4, ' ');
iter->append_descr(s_out, indent < 0 ? indent : indent + 1, '"');
}
if (!vec->empty()) {
s_out += nl;
if (indent > 0) s_out.append(indent * 4, ' ');
}
s_out += "]";
} break;
case Map: {
if (!value.map || !*value.map)
throw Exception::value_error("Invalid map value encountered");
Map_type *map = value.map->get();
Map_type::iterator myend = map->end(), mybegin = map->begin();
s_out += "{";
if (!map->empty()) s_out += nl;
for (Map_type::iterator iter = mybegin; iter != myend; ++iter) {
if (iter != mybegin) s_out += ", " + nl;
if (indent >= 0) s_out.append((indent + 1) * 4, ' ');
s_out += quote_string(iter->first, '"') + ": ";
iter->second.append_descr(s_out, indent < 0 ? indent : indent + 1, '"');
}
if (!map->empty()) {
s_out += nl;
if (indent > 0) s_out.append(indent * 4, ' ');
}
s_out += "}";
} break;
case MapRef:
s_out.append("mapref");
break;
case Function:
value.func->get()->append_descr(&s_out, indent, quote_strings);
break;
case Binary:
s_out += shcore::string_to_hex(*value.s);
break;
}
return s_out;
}
std::string &Value::append_repr(std::string &s_out) const {
switch (type) {
case Undefined:
s_out.append("undefined");
break;
case shcore::Null:
s_out.append("null");
break;
case Bool:
if (value.b)
s_out += "true";
else
s_out += "false";
break;
case Integer: {
std::ostringstream value_i;
value_i << value.i;
s_out += value_i.str();
} break;
case UInteger: {
std::ostringstream value_ui;
value_ui << value.ui;
s_out += value_ui.str();
} break;
case Float: {
s_out += str_format("%g", value.d);
} break;
case String: {
std::string &s = *value.s;
s_out += "\"";
for (size_t i = 0; i < s.length(); i++) {
unsigned char c = s[i];
// non-printable ascii char
if (!(0x20 <= c && c < 0x7f)) {
s_out += "\\x";
s_out += "0123456789abcdef"[c >> 4];
s_out += "0123456789abcdef"[c & 0xf];
continue;
}
switch (c) {
case '\n':
s_out += "\\n";
break;
case '"':
s_out += "\\\"";
break;
case '\'':
s_out += "\\\'";
break;
case '\a':
s_out += "\\a";
break;
case '\b':
s_out += "\\b";
break;
case '\f':
s_out += "\\f";
break;
case '\r':
s_out += "\\r";
break;
case '\t':
s_out += "\\t";
break;
case '\v':
s_out += "\\v";
break;
case '\\':
s_out += "\\\\";
break;
default:
s_out += c;
}
}
s_out += "\"";
} break;
case Object:
s_out = (*value.o)->append_repr(s_out);
break;
case Array: {
Array_type *vec = value.array->get();
Array_type::iterator myend = vec->end(), mybegin = vec->begin();
s_out += "[";
for (Array_type::iterator iter = mybegin; iter != myend; ++iter) {
if (iter != mybegin) s_out += ", ";
iter->append_repr(s_out);
}
s_out += "]";
} break;
case Map: {
Map_type *map = value.map->get();
Map_type::iterator myend = map->end(), mybegin = map->begin();
s_out += "{";
for (Map_type::iterator iter = mybegin; iter != myend; ++iter) {
if (iter != mybegin) s_out += ", ";
Value key(iter->first);
key.append_repr(s_out);
s_out += ": ";
iter->second.append_repr(s_out);
}
s_out += "}";
} break;
case MapRef:
break;
case Function:
value.func->get()->append_repr(&s_out);
break;
case Binary:
s_out += shcore::string_to_hex(*value.s);
break;
}
return s_out;
}
Value::~Value() noexcept {
switch (type) {
case Undefined:
case shcore::Null:
case Bool:
case Integer:
case UInteger:
case Float:
break;
case Binary:
case String:
delete value.s;
break;
case Object:
delete value.o;
break;
case Array:
delete value.array;
break;
case Map:
delete value.map;
break;
case MapRef:
delete value.mapref;
break;
case Function:
delete value.func;
break;
}
}
inline Exception type_conversion_error(Value_type from, Value_type expected) {
return Exception::type_error("Invalid typecast: " + type_name(expected) +
" expected, but value is " + type_name(from));
}
inline Exception type_range_error(Value_type from, Value_type expected) {
return Exception::type_error("Invalid typecast: " + type_name(expected) +
" expected, but " + type_name(from) +
" value is out of range");
}
bool is_compatible_type(Value_type source_type, Value_type target_type) {
return kTypeConvertible[source_type][target_type];
}
void Value::check_type(Value_type t) const {
if (!is_compatible_type(type, t)) throw type_conversion_error(type, t);
}
bool Value::as_bool() const {
switch (type) {
case Bool:
return value.b;
case Integer:
return value.i != 0;
case UInteger:
return value.ui != 0;
case Float:
return value.d != 0.0;
case String:
try {
return lexical_cast<bool>(*value.s);
} catch (...) {
}
break;
default:
break;
}
throw type_conversion_error(type, Bool);
}
int64_t Value::as_int() const {
switch (type) {
case Integer:
return value.i;
case UInteger:
if (value.ui <= (uint64_t)std::numeric_limits<int64_t>::max())
return static_cast<int64_t>(value.ui);
throw type_range_error(type, Integer);
case Float: {
double integral;
if (std::modf(value.d, &integral) == 0.0 &&
value.d >= -(1LL << DBL_MANT_DIG) && value.d <= (1LL << DBL_MANT_DIG))
return static_cast<int64_t>(value.d);
throw type_range_error(type, Integer);
}
case Bool:
return value.b ? 1 : 0;
case String:
try {
return lexical_cast<int64_t>(*value.s);
} catch (...) {
}
break;
default:
break;
}
throw type_conversion_error(type, Integer);
}
uint64_t Value::as_uint() const {
switch (type) {
case UInteger:
return value.ui;
case Integer:
if (value.i >= 0) return static_cast<uint64_t>(value.i);
throw type_range_error(type, UInteger);
case Float: {
double integral;
if (std::modf(value.d, &integral) == 0.0 && value.d >= 0.0 &&
value.d <= (1LL << DBL_MANT_DIG))
return static_cast<uint64_t>(value.d);
throw type_range_error(type, UInteger);
}
case Bool:
return value.b ? 1 : 0;
case String:
try {
return lexical_cast<uint64_t>(*value.s);
} catch (...) {
}
break;
default:
break;
}
throw type_conversion_error(type, UInteger);
}
double Value::as_double() const {
// DBL_MANT_DIG is the number of bits used to represent the number itself,
// without the exponent or sign
switch (type) {
case UInteger:
if (value.ui <= (1ULL << DBL_MANT_DIG))
return static_cast<double>(value.ui);
// higher values lose precision
throw type_range_error(type, Float);
case Integer:
if (value.i <= (1LL << DBL_MANT_DIG) && value.i >= -(1LL << DBL_MANT_DIG))
return static_cast<double>(value.i);
// higher or lower values lose precision
throw type_range_error(type, Float);
case Float:
return value.d;
case Bool:
return value.b ? 1.0 : 0.0;
case String:
try {
return lexical_cast<double>(*value.s);
} catch (...) {
}
break;
default:
break;
}
throw type_conversion_error(type, Float);
}
std::string Value::as_string() const {
switch (type) {
case UInteger:
return std::to_string(value.ui);
case Integer:
return std::to_string(value.i);
case Float:
return lexical_cast<std::string>(value.d);
case Bool:
return lexical_cast<std::string>(value.b);
case String:
return *value.s;
default:
break;
}
throw type_conversion_error(type, String);
}
std::wstring Value::as_wstring() const {
return shcore::utf8_to_wide(as_string());
}
std::string Value::yaml() const { return "---\n" + yaml(0) + "\n"; }
std::string Value::yaml(int init_indent) const {
// implementation based on: http://yaml.org/spec/1.2/spec.html
static constexpr size_t k_indent_size = 2;
const auto new_line = [](std::string *s, int indent) {
s->append(1, '\n');
s->append(k_indent_size * indent, ' ');
};
const auto map2yaml = [&](const Dictionary_t &m, int indent) {
if (!m) {
// null -> empty string
return std::string{};
}
std::string map;
bool first_item = true;
for (const auto &kv : *m) {
if (first_item) {
first_item = false;
} else {
new_line(&map, indent);
}
map += kv.first + ":";
const auto sub_yaml = kv.second.yaml(indent + 1);
// if value is a map or an array it needs to be in a separate line
if (kv.second.type == Value_type::Map ||
kv.second.type == Value_type::MapRef ||
kv.second.type == Value_type::Array) {
new_line(&map, indent + 1);
} else {
if (!sub_yaml.empty()) {
map += " ";
}
}
map += sub_yaml;
}
return map;
};
const auto string2yaml = [](const std::string &s, int indent) -> std::string {
if (s.empty()) {
// empty string must be explicitly quoted
return "''";
}
auto content = split_string(s, "\n");
if (content.size() == 1) {
// use either plain or quoted style
bool quote = false;
{
// plain string cannot contain leading or trailing white-space
// characters
static constexpr auto k_whitespace = " \t";
if (std::string{k_whitespace}.find(s[0]) != std::string::npos ||
std::string{k_whitespace}.find(s.back()) != std::string::npos) {
quote = true;
}
}
if (!quote) {
// plain string cannot start with an indicator
if (std::string{"-?:,[]{}#&*!|>'\"%@`"}.find(s[0]) !=
std::string::npos) {
if (s.length() > 1 &&
std::string{"-?:"}.find(s[0]) != std::string::npos &&
s[1] != ' ') {
// unless it's '-', '?' or ':' and it's not followed by a space
quote = false;
} else {
quote = true;
}
}
}
if (!quote) {
// plain scalars must never contain the ": " and " #" character
// combinations
if (s.find(": ") != std::string::npos ||
s.find(" #") != std::string::npos) {
quote = true;
}
}
if (quote) {
return "'" + str_replace(s, "'", "''") + "'";
} else {
return s;
}
} else {
// by default set chomping to 'strip'
char chomping = '-';
if (content.back().empty()) {
// if the last line is an empty string, it means that the input string
// ends with a new line character and the extra line is not needed
content.pop_back();
// chomping needs to be set to 'clip', so the final line break is
// included
chomping = 0;
}
if (content.back().empty()) {
// if the last line is still an empty string, set chomping to 'keep',
// so multiple empty lines are preserved
chomping = '+';
}
// use literal block style
std::string literal = "|";
// increase the indent for contents
++indent;
if (content[0][0] == ' ' || content[0][0] == '\t') {
// if the first line starts with a white-space, we need to explicitly
// specify the indent
literal += std::to_string(k_indent_size * indent);
}
// add chomping indicator if not 'clip'
if (chomping) {
literal += chomping;
}
literal += "\n";
for (const auto &line : content) {
literal.append(k_indent_size * indent, ' ');
literal += line;
literal.append(1, '\n');
}
// remove the last new line character
literal.pop_back();
return literal;
}
};
switch (type) {
case Value_type::Undefined:
case Value_type::Null:
// YAML has no notion of these values, they are represented by empty
// strings
return "";
case Value_type::Bool:
case Value_type::Integer:
case Value_type::UInteger:
case Value_type::Float:
case Value_type::Object:
case Value_type::Function:
// we treat these as scalars
return string2yaml(descr(), init_indent);
case Value_type::String:
return string2yaml(*value.s, init_indent);
case Value_type::Array: {
std::string array;
bool first_item = true;
for (const auto &v : **value.array) {
if (first_item) {
first_item = false;
} else {
new_line(&array, init_indent);
}
array += "-";
const auto sub_yaml = v.yaml(init_indent + 1);
if (!sub_yaml.empty()) {
array += " " + sub_yaml;
}
}
return array;
}
case Value_type::Map:
return map2yaml(*value.map, init_indent);
case Value_type::MapRef:
return map2yaml(value.mapref->lock(), init_indent);
case Value_type::Binary:
// TODO(rennox): implement binary
return string2yaml(*value.s, init_indent);
}
throw std::logic_error("Type '" + type_name(type) + "' was not handled.");
}
//---
const std::string &Argument_list::string_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
switch (at(i).type) {
case String:
return *at(i).value.s;
default:
throw Exception::type_error(
str_format("Argument #%u is expected to be a string", (i + 1)));
}
}
bool Argument_list::bool_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
try {
return at(i).as_bool();
} catch (...) {
throw Exception::type_error(
str_format("Argument #%u is expected to be a bool", (i + 1)));
}
}
int64_t Argument_list::int_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
try {
return at(i).as_int();
} catch (...) {
throw Exception::type_error(
str_format("Argument #%u is expected to be an int", (i + 1)));
}
}
uint64_t Argument_list::uint_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
try {
return at(i).as_uint();
} catch (...) {
throw Exception::type_error(
str_format("Argument #%u is expected to be an unsigned int", (i + 1)));
}
}
double Argument_list::double_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
try {
return at(i).as_double();
} catch (...) {
throw Exception::type_error(
str_format("Argument #%u is expected to be a double", (i + 1)));
}
}
std::shared_ptr<Object_bridge> Argument_list::object_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
if (at(i).type != Object)
throw Exception::type_error(
str_format("Argument #%u is expected to be an object", (i + 1)));
return *at(i).value.o;
}
std::shared_ptr<Value::Map_type> Argument_list::map_at(unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
if (at(i).type != Map)
throw Exception::type_error(
str_format("Argument #%u is expected to be a map", (i + 1)));
return *at(i).value.map;
}
std::shared_ptr<Value::Array_type> Argument_list::array_at(
unsigned int i) const {
if (i >= size())
throw Exception::argument_error("Insufficient number of arguments");
if (at(i).type != Array)
throw Exception::type_error(
str_format("Argument #%u is expected to be an array", (i + 1)));
return *at(i).value.array;
}
void Argument_list::ensure_count(unsigned int c, const char *context) const {
if (c != size())
throw Exception::argument_error(
str_format("%s: Invalid number of arguments, expected %u but got %u",
context, c, static_cast<uint32_t>(size())));
}
void Argument_list::ensure_count(unsigned int minc, unsigned int maxc,
const char *context) const {
if (size() < minc || size() > maxc)
throw Exception::argument_error(str_format(
"%s: Invalid number of arguments, expected %u to %u but got %u",
context, minc, maxc, static_cast<uint32_t>(size())));
}
void Argument_list::ensure_at_least(unsigned int minc,
const char *context) const {
if (size() < minc)
throw Exception::argument_error(str_format(
"%s: Invalid number of arguments, expected at least %u but got %u",
context, minc, static_cast<uint32_t>(size())));
}
bool Argument_list::operator==(const Argument_list &other) const {
return _args == other._args;
}
//--
Argument_map::Argument_map() {}
Argument_map::Argument_map(const Value::Map_type &map) : _map(map) {}
const std::string &Argument_map::string_at(const std::string &key) const {
const Value &v(at(key));
switch (v.type) {
case String:
return *v.value.s;
default:
throw Exception::type_error(std::string("Argument ")
.append(key)
.append(" is expected to be a string"));
}
}
bool Argument_map::bool_at(const std::string &key) const {
try {
return at(key).as_bool();
} catch (...) {
throw Exception::type_error(
std::string("Argument '" + key + "' is expected to be a bool"));
}
}
int64_t Argument_map::int_at(const std::string &key) const {
try {
return at(key).as_int();
} catch (...) {
throw Exception::type_error("Argument '" + key +
"' is expected to be an int");
}
}
uint64_t Argument_map::uint_at(const std::string &key) const {
try {
return at(key).as_uint();
} catch (...) {
throw Exception::type_error("Argument '" + key +
"' is expected to be an unsigned int");
}
}
double Argument_map::double_at(const std::string &key) const {
try {
return at(key).as_double();
} catch (...) {
throw Exception::type_error("Argument '" + key +
"' is expected to be a double");
}
}
std::shared_ptr<Object_bridge> Argument_map::object_at(
const std::string &key) const {
const Value &value(at(key));
if (value.type != Object)
throw Exception::type_error("Argument '" + key +
"' is expected to be an object");
return *value.value.o;
}
std::shared_ptr<Value::Map_type> Argument_map::map_at(
const std::string &key) const {
const Value &value(at(key));
if (value.type != Map)
throw Exception::type_error("Argument '" + key +
"' is expected to be a map");
return *value.value.map;
}
std::shared_ptr<Value::Array_type> Argument_map::array_at(
const std::string &key) const {
const Value &value(at(key));
if (value.type != Array)
throw Exception::type_error("Argument '" + key +
"' is expected to be an array");
return *value.value.array;
}
bool Argument_map::comp(const std::string &lhs, const std::string &rhs) {
return lhs.compare(rhs) < 0;
}
bool Argument_map::icomp(const std::string &lhs, const std::string &rhs) {
return shcore::str_casecmp(lhs.c_str(), rhs.c_str()) < 0;
}
void Argument_map::ensure_keys(const std::set<std::string> &mandatory_keys,
const std::set<std::string> &optional_keys,
const char *context, bool case_sensitive) const {
std::vector<std::string> missing_keys;
std::vector<std::string> invalid_keys;
if (!validate_keys(mandatory_keys, optional_keys, missing_keys, invalid_keys,
case_sensitive)) {
std::string msg;
if (!invalid_keys.empty() && !missing_keys.empty()) {
msg.append("Invalid and missing values in ").append(context).append(" ");
msg.append("(invalid: ").append(str_join(invalid_keys, ", "));
msg.append("), (missing: ").append(str_join(missing_keys, ", "));
msg.append(")");
} else if (!invalid_keys.empty()) {
msg.append("Invalid values in ").append(context).append(": ");
msg.append(str_join(invalid_keys, ", "));
} else if (!missing_keys.empty()) {
msg.append("Missing values in ").append(context).append(": ");
msg.append(str_join(missing_keys, ", "));
}
if (!msg.empty()) throw Exception::argument_error(msg);
}
}
bool Argument_map::validate_keys(const std::set<std::string> &mandatory_keys,
const std::set<std::string> &optional_keys,
std::vector<std::string> &missing_keys,
std::vector<std::string> &invalid_keys,
bool case_sensitive) const {
std::map<std::string, std::string,
bool (*)(const std::string &, const std::string &)>
mandatory_aliases(case_sensitive ? Argument_map::comp
: Argument_map::icomp);
missing_keys.clear();
invalid_keys.clear();
std::set<std::string, bool (*)(const std::string &, const std::string &)>
optional(case_sensitive ? Argument_map::comp : Argument_map::icomp);
optional.insert(optional_keys.begin(), optional_keys.end());
for (auto key : mandatory_keys) {
auto aliases = split_string(key, "|");
missing_keys.push_back(aliases[0]);
for (auto alias : aliases) mandatory_aliases[alias] = aliases[0];
}
for (auto k : _map) {
if (mandatory_aliases.find(k.first) != mandatory_aliases.end()) {
auto position = std::find(missing_keys.begin(), missing_keys.end(),
mandatory_aliases[k.first]);
if (position != missing_keys.end())
missing_keys.erase(position);
else
// The same option was specified with two different aliases
// Only the first is considered valid
invalid_keys.push_back(k.first);
} else if (optional.find(k.first) != optional.end()) {
// nop
} else
invalid_keys.push_back(k.first);
}
return missing_keys.empty() && invalid_keys.empty();
}
Option_unpacker::Option_unpacker(const Dictionary_t &options) {
set_options(options);
}
void Option_unpacker::set_options(const Dictionary_t &options) {
m_options = options;
m_unknown.clear();
m_missing.clear();
if (m_options) {
for (const auto &opt : *m_options) m_unknown.insert(opt.first);
}
}
Value Option_unpacker::get_required(const char *name, Value_type type) {
if (!m_options) {
m_missing.insert(name);
return Value();
}
auto opt = m_options->find(name);
if (opt == m_options->end()) {
m_missing.insert(name);
return Value();
} else {
m_unknown.erase(name);
if (type != Undefined && !is_compatible_type(opt->second.type, type)) {
throw Exception::type_error(str_format(
"Option '%s' is expected to be of type %s, but is %s", name,
type_name(type).c_str(), type_name(opt->second.type).c_str()));
}
return opt->second;
}
}
Value Option_unpacker::get_optional(const char *name, Value_type type,
bool case_insensitive) {
if (!m_options) {
return Value();
}
auto opt = m_options->find(name);
if (case_insensitive && opt == m_options->end()) {
for (auto it = m_options->begin(); it != m_options->end(); ++it) {
if (str_caseeq(it->first.c_str(), name)) {
name = it->first.c_str();
opt = it;
break;
}
}
}
if (opt != m_options->end()) {
m_unknown.erase(name);
if (type != Undefined && !is_compatible_type(opt->second.type, type)) {
throw Exception::type_error(str_format(
"Option '%s' is expected to be of type %s, but is %s", name,
type_name(type).c_str(), type_name(opt->second.type).c_str()));
}
return opt->second;
}
return Value();
}
Value Option_unpacker::get_optional_exact(const char *name, Value_type type,
bool case_insensitive) {
if (!m_options) {
return Value();
}
auto opt = m_options->find(name);
if (case_insensitive && opt == m_options->end()) {
for (auto it = m_options->begin(); it != m_options->end(); ++it) {
if (str_caseeq(it->first.c_str(), name)) {
name = it->first.c_str();
opt = it;
break;
}
}
}
if (opt != m_options->end()) {
m_unknown.erase(name);
if (type != Undefined &&
((opt->second.type == String && !is_compatible_type(String, type)) ||
(opt->second.type != String && opt->second.type != type))) {
throw Exception::type_error(str_format(
"Option '%s' is expected to be of type %s, but is %s", name,
type_name(type).c_str(), type_name(opt->second.type).c_str()));
}
return opt->second;
}
return Value();
}
void Option_unpacker::end(const std::string &context) { validate(context); }
void Option_unpacker::validate(const std::string &context) {
std::string msg;
if (!m_unknown.empty() && !m_missing.empty()) {
msg.append("Invalid and missing options ");
if (!context.empty()) msg.append(context + " ");
msg.append("(invalid: ").append(str_join(m_unknown, ", "));
msg.append("), (missing: ").append(str_join(m_missing, ", "));
msg.append(")");
} else if (!m_unknown.empty()) {
msg.append("Invalid options");
if (!context.empty()) msg.append(" " + context);
msg.append(": ");
msg.append(str_join(m_unknown, ", "));
} else if (!m_missing.empty()) {
msg.append("Missing required options");
if (!context.empty()) msg.append(" " + context);
msg.append(": ");
msg.append(str_join(m_missing, ", "));
}
if (!msg.empty()) throw Exception::argument_error(msg);
}
//--
void Object_bridge::append_json(JSON_dumper &dumper) const {
dumper.start_object();
dumper.append_string("class", class_name());
dumper.end_object();
}
std::string &Function_base::append_descr(std::string *s_out, int /* indent */,
int /* quote_strings */) const {
const auto &n = name();
s_out->append("<Function").append(n.empty() ? "" : ":" + n).append(">");
return *s_out;
}
std::string &Function_base::append_repr(std::string *s_out) const {
return append_descr(s_out);
}
void Function_base::append_json(JSON_dumper *dumper) const {
std::string repr;
dumper->append_string(append_repr(&repr));
}
std::ostream &operator<<(std::ostream &os, const Value &v) {
return os << v.descr() << " (" << type_name(v.type) << ")";
}
} // namespace shcore