├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── src ├── json.cpp ├── json.hpp ├── reflection.cpp └── reflection.hpp └── test ├── CMakeLists.txt └── src ├── catch.hpp ├── json └── serialize.cpp ├── main.cpp └── reflection ├── methods.cpp ├── reflection.cpp └── type_id.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.8) 2 | project(xyz_reflect) 3 | 4 | add_subdirectory("${PROJECT_SOURCE_DIR}/test") 5 | 6 | add_definitions(-Wall -Wold-style-cast -std=c++11) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 8 | 9 | set(SOURCE_FILES src/json.cpp src/reflection.cpp) 10 | #add_executable(reflect ${SOURCE_FILES}) 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 xyzdev.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Reflection and Serialization 2 | 3 | A simple header based library for reflection and serialization in C++. 4 | 5 | ### Goals 6 | 7 | - Read and write objects to inspectable and serializable elements 8 | - List and call methods 9 | - Zero per-instance overhead (no virtual methods, etc) 10 | - DRY 11 | - No preprocessor magic 12 | 13 | ### Case 14 | 15 | This library was created primarily for use with an [ECS](https://en.wikipedia.org/wiki/Entity_component_system) architecture, 16 | where it can provide serialization of components, scripting language bindings to get and set components 17 | and call methods on systems, as well as a unified way to access data, e.g. for undo/redo in an editor or manual manipulation in a development console. 18 | Since reflection of every component and system class is required to fully enable this, there must be very little effort required per class. 19 | While performance is a minor concern for most of these cases, it is important that performance and memory use/alignment remain unaffected in "ordinary" use. 20 | When working with components, a name or id is normally available which can be mapped to a store or management class, 21 | while it may be acceptable to rely on virtual methods for reflection of systems. 22 | 23 | ### Solution 24 | 25 | Use visitors and template specialization for double dispatch on action (read, write, call) and member type. 26 | An optional macro removes the need to repeat the name of each field, eliminating a potential source of bugs. 27 | Reading, writing and passing arguments is facilitated by an intermediate JSON element on which inspection, 28 | manipulation and serialization can be performed. 29 | 30 | ## JSON Element and Serialization / Deserialization 31 | 32 | The provided `json::Element` class is utilized by the reflection system, however it should be fairly 33 | straight-forward to replace it with your own intermediate data model. It can of course also be used 34 | on its own, without the reflection system. 35 | 36 | ## Examples 37 | 38 | ### Reading and writing data 39 | 40 | ```cpp 41 | struct Component { 42 | std::string field1; 43 | std::vector field2; 44 | 45 | // Make reflectable: 46 | void reflect(xyz::core::Reflection &r) { 47 | XYZ_REFLECT(r, field1); 48 | XYZ_REFLECT(r, field2); 49 | } 50 | }; 51 | ``` 52 | 53 | ```cpp 54 | // Read: 55 | xyz::core::ReflectionSink sink; 56 | component.reflect(sink); 57 | std::cout << sink.sink.object()["field1"].str(); 58 | 59 | // Write: 60 | xyz::core::ReflectionSource source; 61 | source.source.object()["field1"] = "new value"; 62 | component.reflect(source); 63 | ``` 64 | 65 | ### Type id 66 | 67 | The `type_id` method template will produce an integer identifying a type for the duration of the 68 | program's execution. Note that it is NOT guaranteed to be stable across runs. 69 | 70 | ```cpp 71 | xyz::core::type_id() == xyz::core::type_id(); 72 | xyz::core::type_id() != xyz::core::type_id(); 73 | ``` 74 | 75 | This is useful for safe downcasts without RTTI and dynamic_cast, as well as for looking up manager 76 | classes, etc. 77 | 78 | ```cpp 79 | class StoreBase { 80 | public: 81 | virtual ~StoreBase() {} 82 | virtual int create() = 0; 83 | virtual xyz::json::Element get(int) = 0; 84 | virtual void set(int, xyz::json::Element) = 0; 85 | }; 86 | 87 | template 88 | class Store: public StoreBase { 89 | std::vector components; 90 | 91 | virtual int create(); 92 | virtual xyz::json::Element get(int); 93 | virtual void set(int, xyz::json::Element); 94 | }; 95 | 96 | std::map stores; 97 | 98 | auto COMPONENT_ID = xyz::core::type_id(); 99 | stores[COMPONENT_ID] = new Store(); 100 | ``` 101 | 102 | ```cpp 103 | int myComponent = stores[COMPONENT_ID]->create(); 104 | stores[COMPONENT_ID]->set(myComponent, xyz::json::Element()); 105 | ``` 106 | 107 | ### Calling methods 108 | 109 | ```cpp 110 | class System { 111 | public: 112 | std::vector foo(int x, int y) { ... } 113 | void bar() { ... } 114 | 115 | // Make reflectable: 116 | virtual void reflect(xyz::core::Reflection &r) { 117 | XYZ_REFLECT_METHOD(r, System, foo); 118 | XYZ_REFLECT_METHOD(r, System, bar); 119 | } 120 | }; 121 | ``` 122 | 123 | ```cpp 124 | // Call method: 125 | xyz::json::Array args(2); 126 | args[0] = xyz::json::Number(42); 127 | args[1] = xyz::json::Number(123); 128 | 129 | xyz::core::ReflectionCaller caller("foo", args); 130 | system->reflect(caller); 131 | 132 | if(caller.found) 133 | std::cout << caller.result.array().size(); 134 | ``` 135 | 136 | ### Reflectors 137 | 138 | The `Reflector` class template can be specialized to enable reflection of types which can't be 139 | modified to add a `reflect` method. 140 | 141 | ```cpp 142 | template<> 143 | class xyz::core::Reflector: public xyz::core::AbstractReflector { 144 | public: 145 | typedef Vector3 field_type; 146 | 147 | Reflector(field_type &field) 148 | :field(field) {} 149 | 150 | json::Element read() { 151 | json::Array array(3); 152 | array[0] = field.x; 153 | array[1] = field.y; 154 | array[2] = field.z; 155 | return json::Element(array); 156 | } 157 | 158 | void write(const json::Element &data) { 159 | json::Array array = data.array(); 160 | if(array.size() != 3) { 161 | throw json::TypeError("Vector3 requires array with three Number elements."); 162 | } 163 | field.x = array[0].number(); 164 | field.y = array[1].number(); 165 | field.z = array[2].number(); 166 | } 167 | 168 | protected: 169 | field_type &field; 170 | }; 171 | ``` 172 | 173 | Or simply: 174 | 175 | ```cpp 176 | template<> 177 | xyz::json::Element xyz::core::Reflector::read() { 178 | json::Array array(3); 179 | array[0] = field.x; 180 | array[1] = field.y; 181 | array[2] = field.z; 182 | return json::Element(array); 183 | } 184 | template<> 185 | void xyz::core::Reflector::write(const json::Element &data) { 186 | json::Array array = data.array(); 187 | if(array.size() != 3) { 188 | throw json::TypeError("Vector3 requires array with three Number elements."); 189 | } 190 | field.x = array[0].number(); 191 | field.y = array[1].number(); 192 | field.z = array[2].number(); 193 | } 194 | ``` 195 | 196 | ### TODO 197 | 198 | - Documentation and examples 199 | - Improve readability of code 200 | - Reflector should offer type description, i.e. `{id: , type: string/array/etc, [member: {id:...}] }` 201 | - UTF-8 support in JSON serialization 202 | - Function call on member's methods 203 | - Possibly add some syntactic sugar over `Reflection`, e.g. `result = Sink::get(component)`, `Source::set(component, data)`, `result = Caller::call(component, method, args...)`, etc. 204 | 205 | ## License 206 | 207 | Distributed under the [MIT License](LICENSE.md). 208 | -------------------------------------------------------------------------------- /src/json.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2016 xyzdev.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | */ 24 | #include "json.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | namespace xyz { 32 | namespace json { 33 | 34 | Element::Type Element::getType() const { 35 | return type; 36 | } 37 | 38 | const char *Element::getTypeName() const { 39 | if(type < 0 || type > 5) { 40 | throw TypeError("TypeError: Invalid type."); 41 | } 42 | static const char *names[] = {"NULL", "OBJECT", "ARRAY", "STRING", "NUMBER", "BOOLEAN"}; 43 | return names[type]; 44 | } 45 | 46 | Object &Element::object() { 47 | if(type != OBJECT) throw TypeError(OBJECT); 48 | return _object; 49 | } 50 | 51 | Array &Element::array() { 52 | if(type != ARRAY) throw TypeError(ARRAY); 53 | return _array; 54 | } 55 | 56 | String &Element::str() { 57 | if(type != STRING) throw TypeError(STRING); 58 | return _string; 59 | } 60 | 61 | Number &Element::number() { 62 | if(type != NUMBER) throw TypeError(NUMBER); 63 | return _number; 64 | } 65 | 66 | Boolean &Element::boolean() { 67 | if(type != BOOLEAN) throw TypeError(BOOLEAN); 68 | return _boolean; 69 | } 70 | 71 | const Object &Element::object() const { 72 | if(type != OBJECT) throw TypeError(OBJECT); 73 | return _object; 74 | } 75 | 76 | const Array &Element::array() const { 77 | if(type != ARRAY) throw TypeError(ARRAY); 78 | return _array; 79 | } 80 | 81 | const String &Element::str() const { 82 | if(type != STRING) throw TypeError(STRING); 83 | return _string; 84 | } 85 | 86 | const Number &Element::number() const { 87 | if(type != NUMBER) throw TypeError(NUMBER); 88 | return _number; 89 | } 90 | 91 | const Boolean &Element::boolean() const { 92 | if(type != BOOLEAN) throw TypeError(BOOLEAN); 93 | return _boolean; 94 | } 95 | 96 | String Element::toString() const { 97 | if(isString()) { 98 | return '"' + str() + '"'; 99 | } 100 | if(isNumber()) { 101 | std::ostringstream os; 102 | os << number(); 103 | return os.str(); 104 | } 105 | if(isNull()) { 106 | return "null"; 107 | } 108 | if(isBoolean()) { 109 | return boolean() ? "true" : "false"; 110 | } 111 | if(isArray()) { 112 | std::ostringstream os; 113 | os << "ARRAY [" << array().size() << "]"; 114 | return os.str(); 115 | } 116 | if(isObject()) { 117 | std::ostringstream os; 118 | os << "OBJECT [" << object().size() << "]"; 119 | return os.str(); 120 | } 121 | 122 | throw TypeError("TypeError: Bug: Illegal enum value"); 123 | } 124 | 125 | Element &Element::operator =(const Element &r) { 126 | if(this == &r) { 127 | return *this; 128 | } 129 | 130 | if(r.type == OBJECT) { 131 | if(type == OBJECT) { 132 | // In case r is child of _object. 133 | Object tmp(r._object); 134 | _object.swap(tmp); 135 | } else { 136 | _object = r._object; 137 | } 138 | _array.clear(); 139 | _string.clear(); 140 | } 141 | else if(r.type == ARRAY) { 142 | if(type == ARRAY) { 143 | // In case r is child of _array. 144 | Array tmp(r._array); 145 | _array.swap(tmp); 146 | } else { 147 | _array = r._array; 148 | } 149 | _object.clear(); 150 | _string.clear(); 151 | } 152 | else if(r.type != NULL_VALUE) { 153 | _string = r._string; 154 | _number = r._number; 155 | _boolean = r._boolean; 156 | _object.clear(); 157 | _array.clear(); 158 | } 159 | else { 160 | _object.clear(); 161 | _array.clear(); 162 | _string.clear(); 163 | } 164 | 165 | type = r.type; 166 | 167 | return *this; 168 | } 169 | 170 | bool Element::operator ==(const Element &r) const { 171 | if(type != r.type && !(isNumber() && r.isNumber())) { 172 | return false; 173 | } 174 | if(type == NULL_VALUE) { 175 | return true; 176 | } 177 | if(type == OBJECT) { 178 | return object() == r.object(); 179 | } 180 | if(type == ARRAY) { 181 | return array() == r.array(); 182 | } 183 | if(type == NUMBER) { 184 | return _number == r._number; 185 | } 186 | if(type == BOOLEAN) { 187 | return _boolean == r._boolean; 188 | } 189 | return str() == r.str(); 190 | } 191 | 192 | void Element::swap(Element &r) { 193 | if(this == &r) { 194 | return; 195 | } 196 | 197 | if(r.type == OBJECT || type == OBJECT) { 198 | _object.swap(r._object); 199 | } 200 | 201 | if(r.type == ARRAY || type == ARRAY) { 202 | _array.swap(r._array); 203 | } 204 | 205 | if(r.type == STRING || type == STRING) { 206 | _string.swap(r._string); 207 | } 208 | 209 | bool b = _boolean; 210 | _boolean = r._boolean; 211 | r._boolean = b; 212 | 213 | Number n = _number; 214 | _number = r._number; 215 | r._number = n; 216 | 217 | Type t = type; 218 | type = r.type; 219 | r.type = t; 220 | } 221 | 222 | String::value_type parseHexChar(std::istream &stream, int &line) { 223 | String buf = " "; 224 | if(!stream.read(&(buf[0]), 4)) { 225 | throw SyntaxError("Unexpected end of file while reading character escape sequence.", line, buf[0]); 226 | } 227 | 228 | std::size_t pos = 0; 229 | int val = -1; 230 | 231 | try { 232 | val = std::stoi(buf, &pos, 16); 233 | } catch(std::invalid_argument &e) { 234 | } catch(std::out_of_range &e) { 235 | } 236 | 237 | if(val < 0 || pos != 4) { 238 | throw SyntaxError("Invalid character escape sequence.", line, buf[0]); 239 | } 240 | 241 | if(val > 0xff) { 242 | throw SyntaxError("Escape sequence above Latin-1 not implemented.", line, buf[0]); 243 | } 244 | 245 | return String::value_type(val); 246 | } 247 | 248 | void parseString(std::istream &stream, String &str, int &line) { 249 | // Read string content up to and including terminating quote. 250 | // Opening quote must have been previously consumed from stream. 251 | 252 | std::ostringstream os; 253 | char in; 254 | bool escaped = false; 255 | 256 | while(stream.read(&in, 1)) { 257 | if(escaped) { 258 | if(in == '\\') os << '\\'; 259 | else if(in == '"') os << '"'; 260 | else if(in == 'n') os << '\n'; 261 | else if(in == 'r') os << '\r'; 262 | else if(in == 't') os << '\t'; 263 | else if(in == 'f') os << '\f'; 264 | else if(in == 'b') os << '\b'; 265 | else if(in == '/') os << '/'; 266 | else if(in == 'u') { 267 | os << parseHexChar(stream, line); 268 | } 269 | else { 270 | throw SyntaxError("Illegal string escape sequence", line, in); 271 | } 272 | escaped = false; 273 | } 274 | else { 275 | if(in == '"') { 276 | str = os.str(); 277 | return; 278 | } 279 | if(in == '\\') { 280 | escaped = true; 281 | } 282 | else if((in >= '\x00' && in <= '\x1f') || in == '\x7f' || (in >= '\x80' && in <= '\x9f')) { 283 | throw SyntaxError("Control character in string.", line, in); 284 | } 285 | else { 286 | os << in; 287 | } 288 | } 289 | } 290 | 291 | throw SyntaxError("Unexpected end of file while parsing string.", line, in); 292 | } 293 | 294 | char parseNumber(std::istream &stream, char first, Number &num, int &line) { 295 | // Read number from stream. 296 | // This function is more permissive than the json standard, e.g. allowing leading '+'. 297 | // The first character must have been consumed from stream and passed as "first". 298 | // This function may consume a character past the end of the number, 299 | // in which case it is returned, otherwise ' ' is returned. 300 | 301 | std::ostringstream os; 302 | os << first; 303 | 304 | char in; 305 | char extra = ' '; 306 | 307 | while(stream.read(&in, 1)) { 308 | if((in < '0' || in > '9') && in != '-' && in != '+' && in != '.' && in != 'e' && in != 'E') { 309 | extra = in; 310 | break; 311 | } 312 | os << in; 313 | } 314 | 315 | try { 316 | std::size_t read; 317 | num = std::stod(os.str(), &read); 318 | if(read != os.str().length()) { 319 | throw SyntaxError("Illegal number format.", line, first); 320 | } 321 | } catch(std::invalid_argument &e) { 322 | throw SyntaxError("Failed to parse number", line, first); 323 | } catch(std::out_of_range &e) { 324 | throw SyntaxError("Failed to parse number, value out of range", line, first); 325 | } 326 | 327 | return extra; 328 | } 329 | 330 | void parseNull(std::istream &stream, int &line) { 331 | // Read and verify the primitive "null". 332 | // The first character must have been consumed and verified to be 'n'. 333 | String v = "n "; 334 | if(!stream.read(&v[1], 3) || v != "null") { 335 | throw SyntaxError("Expected \"null\"", line, v[1]); 336 | } 337 | } 338 | 339 | void parseFalse(std::istream &stream, int &line) { 340 | // Read and verify the primitive "false". 341 | // The first character must have been consumed and verified to be 'f'. 342 | String v = "f "; 343 | if(!stream.read(&v[1], 4) || v != "false") { 344 | throw SyntaxError("Expected \"false\"", line, v[1]); 345 | } 346 | } 347 | 348 | void parseTrue(std::istream &stream, int &line) { 349 | // Read and verify the primitive "true". 350 | // The first character must have been consumed and verified to be 't'. 351 | String v = "t "; 352 | if(!stream.read(&v[1], 3) || v != "true") { 353 | throw SyntaxError("Expected \"true\"", line, v[1]); 354 | } 355 | } 356 | 357 | char parsePrimitive(std::istream &stream, char first, Element &el, int &line) { 358 | // Read a primitive from stream (null, bool, number, string, not array or object). 359 | // The first character must have been consumed from stream and passed as "first". 360 | // This function may consume a character past the end of the primitive, 361 | // in which case it is returned, otherwise ' ' is returned. 362 | 363 | if(first == 'n') { 364 | parseNull(stream, line); 365 | el = Element(Element::NULL_VALUE); 366 | } 367 | else if(first == 't') { 368 | parseTrue(stream, line); 369 | el = Element(true); 370 | } 371 | else if(first == 'f') { 372 | parseFalse(stream, line); 373 | el = Element(false); 374 | } 375 | else if(first == '"') { 376 | el = Element(Element::STRING); 377 | parseString(stream, el.str(), line); 378 | } 379 | else if(first == '-' || (first >= '0' && first <= '9')) { 380 | el = Element(Element::NUMBER); 381 | return parseNumber(stream, first, el.number(), line); 382 | } 383 | else { 384 | throw SyntaxError("Primitive must be one of null, true, false, number or quoted string.", line, first); 385 | } 386 | 387 | return ' '; 388 | } 389 | 390 | void skipLineComment(std::istream &stream, int &line) { 391 | char in; 392 | if(!stream.read(&in, 1) || in != '/') { 393 | throw SyntaxError("Expected second '/' to begin line comment.", line, in); 394 | } 395 | 396 | while(stream.read(&in, 1)) { 397 | if(in == '\n' || in == '\r') { 398 | ++line; 399 | break; 400 | } 401 | } 402 | } 403 | 404 | std::istream &deserialize(std::istream &stream, Element &root) 405 | { 406 | enum State { 407 | S_PRE_ELEMENT, // Read an element (root, array item or object value) or close parent array. 408 | S_PRE_KEY, // Inside object, read quoted key name or close object. 409 | S_PRE_SEP, // Inside object (after key), read ':' before value. 410 | S_POST_ELEMENT, // After complete element (key+value if parent is obj). Read comma, close parent array/object or finish. 411 | }; 412 | 413 | State state = S_PRE_ELEMENT; 414 | 415 | // Parents of any element in the stack must not be modified as a reallocation would be very bad. 416 | std::stack nodes; 417 | nodes.push(&root); 418 | root = Element::NULL_VALUE; 419 | 420 | // S_PRE_KEY sets this variable to pass the key (for the following value) to S_PRE_ELEMENT. 421 | String key; 422 | 423 | int line = 1; 424 | char in; 425 | 426 | while(stream.read(&in, 1)) { 427 | redo: 428 | if(in == '/') { 429 | skipLineComment(stream, line); 430 | continue; 431 | } 432 | 433 | // TODO: Respect system's endl for line numbering. 434 | if(in == '\n' || in == '\r') { 435 | ++line; 436 | } 437 | 438 | if(in == ' ' || in == '\r' || in == '\n' || in == '\t') { 439 | continue; 440 | } 441 | 442 | switch(state) { 443 | 444 | case S_PRE_KEY: { 445 | if(in == '"') { 446 | parseString(stream, key, line); 447 | state = S_PRE_SEP; 448 | } 449 | else if(in == '}') { 450 | state = S_POST_ELEMENT; 451 | goto redo; 452 | } 453 | else { 454 | throw SyntaxError("Expected key or closing bracket.", line, in); 455 | } 456 | } break; 457 | 458 | case S_PRE_SEP: { 459 | if(in != ':') { 460 | throw SyntaxError("Expected ':' separating key and value.", line, in); 461 | } 462 | state = S_PRE_ELEMENT; 463 | } break; 464 | 465 | case S_PRE_ELEMENT: { 466 | 467 | if(nodes.top()->isNull()) { 468 | // Root element. 469 | } 470 | else if(nodes.top()->isArray()) { 471 | if(in == ']') { 472 | state = S_POST_ELEMENT; 473 | goto redo; 474 | } 475 | 476 | nodes.top()->array().push_back(Element()); 477 | nodes.push(&(nodes.top()->array().back())); 478 | } 479 | else if(nodes.top()->isObject()) { 480 | nodes.top()->object()[key] = Element(); 481 | nodes.push(&(nodes.top()->object()[key])); 482 | } 483 | 484 | if(in == '[') { 485 | *nodes.top() = Element(Element::ARRAY); 486 | state = S_PRE_ELEMENT; 487 | } 488 | else if(in == '{') { 489 | *nodes.top() = Element(Element::OBJECT); 490 | state = S_PRE_KEY; 491 | } 492 | else { 493 | *nodes.top() = Element(Element::NULL_VALUE); 494 | in = parsePrimitive(stream, in, *nodes.top(), line); 495 | nodes.pop(); 496 | state = S_POST_ELEMENT; 497 | goto redo; 498 | } 499 | } break; 500 | 501 | case S_POST_ELEMENT: { 502 | if(nodes.empty()) { 503 | throw SyntaxError("Input after end.", line, in); 504 | } 505 | 506 | if(in == ',') { 507 | state = nodes.top()->isArray() ? S_PRE_ELEMENT : S_PRE_KEY; 508 | } 509 | else if(in == ']') { 510 | if(!nodes.top()->isArray()) { 511 | throw SyntaxError("Token ']' is illegal inside object.", line, in); 512 | } 513 | nodes.pop(); 514 | state = S_POST_ELEMENT; 515 | } 516 | else if(in == '}') { 517 | if(!nodes.top()->isObject()) { 518 | throw SyntaxError("Token '}' is illegal inside array.", line, in); 519 | } 520 | nodes.pop(); 521 | state = S_POST_ELEMENT; 522 | } 523 | else { 524 | throw SyntaxError("Expected ',' or closing bracket.", line, in); 525 | } 526 | } break; 527 | 528 | } 529 | } 530 | 531 | if(!(state == S_POST_ELEMENT && nodes.empty())) { 532 | throw SyntaxError("Unexpected end of file.", line, in); 533 | } 534 | 535 | return stream; 536 | } 537 | 538 | Element deserialize(const String &str) 539 | { 540 | std::istringstream is(str); 541 | Element el; 542 | deserialize(is, el); 543 | 544 | return el; 545 | } 546 | 547 | void serializeString(std::ostream &stream, const Element &node) { 548 | stream << '"'; 549 | const String &str = node.str(); 550 | for(String::const_iterator it = str.begin(); it != str.end(); ++it) { 551 | if(*it == '\\') stream << "\\\\"; 552 | else if(*it == '"') stream << "\\\""; 553 | else if(*it == '\n') stream << "\\n"; 554 | else if(*it == '\r') stream << "\\r"; 555 | else if(*it == '\t') stream << "\\t"; 556 | else if(*it == '\f') stream << "\\f"; 557 | else if(*it == '\b') stream << "\\b"; 558 | else if((*it >= '\x00' && *it <= '\x1f') || 559 | (*it == '\x7f') || 560 | (*it >= '\x80' && *it <= '\x9f') || 561 | (*it >= '\x80' && *it <= '\xff')) { 562 | // Print control and extended characters as unicode escape sequence. 563 | int code = reinterpret_cast(*it); 564 | auto flags = stream.flags(); 565 | stream << "\\u" << std::setfill('0') << std::setw(4) << std::hex << code; 566 | stream.flags(flags); 567 | } 568 | else stream << *it; 569 | } 570 | stream << '"'; 571 | } 572 | 573 | std::ostream &serialize(std::ostream &stream, const Element &node, int indent) 574 | { 575 | bool compact = indent < 0; 576 | 577 | String thisIndent = String(String::size_type(compact ? 0 : indent), ' '); 578 | String nextIndent = String(String::size_type(compact ? 0 : (indent + 2)), ' '); 579 | 580 | if(node.getType() == Element::NULL_VALUE) { 581 | stream << "null"; 582 | } 583 | else if(node.getType() == Element::BOOLEAN) { 584 | stream << (node.boolean() ? "true" : "false"); 585 | } 586 | else if(node.getType() == Element::STRING) { 587 | serializeString(stream, node); 588 | } 589 | else if(node.getType() == Element::NUMBER) { 590 | const Number &num = node.number(); 591 | if(std::isfinite(num)) stream << num; 592 | else stream << "null"; 593 | } 594 | else if(node.getType() == Element::OBJECT) { 595 | if(node.object().empty()) { 596 | stream << "{}"; 597 | } 598 | else { 599 | stream << '{'; 600 | 601 | if(compact) stream << ' '; 602 | else stream << std::endl; 603 | 604 | for(Object::const_iterator child = node.object().begin(); child != node.object().end(); ++child) { 605 | stream << nextIndent << '"' << child->first << "\": "; 606 | 607 | serialize(stream, child->second, compact ? -2 : indent + 2); 608 | 609 | if(child->first != node.object().rbegin()->first) { 610 | stream << ','; 611 | } 612 | 613 | if(compact) stream << ' '; 614 | else stream << std::endl; 615 | } 616 | 617 | stream << thisIndent << '}'; 618 | } 619 | } 620 | else if(node.getType() == Element::ARRAY) { 621 | if(node.array().empty()) { 622 | stream << "[]"; 623 | } 624 | else { 625 | stream << '['; 626 | 627 | if(compact) stream << ' '; 628 | else stream << std::endl; 629 | 630 | for(Array::const_iterator child = node.array().begin(); child != node.array().end(); ++child) { 631 | if(!compact) { 632 | stream << nextIndent; 633 | } 634 | 635 | serialize(stream, *child, compact ? -2 : indent + 2); 636 | 637 | if(&*child != &*(node.array().rbegin())) { 638 | stream << ','; 639 | } 640 | 641 | if(compact) stream << ' '; 642 | else stream << std::endl; 643 | } 644 | 645 | if(!compact) stream << thisIndent; 646 | stream << ']'; 647 | } 648 | } 649 | 650 | if(indent == 0 || indent == -1) { 651 | stream << std::flush; 652 | } 653 | 654 | return stream; 655 | } 656 | 657 | std::ostream &serialize(std::ostream &stream, const Element &node, bool indent) 658 | { 659 | return serialize(stream, node, indent ? 0 : -1); 660 | } 661 | 662 | String serialize(const Element &node, bool indent) { 663 | std::ostringstream os; 664 | serialize(os, node, indent ? 0 : -1); 665 | return os.str(); 666 | } 667 | 668 | } 669 | } 670 | -------------------------------------------------------------------------------- /src/json.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2016 xyzdev.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | */ 24 | #ifndef XYZDEV_JSON_HPP 25 | #define XYZDEV_JSON_HPP 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace xyz { 33 | namespace json { 34 | 35 | class Element; 36 | 37 | typedef std::string String; 38 | typedef std::map Object; 39 | typedef std::vector Array; 40 | typedef bool Boolean; 41 | typedef double Number; 42 | 43 | class Element { 44 | public: 45 | enum Type { NULL_VALUE = 0, OBJECT, ARRAY, STRING, NUMBER, BOOLEAN }; 46 | 47 | Element():type(NULL_VALUE) {} 48 | Element(const Element &r):type(NULL_VALUE) { *this = r; } 49 | Element(Type type):type(type) {} 50 | Element(const Object &object):type(OBJECT),_object(object) {} 51 | Element(const Array &array):type(ARRAY),_array(array) {} 52 | Element(bool boolean):type(BOOLEAN),_boolean(boolean) {} 53 | Element(Number number):type(NUMBER),_number(number) {} 54 | Element(const String &str):type(STRING),_string(str) {} 55 | Element(const char *str):type(STRING),_string(str) {} 56 | 57 | Type getType() const; 58 | const char *getTypeName() const; 59 | 60 | bool empty() const { 61 | return isNull() || 62 | (isBoolean() && !boolean()) || 63 | (isNumber() && !number()) || 64 | (_object.empty() && _array.empty() && _string.empty()); 65 | } 66 | 67 | bool isPrimitive() const { return type != OBJECT && type != ARRAY; } 68 | 69 | bool isNull() const { return type == NULL_VALUE; } 70 | bool isObject() const { return type == OBJECT; } 71 | bool isArray() const { return type == ARRAY; } 72 | bool isString() const { return type == STRING; } 73 | bool isNumber() const { return type == NUMBER; } 74 | bool isBoolean() const { return type == BOOLEAN; } 75 | 76 | Object &object(); 77 | Array &array(); 78 | String &str(); 79 | Boolean &boolean(); 80 | Number &number(); 81 | 82 | const Object &object() const; 83 | const Array &array() const; 84 | const String &str() const; 85 | const Boolean &boolean() const; 86 | const Number &number() const; 87 | 88 | String toString() const; 89 | 90 | void swap(Element &r); 91 | 92 | Element &operator =(const Element &r); 93 | 94 | bool operator ==(const Element &r) const; 95 | 96 | protected: 97 | Type type; 98 | Object _object; 99 | Array _array; 100 | String _string; 101 | Number _number; 102 | Boolean _boolean; 103 | }; 104 | 105 | class TypeError: public std::exception { 106 | public: 107 | TypeError() 108 | :msg("TypeError"), 109 | expected(Element::NULL_VALUE) {} 110 | 111 | TypeError(const char *msg) 112 | :msg(msg), 113 | expected(Element::NULL_VALUE) {} 114 | 115 | TypeError(Element::Type expected) 116 | :msg(nullptr), 117 | expected(expected) {} 118 | 119 | const char *what() const throw() { 120 | if(msg) return msg; 121 | 122 | if(expected >= 0 && expected <= 5) { 123 | static const char *typeNames[] = { 124 | "TypeError: Expected NULL", 125 | "TypeError: Expected OBJECT", 126 | "TypeError: Expected ARRAY", 127 | "TypeError: Expected STRING", 128 | "TypeError: Expected NUMBER", 129 | "TypeError: Expected BOOLEAN" 130 | }; 131 | return typeNames[expected]; 132 | } 133 | 134 | return "TypeError: Expected illegal type"; 135 | } 136 | 137 | const char * const msg; 138 | const Element::Type expected; 139 | }; 140 | 141 | class SyntaxError: public std::exception { 142 | public: 143 | SyntaxError(const char *msg, int line, char chr): 144 | msg(msg),line(line),chr(chr) 145 | {} 146 | 147 | const char *what() const throw() { 148 | return msg; 149 | } 150 | 151 | const char *msg; 152 | int line; 153 | char chr; 154 | }; 155 | 156 | String serialize(const Element &node, bool indent = false); 157 | std::ostream &serialize(std::ostream &stream, const Element &node, bool indent = false); 158 | 159 | Element deserialize(const String &str); 160 | std::istream &deserialize(std::istream &stream, Element &element); 161 | } 162 | } 163 | 164 | #endif 165 | -------------------------------------------------------------------------------- /src/reflection.cpp: -------------------------------------------------------------------------------- 1 | #include "reflection.hpp" 2 | -------------------------------------------------------------------------------- /src/reflection.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2016 xyzdev.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | */ 24 | #ifndef XYZDEV_REFLECTION_HPP 25 | #define XYZDEV_REFLECTION_HPP 26 | 27 | #include "json.hpp" 28 | #include 29 | #include 30 | #include 31 | 32 | /** 33 | * Reflector: Reads or writes a member to/from json::Element. 34 | * Reflection: Visits each reflector and decides when to make them read, write or call the underlying member. 35 | * Reflectable: Any class with a reflect method (void reflect(Reflection &)). Uses XYZ_REFLECT(...) et al. to create reflectors and pass them to reflection. 36 | */ 37 | 38 | namespace xyz { 39 | namespace core { 40 | 41 | typedef unsigned TypeId; 42 | 43 | namespace detail { 44 | 45 | template 46 | json::String toString(const Src &src) { 47 | std::ostringstream os; 48 | os << src; 49 | return os.str(); 50 | } 51 | 52 | template 53 | Dest fromString(const json::String &src) { 54 | std::ostringstream is(src); 55 | Dest dest = Dest(); 56 | is >> dest; 57 | return dest; 58 | } 59 | 60 | template<> 61 | inline json::String toString(const json::String &src) { 62 | return src; 63 | } 64 | 65 | template<> 66 | inline json::String fromString(const json::String &src) { 67 | return src; 68 | } 69 | 70 | struct TypeIdGenerator { 71 | static TypeId next() { 72 | static TypeId lastId = 0; 73 | return ++lastId; 74 | } 75 | }; 76 | 77 | } 78 | 79 | template 80 | TypeId type_id() { 81 | static TypeId id = detail::TypeIdGenerator::next(); 82 | return id; 83 | } 84 | 85 | class AbstractReflector { 86 | public: 87 | virtual json::Element read() = 0; 88 | virtual void write(const json::Element &data) = 0; 89 | virtual bool isMethod() { return false; } 90 | 91 | virtual json::Element call(const json::Array &data) { 92 | throw json::TypeError(); 93 | } 94 | }; 95 | 96 | template 97 | class Reflector: public AbstractReflector { 98 | public: 99 | typedef Field field_type; 100 | 101 | Reflector(Field &field) 102 | :field(field) {} 103 | 104 | json::Element read() { 105 | return json::Element(detail::toString(field)); 106 | } 107 | 108 | void write(const json::Element &data) { 109 | if(data.getType() != json::Element::NULL_VALUE) { 110 | field = detail::fromString(data.str()); 111 | } else { 112 | field = Field(); 113 | } 114 | } 115 | 116 | protected: 117 | Field &field; 118 | }; 119 | 120 | template<> 121 | class Reflector: public AbstractReflector { 122 | public: 123 | typedef void field_type; 124 | 125 | virtual json::Element read() { 126 | return json::Element::NULL_VALUE; 127 | } 128 | 129 | virtual void write(const json::Element &data) { 130 | if(!data.isNull()) throw json::TypeError("TypeError: Tried to write to void type."); 131 | } 132 | }; 133 | 134 | template<> 135 | class Reflector: public AbstractReflector { 136 | public: 137 | typedef json::String field_type; 138 | 139 | Reflector(field_type &field) 140 | :field(field) {} 141 | 142 | json::Element read() { 143 | return json::Element(field); 144 | } 145 | 146 | void write(const json::Element &data) { 147 | if(data.getType() != json::Element::NULL_VALUE) { 148 | field = data.str(); 149 | } else { 150 | field = field_type(); 151 | } 152 | } 153 | 154 | protected: 155 | field_type &field; 156 | }; 157 | 158 | template 159 | class Reflector::value>::type>: public AbstractReflector { 160 | public: 161 | Reflector(Field &field):field(field) {} 162 | 163 | json::Element read() { 164 | return json::Element(json::Number(field)); 165 | } 166 | 167 | void write(const json::Element &data) { 168 | field = Field(data.number()); 169 | } 170 | 171 | protected: 172 | Field &field; 173 | }; 174 | 175 | template 176 | class Reflector::value>::type>: public AbstractReflector { 177 | public: 178 | Reflector(Field &field):field(field) {} 179 | 180 | json::Element read() { 181 | return json::Element(json::Number(field)); 182 | } 183 | 184 | void write(const json::Element &data) { 185 | field = Field(data.number()); 186 | } 187 | 188 | protected: 189 | Field &field; 190 | }; 191 | 192 | template<> inline json::Element Reflector::read() { 193 | return json::Element(json::Boolean(field)); 194 | } 195 | 196 | template<> inline void Reflector::write(const json::Element &data) { 197 | field = (data.getType() != json::Element::NULL_VALUE) ? bool(data.boolean()) : bool(); 198 | } 199 | 200 | template<> inline json::Element Reflector::read() { 201 | return field; 202 | } 203 | 204 | template<> inline void Reflector::write(const json::Element &data) { 205 | field = data; 206 | } 207 | 208 | // TODO: This breaks non-collection templated fields. 209 | template class Container, typename ... Args> 210 | class Reflector< Container >: public AbstractReflector { 211 | public: 212 | typedef Container field_type; 213 | typedef typename field_type::value_type element_type; 214 | 215 | Reflector(field_type &field) 216 | :field(field) {} 217 | 218 | json::Element read() { 219 | json::Array array; 220 | for(typename field_type::iterator i = field.begin(); i != field.end(); ++i) { 221 | Reflector refl(*i); 222 | array.push_back(refl.read()); 223 | } 224 | return json::Element(array); 225 | }; 226 | 227 | void write(const json::Element &data) { 228 | std::vector v; 229 | if(data.getType() != json::Element::NULL_VALUE) { 230 | for(json::Array::const_iterator i = data.array().begin(); i != data.array().end(); ++i) { 231 | element_type elem; 232 | Reflector refl(elem); 233 | refl.write(*i); 234 | v.push_back(elem); 235 | } 236 | } 237 | field = field_type(v.begin(), v.end()); 238 | } 239 | 240 | protected: 241 | field_type &field; 242 | }; 243 | 244 | template 245 | class Reflector< std::map >: public AbstractReflector { 246 | public: 247 | typedef std::map field_type; 248 | 249 | Reflector(field_type &field) 250 | :field(field) {} 251 | 252 | json::Element read() { 253 | json::Object obj; 254 | for(typename field_type::iterator i = field.begin(); i != field.end(); ++i) { 255 | Reflector refl(i->second); 256 | obj[detail::toString(i->first)] = refl.read(); 257 | } 258 | return json::Element(obj); 259 | } 260 | 261 | void write(const json::Element &data) { 262 | field.clear(); 263 | if(data.getType() != json::Element::NULL_VALUE) { 264 | for(json::Object::const_iterator i = data.object().begin(); i != data.object().end(); ++i) { 265 | Value elem; 266 | Reflector refl(elem); 267 | refl.write(i->second); 268 | field[detail::fromString(i->first)] = elem; 269 | } 270 | } 271 | } 272 | 273 | protected: 274 | field_type &field; 275 | }; 276 | 277 | template 278 | class PropertyReflector: public AbstractReflector { 279 | public: 280 | PropertyReflector(Class &instance, Property (Class::*getter)(), void (Class::*setter)(Property)) 281 | :instance(instance), 282 | getter(getter), 283 | setter(setter) 284 | { } 285 | 286 | json::Element read() { 287 | Property val((instance.*getter)()); 288 | Reflector refl(val); 289 | return refl.read(); 290 | } 291 | 292 | void write(const json::Element &data) { 293 | Property val((instance.*getter)()); 294 | Reflector refl(val); 295 | refl.write(data); 296 | (instance.*setter)(val); 297 | } 298 | 299 | protected: 300 | Class &instance; 301 | Property (Class::*getter)(); 302 | void (Class::*setter)(Property); 303 | }; 304 | 305 | class Reflection { 306 | public: 307 | virtual void visit(AbstractReflector &reflector, const char *name) = 0; 308 | }; 309 | 310 | class ReflectionSink: public Reflection { 311 | public: 312 | ReflectionSink():methods(false),sink(json::Element::OBJECT) {} 313 | 314 | virtual void visit(AbstractReflector &reflector, const char *name) { 315 | if(reflector.isMethod() != methods) return; 316 | 317 | json::Element data = reflector.read(); 318 | if(name) { 319 | sink.object()[name] = data; 320 | } 321 | else { 322 | sink = data; 323 | } 324 | } 325 | 326 | bool methods; 327 | json::Element sink; 328 | }; 329 | 330 | class ReflectionSource: public Reflection { 331 | public: 332 | ReflectionSource():source(json::Element::OBJECT) {} 333 | ReflectionSource(const json::Element &source):source(source) {} 334 | 335 | virtual void visit(AbstractReflector &reflector, const char *name) { 336 | if(reflector.isMethod()) return; 337 | 338 | if(name) { 339 | json::Object::iterator it = source.object().find(name); 340 | if (it != source.object().end()) { 341 | reflector.write(it->second); 342 | } 343 | } 344 | else { 345 | reflector.write(source); 346 | } 347 | } 348 | 349 | json::Element source; 350 | }; 351 | 352 | class ReflectionCaller: public Reflection { 353 | public: 354 | ReflectionCaller(json::String name, json::Array args) 355 | :name(name), 356 | args(args), 357 | found(false) {} 358 | 359 | virtual void visit(xyz::core::AbstractReflector &reflector, const char *name) { 360 | if(!reflector.isMethod() || this->name != name) { // TODO: Methods in members. 361 | return; 362 | } 363 | 364 | found = true; 365 | result = reflector.call(args); 366 | } 367 | 368 | json::String name; 369 | json::Array args; 370 | json::Element result; 371 | bool found; 372 | }; 373 | 374 | template 375 | struct can_reflect { 376 | private: 377 | template 378 | static typename std::is_same().reflect(std::declval())), void>::type test(int); 379 | 380 | template 381 | static std::false_type test(...); 382 | 383 | public: 384 | static constexpr bool value = decltype(test(0))::value; 385 | }; 386 | 387 | template 388 | class Reflector::value>::type>: public AbstractReflector { 389 | public: 390 | 391 | Reflector(Field &field):field(field) {} 392 | 393 | json::Element read() { 394 | ReflectionSink sink; 395 | field.reflect(sink); 396 | return sink.sink; 397 | } 398 | 399 | void write(const json::Element &data) { 400 | if(data.getType() != json::Element::NULL_VALUE) { 401 | ReflectionSource source(data); 402 | field.reflect(source); 403 | } else { 404 | field = Field(); 405 | } 406 | } 407 | 408 | protected: 409 | Field &field; 410 | }; 411 | 412 | namespace detail { 413 | 414 | template 415 | struct Binding { 416 | typedef typename std::remove_const::type>::type arg_type; 417 | 418 | static arg_type get(const json::Array &args) { 419 | auto arg = arg_type(); 420 | Reflector refl(arg); 421 | refl.write(args[args.size()-(Idx+1)]); 422 | return arg; 423 | } 424 | }; 425 | 426 | template 427 | struct Caller { 428 | template 429 | struct Bind { 430 | typedef Caller caller_t; 431 | }; 432 | 433 | static void validate(json::Array::size_type size) { 434 | if(size != Count) { 435 | throw json::TypeError("TypeError: Incorrect number of arguments."); 436 | } 437 | } 438 | 439 | template 440 | struct inner { 441 | static json::Element call(Class &instance, Method method, const json::Array &args) { 442 | validate(args.size()); 443 | Result result = (instance.*method)(Bindings::get(args) ...); 444 | Reflector refl(result); 445 | return refl.read(); 446 | } 447 | }; 448 | 449 | template 450 | struct inner { 451 | static json::Element call(Class &instance, Method method, const json::Array &args) { 452 | validate(args.size()); 453 | (instance.*method)(Bindings::get(args) ...); 454 | return json::Element::NULL_VALUE; 455 | } 456 | }; 457 | }; 458 | 459 | template 460 | struct Binder { 461 | typedef Binding binding; 462 | typedef typename Caller::template Bind::caller_t caller; 463 | typedef typename Binder::leaf_t leaf_t; 464 | }; 465 | 466 | template 467 | struct Binder<0, Caller, Arg, Args...> { 468 | typedef Binding<0, Arg> binding; 469 | typedef typename Caller::template Bind::caller_t caller; 470 | typedef Binder<0, Caller, Arg, Args ...> leaf_t; 471 | 472 | template 473 | static json::Element call(Class &instance, Method method, const json::Array &args) { 474 | return (caller::template inner::call(instance, method, args)); 475 | } 476 | }; 477 | 478 | template 479 | json::Element type() { 480 | typedef typename std::remove_const::type>::type real_type; 481 | auto arg1 = real_type(); 482 | Reflector refl(arg1); 483 | return refl.read().getTypeName(); 484 | } 485 | 486 | } 487 | 488 | template 489 | class MethodReflector: public AbstractReflector { 490 | public: 491 | typedef Result (Class::*method_type)(Args ...); 492 | 493 | MethodReflector(Class &instance, method_type method) 494 | :instance(instance), 495 | method(method) {} 496 | 497 | virtual bool isMethod() { return true; } 498 | 499 | virtual json::Element read() { 500 | json::Array sig; 501 | sig.push_back("func"); 502 | if(sizeof...(Args) > 0) { 503 | json::Element types[] = { detail::type() ... }; 504 | for(unsigned i = 0; i < sizeof(types) / sizeof(types[0]); ++i) { 505 | sig.push_back(types[i]); 506 | } 507 | } 508 | return sig; 509 | } 510 | 511 | virtual void write(const json::Element &data) { 512 | throw json::TypeError(); 513 | } 514 | 515 | template 516 | struct inner { 517 | static inline json::Element call(Class &instance, method_type method, const json::Array &args) { 518 | using namespace detail; 519 | return Binder, Argss ...>::leaf_t::template call(instance, method, args); 520 | } 521 | }; 522 | 523 | template 524 | struct inner { 525 | static inline json::Element call(Class &instance, method_type method, const json::Array &args) { 526 | using namespace detail; 527 | return Caller<0>::inner::call(instance, method, args); 528 | } 529 | }; 530 | 531 | virtual json::Element call(const json::Array &args) { 532 | return inner::call(instance, method, args); 533 | } 534 | 535 | Class &instance; 536 | method_type method; 537 | }; 538 | 539 | template 540 | Field &reflect(Reflection &reflection, Field &field, const char *name) { 541 | Reflector reflector(field); 542 | reflection.visit(reflector, name); 543 | return field; 544 | } 545 | 546 | template 547 | Field &reflect_custom(Reflection &reflection, Field &field, const char *name) { 548 | ReflectorClass reflector(field); 549 | reflection.visit(reflector, name); 550 | return field; 551 | } 552 | 553 | template 554 | void reflect_property(Reflection &reflection, 555 | Class &instance, 556 | Property (Class::*getter)(), 557 | void (Class::*setter)(Property), 558 | const char *name) { 559 | PropertyReflector reflector(instance, getter, setter); 560 | reflection.visit(reflector, name); 561 | } 562 | 563 | template 564 | void reflect_method(Reflection &reflection, 565 | Class &instance, 566 | Result (Class::*method)(Args ...), 567 | const char *name) { 568 | MethodReflector reflector(instance, method); 569 | reflection.visit(reflector, name); 570 | } 571 | 572 | } 573 | } 574 | 575 | #define XYZ_REFLECT(reflection, field) (::xyz::core::reflect(reflection, field, #field)) 576 | #define XYZ_REFLECT_METHOD(reflection, cls, field) (::xyz::core::reflect_method(reflection, *this, &cls::field, #field)) 577 | 578 | #endif 579 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.8) 2 | project(xyz_reflect_test) 3 | 4 | include_directories("${PROJECT_BINARY_DIR}/src") 5 | 6 | include_directories("${PROJECT_SOURCE_DIR}/../src") 7 | 8 | file(GLOB_RECURSE SOURCE_FILES 9 | "src/*.hpp" 10 | "src/*.cpp" 11 | 12 | "../src/json.cpp" 13 | "../src/reflection.cpp" 14 | ) 15 | 16 | add_definitions(-Wall -Wold-style-cast -std=c++11) 17 | 18 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 19 | #target_link_libraries(${PROJECT_NAME}) 20 | -------------------------------------------------------------------------------- /test/src/json/serialize.cpp: -------------------------------------------------------------------------------- 1 | #include "../catch.hpp" 2 | #include "json.hpp" 3 | 4 | using namespace xyz::json; 5 | 6 | TEST_CASE("Serialize primitives", "[core] [json]") { 7 | REQUIRE(serialize(Element(Element::NULL_VALUE), true) == "null");; 8 | REQUIRE(serialize(Element(Element::NULL_VALUE), false) == "null"); 9 | REQUIRE(serialize(Element(Number(5.0)), true) == "5"); 10 | REQUIRE(serialize(Element(Number(5.0)), false) == "5"); 11 | REQUIRE(serialize(Element(Number(-5.5)), true) == "-5.5"); 12 | REQUIRE(serialize(Element(Number(-5.5)), false) == "-5.5"); 13 | REQUIRE(serialize(Element("<\" \\>"), true) == "\"<\\\" \\\\>\""); 14 | REQUIRE(serialize(Element("<\" \\>"), false) == "\"<\\\" \\\\>\""); 15 | REQUIRE(serialize(Element("\xc4"), false) == "\"\\u00c4\""); 16 | REQUIRE(serialize(Element(true), true) == "true"); 17 | REQUIRE(serialize(Element(true), false) == "true"); 18 | REQUIRE(serialize(Element(false), true) == "false"); 19 | REQUIRE(serialize(Element(false), false) == "false"); 20 | } 21 | 22 | TEST_CASE("Serialize array", "[core] [json]") { 23 | Array ar; 24 | ar.push_back(Element(Element::NULL_VALUE)); 25 | ar.push_back(Element("str")); 26 | ar.push_back(Element(Object())); 27 | ar.push_back(Element(false)); 28 | ar.push_back(Element(Number(5.0))); 29 | Element el(ar); 30 | 31 | REQUIRE(serialize(el, true) == "[\n null,\n \"str\",\n {},\n false,\n 5\n]"); 32 | REQUIRE(serialize(el, false) == "[ null, \"str\", {}, false, 5 ]"); 33 | } 34 | 35 | TEST_CASE("Serialize object", "[core] [json]") { 36 | Object obj; 37 | obj["str"] = "foo"; 38 | Element el(obj); 39 | 40 | REQUIRE(serialize(el, true) == "{\n \"str\": \"foo\"\n}"); 41 | REQUIRE(serialize(el, false) == "{ \"str\": \"foo\" }"); 42 | 43 | obj.clear(); 44 | obj["bool"] = true; 45 | el = Element(obj); 46 | 47 | REQUIRE(serialize(el, true) == "{\n \"bool\": true\n}"); 48 | REQUIRE(serialize(el, false) == "{ \"bool\": true }"); 49 | 50 | obj.clear(); 51 | obj["arr"] = Array(); 52 | el = Element(obj); 53 | 54 | REQUIRE(serialize(el, true) == "{\n \"arr\": []\n}"); 55 | REQUIRE(serialize(el, false) == "{ \"arr\": [] }"); 56 | } 57 | 58 | // TODO: Negative tests. 59 | 60 | TEST_CASE("Deserialize empty", "[core] [json]") { 61 | REQUIRE(serialize(deserialize("{}")) == "{}"); 62 | REQUIRE(serialize(deserialize("[]")) == "[]"); 63 | REQUIRE(serialize(deserialize("{ }")) == "{}"); 64 | REQUIRE(serialize(deserialize("[ ]")) == "[]"); 65 | REQUIRE(serialize(deserialize("{ }")) == "{}"); 66 | REQUIRE(serialize(deserialize("[ ]")) == "[]"); 67 | REQUIRE(serialize(deserialize("\"\"")) == "\"\""); 68 | } 69 | 70 | TEST_CASE("Deserialize ignore whitespace", "[core] [json]") { 71 | REQUIRE(serialize(deserialize(" {}")) == "{}"); 72 | REQUIRE(serialize(deserialize(" {}")) == "{}"); 73 | REQUIRE(serialize(deserialize("{} ")) == "{}"); 74 | REQUIRE(serialize(deserialize("{} ")) == "{}"); 75 | REQUIRE(serialize(deserialize(" { \"\" : [ ] } ")) == "{ \"\": [] }"); 76 | REQUIRE(serialize(deserialize("{\"\":[]}")) == "{ \"\": [] }"); 77 | REQUIRE(serialize(deserialize(" [ 1,2, 3 , 4] ")) == "[ 1, 2, 3, 4 ]"); 78 | } 79 | 80 | TEST_CASE("Deserialize object", "[core] [json]") { 81 | REQUIRE(serialize(deserialize("{\"a\":1}")) == "{ \"a\": 1 }"); 82 | REQUIRE(serialize(deserialize("{\"a\":[1]}")) == "{ \"a\": [ 1 ] }"); 83 | } 84 | 85 | TEST_CASE("Deserialize string", "[core] [json]") { 86 | REQUIRE(deserialize("\"<\\\" \\\\>\"") == Element("<\" \\>")); 87 | REQUIRE(deserialize("\"\\u0000\"") == Element(String(1, '\0'))); 88 | REQUIRE(deserialize("\"\\u00c4\"") == Element(String(1, '\xc4'))); 89 | REQUIRE(deserialize("\"\\u00C4\"") == Element(String(1, '\xc4'))); 90 | REQUIRE(deserialize("\"\xc4\"") == Element(String(1, '\xc4'))); 91 | 92 | try { 93 | deserialize("\"\\u0100\""); 94 | 95 | FAIL("Expected exception on multi-byte escape sequence"); 96 | } 97 | catch (SyntaxError e) { 98 | REQUIRE(String(e.msg) == "Escape sequence above Latin-1 not implemented."); 99 | REQUIRE(e.line == 1); 100 | } 101 | 102 | try { 103 | deserialize(String("\"") + String(1, '\x80') + String("\"")); 104 | 105 | FAIL("Expected exception on control character in string"); 106 | } 107 | catch (SyntaxError e) { 108 | REQUIRE(String(e.msg) == "Control character in string."); 109 | REQUIRE(e.line == 1); 110 | } 111 | 112 | try { 113 | deserialize(String("\"\n\"")); 114 | 115 | FAIL("Expected exception on newline in string"); 116 | } 117 | catch (SyntaxError e) { 118 | REQUIRE(String(e.msg) == "Control character in string."); 119 | REQUIRE(e.line == 1); 120 | } 121 | 122 | try { 123 | deserialize(String("\"\r\"")); 124 | 125 | FAIL("Expected exception on carriage return in string"); 126 | } 127 | catch (SyntaxError e) { 128 | REQUIRE(String(e.msg) == "Control character in string."); 129 | REQUIRE(e.line == 1); 130 | } 131 | } 132 | 133 | TEST_CASE("Deserialize number", "[core] [json]") { 134 | REQUIRE(deserialize("0") == Element(Number(0))); 135 | REQUIRE(deserialize("0.0") == Element(Number(0))); 136 | REQUIRE(deserialize("0.1") == Element(Number(0.1))); 137 | REQUIRE(deserialize("10") == Element(Number(10))); 138 | REQUIRE(deserialize("-0") == Element(Number(0))); 139 | REQUIRE(deserialize("-0.0") == Element(Number(0))); 140 | REQUIRE(deserialize("-123.0") == Element(Number(-123))); 141 | REQUIRE(deserialize("12e-1") == Element(Number(1.2))); 142 | } 143 | 144 | TEST_CASE("Deserialize line comment", "[core] [json]") { 145 | Array ar; 146 | ar.push_back(Element(Element::NULL_VALUE)); 147 | REQUIRE(deserialize("// x\n [ null//\n]//") == Element(ar)); 148 | } 149 | -------------------------------------------------------------------------------- /test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | 4 | TEST_CASE("Test CATCH", "[CATCH]") { 5 | REQUIRE(1 == 1); 6 | REQUIRE(1 != 0); 7 | } 8 | -------------------------------------------------------------------------------- /test/src/reflection/methods.cpp: -------------------------------------------------------------------------------- 1 | #include "../catch.hpp" 2 | #include "reflection.hpp" 3 | #include 4 | 5 | using namespace xyz::json; 6 | using xyz::json::String; 7 | using xyz::core::Reflection; 8 | using xyz::core::ReflectionSink; 9 | using xyz::core::ReflectionSource; 10 | using xyz::core::ReflectionCaller; 11 | 12 | namespace { 13 | class BasicReflectable { 14 | public: 15 | void reflect(Reflection &refl) { 16 | XYZ_REFLECT(refl, integer); 17 | XYZ_REFLECT(refl, nonsigned); 18 | XYZ_REFLECT(refl, boolean); 19 | XYZ_REFLECT(refl, floating); 20 | XYZ_REFLECT(refl, floatinger); 21 | XYZ_REFLECT(refl, text); 22 | } 23 | 24 | int integer; 25 | unsigned nonsigned; 26 | bool boolean; 27 | float floating; 28 | double floatinger; 29 | String text; 30 | }; 31 | 32 | class JsonReflectable { 33 | public: 34 | void reflect(Reflection &refl) { 35 | XYZ_REFLECT(refl, element); 36 | } 37 | 38 | xyz::json::Element element; 39 | }; 40 | 41 | class MethodReflectable { 42 | public: 43 | void reflect(Reflection &refl) { 44 | XYZ_REFLECT(refl, methVal1); 45 | XYZ_REFLECT(refl, methVal2); 46 | XYZ_REFLECT_METHOD(refl, MethodReflectable, noArgs); 47 | XYZ_REFLECT_METHOD(refl, MethodReflectable, method); 48 | XYZ_REFLECT_METHOD(refl, MethodReflectable, method2); 49 | XYZ_REFLECT_METHOD(refl, MethodReflectable, echo); 50 | } 51 | 52 | String noArgs() { return "no args"; } 53 | void method(int arg) { methVal1 = arg; } 54 | void method2(int arg1, const String &arg2) { methVal1 = arg1; methVal2 = arg2; } 55 | int echo(int arg) { return arg; } 56 | int methVal1; 57 | String methVal2; 58 | }; 59 | 60 | class CompositeMethodReflectable { 61 | public: 62 | void reflect(Reflection &refl) { 63 | XYZ_REFLECT_METHOD(refl, CompositeMethodReflectable, compositeArg); 64 | } 65 | 66 | String compositeArg(std::map arg) { return arg["text"]; } 67 | }; 68 | 69 | class ComplexMethodReflectable { 70 | public: 71 | void reflect(Reflection &refl) { 72 | XYZ_REFLECT_METHOD(refl, ComplexMethodReflectable, complexArg); 73 | } 74 | 75 | String complexArg(BasicReflectable arg) { return arg.text; } 76 | }; 77 | 78 | class SimpleClassWithMethod { 79 | public: 80 | String complexArg(BasicReflectable arg) { return arg.text; } 81 | }; 82 | 83 | class DerivedComplexMethodReflectable: public SimpleClassWithMethod { 84 | public: 85 | void reflect(Reflection &refl) { 86 | XYZ_REFLECT_METHOD(refl, SimpleClassWithMethod, complexArg); 87 | } 88 | }; 89 | } 90 | 91 | TEST_CASE("Method reflection (call)", "[core] [reflection]") { 92 | // Given: 93 | MethodReflectable reflectable; 94 | reflectable.methVal1 = 0; 95 | 96 | // When: 97 | Array args; 98 | args.push_back(Element(5.0f)); 99 | ReflectionCaller caller("method", args); 100 | reflectable.reflect(caller); 101 | 102 | // Then: 103 | REQUIRE(caller.found); 104 | REQUIRE(caller.result.isNull()); 105 | REQUIRE(reflectable.methVal1 == 5); 106 | } 107 | 108 | TEST_CASE("Method reflection multiple parameters (call)", "[core] [reflection]") { 109 | // Given: 110 | MethodReflectable reflectable; 111 | reflectable.methVal1 = 0; 112 | reflectable.methVal2 = ""; 113 | 114 | // When: 115 | Array args; 116 | args.push_back(Element(10.0f)); 117 | args.push_back(Element("Sea shells she sells.")); 118 | ReflectionCaller caller("method2", args); 119 | reflectable.reflect(caller); 120 | 121 | // Then: 122 | REQUIRE(caller.found); 123 | REQUIRE(caller.result.isNull()); 124 | REQUIRE(reflectable.methVal1 == 10); 125 | REQUIRE(reflectable.methVal2 == "Sea shells she sells."); 126 | } 127 | 128 | TEST_CASE("Method reflection no parameters (call)", "[core] [reflection]") { 129 | // Given: 130 | MethodReflectable reflectable; 131 | 132 | // When: 133 | Array args; 134 | ReflectionCaller caller("noArgs", args); 135 | reflectable.reflect(caller); 136 | 137 | // Then: 138 | REQUIRE(caller.found); 139 | REQUIRE(caller.result.isString()); 140 | REQUIRE(caller.result.str() == "no args"); 141 | } 142 | 143 | TEST_CASE("Method reflection with return value (call)", "[core] [reflection]") { 144 | // Given: 145 | MethodReflectable reflectable; 146 | 147 | // When: 148 | Array args; 149 | args.push_back(Element(11.0f)); 150 | ReflectionCaller caller("echo", args); 151 | reflectable.reflect(caller); 152 | 153 | // Then: 154 | REQUIRE(caller.found); 155 | REQUIRE(caller.result.isNumber()); 156 | REQUIRE(caller.result.number() == 11); 157 | } 158 | 159 | TEST_CASE("Method reflection with composite argument (call)", "[core] [reflection]") { 160 | // Given: 161 | CompositeMethodReflectable reflectable; 162 | 163 | // When: 164 | Object arg; 165 | arg["text"] = "hello"; 166 | Array args; 167 | args.push_back(arg); 168 | ReflectionCaller caller("compositeArg", args); 169 | reflectable.reflect(caller); 170 | 171 | // Then: 172 | REQUIRE(caller.found); 173 | REQUIRE(caller.result.isString()); 174 | REQUIRE(caller.result.str() == "hello"); 175 | } 176 | 177 | TEST_CASE("Method reflection with complex argument (call)", "[core] [reflection]") { 178 | // Given: 179 | ComplexMethodReflectable reflectable; 180 | 181 | // When: 182 | Object arg; 183 | arg["text"] = "hello"; 184 | Array args; 185 | args.push_back(arg); 186 | ReflectionCaller caller("complexArg", args); 187 | reflectable.reflect(caller); 188 | 189 | // Then: 190 | REQUIRE(caller.found); 191 | REQUIRE(caller.result.isString()); 192 | REQUIRE(caller.result.str() == "hello"); 193 | } 194 | 195 | TEST_CASE("Inherited method reflection with complex argument (call)", "[core] [reflection]") { 196 | // Given: 197 | DerivedComplexMethodReflectable reflectable; 198 | 199 | // When: 200 | Object arg; 201 | arg["text"] = "hello"; 202 | Array args; 203 | args.push_back(arg); 204 | ReflectionCaller caller("complexArg", args); 205 | reflectable.reflect(caller); 206 | 207 | // Then: 208 | REQUIRE(caller.found); 209 | REQUIRE(caller.result.isString()); 210 | REQUIRE(caller.result.str() == "hello"); 211 | } 212 | 213 | TEST_CASE("Method reflection (sink)", "[core] [reflection]") { 214 | // Given: 215 | MethodReflectable reflectable; 216 | 217 | // When: 218 | ReflectionSink sink; 219 | sink.methods = true; 220 | reflectable.reflect(sink); 221 | 222 | // Then: 223 | REQUIRE(sink.sink.isObject()); 224 | REQUIRE(sink.sink.object().size() == 4); 225 | REQUIRE(sink.sink.object()["method"].isArray()); 226 | REQUIRE(sink.sink.object()["method"].array().size() == 2); 227 | REQUIRE(sink.sink.object()["method"].array()[0] == Element("func")); 228 | REQUIRE(sink.sink.object()["method"].array()[1] == Element("NUMBER")); 229 | REQUIRE(sink.sink.object()["method2"].isArray()); 230 | REQUIRE(sink.sink.object()["method2"].array().size() == 3); 231 | REQUIRE(sink.sink.object()["method2"].array()[0] == Element("func")); 232 | REQUIRE(sink.sink.object()["method2"].array()[1] == Element("NUMBER")); 233 | REQUIRE(sink.sink.object()["method2"].array()[2] == Element("STRING")); 234 | REQUIRE(sink.sink.object()["noArgs"].isArray()); 235 | REQUIRE(sink.sink.object()["noArgs"].array().size() == 1); 236 | REQUIRE(sink.sink.object()["noArgs"].array()[0] == Element("func")); 237 | REQUIRE(sink.sink.object()["echo"].isArray()); 238 | REQUIRE(sink.sink.object()["echo"].array().size() == 2); 239 | REQUIRE(sink.sink.object()["echo"].array()[0] == Element("func")); 240 | REQUIRE(sink.sink.object()["echo"].array()[1] == Element("NUMBER")); 241 | } 242 | 243 | TEST_CASE("Reflect and ignore method (sink)", "[core] [reflection]") { 244 | // Given: 245 | MethodReflectable reflectable; 246 | 247 | // When: 248 | ReflectionSink sink; 249 | reflectable.reflect(sink); 250 | 251 | // Then: 252 | REQUIRE(sink.sink.isObject()); 253 | REQUIRE(sink.sink.object().size() == 2); 254 | REQUIRE(sink.sink.object()["methVal1"].isNumber()); 255 | REQUIRE(sink.sink.object()["methVal2"].isString()); 256 | } 257 | 258 | TEST_CASE("Reflect and ignore method (source)", "[core] [reflection]") { 259 | // Given: 260 | MethodReflectable reflectable; 261 | 262 | // When: 263 | ReflectionSource source; 264 | reflectable.reflect(source); 265 | 266 | // Then: 267 | // No exception, passed. 268 | } 269 | -------------------------------------------------------------------------------- /test/src/reflection/reflection.cpp: -------------------------------------------------------------------------------- 1 | #include "../catch.hpp" 2 | #include "reflection.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace xyz::json; 8 | using xyz::json::String; 9 | using xyz::core::Reflection; 10 | using xyz::core::ReflectionSink; 11 | using xyz::core::ReflectionSource; 12 | using xyz::core::ReflectionCaller; 13 | 14 | namespace { 15 | class BasicReflectable { 16 | public: 17 | void reflect(Reflection &refl) { 18 | XYZ_REFLECT(refl, integer); 19 | XYZ_REFLECT(refl, nonsigned); 20 | XYZ_REFLECT(refl, boolean); 21 | XYZ_REFLECT(refl, floating); 22 | XYZ_REFLECT(refl, floatinger); 23 | XYZ_REFLECT(refl, text); 24 | } 25 | 26 | int integer; 27 | unsigned nonsigned; 28 | bool boolean; 29 | float floating; 30 | double floatinger; 31 | String text; 32 | }; 33 | 34 | class CompositeReflectable { 35 | public: 36 | void reflect(Reflection &refl) { 37 | XYZ_REFLECT(refl, map); 38 | XYZ_REFLECT(refl, vector); 39 | XYZ_REFLECT(refl, list); 40 | } 41 | 42 | std::map map; 43 | std::vector vector; 44 | std::list list; 45 | }; 46 | 47 | class PropertyReflectable { 48 | public: 49 | void reflect(Reflection &refl) { 50 | xyz::core::reflect_property(refl, *this, &PropertyReflectable::getValue, &PropertyReflectable::setValue, "value"); 51 | } 52 | 53 | void setValue(int v) { 54 | value = v; 55 | } 56 | 57 | int getValue() { 58 | return value; 59 | } 60 | 61 | int value; 62 | }; 63 | 64 | class ComplexReflectable { 65 | public: 66 | void reflect(Reflection &refl) { 67 | XYZ_REFLECT(refl, basic); 68 | } 69 | 70 | BasicReflectable basic; 71 | }; 72 | 73 | class JsonReflectable { 74 | public: 75 | void reflect(Reflection &refl) { 76 | XYZ_REFLECT(refl, element); 77 | } 78 | 79 | xyz::json::Element element; 80 | }; 81 | } 82 | 83 | TEST_CASE("Basic reflection (sink)", "[core] [reflection]") { 84 | // Given: 85 | BasicReflectable expected; 86 | expected.integer = -10; 87 | expected.nonsigned = 53467342; 88 | expected.boolean = false; 89 | expected.floating = 3.1415f; 90 | expected.floatinger = 3.141592654; 91 | expected.text = "This text should be reflected!"; 92 | 93 | // When: 94 | ReflectionSink sink; 95 | expected.reflect(sink); 96 | 97 | // Then: 98 | REQUIRE(sink.sink.isObject()); 99 | 100 | Object actual = sink.sink.object(); 101 | REQUIRE(actual["integer"].number() == expected.integer); 102 | REQUIRE(actual["nonsigned"].number() == expected.nonsigned); 103 | REQUIRE(actual["boolean"].boolean() == expected.boolean); 104 | REQUIRE(actual["floating"].number() == expected.floating); 105 | REQUIRE(actual["floatinger"].number() == expected.floatinger); 106 | REQUIRE(actual["text"].str() == expected.text); 107 | } 108 | 109 | TEST_CASE("Basic reflection (source)", "[core] [reflection]") { 110 | // Given: 111 | Object expected; 112 | expected["integer"] = Number(12958); 113 | expected["nonsigned"] = Number(7612958); 114 | expected["boolean"] = Boolean(true); 115 | expected["floating"] = Number(43246.6654f); 116 | expected["floatinger"] = Number(475.535723235467436731); 117 | expected["text"] = "A nice little string w/ some [characters']\"}"; 118 | 119 | // When: 120 | BasicReflectable actual; 121 | ReflectionSource source(expected); 122 | actual.reflect(source); 123 | 124 | // Then: 125 | REQUIRE(expected["integer"].number() == actual.integer); 126 | REQUIRE(expected["nonsigned"].number() == actual.nonsigned); 127 | REQUIRE(expected["boolean"].boolean() == actual.boolean); 128 | REQUIRE(expected["floating"].number() == actual.floating); 129 | REQUIRE(expected["floatinger"].number() == actual.floatinger); 130 | REQUIRE(expected["text"].str() == actual.text); 131 | } 132 | 133 | TEST_CASE("Basic reflection (bidirectional)", "[core] [reflection]") { 134 | // Given: 135 | BasicReflectable expected; 136 | expected.integer = 10; 137 | expected.nonsigned = 53467342; 138 | expected.boolean = false; 139 | expected.floating = 3.1415f; 140 | expected.floatinger = 3.141592654; 141 | expected.text = "This text should be reflected!"; 142 | 143 | // When: 144 | ReflectionSink sink; 145 | expected.reflect(sink); 146 | 147 | BasicReflectable actual; 148 | ReflectionSource source(sink.sink); 149 | actual.reflect(source); 150 | 151 | // Then: 152 | REQUIRE(actual.integer == expected.integer); 153 | REQUIRE(actual.nonsigned == expected.nonsigned); 154 | REQUIRE(actual.boolean == expected.boolean); 155 | REQUIRE(actual.floating == expected.floating); 156 | REQUIRE(actual.floatinger == expected.floatinger); 157 | REQUIRE(actual.text == expected.text); 158 | } 159 | 160 | TEST_CASE("Composite reflection (sink)", "[core] [reflection]") { 161 | // Given: 162 | CompositeReflectable expected; 163 | expected.map["one"] = "a"; 164 | expected.map["two"] = "b"; 165 | expected.vector.push_back(1); 166 | expected.vector.push_back(2); 167 | expected.list.push_back(1000.5); 168 | expected.list.push_back(2000.5); 169 | 170 | // When: 171 | ReflectionSink sink; 172 | expected.reflect(sink); 173 | 174 | // Then: 175 | REQUIRE(sink.sink.isObject()); 176 | 177 | Object actual = sink.sink.object(); 178 | REQUIRE(actual["map"].isObject()); 179 | REQUIRE(actual["vector"].isArray()); 180 | REQUIRE(actual["list"].isArray()); 181 | 182 | REQUIRE(actual["map"].object().size() == 2); 183 | REQUIRE(actual["vector"].array().size() == 2); 184 | REQUIRE(actual["list"].array().size() == 2); 185 | 186 | REQUIRE(actual["map"].object()["one"].str() == expected.map["one"]); 187 | REQUIRE(actual["map"].object()["two"].str() == expected.map["two"]); 188 | REQUIRE(actual["vector"].array()[0].number() == expected.vector[0]); 189 | REQUIRE(actual["vector"].array()[1].number() == expected.vector[1]); 190 | REQUIRE(actual["list"].array()[0].number() == expected.list.front()); 191 | REQUIRE(actual["list"].array()[1].number() == expected.list.back()); 192 | } 193 | 194 | TEST_CASE("Composite reflection (source)", "[core] [reflection]") { 195 | // Given: 196 | Object expected; 197 | expected["map"] = Object(); 198 | expected["map"].object()["hello"] = "world"; 199 | expected["map"].object()["foo"] = "bar"; 200 | expected["vector"] = Array(); 201 | expected["vector"].array().push_back(Number(42)); 202 | expected["vector"].array().push_back(Number(666)); 203 | expected["list"] = Array(); 204 | expected["list"].array().push_back(Number(42)); 205 | expected["list"].array().push_back(Number(666)); 206 | 207 | // When: 208 | CompositeReflectable actual; 209 | ReflectionSource source(expected); 210 | actual.reflect(source); 211 | 212 | // Then: 213 | REQUIRE(actual.map.size() == 2); 214 | REQUIRE(actual.vector.size() == 2); 215 | REQUIRE(actual.list.size() == 2); 216 | 217 | REQUIRE(expected["map"].object()["hello"].str() == actual.map["hello"]); 218 | REQUIRE(expected["map"].object()["foo"].str() == actual.map["foo"]); 219 | REQUIRE(expected["vector"].array()[0].number() == actual.vector[0]); 220 | REQUIRE(expected["vector"].array()[1].number() == actual.vector[1]); 221 | REQUIRE(expected["list"].array()[0].number() == actual.list.front()); 222 | REQUIRE(expected["list"].array()[1].number() == actual.list.back()); 223 | } 224 | 225 | TEST_CASE("Composite reflection (bidirectional)", "[core] [reflection]") { 226 | // Given: 227 | CompositeReflectable expected; 228 | expected.map["one"] = "a"; 229 | expected.map["two"] = "b"; 230 | expected.vector.push_back(1); 231 | expected.vector.push_back(2); 232 | expected.list.push_back(1000.5); 233 | expected.list.push_back(2000.5); 234 | 235 | // When: 236 | ReflectionSink sink; 237 | expected.reflect(sink); 238 | 239 | CompositeReflectable actual; 240 | ReflectionSource source(sink.sink); 241 | actual.reflect(source); 242 | 243 | // Then: 244 | REQUIRE(actual.map.size() == expected.map.size()); 245 | REQUIRE(actual.vector.size() == expected.vector.size()); 246 | REQUIRE(actual.list.size() == expected.list.size()); 247 | 248 | REQUIRE(expected.map["hello"] == actual.map["hello"]); 249 | REQUIRE(expected.map["foo"] == actual.map["foo"]); 250 | REQUIRE(expected.vector[0] == actual.vector[0]); 251 | REQUIRE(expected.vector[1] == actual.vector[1]); 252 | REQUIRE(expected.list.front() == actual.list.front()); 253 | REQUIRE(expected.list.back() == actual.list.back()); 254 | } 255 | 256 | TEST_CASE("Property reflection (sink)", "[core] [reflection]") { 257 | // Given: 258 | PropertyReflectable expected; 259 | expected.value = -10; 260 | 261 | // When: 262 | ReflectionSink sink; 263 | expected.reflect(sink); 264 | 265 | // Then: 266 | REQUIRE(sink.sink.isObject()); 267 | 268 | Object actual = sink.sink.object(); 269 | REQUIRE(actual["value"].number() == expected.value); 270 | } 271 | 272 | TEST_CASE("Property reflection (source)", "[core] [reflection]") { 273 | // Given: 274 | Object expected; 275 | expected["value"] = Number(65654); 276 | 277 | // When: 278 | PropertyReflectable actual; 279 | ReflectionSource source(expected); 280 | actual.reflect(source); 281 | 282 | // Then: 283 | REQUIRE(expected["value"].number() == actual.value); 284 | } 285 | 286 | TEST_CASE("Property reflection (bidirectional)", "[core] [reflection]") { 287 | // Given: 288 | PropertyReflectable expected; 289 | expected.value = 6575; 290 | 291 | // When: 292 | ReflectionSink sink; 293 | expected.reflect(sink); 294 | 295 | PropertyReflectable actual; 296 | ReflectionSource source(sink.sink); 297 | actual.reflect(source); 298 | 299 | // Then: 300 | REQUIRE(actual.value == expected.value); 301 | } 302 | 303 | TEST_CASE("Complex (recursive) reflection (sink)", "[core] [reflection]") { 304 | // Given: 305 | ComplexReflectable expected; 306 | expected.basic.integer = -10; 307 | expected.basic.nonsigned = 53467342; 308 | expected.basic.boolean = false; 309 | expected.basic.floating = 3.1415f; 310 | expected.basic.floatinger = 3.141592654; 311 | expected.basic.text = "This text should be reflected!"; 312 | 313 | // When: 314 | ReflectionSink sink; 315 | expected.reflect(sink); 316 | 317 | // Then: 318 | REQUIRE(sink.sink.isObject()); 319 | REQUIRE(sink.sink.object().size() == 1); 320 | REQUIRE(sink.sink.object()["basic"].isObject()); 321 | 322 | Object &actual = sink.sink.object()["basic"].object(); 323 | REQUIRE(actual["integer"].number() == expected.basic.integer); 324 | REQUIRE(actual["nonsigned"].number() == expected.basic.nonsigned); 325 | REQUIRE(actual["boolean"].boolean() == expected.basic.boolean); 326 | REQUIRE(actual["floating"].number() == expected.basic.floating); 327 | REQUIRE(actual["floatinger"].number() == expected.basic.floatinger); 328 | REQUIRE(actual["text"].str() == expected.basic.text); 329 | } 330 | 331 | TEST_CASE("Json reflection (bidirectional)", "[core] [reflection]") { 332 | // Given: 333 | JsonReflectable expected; 334 | expected.element = Object(); 335 | expected.element.object()["str"] = "xyz"; 336 | expected.element.object()["arr"] = Array(); 337 | expected.element.object()["arr"].array().push_back(Number(1)); 338 | expected.element.object()["arr"].array().push_back(Number(2)); 339 | 340 | // When: 341 | ReflectionSink sink; 342 | expected.reflect(sink); 343 | 344 | // Then: 345 | REQUIRE(sink.sink.getType() == Element::OBJECT); 346 | REQUIRE(sink.sink.object().size() == 1); 347 | REQUIRE(sink.sink.object()["element"] == expected.element); 348 | 349 | // When: 350 | JsonReflectable actual; 351 | ReflectionSource source(sink.sink); 352 | actual.reflect(source); 353 | 354 | // Then: 355 | REQUIRE(actual.element == expected.element); 356 | } 357 | -------------------------------------------------------------------------------- /test/src/reflection/type_id.cpp: -------------------------------------------------------------------------------- 1 | #include "../catch.hpp" 2 | #include "reflection.hpp" 3 | 4 | using xyz::core::TypeId; 5 | using xyz::core::type_id; 6 | 7 | namespace { 8 | 9 | struct XType { 10 | TypeId getType() { 11 | return type_id(); 12 | } 13 | }; 14 | 15 | struct YType { 16 | TypeId getType() { 17 | return type_id(); 18 | } 19 | }; 20 | 21 | } 22 | 23 | TEST_CASE("Static type id", "[core] [type_id]") { 24 | REQUIRE(type_id() != 0); 25 | REQUIRE(type_id() != 0); 26 | 27 | REQUIRE(type_id() == type_id()); 28 | REQUIRE(type_id() == type_id()); 29 | REQUIRE(type_id() != type_id()); 30 | 31 | XType x; 32 | YType y; 33 | 34 | REQUIRE(x.getType() == type_id()); 35 | REQUIRE(y.getType() == type_id()); 36 | REQUIRE(x.getType() != type_id()); 37 | REQUIRE(y.getType() != type_id()); 38 | 39 | REQUIRE(x.getType() == x.getType()); 40 | REQUIRE(y.getType() == y.getType()); 41 | REQUIRE(x.getType() != y.getType()); 42 | } 43 | --------------------------------------------------------------------------------