From 6ae0a8a819dde031eaddf7bf85a34e50f196780c Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 15 Aug 2017 17:30:39 -0400 Subject: Wire up expression parsing Also extract *Expression::parse into separate headers to avoid dependency on rapidjson conversion methods --- cmake/core-files.cmake | 10 + .../conversion/data_driven_property_value.hpp | 14 ++ include/mbgl/style/conversion/expression.hpp | 19 +- include/mbgl/style/conversion/property_value.hpp | 13 ++ include/mbgl/style/expression/curve.hpp | 192 ------------------- include/mbgl/style/expression/literal.hpp | 72 +------ include/mbgl/style/expression/match.hpp | 187 ------------------- include/mbgl/style/expression/parse.hpp | 26 +-- .../style/expression/parse/array_assertion.hpp | 73 ++++++++ include/mbgl/style/expression/parse/case.hpp | 66 +++++++ include/mbgl/style/expression/parse/coalesce.hpp | 45 +++++ .../style/expression/parse/compound_expression.hpp | 50 +++++ include/mbgl/style/expression/parse/curve.hpp | 207 +++++++++++++++++++++ include/mbgl/style/expression/parse/let.hpp | 0 include/mbgl/style/expression/parse/literal.hpp | 104 +++++++++++ include/mbgl/style/expression/parse/match.hpp | 203 ++++++++++++++++++++ include/mbgl/style/function/camera_function.hpp | 7 + include/mbgl/style/function/composite_function.hpp | 12 ++ include/mbgl/style/function/source_function.hpp | 7 + src/mbgl/style/expression/value.cpp | 37 ++-- 20 files changed, 863 insertions(+), 481 deletions(-) create mode 100644 include/mbgl/style/expression/parse/array_assertion.hpp create mode 100644 include/mbgl/style/expression/parse/case.hpp create mode 100644 include/mbgl/style/expression/parse/coalesce.hpp create mode 100644 include/mbgl/style/expression/parse/compound_expression.hpp create mode 100644 include/mbgl/style/expression/parse/curve.hpp create mode 100644 include/mbgl/style/expression/parse/let.hpp create mode 100644 include/mbgl/style/expression/parse/literal.hpp create mode 100644 include/mbgl/style/expression/parse/match.hpp 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 #include #include +#include +#include namespace mbgl { namespace style { @@ -20,6 +22,18 @@ struct Converter> { return {}; } return DataDrivenPropertyValue(*constant); + } else if (objectMember(value, "expression")) { + optional> expression = convert>(*objectMember(value, "expression"), error, valueTypeToExpressionType()); + if (!expression) { + return {}; + } + if ((*expression)->isFeatureConstant()) { + return DataDrivenPropertyValue(CameraFunction(std::move(*expression))); + } else if ((*expression)->isZoomConstant()) { + return DataDrivenPropertyValue(SourceFunction(std::move(*expression))); + } else { + return DataDrivenPropertyValue(CompositeFunction(std::move(*expression))); + } } else if (!objectMember(value, "property")) { optional> function = convert>(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 #include +#include #include namespace mbgl { @@ -12,12 +13,20 @@ using namespace mbgl::style::expression; template<> struct Converter> { template - optional> operator()(const V& value, Error& error) const { - auto parsed = parseExpression(value, ParsingContext()); - if (parsed.template is>()) { - return std::move(parsed.template get>()); + optional> operator()(const V& value, Error& error, type::Type expected) const { + std::vector errors; + auto parsed = parseExpression(value, ParsingContext(errors, expected)); + if (parsed) { + return std::move(*parsed); } - error = { parsed.template get().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 #include #include +#include +#include namespace mbgl { namespace style { @@ -14,6 +16,17 @@ struct Converter> { optional> operator()(const Value& value, Error& error) const { if (isUndefined(value)) { return PropertyValue(); + } else if (isObject(value) && objectMember(value, "expression")) { + optional> expression = convert>(*objectMember(value, "expression"), error, valueTypeToExpressionType()); + if (!expression) { + return {}; + } + if ((*expression)->isFeatureConstant()) { + return { CameraFunction(std::move(*expression)) }; + } else { + error = { "data-driven style property not supported " }; + return {}; + } } else if (isObject(value)) { optional> function = convert>(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 #include #include -#include -#include 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 class Curve : public Expression { public: @@ -151,187 +140,6 @@ private: std::map> stops; }; -struct ParseCurve { - template - 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 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 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(*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> stops; - optional outputType = ctx.expected; - - double previous = - std::numeric_limits::infinity(); - for (std::size_t i = 3; i + 1 < length; i += 2) { - const optional& labelValue = toValue(arrayMember(value, i)); - optional label; - optional 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(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(n)}; - } - }, - [&](double n) { - if (!Value::isSafeNumericValue(n)) { - labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; - } else { - label = {static_cast(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() && - *outputType != type::Number && - *outputType != type::Color && - !( - outputType->is() && - outputType->get().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>( - *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>>( - *outputType, - ExponentialInterpolator(base), - std::move(*input), - std::move(stops) - )); - }, - [&](const type::ColorType&) -> ParseResult { - return ParseResult(std::make_unique>>( - *outputType, - ExponentialInterpolator(base), - std::move(*input), - std::move(stops) - )); - }, - [&](const type::Array& arrayType) -> ParseResult { - if (arrayType.itemType == type::Number && arrayType.N) { - return ParseResult(std::make_unique>>>( - *outputType, - ExponentialInterpolator>(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 static ParseResult parse(const V& value, ParsingContext ctx) { - const optional& 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() && - parsedValue->template is>() + parsedValue.template is>() ) { - auto type = typeOf(*parsedValue).template get(); + auto type = typeOf(parsedValue).template get(); auto expected = ctx.expected->template get(); if ( type.N && (*type.N == 0) && (!expected.N || (*expected.N == 0)) ) { - return ParseResult(std::make_unique(expected, parsedValue->template get>())); + return ParseResult(std::make_unique(expected, parsedValue.template get>())); } } - return ParseResult(std::make_unique(*parsedValue)); + return ParseResult(std::make_unique(parsedValue)); } private: - template - static optional parseValue(const V& value, ParsingContext ctx) { - using namespace mbgl::style::conversion; - if (isUndefined(value)) return {Null}; - if (isObject(value)) { - std::unordered_map result; - bool error = false; - eachMember(value, [&] (const std::string& k, const V& v) -> optional { - if (!error) { - optional memberValue = parseValue(v, ctx); - if (memberValue) { - result.emplace(k, *memberValue); - } else { - error = true; - } - } - return {}; - }); - return error ? optional() : optional(result); - } - - if (isArray(value)) { - std::vector result; - const auto length = arrayLength(value); - for(std::size_t i = 0; i < length; i++) { - optional item = parseValue(arrayMember(value, i), ctx); - if (item) { - result.emplace_back(*item); - } else { - return optional(); - } - } - return optional(result); - } - - optional 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(toExpressionValue(*v)); - } - ); - } - - template - static optional 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(); - } else { - return {static_cast(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 otherwise; }; -struct ParseMatch { - template - 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 inputType; - optional outputType = ctx.expected; - std::vector, - std::unique_ptr>> cases; - - for (size_t i = 2; i + 1 < length; i += 2) { - const auto& label = arrayMember(value, i); - - ParsingContext labelContext(ctx, i); - std::vector 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& inputValue = parseInputValue(arrayMember(label, j), ParsingContext(ctx, i), inputType); - if (!inputValue) { - return ParseResult(); - } - labels.push_back(*inputValue); - } - } else { - const optional& 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(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); - }, - [&](const type::StringType&) { - return create(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); - }, - [&](const auto&) { - assert(false); - return ParseResult(); - } - ); - } - -private: - template - static optional parseInputValue(const V& input, ParsingContext ctx, optional& inputType) { - using namespace mbgl::style::conversion; - optional result; - optional 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(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(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(); - } - - return result; - } - - template - static ParseResult create(type::Type outputType, - std::unique_ptrinput, - std::vector, - std::unique_ptr>> cases, - std::unique_ptr otherwise, - ParsingContext ctx) { - typename Match::Cases typedCases; - - std::size_t index = 2; - for (std::pair, - std::unique_ptr>& pair : cases) { - std::shared_ptr result = std::move(pair.second); - for (const InputType& label : pair.first) { - const auto& typedLabel = label.template get(); - 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>( - 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 #include -#include -#include #include -#include -#include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include 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 +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseArrayAssertion { + template + static ParseResult parse(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + + static std::unordered_map 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 itemType; + optional N; + if (length > 2) { + optional 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(*n); + } + + auto input = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, length - 1, {type::Value})); + if (!input) { + return input; + } + + return ParseResult(std::make_unique( + 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 +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCase { + template + 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 outputType = ctx.expected; + + std::vector 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(*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 +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCoalesce { + template + 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 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(*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 +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseCompoundExpression { + template + 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> 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 +#include +#include +#include +#include +#include + +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 + 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 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 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(*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> stops; + optional outputType = ctx.expected; + + double previous = - std::numeric_limits::infinity(); + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional& labelValue = toValue(arrayMember(value, i)); + optional label; + optional 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(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(n)}; + } + }, + [&](double n) { + if (!Value::isSafeNumericValue(n)) { + labelError = {"Numeric values must be no larger than " + std::to_string(Value::max()) + "."}; + } else { + label = {static_cast(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() && + *outputType != type::Number && + *outputType != type::Color && + !( + outputType->is() && + outputType->get().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>( + *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>>( + *outputType, + ExponentialInterpolator(base), + std::move(*input), + std::move(stops) + )); + }, + [&](const type::ColorType&) -> ParseResult { + return ParseResult(std::make_unique>>( + *outputType, + ExponentialInterpolator(base), + std::move(*input), + std::move(stops) + )); + }, + [&](const type::Array& arrayType) -> ParseResult { + if (arrayType.itemType == type::Number && arrayType.N) { + return ParseResult(std::make_unique>>>( + *outputType, + ExponentialInterpolator>(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 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 +#include +#include +#include +#include +#include +#include +#include + + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseLiteral { + template + static ParseResult parse(const V& value, ParsingContext ctx) { + const optional& 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() && + parsedValue->template is>() + ) { + auto type = typeOf(*parsedValue).template get(); + auto expected = ctx.expected->template get(); + if ( + type.N && (*type.N == 0) && + (!expected.N || (*expected.N == 0)) + ) { + return ParseResult(std::make_unique(expected, parsedValue->template get>())); + } + } + return ParseResult(std::make_unique(*parsedValue)); + } + template + static optional parseValue(const V& value, ParsingContext ctx) { + using namespace mbgl::style::conversion; + if (isUndefined(value)) return {Null}; + if (isObject(value)) { + std::unordered_map result; + bool error = false; + eachMember(value, [&] (const std::string& k, const V& v) -> optional { + if (!error) { + optional memberValue = parseValue(v, ctx); + if (memberValue) { + result.emplace(k, *memberValue); + } else { + error = true; + } + } + return {}; + }); + return error ? optional() : optional(result); + } + + if (isArray(value)) { + std::vector result; + const auto length = arrayLength(value); + for(std::size_t i = 0; i < length; i++) { + optional item = parseValue(arrayMember(value, i), ctx); + if (item) { + result.emplace_back(*item); + } else { + return optional(); + } + } + return optional(result); + } + + optional 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(toExpressionValue(*v)); + } + ); + } + + template + static optional 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(); + } else { + return {static_cast(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 +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +struct ParseMatch { + template + 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 inputType; + optional outputType = ctx.expected; + std::vector, + std::unique_ptr>> cases; + + for (size_t i = 2; i + 1 < length; i += 2) { + const auto& label = arrayMember(value, i); + + ParsingContext labelContext(ctx, i); + std::vector 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& inputValue = parseInputValue(arrayMember(label, j), ParsingContext(ctx, i), inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + } else { + const optional& 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(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); + }, + [&](const type::StringType&) { + return create(*outputType, std::move(*input), std::move(cases), std::move(*otherwise), ctx); + }, + [&](const auto&) { + assert(false); + return ParseResult(); + } + ); + } + +private: + template + static optional parseInputValue(const V& input, ParsingContext ctx, optional& inputType) { + using namespace mbgl::style::conversion; + optional result; + optional 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(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(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(); + } + + return result; + } + + template + static ParseResult create(type::Type outputType, + std::unique_ptrinput, + std::vector, + std::unique_ptr>> cases, + std::unique_ptr otherwise, + ParsingContext ctx) { + typename Match::Cases typedCases; + + std::size_t index = 2; + for (std::pair, + std::unique_ptr>& pair : cases) { + std::shared_ptr result = std::move(pair.second); + for (const InputType& label : pair.first) { + const auto& typedLabel = label.template get(); + 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>( + 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>, variant< IntervalStops>>; + + CameraFunction(std::unique_ptr 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; using Curve = expression::Curve; + CompositeFunction(std::unique_ptr expression_) + : expression(std::move(expression_)), + interpolator([&]() -> Interpolator { + optional zoomCurve = findZoomCurve(expression.get()); + assert(zoomCurve); + return (*zoomCurve)->getInterpolator(); + }()) + { + assert(!expression->isZoomConstant()); + assert(!expression->isFeatureConstant()); + } + CompositeFunction(std::string property_, Stops stops_, optional 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, IdentityStops>>; + SourceFunction(std::unique_ptr expression_) + : expression(std::move(expression_)) + { + assert(expression->isZoomConstant()); + assert(!expression->isFeatureConstant()); + } + SourceFunction(std::string property_, Stops stops_, optional 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 toArrayValue(const Container& value) { return result; } -template -optional fromArrayValue(const std::vector& value) { - Container result; - auto it = result.begin(); - for(const Value& item : value) { - optional convertedItem = Converter::fromExpressionValue(item); - if (!convertedItem) { - return optional(); - } - *it = *convertedItem; - it = std::next(it); - } - return result; -} - template struct Converter> { static Value toExpressionValue(const std::array& value) { @@ -167,7 +152,17 @@ struct Converter> { return value.match( [&] (const std::vector& v) -> optional> { if (v.size() != N) return optional>(); - return fromArrayValue>(v); + std::array result; + auto it = result.begin(); + for(const Value& item : v) { + optional convertedItem = Converter::fromExpressionValue(item); + if (!convertedItem) { + return optional>(); + } + *it = *convertedItem; + it = std::next(it); + } + return result; }, [&] (const auto&) { return optional>(); } ); @@ -187,7 +182,15 @@ struct Converter> { static optional> fromExpressionValue(const Value& value) { return value.match( [&] (const std::vector& v) -> optional> { - return fromArrayValue>(v); + std::vector result; + for(const Value& item : v) { + optional convertedItem = Converter::fromExpressionValue(item); + if (!convertedItem) { + return optional>(); + } + result.push_back(*convertedItem); + } + return result; }, [&] (const auto&) { return optional>(); } ); -- cgit v1.2.1