diff options
author | Anand Thakker <github@anandthakker.net> | 2017-08-15 17:30:39 -0400 |
---|---|---|
committer | Anand Thakker <github@anandthakker.net> | 2017-10-13 12:50:50 -0400 |
commit | 6ae0a8a819dde031eaddf7bf85a34e50f196780c (patch) | |
tree | cfc4d19e148d31a6cb4ac99c7bd616311f7a58e9 | |
parent | d26c30152a4fbe7510d64430504b85d8b5ab4a1d (diff) | |
download | qtlocation-mapboxgl-6ae0a8a819dde031eaddf7bf85a34e50f196780c.tar.gz |
Wire up expression parsing
Also extract *Expression::parse into separate headers to avoid
dependency on rapidjson conversion methods
20 files changed, 863 insertions, 481 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 9b66fce69c..c4a98f3ddc 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -415,6 +415,16 @@ set(MBGL_CORE_FILES src/mbgl/style/expression/match.cpp src/mbgl/style/expression/value.cpp + # style/expression/parse + include/mbgl/style/expression/parse/array_assertion.hpp + include/mbgl/style/expression/parse/case.hpp + include/mbgl/style/expression/parse/coalesce.hpp + include/mbgl/style/expression/parse/compound_expression.hpp + include/mbgl/style/expression/parse/curve.hpp + include/mbgl/style/expression/parse/let.hpp + include/mbgl/style/expression/parse/literal.hpp + include/mbgl/style/expression/parse/match.hpp + # style/function include/mbgl/style/function/camera_function.hpp include/mbgl/style/function/categorical_stops.hpp diff --git a/include/mbgl/style/conversion/data_driven_property_value.hpp b/include/mbgl/style/conversion/data_driven_property_value.hpp index b7e3bef172..7b99935565 100644 --- a/include/mbgl/style/conversion/data_driven_property_value.hpp +++ b/include/mbgl/style/conversion/data_driven_property_value.hpp @@ -4,6 +4,8 @@ #include <mbgl/style/conversion.hpp> #include <mbgl/style/conversion/constant.hpp> #include <mbgl/style/conversion/function.hpp> +#include <mbgl/style/conversion/expression.hpp> +#include <mbgl/style/expression/value.hpp> namespace mbgl { namespace style { @@ -20,6 +22,18 @@ struct Converter<DataDrivenPropertyValue<T>> { return {}; } return DataDrivenPropertyValue<T>(*constant); + } else if (objectMember(value, "expression")) { + optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(*objectMember(value, "expression"), error, valueTypeToExpressionType<T>()); + if (!expression) { + return {}; + } + if ((*expression)->isFeatureConstant()) { + return DataDrivenPropertyValue<T>(CameraFunction<T>(std::move(*expression))); + } else if ((*expression)->isZoomConstant()) { + return DataDrivenPropertyValue<T>(SourceFunction<T>(std::move(*expression))); + } else { + return DataDrivenPropertyValue<T>(CompositeFunction<T>(std::move(*expression))); + } } else if (!objectMember(value, "property")) { optional<CameraFunction<T>> function = convert<CameraFunction<T>>(value, error); if (!function) { diff --git a/include/mbgl/style/conversion/expression.hpp b/include/mbgl/style/conversion/expression.hpp index bce7c91396..601916250d 100644 --- a/include/mbgl/style/conversion/expression.hpp +++ b/include/mbgl/style/conversion/expression.hpp @@ -2,6 +2,7 @@ #include <memory> #include <mbgl/style/expression/parse.hpp> +#include <mbgl/style/expression/type.hpp> #include <mbgl/style/conversion.hpp> namespace mbgl { @@ -12,12 +13,20 @@ using namespace mbgl::style::expression; template<> struct Converter<std::unique_ptr<Expression>> { template <class V> - optional<std::unique_ptr<Expression>> operator()(const V& value, Error& error) const { - auto parsed = parseExpression(value, ParsingContext()); - if (parsed.template is<std::unique_ptr<Expression>>()) { - return std::move(parsed.template get<std::unique_ptr<Expression>>()); + optional<std::unique_ptr<Expression>> operator()(const V& value, Error& error, type::Type expected) const { + std::vector<ParsingError> errors; + auto parsed = parseExpression(value, ParsingContext(errors, expected)); + if (parsed) { + return std::move(*parsed); } - error = { parsed.template get<CompileError>().message }; + std::string combinedError; + for (const ParsingError& parsingError : errors) { + if (combinedError.size() > 0) { + combinedError += "\n"; + } + combinedError += parsingError.key + ": " + parsingError.message; + } + error = { combinedError }; return {}; }; }; diff --git a/include/mbgl/style/conversion/property_value.hpp b/include/mbgl/style/conversion/property_value.hpp index 3780381b23..2caa801e34 100644 --- a/include/mbgl/style/conversion/property_value.hpp +++ b/include/mbgl/style/conversion/property_value.hpp @@ -4,6 +4,8 @@ #include <mbgl/style/conversion.hpp> #include <mbgl/style/conversion/constant.hpp> #include <mbgl/style/conversion/function.hpp> +#include <mbgl/style/conversion/expression.hpp> +#include <mbgl/style/expression/value.hpp> namespace mbgl { namespace style { @@ -14,6 +16,17 @@ struct Converter<PropertyValue<T>> { optional<PropertyValue<T>> operator()(const Value& value, Error& error) const { if (isUndefined(value)) { return PropertyValue<T>(); + } else if (isObject(value) && objectMember(value, "expression")) { + optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(*objectMember(value, "expression"), error, valueTypeToExpressionType<T>()); + if (!expression) { + return {}; + } + if ((*expression)->isFeatureConstant()) { + return { CameraFunction<T>(std::move(*expression)) }; + } else { + error = { "data-driven style property not supported " }; + return {}; + } } else if (isObject(value)) { optional<CameraFunction<T>> function = convert<CameraFunction<T>>(value, error); if (!function) { diff --git a/include/mbgl/style/expression/curve.hpp b/include/mbgl/style/expression/curve.hpp index 9b7a6b64f5..a3cf7fa63f 100644 --- a/include/mbgl/style/expression/curve.hpp +++ b/include/mbgl/style/expression/curve.hpp @@ -5,8 +5,6 @@ #include <mbgl/util/range.hpp> #include <mbgl/style/expression/expression.hpp> #include <mbgl/style/expression/compound_expression.hpp> -#include <mbgl/style/expression/parsing_context.hpp> -#include <mbgl/style/conversion.hpp> namespace mbgl { namespace style { @@ -58,15 +56,6 @@ public: } }; -namespace detail { - -// used for storing intermediate state during parsing -struct ExponentialInterpolation { float base; std::string name = "exponential"; }; -struct StepInterpolation {}; - -} // namespace detail - - template <typename InterpolatorT> class Curve : public Expression { public: @@ -151,187 +140,6 @@ private: std::map<float, std::unique_ptr<Expression>> stops; }; -struct ParseCurve { - template <typename V> - static ParseResult parse(const V& value, ParsingContext ctx) { - using namespace mbgl::style::conversion; - assert(isArray(value)); - auto length = arrayLength(value); - if (length < 5) { - ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "."); - return ParseResult(); - } - - // [curve, interp, input, 2 * (n pairs)...] - if (length % 2 != 1) { - ctx.error("Expected an even number of arguments."); - return ParseResult(); - } - - const auto& interp = arrayMember(value, 1); - if (!isArray(interp) || arrayLength(interp) == 0) { - ctx.error("Expected an interpolation type expression."); - return ParseResult(); - } - - variant<detail::StepInterpolation, - detail::ExponentialInterpolation> interpolation; - - const auto& interpName = toString(arrayMember(interp, 0)); - if (interpName && *interpName == "step") { - interpolation = detail::StepInterpolation{}; - } else if (interpName && *interpName == "linear") { - interpolation = detail::ExponentialInterpolation { 1.0f, "linear" }; - } else if (interpName && *interpName == "exponential") { - optional<double> base; - if (arrayLength(interp) == 2) { - base = toDouble(arrayMember(interp, 1)); - } - if (!base) { - ctx.error("Exponential interpolation requires a numeric base."); - return ParseResult(); - } - interpolation = detail::ExponentialInterpolation { static_cast<float>(*base) }; - } else { - ctx.error("Unknown interpolation type " + (interpName ? *interpName : "")); - return ParseResult(); - } - - ParseResult input = parseExpression(arrayMember(value, 2), ParsingContext(ctx, 2, {type::Number})); - if (!input) { - return input; - } - - std::map<float, std::unique_ptr<Expression>> stops; - optional<type::Type> outputType = ctx.expected; - - double previous = - std::numeric_limits<double>::infinity(); - for (std::size_t i = 3; i + 1 < length; i += 2) { - const optional<mbgl::Value>& labelValue = toValue(arrayMember(value, i)); - optional<double> label; - optional<std::string> labelError; - if (labelValue) { - labelValue->match( - [&](uint64_t n) { - if (!Value::isSafeNumericValue(n)) { - labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; - } else { - label = {static_cast<double>(n)}; - } - }, - [&](int64_t n) { - if (!Value::isSafeNumericValue(n)) { - labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; - } else { - label = {static_cast<double>(n)}; - } - }, - [&](double n) { - if (!Value::isSafeNumericValue(n)) { - labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; - } else { - label = {static_cast<double>(n)}; - } - }, - [&](const auto&) {} - ); - } - if (!label) { - ctx.error(labelError ? *labelError : - R"(Input/output pairs for "curve" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", - i); - return ParseResult(); - } - - if (*label < previous) { - ctx.error( - R"(Input/output pairs for "curve" expressions must be arranged with input values in strictly ascending order.)", - i - ); - return ParseResult(); - } - previous = *label; - - auto output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, i + 1, outputType)); - if (!output) { - return ParseResult(); - } - if (!outputType) { - outputType = (*output)->getType(); - } - - stops.emplace(*label, std::move(*output)); - } - - assert(outputType); - - if ( - !interpolation.template is<detail::StepInterpolation>() && - *outputType != type::Number && - *outputType != type::Color && - !( - outputType->is<type::Array>() && - outputType->get<type::Array>().itemType == type::Number - ) - ) - { - ctx.error("Type " + toString(*outputType) + - " is not interpolatable, and thus cannot be used as a " + - *interpName + " curve's output type."); - return ParseResult(); - } - - return interpolation.match( - [&](const detail::StepInterpolation&) -> ParseResult { - return ParseResult(std::make_unique<Curve<StepInterpolator>>( - *outputType, - StepInterpolator(), - std::move(*input), - std::move(stops) - )); - }, - [&](const detail::ExponentialInterpolation& exponentialInterpolation) -> ParseResult { - const float base = exponentialInterpolation.base; - return outputType->match( - [&](const type::NumberType&) -> ParseResult { - return ParseResult(std::make_unique<Curve<ExponentialInterpolator<float>>>( - *outputType, - ExponentialInterpolator<float>(base), - std::move(*input), - std::move(stops) - )); - }, - [&](const type::ColorType&) -> ParseResult { - return ParseResult(std::make_unique<Curve<ExponentialInterpolator<mbgl::Color>>>( - *outputType, - ExponentialInterpolator<mbgl::Color>(base), - std::move(*input), - std::move(stops) - )); - }, - [&](const type::Array& arrayType) -> ParseResult { - if (arrayType.itemType == type::Number && arrayType.N) { - return ParseResult(std::make_unique<Curve<ExponentialInterpolator<std::vector<Value>>>>( - *outputType, - ExponentialInterpolator<std::vector<Value>>(base), - std::move(*input), - std::move(stops) - )); - } else { - assert(false); // interpolability already checked above. - return ParseResult(); - } - }, - [&](const auto&) { - assert(false); // interpolability already checked above. - return ParseResult(); - } - ); - } - ); - } -}; - } // namespace expression } // namespace style } // namespace mbgl diff --git a/include/mbgl/style/expression/literal.hpp b/include/mbgl/style/expression/literal.hpp index 6a6b62103b..96c48836bf 100644 --- a/include/mbgl/style/expression/literal.hpp +++ b/include/mbgl/style/expression/literal.hpp @@ -28,89 +28,27 @@ public: template <class V> static ParseResult parse(const V& value, ParsingContext ctx) { - const optional<Value>& parsedValue = parseValue(value, ctx); - - if (!parsedValue) { - return ParseResult(); - } + const Value& parsedValue = parseValue(value); // special case: infer the item type if possible for zero-length arrays if ( ctx.expected && ctx.expected->template is<type::Array>() && - parsedValue->template is<std::vector<Value>>() + parsedValue.template is<std::vector<Value>>() ) { - auto type = typeOf(*parsedValue).template get<type::Array>(); + auto type = typeOf(parsedValue).template get<type::Array>(); auto expected = ctx.expected->template get<type::Array>(); if ( type.N && (*type.N == 0) && (!expected.N || (*expected.N == 0)) ) { - return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>())); + return ParseResult(std::make_unique<Literal>(expected, parsedValue.template get<std::vector<Value>>())); } } - return ParseResult(std::make_unique<Literal>(*parsedValue)); + return ParseResult(std::make_unique<Literal>(parsedValue)); } private: - template <class V> - static optional<Value> parseValue(const V& value, ParsingContext ctx) { - using namespace mbgl::style::conversion; - if (isUndefined(value)) return {Null}; - if (isObject(value)) { - std::unordered_map<std::string, Value> result; - bool error = false; - eachMember(value, [&] (const std::string& k, const V& v) -> optional<conversion::Error> { - if (!error) { - optional<Value> memberValue = parseValue(v, ctx); - if (memberValue) { - result.emplace(k, *memberValue); - } else { - error = true; - } - } - return {}; - }); - return error ? optional<Value>() : optional<Value>(result); - } - - if (isArray(value)) { - std::vector<Value> result; - const auto length = arrayLength(value); - for(std::size_t i = 0; i < length; i++) { - optional<Value> item = parseValue(arrayMember(value, i), ctx); - if (item) { - result.emplace_back(*item); - } else { - return optional<Value>(); - } - } - return optional<Value>(result); - } - - optional<mbgl::Value> v = toValue(value); - assert(v); - - return v->match( - [&](uint64_t n) { return checkNumber(n, ctx); }, - [&](int64_t n) { return checkNumber(n, ctx); }, - [&](double n) { return checkNumber(n, ctx); }, - [&](const auto&) { - return optional<Value>(toExpressionValue(*v)); - } - ); - } - - template <typename T> - static optional<Value> checkNumber(T n, ParsingContext ctx) { - if (!Value::isSafeNumericValue(n)) { - ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); - return optional<Value>(); - } else { - return {static_cast<double>(n)}; - } - } - Value value; }; diff --git a/include/mbgl/style/expression/match.hpp b/include/mbgl/style/expression/match.hpp index 54a3cec9a1..7e9cb95969 100644 --- a/include/mbgl/style/expression/match.hpp +++ b/include/mbgl/style/expression/match.hpp @@ -53,193 +53,6 @@ private: std::unique_ptr<Expression> otherwise; }; -struct ParseMatch { - template <class V> - static ParseResult parse(const V& value, ParsingContext ctx) { - using namespace mbgl::style::conversion; - - assert(isArray(value)); - auto length = arrayLength(value); - if (length < 5) { - ctx.error( - "Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "." - ); - return ParseResult(); - } - - // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise] - if (length % 2 != 1) { - ctx.error("Expected an even number of arguments."); - return ParseResult(); - } - - optional<type::Type> inputType; - optional<type::Type> outputType = ctx.expected; - std::vector<std::pair<std::vector<InputType>, - std::unique_ptr<Expression>>> cases; - - for (size_t i = 2; i + 1 < length; i += 2) { - const auto& label = arrayMember(value, i); - - ParsingContext labelContext(ctx, i); - std::vector<InputType> labels; - // Match pair inputs are provided as either a literal value or a - // raw JSON array of string / number / boolean values. - if (isArray(label)) { - auto groupLength = arrayLength(label); - if (groupLength == 0) { - labelContext.error("Expected at least one branch label."); - return ParseResult(); - } - - for (size_t j = 0; j < groupLength; j++) { - const optional<InputType>& inputValue = parseInputValue(arrayMember(label, j), ParsingContext(ctx, i), inputType); - if (!inputValue) { - return ParseResult(); - } - labels.push_back(*inputValue); - } - } else { - const optional<InputType>& inputValue = parseInputValue(label, ParsingContext(ctx, i), inputType); - if (!inputValue) { - return ParseResult(); - } - labels.push_back(*inputValue); - } - - ParseResult output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, i + 1, outputType)); - if (!output) { - return ParseResult(); - } - - if (!outputType) { - outputType = (*output)->getType(); - } - - cases.push_back(std::make_pair(std::move(labels), std::move(*output))); - } - - auto input = parseExpression(arrayMember(value, 1), ParsingContext(ctx, 1, inputType)); - if (!input) { - return ParseResult(); - } - - auto otherwise = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, length - 1, outputType)); - if (!otherwise) { - return ParseResult(); - } - - assert(inputType && outputType); - - return inputType->match( - [&](const type::NumberType&) { - return create<int64_t>(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); - }, - [&](const type::StringType&) { - return create<std::string>(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); - }, - [&](const auto&) { - assert(false); - return ParseResult(); - } - ); - } - -private: - template <typename V> - static optional<InputType> parseInputValue(const V& input, ParsingContext ctx, optional<type::Type>& inputType) { - using namespace mbgl::style::conversion; - optional<InputType> result; - optional<type::Type> type; - - auto value = toValue(input); - - if (value) { - value->match( - [&] (uint64_t n) { - if (!Value::isSafeNumericValue(n)) { - ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); - } else { - type = {type::Number}; - result = {static_cast<int64_t>(n)}; - } - }, - [&] (int64_t n) { - if (!Value::isSafeNumericValue(n)) { - ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); - } else { - type = {type::Number}; - result = {n}; - } - }, - [&] (double n) { - if (!Value::isSafeNumericValue(n)) { - ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); - } else if (n != ceilf(n)) { - ctx.error("Numeric branch labels must be integer values."); - } else { - type = {type::Number}; - result = {static_cast<int64_t>(n)}; - } - }, - [&] (const std::string& s) { - type = {type::String}; - result = {s}; - }, - [&] (const auto&) { - ctx.error("Branch labels must be numbers or strings."); - } - ); - } else { - ctx.error("Branch labels must be numbers or strings."); - } - - if (!type) { - return result; - } - - if (!inputType) { - inputType = type; - } else if (checkSubtype(*inputType, *type, ctx)) { - return optional<InputType>(); - } - - return result; - } - - template <typename T> - static ParseResult create(type::Type outputType, - std::unique_ptr<Expression>input, - std::vector<std::pair<std::vector<InputType>, - std::unique_ptr<Expression>>> cases, - std::unique_ptr<Expression> otherwise, - ParsingContext ctx) { - typename Match<T>::Cases typedCases; - - std::size_t index = 2; - for (std::pair<std::vector<InputType>, - std::unique_ptr<Expression>>& pair : cases) { - std::shared_ptr<Expression> result = std::move(pair.second); - for (const InputType& label : pair.first) { - const auto& typedLabel = label.template get<T>(); - if (typedCases.find(typedLabel) != typedCases.end()) { - ctx.error("Branch labels must be unique.", index); - return ParseResult(); - } - typedCases.emplace(typedLabel, result); - } - - index += 2; - } - return ParseResult(std::make_unique<Match<T>>( - outputType, - std::move(input), - std::move(typedCases), - std::move(otherwise) - )); - } -}; - } // namespace expression } // namespace style } // namespace mbgl diff --git a/include/mbgl/style/expression/parse.hpp b/include/mbgl/style/expression/parse.hpp index 79a3276227..68f336e3ed 100644 --- a/include/mbgl/style/expression/parse.hpp +++ b/include/mbgl/style/expression/parse.hpp @@ -2,15 +2,15 @@ #include <memory> #include <mbgl/style/conversion.hpp> -#include <mbgl/style/expression/array_assertion.hpp> -#include <mbgl/style/expression/case.hpp> #include <mbgl/style/expression/check_subtype.hpp> -#include <mbgl/style/expression/coalesce.hpp> -#include <mbgl/style/expression/compound_expression.hpp> -#include <mbgl/style/expression/curve.hpp> #include <mbgl/style/expression/expression.hpp> -#include <mbgl/style/expression/literal.hpp> -#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/parse/array_assertion.hpp> +#include <mbgl/style/expression/parse/case.hpp> +#include <mbgl/style/expression/parse/coalesce.hpp> +#include <mbgl/style/expression/parse/compound_expression.hpp> +#include <mbgl/style/expression/parse/curve.hpp> +#include <mbgl/style/expression/parse/literal.hpp> +#include <mbgl/style/expression/parse/match.hpp> #include <mbgl/style/expression/parsing_context.hpp> namespace mbgl { @@ -69,19 +69,19 @@ ParseResult parseExpression(const V& value, ParsingContext context) return ParseResult(); } - parsed = Literal::parse(arrayMember(value, 1), context); + parsed = ParseLiteral::parse(arrayMember(value, 1), context); } else if (*op == "match") { parsed = ParseMatch::parse(value, context); } else if (*op == "curve") { parsed = ParseCurve::parse(value, context); } else if (*op == "coalesce") { - parsed = Coalesce::parse(value, context); + parsed = ParseCoalesce::parse(value, context); } else if (*op == "case") { - parsed = Case::parse(value, context); + parsed = ParseCase::parse(value, context); } else if (*op == "array") { - parsed = ArrayAssertion::parse(value, context); + parsed = ParseArrayAssertion::parse(value, context); } else { - parsed = CompoundExpressions::parse(value, context); + parsed = ParseCompoundExpression::parse(value, context); } } else { if (isObject(value)) { @@ -89,7 +89,7 @@ ParseResult parseExpression(const V& value, ParsingContext context) return ParseResult(); } - parsed = Literal::parse(value, context); + parsed = ParseLiteral::parse(value, context); } if (!parsed) { diff --git a/include/mbgl/style/expression/parse/array_assertion.hpp b/include/mbgl/style/expression/parse/array_assertion.hpp new file mode 100644 index 0000000000..1549a468f6 --- /dev/null +++ b/include/mbgl/style/expression/parse/array_assertion.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/util/optional.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseArrayAssertion { + template <class V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + + static std::unordered_map<std::string, type::Type> itemTypes { + {"string", type::String}, + {"number", type::Number}, + {"boolean", type::Boolean} + }; + + auto length = arrayLength(value); + if (length < 2 || length > 4) { + ctx.error("Expected 1, 2, or 3 arguments, but found " + std::to_string(length - 1) + " instead."); + return ParseResult(); + } + + optional<type::Type> itemType; + optional<std::size_t> N; + if (length > 2) { + optional<std::string> itemTypeName = toString(arrayMember(value, 1)); + auto it = itemTypeName ? itemTypes.find(*itemTypeName) : itemTypes.end(); + if (it == itemTypes.end()) { + ctx.error( + R"(The item type argument of "array" must be one of string, number, boolean)", + 1 + ); + return ParseResult(); + } + itemType = it->second; + } else { + itemType = {type::Value}; + } + + if (length > 3) { + auto n = toNumber(arrayMember(value, 2)); + if (!n || *n != ceilf(*n)) { + ctx.error( + R"(The length argument to "array" must be a positive integer literal.)", + 2 + ); + return ParseResult(); + } + N = optional<std::size_t>(*n); + } + + auto input = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, length - 1, {type::Value})); + if (!input) { + return input; + } + + return ParseResult(std::make_unique<ArrayAssertion>( + type::Array(*itemType, N), + std::move(*input) + )); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/case.hpp b/include/mbgl/style/expression/parse/case.hpp new file mode 100644 index 0000000000..8dfa390328 --- /dev/null +++ b/include/mbgl/style/expression/parse/case.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/expression/case.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/type.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCase { + template <class V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 4) { + ctx.error("Expected at least 3 arguments, but found only " + std::to_string(length - 1) + "."); + return ParseResult(); + } + + // Expect even-length array: ["case", 2 * (n pairs)..., otherwise] + if (length % 2 != 0) { + ctx.error("Expected an odd number of arguments"); + return ParseResult(); + } + + optional<type::Type> outputType = ctx.expected; + + std::vector<Case::Branch> branches; + for (size_t i = 1; i + 1 < length; i += 2) { + auto test = parseExpression(arrayMember(value, i), ParsingContext(ctx, i, {type::Boolean})); + if (!test) { + return test; + } + + auto output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, i + 1, outputType)); + if (!output) { + return output; + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + branches.push_back(std::make_pair(std::move(*test), std::move(*output))); + } + + assert(outputType); + + auto otherwise = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, length - 1, outputType)); + if (!otherwise) { + return otherwise; + } + + return ParseResult(std::make_unique<Case>(*outputType, + std::move(branches), + std::move(*otherwise))); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/coalesce.hpp b/include/mbgl/style/expression/parse/coalesce.hpp new file mode 100644 index 0000000000..fd368fd5ec --- /dev/null +++ b/include/mbgl/style/expression/parse/coalesce.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <vector> +#include <memory> +#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/conversion.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCoalesce { + template <typename V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + Coalesce::Args args; + optional<type::Type> outputType = ctx.expected; + for (std::size_t i = 1; i < length; i++) { + auto parsed = parseExpression(arrayMember(value, i), ParsingContext(ctx, i, outputType)); + if (!parsed) { + return parsed; + } + if (!outputType) { + outputType = (*parsed)->getType(); + } + args.push_back(std::move(*parsed)); + } + + assert(outputType); + return ParseResult(std::make_unique<Coalesce>(*outputType, std::move(args))); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/compound_expression.hpp b/include/mbgl/style/expression/parse/compound_expression.hpp new file mode 100644 index 0000000000..248ba4c102 --- /dev/null +++ b/include/mbgl/style/expression/parse/compound_expression.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include <array> +#include <vector> +#include <memory> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/expression/value.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCompoundExpression { + template <class V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + assert(isArray(value) && arrayLength(value) > 0); + const auto& name = toString(arrayMember(value, 0)); + assert(name); + + auto it = CompoundExpressions::definitions.find(*name); + if (it == CompoundExpressions::definitions.end()) { + ctx.error( + R"(Unknown expression ")" + *name + R"(". If you wanted a literal array, use ["literal", [...]].)", + 0 + ); + return ParseResult(); + } + const CompoundExpressions::Definition& definition = it->second; + + // parse subexpressions first + std::vector<std::unique_ptr<Expression>> args; + auto length = arrayLength(value); + for (std::size_t i = 1; i < length; i++) { + auto parsed = parseExpression(arrayMember(value, i), ParsingContext(ctx, i)); + if (!parsed) { + return parsed; + } + args.push_back(std::move(*parsed)); + } + return CompoundExpressions::create(*name, definition, std::move(args), ctx); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/curve.hpp b/include/mbgl/style/expression/parse/curve.hpp new file mode 100644 index 0000000000..537c1ef1e2 --- /dev/null +++ b/include/mbgl/style/expression/parse/curve.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include <map> +#include <memory> +#include <mbgl/util/optional.hpp> +#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +namespace detail { + +// used for storing intermediate state during parsing +struct ExponentialInterpolation { float base; std::string name = "exponential"; }; +struct StepInterpolation {}; + +} // namespace detail + +struct ParseCurve { + template <typename V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 5) { + ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "."); + return ParseResult(); + } + + // [curve, interp, input, 2 * (n pairs)...] + if (length % 2 != 1) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + const auto& interp = arrayMember(value, 1); + if (!isArray(interp) || arrayLength(interp) == 0) { + ctx.error("Expected an interpolation type expression."); + return ParseResult(); + } + + variant<detail::StepInterpolation, + detail::ExponentialInterpolation> interpolation; + + const auto& interpName = toString(arrayMember(interp, 0)); + if (interpName && *interpName == "step") { + interpolation = detail::StepInterpolation{}; + } else if (interpName && *interpName == "linear") { + interpolation = detail::ExponentialInterpolation { 1.0f, "linear" }; + } else if (interpName && *interpName == "exponential") { + optional<double> base; + if (arrayLength(interp) == 2) { + base = toDouble(arrayMember(interp, 1)); + } + if (!base) { + ctx.error("Exponential interpolation requires a numeric base."); + return ParseResult(); + } + interpolation = detail::ExponentialInterpolation { static_cast<float>(*base) }; + } else { + ctx.error("Unknown interpolation type " + (interpName ? *interpName : "")); + return ParseResult(); + } + + ParseResult input = parseExpression(arrayMember(value, 2), ParsingContext(ctx, 2, {type::Number})); + if (!input) { + return input; + } + + std::map<float, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType = ctx.expected; + + double previous = - std::numeric_limits<double>::infinity(); + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value>& labelValue = toValue(arrayMember(value, i)); + optional<double> label; + optional<std::string> labelError; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (!Value::isSafeNumericValue(n)) { + labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (!Value::isSafeNumericValue(n)) { + labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (!Value::isSafeNumericValue(n)) { + labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(labelError ? *labelError : + R"(Input/output pairs for "curve" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", + i); + return ParseResult(); + } + + if (*label < previous) { + ctx.error( + R"(Input/output pairs for "curve" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, i + 1, outputType)); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + if ( + !interpolation.template is<detail::StepInterpolation>() && + *outputType != type::Number && + *outputType != type::Color && + !( + outputType->is<type::Array>() && + outputType->get<type::Array>().itemType == type::Number + ) + ) + { + ctx.error("Type " + toString(*outputType) + + " is not interpolatable, and thus cannot be used as a " + + *interpName + " curve's output type."); + return ParseResult(); + } + + return interpolation.match( + [&](const detail::StepInterpolation&) -> ParseResult { + return ParseResult(std::make_unique<Curve<StepInterpolator>>( + *outputType, + StepInterpolator(), + std::move(*input), + std::move(stops) + )); + }, + [&](const detail::ExponentialInterpolation& exponentialInterpolation) -> ParseResult { + const float base = exponentialInterpolation.base; + return outputType->match( + [&](const type::NumberType&) -> ParseResult { + return ParseResult(std::make_unique<Curve<ExponentialInterpolator<float>>>( + *outputType, + ExponentialInterpolator<float>(base), + std::move(*input), + std::move(stops) + )); + }, + [&](const type::ColorType&) -> ParseResult { + return ParseResult(std::make_unique<Curve<ExponentialInterpolator<mbgl::Color>>>( + *outputType, + ExponentialInterpolator<mbgl::Color>(base), + std::move(*input), + std::move(stops) + )); + }, + [&](const type::Array& arrayType) -> ParseResult { + if (arrayType.itemType == type::Number && arrayType.N) { + return ParseResult(std::make_unique<Curve<ExponentialInterpolator<std::vector<Value>>>>( + *outputType, + ExponentialInterpolator<std::vector<Value>>(base), + std::move(*input), + std::move(stops) + )); + } else { + assert(false); // interpolability already checked above. + return ParseResult(); + } + }, + [&](const auto&) { + assert(false); // interpolability already checked above. + return ParseResult(); + } + ); + } + ); + } +}; + + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/let.hpp b/include/mbgl/style/expression/parse/let.hpp new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/include/mbgl/style/expression/parse/let.hpp diff --git a/include/mbgl/style/expression/parse/literal.hpp b/include/mbgl/style/expression/parse/literal.hpp new file mode 100644 index 0000000000..0d17c9f384 --- /dev/null +++ b/include/mbgl/style/expression/parse/literal.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include <vector> +#include <memory> +#include <mbgl/util/optional.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/expression/value.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseLiteral { + template <class V> + static ParseResult parse(const V& value, ParsingContext ctx) { + const optional<Value>& parsedValue = parseValue(value, ctx); + + if (!parsedValue) { + return ParseResult(); + } + + // special case: infer the item type if possible for zero-length arrays + if ( + ctx.expected && + ctx.expected->template is<type::Array>() && + parsedValue->template is<std::vector<Value>>() + ) { + auto type = typeOf(*parsedValue).template get<type::Array>(); + auto expected = ctx.expected->template get<type::Array>(); + if ( + type.N && (*type.N == 0) && + (!expected.N || (*expected.N == 0)) + ) { + return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>())); + } + } + return ParseResult(std::make_unique<Literal>(*parsedValue)); + } + template <class V> + static optional<Value> parseValue(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + if (isUndefined(value)) return {Null}; + if (isObject(value)) { + std::unordered_map<std::string, Value> result; + bool error = false; + eachMember(value, [&] (const std::string& k, const V& v) -> optional<conversion::Error> { + if (!error) { + optional<Value> memberValue = parseValue(v, ctx); + if (memberValue) { + result.emplace(k, *memberValue); + } else { + error = true; + } + } + return {}; + }); + return error ? optional<Value>() : optional<Value>(result); + } + + if (isArray(value)) { + std::vector<Value> result; + const auto length = arrayLength(value); + for(std::size_t i = 0; i < length; i++) { + optional<Value> item = parseValue(arrayMember(value, i), ctx); + if (item) { + result.emplace_back(*item); + } else { + return optional<Value>(); + } + } + return optional<Value>(result); + } + + optional<mbgl::Value> v = toValue(value); + assert(v); + + return v->match( + [&](uint64_t n) { return checkNumber(n, ctx); }, + [&](int64_t n) { return checkNumber(n, ctx); }, + [&](double n) { return checkNumber(n, ctx); }, + [&](const auto&) { + return optional<Value>(toExpressionValue(*v)); + } + ); + } + + template <typename T> + static optional<Value> checkNumber(T n, ParsingContext ctx) { + if (!Value::isSafeNumericValue(n)) { + ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); + return optional<Value>(); + } else { + return {static_cast<double>(n)}; + } + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/parse/match.hpp b/include/mbgl/style/expression/parse/match.hpp new file mode 100644 index 0000000000..4406b74002 --- /dev/null +++ b/include/mbgl/style/expression/parse/match.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include <memory> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseMatch { + template <class V> + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 5) { + ctx.error( + "Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "." + ); + return ParseResult(); + } + + // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise] + if (length % 2 != 1) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + optional<type::Type> inputType; + optional<type::Type> outputType = ctx.expected; + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> cases; + + for (size_t i = 2; i + 1 < length; i += 2) { + const auto& label = arrayMember(value, i); + + ParsingContext labelContext(ctx, i); + std::vector<InputType> labels; + // Match pair inputs are provided as either a literal value or a + // raw JSON array of string / number / boolean values. + if (isArray(label)) { + auto groupLength = arrayLength(label); + if (groupLength == 0) { + labelContext.error("Expected at least one branch label."); + return ParseResult(); + } + + for (size_t j = 0; j < groupLength; j++) { + const optional<InputType>& inputValue = parseInputValue(arrayMember(label, j), ParsingContext(ctx, i), inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + } else { + const optional<InputType>& inputValue = parseInputValue(label, ParsingContext(ctx, i), inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + + ParseResult output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, i + 1, outputType)); + if (!output) { + return ParseResult(); + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + cases.push_back(std::make_pair(std::move(labels), std::move(*output))); + } + + auto input = parseExpression(arrayMember(value, 1), ParsingContext(ctx, 1, inputType)); + if (!input) { + return ParseResult(); + } + + auto otherwise = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, length - 1, outputType)); + if (!otherwise) { + return ParseResult(); + } + + assert(inputType && outputType); + + return inputType->match( + [&](const type::NumberType&) { + return create<int64_t>(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); + }, + [&](const type::StringType&) { + return create<std::string>(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); + }, + [&](const auto&) { + assert(false); + return ParseResult(); + } + ); + } + +private: + template <typename V> + static optional<InputType> parseInputValue(const V& input, ParsingContext ctx, optional<type::Type>& inputType) { + using namespace mbgl::style::conversion; + optional<InputType> result; + optional<type::Type> type; + + auto value = toValue(input); + + if (value) { + value->match( + [&] (uint64_t n) { + if (!Value::isSafeNumericValue(n)) { + ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (int64_t n) { + if (!Value::isSafeNumericValue(n)) { + ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); + } else { + type = {type::Number}; + result = {n}; + } + }, + [&] (double n) { + if (!Value::isSafeNumericValue(n)) { + ctx.error("Numeric values must be no larger than " + std::to_string(Value::max()) + "."); + } else if (n != ceilf(n)) { + ctx.error("Numeric branch labels must be integer values."); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (const std::string& s) { + type = {type::String}; + result = {s}; + }, + [&] (const auto&) { + ctx.error("Branch labels must be numbers or strings."); + } + ); + } else { + ctx.error("Branch labels must be numbers or strings."); + } + + if (!type) { + return result; + } + + if (!inputType) { + inputType = type; + } else if (checkSubtype(*inputType, *type, ctx)) { + return optional<InputType>(); + } + + return result; + } + + template <typename T> + static ParseResult create(type::Type outputType, + std::unique_ptr<Expression>input, + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> cases, + std::unique_ptr<Expression> otherwise, + ParsingContext ctx) { + typename Match<T>::Cases typedCases; + + std::size_t index = 2; + for (std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>& pair : cases) { + std::shared_ptr<Expression> result = std::move(pair.second); + for (const InputType& label : pair.first) { + const auto& typedLabel = label.template get<T>(); + if (typedCases.find(typedLabel) != typedCases.end()) { + ctx.error("Branch labels must be unique.", index); + return ParseResult(); + } + typedCases.emplace(typedLabel, result); + } + + index += 2; + } + return ParseResult(std::make_unique<Match<T>>( + outputType, + std::move(input), + std::move(typedCases), + std::move(otherwise) + )); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/function/camera_function.hpp b/include/mbgl/style/function/camera_function.hpp index 5a0f5ffe4a..55fd5cd7cc 100644 --- a/include/mbgl/style/function/camera_function.hpp +++ b/include/mbgl/style/function/camera_function.hpp @@ -19,6 +19,13 @@ public: IntervalStops<T>>, variant< IntervalStops<T>>>; + + CameraFunction(std::unique_ptr<expression::Expression> expression_) + : expression(std::move(expression_)) + { + assert(!expression->isZoomConstant()); + assert(expression->isFeatureConstant()); + } CameraFunction(Stops stops_) : stops(std::move(stops_)), diff --git a/include/mbgl/style/function/composite_function.hpp b/include/mbgl/style/function/composite_function.hpp index f7cbab1b21..7d77d5e552 100644 --- a/include/mbgl/style/function/composite_function.hpp +++ b/include/mbgl/style/function/composite_function.hpp @@ -50,6 +50,18 @@ public: using Interpolator = expression::ExponentialInterpolator<T>; using Curve = expression::Curve<Interpolator>; + CompositeFunction(std::unique_ptr<expression::Expression> expression_) + : expression(std::move(expression_)), + interpolator([&]() -> Interpolator { + optional<Curve*> zoomCurve = findZoomCurve(expression.get()); + assert(zoomCurve); + return (*zoomCurve)->getInterpolator(); + }()) + { + assert(!expression->isZoomConstant()); + assert(!expression->isFeatureConstant()); + } + CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {}) : property(std::move(property_)), stops(std::move(stops_)), diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp index 0ae8357d7b..d65d03857a 100644 --- a/include/mbgl/style/function/source_function.hpp +++ b/include/mbgl/style/function/source_function.hpp @@ -29,6 +29,13 @@ public: CategoricalStops<T>, IdentityStops<T>>>; + SourceFunction(std::unique_ptr<expression::Expression> expression_) + : expression(std::move(expression_)) + { + assert(expression->isZoomConstant()); + assert(!expression->isFeatureConstant()); + } + SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {}) : property(std::move(property_)), stops(std::move(stops_)), diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp index cbcf7e2206..0f257a4c8b 100644 --- a/src/mbgl/style/expression/value.cpp +++ b/src/mbgl/style/expression/value.cpp @@ -142,21 +142,6 @@ std::vector<Value> toArrayValue(const Container& value) { return result; } -template <typename T, typename Container> -optional<Container> fromArrayValue(const std::vector<Value>& value) { - Container result; - auto it = result.begin(); - for(const Value& item : value) { - optional<T> convertedItem = Converter<T>::fromExpressionValue(item); - if (!convertedItem) { - return optional<Container>(); - } - *it = *convertedItem; - it = std::next(it); - } - return result; -} - template <typename T, std::size_t N> struct Converter<std::array<T, N>> { static Value toExpressionValue(const std::array<T, N>& value) { @@ -167,7 +152,17 @@ struct Converter<std::array<T, N>> { return value.match( [&] (const std::vector<Value>& v) -> optional<std::array<T, N>> { if (v.size() != N) return optional<std::array<T, N>>(); - return fromArrayValue<T, std::array<T, N>>(v); + std::array<T, N> result; + auto it = result.begin(); + for(const Value& item : v) { + optional<T> convertedItem = Converter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::array<T, N>>(); + } + *it = *convertedItem; + it = std::next(it); + } + return result; }, [&] (const auto&) { return optional<std::array<T, N>>(); } ); @@ -187,7 +182,15 @@ struct Converter<std::vector<T>> { static optional<std::vector<T>> fromExpressionValue(const Value& value) { return value.match( [&] (const std::vector<Value>& v) -> optional<std::vector<T>> { - return fromArrayValue<T, std::vector<T>>(v); + std::vector<T> result; + for(const Value& item : v) { + optional<T> convertedItem = Converter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::vector<T>>(); + } + result.push_back(*convertedItem); + } + return result; }, [&] (const auto&) { return optional<std::vector<T>>(); } ); |