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