From 089c4e413fbe80711ebd874520d3b8fdcb997112 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 22 Jun 2016 12:31:49 -0700 Subject: [core] Split up and clean up conversion.hpp --- include/mbgl/style/conversion.hpp | 377 ++++---------------------------------- 1 file changed, 36 insertions(+), 341 deletions(-) (limited to 'include/mbgl/style/conversion.hpp') diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp index 18326c8dd1..eff7e293a3 100644 --- a/include/mbgl/style/conversion.hpp +++ b/include/mbgl/style/conversion.hpp @@ -1,39 +1,32 @@ #pragma once -#include -#include -#include - #include -#include -#include -#include - -#include -#include -#include namespace mbgl { namespace style { namespace conversion { /* - This namespace defines a generic conversion from a templated type `V` representing a JSON object conforming - to a style property from the Mapbox Style Specification, to `PropertyValue`: + The `conversion` namespace defines conversions from a templated type `V` representing a JSON + object conforming to the schema defined by the Mapbox Style Specification, to the various C++ + types that form the C++ model of that domain: - template - Result> convertPropertyValue(const V& value); + * `std::unique_ptr` + * `std::unique_ptr` + * `Filter` + * `PropertyValue` - This is used concretely for conversions from RapidJSON types in mbgl core, and from v8 types in - the node bindings. + A single template function serves as the public interface: - It also defines a convertion from `V` to `Filter`, representing a JSON object conforming to a Style - Specification filter object: + template + Result convert(const V& value); - template - Result convertFilter(const V& value); + Where `T` is one of the above types. If the conversion fails, the `Error` variant of `Result` is + returned, which includes diagnostic text suitable for presentation to a library user. Otherwise, + the `T` variant of `Result` is returned. - The requirements are that the following are legal expressions for a value `v` of type `const V&`: + The implementation of `convert` requires that the following are legal expressions for a value `v` + of type `const V&`: * `isArray(v)` -- returns a boolean indicating whether `v` represents a JSON array * `arrayLength(v)` -- called only if `isArray(v)`; returns a size_t length @@ -51,341 +44,43 @@ namespace conversion { absence indicating `v` is not a boolean, number, or string. Numbers should be converted to unsigned integer, signed integer, or floating point, in descending preference. - If for any reason the conversion fails, the result of `convertPropertyValue` will be the `Error` variant, - which includes explanatory text. + The mbgl core implements these requirements for RapidJSON types, and the node bindings implement + them for v8 types. */ struct Error { const char * message; }; -template -using Result = variant; - -template -struct ConstantConverter {}; - -template -struct ConstantConverter { - Result operator()(const V& value) const { - optional converted = toBool(value); - if (!converted) { - return Error { "value must be a boolean" }; - } - return *converted; - } -}; - -template -struct ConstantConverter { - Result operator()(const V& value) const { - optional converted = toNumber(value); - if (!converted) { - return Error { "value must be a number" }; - } - return *converted; - } -}; +template +class Result : private variant { +public: + using variant::variant; -template -struct ConstantConverter { - Result operator()(const V& value) const { - optional converted = toString(value); - if (!converted) { - return Error { "value must be a string" }; - } - return *converted; + explicit operator bool() const { + return this->template is(); } -}; - -template -struct ConstantConverter::value>> { - Result operator()(const V& value) const { - optional string = toString(value); - if (!string) { - return Error { "value must be a string" }; - } - - const auto result = Enum::toEnum(*string); - if (!result) { - return Error { "value must be a valid enumeration value" }; - } - - return *result; - } -}; - -template -struct ConstantConverter { - Result operator()(const V& value) const { - optional string = toString(value); - if (!string) { - return Error { "value must be a string" }; - } - - optional color = Color::parse(*string); - if (!color) { - return Error { "value must be a valid color" }; - } - - return *color; - } -}; - -template -struct ConstantConverter> { - Result> operator()(const V& value) const { - if (!isArray(value) || arrayLength(value) != 2) { - return Error { "value must be an array of two numbers" }; - } - optional first = toNumber(arrayMember(value, 0)); - optional second = toNumber(arrayMember(value, 1)); - if (!first || !second) { - return Error { "value must be an array of two numbers" }; - } - - return std::array {{ *first, *second }}; + T& operator*() { + assert(this->template is()); + return this->template get(); } -}; - -template -struct ConstantConverter> { - Result> operator()(const V& value) const { - if (!isArray(value) || arrayLength(value) != 4) { - return Error { "value must be an array of four numbers" }; - } - optional first = toNumber(arrayMember(value, 0)); - optional second = toNumber(arrayMember(value, 1)); - optional third = toNumber(arrayMember(value, 2)); - optional fourth = toNumber(arrayMember(value, 3)); - if (!first || !second) { - return Error { "value must be an array of four numbers" }; - } - - return std::array {{ *first, *second, *third, *fourth }}; + const T& operator*() const { + assert(this->template is()); + return this->template get(); } -}; - -template -struct ConstantConverter> { - Result> operator()(const V& value) const { - if (!isArray(value)) { - return Error { "value must be an array" }; - } - std::vector result; - result.reserve(arrayLength(value)); - - for (std::size_t i = 0; i < arrayLength(value); ++i) { - optional number = toNumber(arrayMember(value, i)); - if (!number) { - return Error { "value must be an array of numbers" }; - } - result.push_back(*number); - } - - return result; + const Error& error() const { + assert(this->template is()); + return this->template get(); } }; -template -struct ConstantConverter> { - Result> operator()(const V& value) const { - if (!isArray(value)) { - return Error { "value must be an array" }; - } - - std::vector result; - result.reserve(arrayLength(value)); - - for (std::size_t i = 0; i < arrayLength(value); ++i) { - optional string = toString(arrayMember(value, i)); - if (!string) { - return Error { "value must be an array of strings" }; - } - result.push_back(*string); - } - - return result; - } -}; +template +struct Converter; template -Result> convertPropertyValue(const V& value) { - if (!isObject(value)) { - Result constant = ConstantConverter()(value); - if (constant.template is()) { - return constant.template get(); - } - return constant.template get(); - } - - auto stopsValue = objectMember(value, "stops"); - if (!stopsValue) { - return Error { "function value must specify stops" }; - } - - if (!isArray(*stopsValue)) { - return Error { "function stops must be an array" }; - } - - std::vector> stops; - for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { - const auto& stopValue = arrayMember(*stopsValue, i); - - if (!isArray(stopValue)) { - return Error { "function stop must be an array" }; - } - - if (arrayLength(stopValue) != 2) { - return Error { "function stop must have two elements" }; - } - - optional z = toNumber(arrayMember(stopValue, 0)); - if (!z) { - return Error { "function stop zoom level must be a number" }; - } - - Result v = ConstantConverter()(arrayMember(stopValue, 1)); - if (v.template is()) { - return v.template get(); - } - - stops.emplace_back(*z, v.template get()); - } - - auto baseValue = objectMember(value, "base"); - if (!baseValue) { - return Function(stops, 1.0f); - } - - optional base = toNumber(*baseValue); - if (!base) { - return Error { "function base must be a number"}; - } - - return Function(stops, *base); -} - -Result normalizeFilterValue(const std::string& key, const optional&); - -template -Result convertFilter(const V& value); - -template -Result parseUnaryFilter(const V& value) { - if (arrayLength(value) < 2) { - return Error { "filter expression must have 2 elements" }; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - return Error { "filter expression key must be a string" }; - } - - return FilterType { *key }; -} - -template -Result parseBinaryFilter(const V& value) { - if (arrayLength(value) < 3) { - return Error { "filter expression must have 3 elements" }; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - return Error { "filter expression key must be a string" }; - } - - Result filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, 2))); - if (filterValue.is()) { - return filterValue.get(); - } - - return FilterType { *key, filterValue.get() }; -} - -template -Result parseSetFilter(const V& value) { - if (arrayLength(value) < 2) { - return Error { "filter expression must at least 2 elements" }; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - return Error { "filter expression key must be a string" }; - } - - std::vector values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - Result filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, i))); - if (filterValue.is()) { - return filterValue.get(); - } - values.push_back(filterValue.get()); - } - - return FilterType { *key, std::move(values) }; -} - -template -Result parseCompoundFilter(const V& value) { - std::vector filters; - for (std::size_t i = 1; i < arrayLength(value); ++i) { - Result element = convertFilter(arrayMember(value, i)); - if (element.is()) { - return element; - } - filters.push_back(element.get()); - } - - return FilterType { std::move(filters) }; -} - -template -Result convertFilter(const V& value) { - if (!isArray(value)) { - return Error { "filter expression must be an array" }; - } - - if (arrayLength(value) < 1) { - return Error { "filter expression must have at least 1 element" }; - } - - optional op = toString(arrayMember(value, 0)); - if (!op) { - return Error { "filter operator must be a string" }; - } - - if (*op == "==") { - return parseBinaryFilter(value); - } else if (*op == "!=") { - return parseBinaryFilter(value); - } else if (*op == ">") { - return parseBinaryFilter(value); - } else if (*op == ">=") { - return parseBinaryFilter(value); - } else if (*op == "<") { - return parseBinaryFilter(value); - } else if (*op == "<=") { - return parseBinaryFilter(value); - } else if (*op == "in") { - return parseSetFilter(value); - } else if (*op == "!in") { - return parseSetFilter(value); - } else if (*op == "all") { - return parseCompoundFilter(value); - } else if (*op == "any") { - return parseCompoundFilter(value); - } else if (*op == "none") { - return parseCompoundFilter(value); - } else if (*op == "has") { - return parseUnaryFilter(value); - } else if (*op == "!has") { - return parseUnaryFilter(value); - } - - return Error { "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\"" }; +Result convert(const V& value) { + return Converter()(value); } } // namespace conversion -- cgit v1.2.1