diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/core/common/Variant.cpp | 204 | ||||
-rw-r--r-- | src/core/common/Variant.hpp | 99 | ||||
-rw-r--r-- | src/core/common/VariantConverter.cpp | 255 | ||||
-rw-r--r-- | src/core/common/VariantConverter.hpp | 72 | ||||
-rw-r--r-- | src/core/common/VariantWriter.cpp | 165 | ||||
-rw-r--r-- | src/core/common/VariantWriter.hpp | 57 | ||||
-rw-r--r-- | src/core/model/Typesystem.cpp | 42 | ||||
-rw-r--r-- | test/core/common/VariantWriterTest.cpp | 53 |
9 files changed, 735 insertions, 215 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 781b9c4..88baf05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,9 @@ ADD_LIBRARY(ousia_core src/core/common/Terminal src/core/common/Utils src/core/common/Variant + src/core/common/VariantConverter src/core/common/VariantReader + src/core/common/VariantWriter src/core/managed/Events src/core/managed/Managed src/core/managed/Manager @@ -202,6 +204,7 @@ IF(TEST) test/core/common/LoggerTest test/core/common/RttiTest test/core/common/VariantReaderTest + test/core/common/VariantWriterTest test/core/common/VariantTest test/core/common/UtilsTest test/core/managed/ManagedContainerTest diff --git a/src/core/common/Variant.cpp b/src/core/common/Variant.cpp index e216aa1..b1a65ea 100644 --- a/src/core/common/Variant.cpp +++ b/src/core/common/Variant.cpp @@ -20,8 +20,11 @@ #include <core/managed/Managed.hpp> +#include "Logger.hpp" #include "Utils.hpp" #include "Variant.hpp" +#include "VariantConverter.hpp" +#include "VariantWriter.hpp" namespace ousia { @@ -68,115 +71,124 @@ const char *Variant::getTypeName(Type type) Variant::boolType Variant::toBool() const { - switch (getType()) { - case Type::NULLPTR: - return false; - case Type::BOOL: - return asBool(); - case Type::INT: - return asInt() != 0; - case Type::DOUBLE: - return asDouble() != 0.0; - default: - return true; - } - return false; + ExceptionLogger logger; + Variant res{*this}; + VariantConverter::toBool(res, logger, VariantConverter::Mode::ALL); + return res.asBool(); } Variant::intType Variant::toInt() const { - switch (getType()) { - case Type::NULLPTR: - return 0; - case Type::BOOL: - return asBool() ? 1 : 0; - case Type::INT: - return asInt(); - case Type::DOUBLE: - return asDouble(); - case Type::STRING: - case Type::MAGIC: - return 0; // TODO: Parse string as int - case Type::ARRAY: { - // JavaScript behaviour when converting arrays to ints - const arrayType &a = asArray(); - return (a.size() == 1) ? a[0].toInt() : 0; - } - default: - return 0; - } - return false; + ExceptionLogger logger; + Variant res{*this}; + VariantConverter::toInt(res, logger, VariantConverter::Mode::ALL); + return res.asInt(); } Variant::doubleType Variant::toDouble() const { - switch (getType()) { - case Type::NULLPTR: - return 0.0; - case Type::BOOL: - return asBool() ? 1.0 : 0.0; - case Type::INT: - return asInt(); - case Type::DOUBLE: - return asDouble(); - case Type::STRING: - case Type::MAGIC: - return 0.0; // TODO: Parse string as double - case Type::ARRAY: { - // JavaScript behaviour when converting array to doubles - const arrayType &a = asArray(); - return (a.size() == 1) ? a[0].toDouble() : 0; - } - default: - return 0.0; - } - return false; + ExceptionLogger logger; + Variant res{*this}; + VariantConverter::toDouble(res, logger, VariantConverter::Mode::ALL); + return res.asDouble(); } Variant::stringType Variant::toString(bool escape) const { - switch (getType()) { - case Type::NULLPTR: - return "null"; - case Type::BOOL: - return asBool() ? "true" : "false"; - case Type::INT: { - std::stringstream ss; - ss << asInt(); - return ss.str(); - } - case Type::DOUBLE: { - std::stringstream ss; - ss << asDouble(); - return ss.str(); - } - case Type::STRING: { - case Type::MAGIC: - // TODO: Use proper serialization function - if (escape) { - std::stringstream ss; - ss << "\"" << asString() << "\""; - return ss.str(); - } else { - return asString(); - } - } - case Type::ARRAY: - return Utils::join(asArray(), ", ", "[", "]"); - case Type::MAP: - return Utils::join(asMap(), ", ", "{", "}"); - case Type::OBJECT: { - std::stringstream ss; - ss << "<object " << ptrVal << ">"; - return ss.str(); - } - case Type::FUNCTION: { - std::stringstream ss; - ss << "<function " << static_cast<functionType*>(ptrVal)->get() << ">"; - return ss.str(); - } + ExceptionLogger logger; + Variant res{*this}; + VariantConverter::toString(res, logger, VariantConverter::Mode::ALL); + return res.asString(); +} + +/* Output stream operators */ + +std::ostream &operator<<(std::ostream &os, const Variant &v) +{ + VariantWriter::writeJson(v, os, true); + return os; +} + +/* Comparison operators */ + +bool operator<(const Variant &lhs, const Variant &rhs) +{ + // If the types do not match, we can not do a meaningful comparison. + if (lhs.getType() != rhs.getType()) { + throw Variant::TypeException(lhs.getType(), rhs.getType()); + } + switch (lhs.getType()) { + case Variant::Type::NULLPTR: + return false; + case Variant::Type::BOOL: + return lhs.boolVal < rhs.boolVal; + case Variant::Type::INT: + return lhs.intVal < rhs.intVal; + case Variant::Type::DOUBLE: + return lhs.doubleVal < rhs.doubleVal; + case Variant::Type::MAGIC: + case Variant::Type::STRING: + return lhs.asString() < rhs.asString(); + case Variant::Type::ARRAY: + return lhs.asArray() < rhs.asArray(); + case Variant::Type::MAP: + return lhs.asMap() < rhs.asMap(); + case Variant::Type::OBJECT: + return lhs.asObject().get() < rhs.asObject().get(); + case Variant::Type::FUNCTION: + return lhs.asFunction() < rhs.asFunction(); + } + throw OusiaException("Internal Error! Unknown type!"); +} + +bool operator>(const Variant &lhs, const Variant &rhs) +{ + return rhs < lhs; +} + +bool operator<=(const Variant &lhs, const Variant &rhs) +{ + return !(lhs > rhs); +} + +bool operator>=(const Variant &lhs, const Variant &rhs) +{ + return !(lhs < rhs); +} + +bool operator==(const Variant &lhs, const Variant &rhs) +{ + if (lhs.getType() != rhs.getType()) { + return false; + } + switch (lhs.getType()) { + case Variant::Type::NULLPTR: + return true; + case Variant::Type::BOOL: + return lhs.boolVal == rhs.boolVal; + case Variant::Type::INT: + return lhs.intVal == rhs.intVal; + case Variant::Type::DOUBLE: + return lhs.doubleVal == rhs.doubleVal; + case Variant::Type::STRING: + case Variant::Type::MAGIC: + return lhs.asString() == rhs.asString(); + case Variant::Type::ARRAY: + return lhs.asArray() == rhs.asArray(); + case Variant::Type::MAP: + return lhs.asMap() == rhs.asMap(); + case Variant::Type::OBJECT: + return lhs.asObject() == rhs.asObject(); + case Variant::Type::FUNCTION: + return lhs.asFunction() == rhs.asFunction(); } - return ""; + throw OusiaException("Internal Error! Unknown type!"); } + +bool operator!=(const Variant &lhs, const Variant &rhs) +{ + return !(lhs == rhs); +} + } diff --git a/src/core/common/Variant.hpp b/src/core/common/Variant.hpp index 9c061c1..254e748 100644 --- a/src/core/common/Variant.hpp +++ b/src/core/common/Variant.hpp @@ -864,23 +864,17 @@ public: */ const char *getTypeName() { return Variant::getTypeName(getType()); } - /** - * Prints the Variant to the output stream. + /* + * Output stream operator. */ - friend std::ostream &operator<<(std::ostream &os, const Variant &v) - { - return os << v.toString(true); - } /** - * Prints a key value pair to the output stream. + * Prints the Variant to the output stream as JSON data. + * + * @param os is the output stream the variant should be written to. + * @param v is the variant that should be written to the output stream. */ - friend std::ostream &operator<<(std::ostream &os, - const mapType::value_type &v) - { - // TODO: Use proper serialization function - return os << "\"" << v.first << "\": " << v.second.toString(true); - } + friend std::ostream &operator<<(std::ostream &os, const Variant &v); /* * Comprison operators. @@ -895,35 +889,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs is smaller than rhs. */ - friend bool operator<(const Variant &lhs, const Variant &rhs) - { - // If the types do not match, we can not do a meaningful comparison. - if (lhs.getType() != rhs.getType()) { - throw TypeException(lhs.getType(), rhs.getType()); - } - switch (lhs.getType()) { - case Type::NULLPTR: - return false; - case Type::BOOL: - return lhs.boolVal < rhs.boolVal; - case Type::INT: - return lhs.intVal < rhs.intVal; - case Type::DOUBLE: - return lhs.doubleVal < rhs.doubleVal; - case Type::MAGIC: - case Type::STRING: - return lhs.asString() < rhs.asString(); - case Type::ARRAY: - return lhs.asArray() < rhs.asArray(); - case Type::MAP: - return lhs.asMap() < rhs.asMap(); - case Type::OBJECT: - return lhs.asObject().get() < rhs.asObject().get(); - case Type::FUNCTION: - return lhs.asFunction() < rhs.asFunction(); - } - throw OusiaException("Internal Error! Unknown type!"); - } + friend bool operator<(const Variant &lhs, const Variant &rhs); /** * Returns true if the given left hand side is larger than the right hand @@ -934,10 +900,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs is larger than rhs. */ - friend bool operator>(const Variant &lhs, const Variant &rhs) - { - return rhs < lhs; - } + friend bool operator>(const Variant &lhs, const Variant &rhs); /** * Returns true if the given left hand side is smaller or equal to the @@ -948,10 +911,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs is smaller than or equal to rhs. */ - friend bool operator<=(const Variant &lhs, const Variant &rhs) - { - return !(lhs > rhs); - } + friend bool operator<=(const Variant &lhs, const Variant &rhs); /** * Returns true if the given left hand side is larger or equal to the @@ -962,10 +922,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs is larger than or equal to rhs. */ - friend bool operator>=(const Variant &lhs, const Variant &rhs) - { - return !(lhs < rhs); - } + friend bool operator>=(const Variant &lhs, const Variant &rhs); /** * Returns true if the given left hand side and right hand side are equal. @@ -976,34 +933,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs equals rhs. */ - friend bool operator==(const Variant &lhs, const Variant &rhs) - { - if (lhs.getType() != rhs.getType()) { - return false; - } - switch (lhs.getType()) { - case Type::NULLPTR: - return true; - case Type::BOOL: - return lhs.boolVal == rhs.boolVal; - case Type::INT: - return lhs.intVal == rhs.intVal; - case Type::DOUBLE: - return lhs.doubleVal == rhs.doubleVal; - case Type::STRING: - case Type::MAGIC: - return lhs.asString() == rhs.asString(); - case Type::ARRAY: - return lhs.asArray() == rhs.asArray(); - case Type::MAP: - return lhs.asMap() == rhs.asMap(); - case Type::OBJECT: - return lhs.asObject() == rhs.asObject(); - case Type::FUNCTION: - return lhs.asFunction() == rhs.asFunction(); - } - throw OusiaException("Internal Error! Unknown type!"); - } + friend bool operator==(const Variant &lhs, const Variant &rhs); /** * Returns true if the given left hand side are equal. Uses the comparison @@ -1014,10 +944,7 @@ public: * @param rhs is the right hand side of the comparison. * @return true if lhs is not equal to rhs. */ - friend bool operator!=(const Variant &lhs, const Variant &rhs) - { - return !(lhs == rhs); - } + friend bool operator!=(const Variant &lhs, const Variant &rhs); }; } diff --git a/src/core/common/VariantConverter.cpp b/src/core/common/VariantConverter.cpp new file mode 100644 index 0000000..f9d465e --- /dev/null +++ b/src/core/common/VariantConverter.cpp @@ -0,0 +1,255 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> +#include <sstream> + +#include "Logger.hpp" +#include "Number.hpp" +#include "Rtti.hpp" +#include "Variant.hpp" +#include "VariantConverter.hpp" +#include "VariantWriter.hpp" + +namespace ousia { + +static std::string msgUnexpectedType(Variant::Type actualType, + Variant::Type requestedType) +{ + return std::string("Cannot convert ") + Variant::getTypeName(actualType) + + std::string(" to ") + Variant::getTypeName(requestedType); +} + +bool VariantConverter::toBool(Variant &var, Logger &logger, Mode mode) +{ + // Perform safe conversions + const Variant::Type type = var.getType(); + switch (type) { + case Variant::Type::BOOL: + // No conversion needed if "var" already is a boolean + return true; + default: + break; + } + + // Perform potentially dangerous conversions in the "ALL" mode + if (mode == Mode::ALL) { + switch (var.getType()) { + case Variant::Type::NULLPTR: + var = false; + return true; + case Variant::Type::INT: + var = var.asInt() != 0; + return true; + case Variant::Type::DOUBLE: + var = var.asDouble() != 0.0; + return true; + default: + var = true; + return true; + } + } + + // No conversion possible, assign default value and log error + logger.error(msgUnexpectedType(var.getType(), Variant::Type::BOOL)); + var = false; + return false; +} + +bool VariantConverter::toInt(Variant &var, Logger &logger, Mode mode) +{ + // Perform safe conversions + const Variant::Type type = var.getType(); + switch (type) { + case Variant::Type::INT: + // No conversion needed if "var" already is an integer + return true; + default: + break; + } + + // Perform all potentially dangerous conversions in the "ALL" mode + if (mode == Mode::ALL) { + switch (type) { + case Variant::Type::NULLPTR: + var = 0; + return true; + case Variant::Type::BOOL: + var = var.asBool() ? 1 : 0; + return true; + case Variant::Type::DOUBLE: + var = (Variant::intType)var.asDouble(); + return true; + case Variant::Type::STRING: + case Variant::Type::MAGIC: { + Number n; + n.parse(var.asString(), logger); + if (n.isInt()) { + var = (Variant::intType)n.intValue(); + } else { + var = (Variant::doubleType)n.doubleValue(); + } + return true; + } + case Variant::Type::ARRAY: { + try { + // JavaScript behaviour when converting arrays to doubles + const Variant::arrayType &a = var.asArray(); + var = (a.size() == 1) ? a[0].toInt() : 0.0; + return true; + } + catch (LoggableException ex) { + logger.log(ex); + } + } + default: + break; + } + } + + // No conversion possible, assign default value and log error + logger.error(msgUnexpectedType(var.getType(), Variant::Type::INT)); + var = 0; + return false; +} + +bool VariantConverter::toDouble(Variant &var, Logger &logger, Mode mode) +{ + // Perform safe conversions + const Variant::Type type = var.getType(); + switch (type) { + case Variant::Type::DOUBLE: + // No conversion needed if "var" already is a double + return true; + case Variant::Type::INT: + // Converting integers to doubles is safe + var = (Variant::doubleType)var.asInt(); + return true; + default: + break; + } + + // Perform all potentially dangerous conversions in the "ALL" mode + if (mode == Mode::ALL) { + switch (type) { + case Variant::Type::NULLPTR: + var = 0.0; + return true; + case Variant::Type::BOOL: + var = var.asBool() ? 1.0 : 0.0; + return true; + case Variant::Type::STRING: + case Variant::Type::MAGIC: { + Number n; + n.parse(var.asString(), logger); + var = (Variant::doubleType)n.doubleValue(); + return true; + } + case Variant::Type::ARRAY: { + try { + // JavaScript behaviour when converting arrays to doubles + const Variant::arrayType &a = var.asArray(); + var = (a.size() == 1) ? a[0].toDouble() : 0.0; + return true; + } + catch (LoggableException ex) { + logger.log(ex); + } + } + default: + break; + } + } + + // No conversion possible, assign default value and log error + logger.error(msgUnexpectedType(var.getType(), Variant::Type::DOUBLE)); + var = 0.0; + return false; +} + +bool VariantConverter::toString(Variant &var, Logger &logger, Mode mode) +{ + // Perform safe conversions (all these operations are considered "lossless") + const Variant::Type type = var.getType(); + switch (type) { + case Variant::Type::NULLPTR: + var = "null"; + return true; + case Variant::Type::BOOL: + var = var.asBool() ? "true" : "false"; + return true; + case Variant::Type::INT: { + std::stringstream ss; + ss << var.asInt(); + var = ss.str().c_str(); + return true; + } + case Variant::Type::DOUBLE: { + std::stringstream ss; + ss << var.asDouble(); + var = ss.str().c_str(); + return true; + } + case Variant::Type::MAGIC: + case Variant::Type::STRING: + // No conversion needed if "var" already is a string (or a magic + // string value) + return true; + default: + break; + } + + // Perform lossy conversions + if (mode == Mode::ALL) { + switch (type) { + case Variant::Type::ARRAY: + case Variant::Type::MAP: { + std::stringstream ss; + VariantWriter::writeJson(var, ss, false); + var = ss.str().c_str(); + return true; + } + case Variant::Type::OBJECT: { + // Print object address and type + Variant::objectType obj = var.asObject(); + std::stringstream ss; + ss << "<object " << obj.get() << " (" << obj->type().name << ")" + << ">"; + var = ss.str().c_str(); + return true; + } + case Variant::Type::FUNCTION: { + // Print function pointer address + Variant::functionType obj = var.asFunction(); + std::stringstream ss; + ss << "<function " << obj.get() << ">"; + var = ss.str().c_str(); + return true; + } + default: + break; + } + } + + // No conversion possible, assign default value and log error + logger.error(msgUnexpectedType(var.getType(), Variant::Type::STRING)); + var = ""; + return false; +} +} + diff --git a/src/core/common/VariantConverter.hpp b/src/core/common/VariantConverter.hpp new file mode 100644 index 0000000..b683083 --- /dev/null +++ b/src/core/common/VariantConverter.hpp @@ -0,0 +1,72 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _OUSIA_VARIANT_CONVERTER_HPP_ +#define _OUSIA_VARIANT_CONVERTER_HPP_ + +namespace ousia { + +// Forward declaration +class Variant; +class Logger; + +/** + * The VariantConverter class is used to convert a variant to a certain + * prespecified type. The functions ensure that the variant has the requested + * type, even if the conversion fails. + */ +class VariantConverter { + +public: + /** + * Enumeration used to define the mode of conversion -- either only safe + * conversions (without any data loss) are performed, or all possible + * conversions are tried (with possible data loss). + */ + enum class Mode { + SAFE, ALL + }; + + /** + * Makes sure the given variant is a boolean. If the "mode" parameter is + * set to Mode::SAFE, only booleans can be converted to booleans. For all + * other types the conversion fails. If "mode" is set to Mode::ALL, nullptr + * values and zero numeric values are treated as "false", all other values + * are treated as "true". + * + * @param var is instance of the Variant class that should be converted to + * the requested type. + * @param logger is a reference to the logger instance into which messages + * should be logged. + * @param mode is the conversion mode. See method description for the exact + * effect. + */ + static bool toBool(Variant &var, Logger &logger, Mode mode = Mode::SAFE); + + static bool toInt(Variant &var, Logger &logger, Mode mode = Mode::SAFE); + + static bool toDouble(Variant &var, Logger &logger, Mode mode = Mode::SAFE); + + static bool toString(Variant &var, Logger &logger, Mode mode = Mode::SAFE); + +}; + +} + +#endif /* _OUSIA_VARIANT_CONVERTER_HPP_ */ + diff --git a/src/core/common/VariantWriter.cpp b/src/core/common/VariantWriter.cpp new file mode 100644 index 0000000..39dfc3b --- /dev/null +++ b/src/core/common/VariantWriter.cpp @@ -0,0 +1,165 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "Variant.hpp" +#include "VariantWriter.hpp" + +namespace ousia { + +/** + * Helper function used to write a JSON string, including quotation marks. + * + * @param str is the string that should be serialized. + * @param stream is the stream to which the JSON string should be written. + */ +static void writeJsonString(const std::string str, std::ostream &stream) +{ + stream << "\""; + for (char c : str) { + switch (c) { + case '\b': + stream << "\\b"; + break; + case '\f': + stream << "\\f"; + break; + case '\n': + stream << "\\n"; + break; + case '\r': + stream << "\\r"; + break; + case '\t': + stream << "\\t"; + break; + case '\v': + stream << "\\v"; + break; + case '\\': + stream << "\\"; + break; + case '"': + stream << "\\\""; + break; + default: + stream << c; + break; + } + } + stream << "\""; +} + +/** + * Helper function used to write the indentation, but only if the pretty mode + * is enabled. + * + * @param stream is the stream the result should be written to. + * @param pretty if false, no indentation is written. + */ +static void writeIndentation(std::ostream &stream, bool pretty, int level) +{ + if (pretty) { + for (int i = 0; i < level; i++) { + stream << "\t"; + } + } +} + +/** + * Helper function used to write a linebreak, but only if the pretty mode is + * enabled. + * + * @param stream is the stream the result should be written to. + * @param pretty if false, no linebreak is written. + */ +static void writeLinebreak(std::ostream &stream, bool pretty) +{ + if (pretty) { + stream << "\n"; + } +} + +/** + * Helper function used to serialize JSON with indentation. + * + * @param var is the variant that should be serialized. + * @param stream is the stream the result should be written to. + * @param pretty if true, the resulting value is properly indented. + * @param level is the current indentation level. + */ +static void writeJsonInternal(const Variant &var, std::ostream &stream, + bool pretty, int level) +{ + switch (var.getType()) { + case Variant::Type::NULLPTR: + case Variant::Type::BOOL: + case Variant::Type::INT: + case Variant::Type::DOUBLE: + case Variant::Type::FUNCTION: + case Variant::Type::OBJECT: + stream << var.toString(); + return; + case Variant::Type::STRING: + case Variant::Type::MAGIC: + writeJsonString(var.toString(), stream); + return; + case Variant::Type::ARRAY: { + stream << "["; + writeLinebreak(stream, pretty); + const Variant::arrayType &arr = var.asArray(); + for (size_t i = 0; i < arr.size(); i++) { + writeIndentation(stream, pretty, level + 1); + writeJsonInternal(arr[i], stream, pretty, level + 1); + if (i + 1 != arr.size()) { + stream << ","; + } + writeLinebreak(stream, pretty); + } + writeIndentation(stream, pretty, level); + stream << "]"; + return; + } + case Variant::Type::MAP: { + writeIndentation(stream, pretty, level); + stream << "{"; + writeLinebreak(stream, pretty); + const Variant::mapType &map = var.asMap(); + for (auto it = map.cbegin(); it != map.cend();) { + writeIndentation(stream, pretty, level + 1); + writeJsonString(it->first, stream); + stream << (pretty ? ": " : ":"); + writeJsonInternal(it->second, stream, pretty, level + 1); + if ((++it) != map.cend()) { + stream << ","; + } + writeLinebreak(stream, pretty); + } + writeIndentation(stream, pretty, level); + stream << "}"; + return; + } + } +} + +void VariantWriter::writeJson(const Variant &var, std::ostream &stream, + bool pretty) +{ + writeJsonInternal(var, stream, pretty, 0); +} +} + diff --git a/src/core/common/VariantWriter.hpp b/src/core/common/VariantWriter.hpp new file mode 100644 index 0000000..211da34 --- /dev/null +++ b/src/core/common/VariantWriter.hpp @@ -0,0 +1,57 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file VariantWriter.hpp + * + * Contains the VariantWriter class which provides serialization functions for + * Variant types. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_VARIANT_WRITER_HPP_ +#define _OUSIA_VARIANT_WRITER_HPP_ + +#include <ostream> + +namespace ousia { + +// Forward declaration +class Variant; + +/** + * Class which provides serialization functions for writing variants to an + * output stream in various formats. + */ +class VariantWriter { +public: + /** + * Dumps the Variant as JSON data. + * + * @param var is the variant that should be serialized. + * @param stream is the stream the result should be written to. + * @param pretty if true, the resulting value is properly indented. + */ + static void writeJson(const Variant &var, std::ostream &stream, + bool pretty = true); +}; +} + +#endif /* _OUSIA_VARIANT_WRITER_HPP_ */ + diff --git a/src/core/model/Typesystem.cpp b/src/core/model/Typesystem.cpp index f082f39..117b50a 100644 --- a/src/core/model/Typesystem.cpp +++ b/src/core/model/Typesystem.cpp @@ -20,6 +20,7 @@ #include <core/common/Rtti.hpp> #include <core/common/Utils.hpp> +#include <core/common/VariantConverter.hpp> namespace ousia { namespace model { @@ -38,57 +39,32 @@ bool Type::build(Variant &data, Logger &logger) const } } -/* Class StringType */ +/* Class BoolType */ -bool StringType::doBuild(Variant &data, Logger &logger) const +bool BoolType::doBuild(Variant &data, Logger &logger) const { - // Cannot convert non-primitive values to strings - if (!data.isPrimitive()) { - throw LoggableException{"Expected string or primitive input."}; - } - - // Perform an implicit type conversion - if (!data.isString()) { - // Convert the variant value to a string and set it - const char *oldName = data.getTypeName(); - data = data.toString().c_str(); - - // Log conversions as these may be potentially unwanted - logger.note(std::string("Implicit conversion from ") + oldName + - " to string."); - } - return true; + return VariantConverter::toBool(data, logger); } /* Class IntType */ bool IntType::doBuild(Variant &data, Logger &logger) const { - if (!data.isInt()) { - throw LoggableException{"Expected integer value."}; - } - return true; + return VariantConverter::toInt(data, logger); } /* Class DoubleType */ bool DoubleType::doBuild(Variant &data, Logger &logger) const { - if (!data.isInt() && !data.isDouble()) { - throw LoggableException{"Expected double value."}; - } - data = Variant{data.toDouble()}; - return true; + return VariantConverter::toDouble(data, logger); } -/* Class BoolType */ +/* Class StringType */ -bool BoolType::doBuild(Variant &data, Logger &logger) const +bool StringType::doBuild(Variant &data, Logger &logger) const { - if (!data.isBool()) { - throw LoggableException("Expected boolean value!"); - } - return true; + return VariantConverter::toString(data, logger); } /* Class EnumType */ diff --git a/test/core/common/VariantWriterTest.cpp b/test/core/common/VariantWriterTest.cpp new file mode 100644 index 0000000..2a0b29f --- /dev/null +++ b/test/core/common/VariantWriterTest.cpp @@ -0,0 +1,53 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sstream> +#include <gtest/gtest.h> + +#include <core/common/Variant.hpp> +#include <core/common/VariantWriter.hpp> + +namespace ousia { + +TEST(VariantWriter, writeJsonPretty) +{ + Variant v{Variant::mapType{{"a", "this is\na\ntest\""}, + {"b", 1}, + {"c", Variant::arrayType{1, 2, 3}}}}; + std::stringstream stream; + VariantWriter::writeJson(v, stream, true); + ASSERT_EQ( + "{\n\t\"a\": \"this is\\na\\ntest\\\"\",\n\t\"b\": 1,\n\t\"c\": " + "[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", + stream.str()); +} + +TEST(VariantWriter, writeJson) +{ + Variant v{Variant::mapType{{"a", "this is\na\ntest\""}, + {"b", 1}, + {"c", Variant::arrayType{1, 2, 3}}}}; + std::stringstream stream; + VariantWriter::writeJson(v, stream, false); + ASSERT_EQ( + "{\"a\":\"this is\\na\\ntest\\\"\",\"b\":1,\"c\":[1,2,3]}", + stream.str()); +} + +} + |