diff options
author | Anand Thakker <github@anandthakker.net> | 2017-07-28 16:32:25 -0400 |
---|---|---|
committer | Anand Thakker <github@anandthakker.net> | 2017-08-11 21:54:51 -0400 |
commit | 29a9f50287ba95463a829c096ef7b12fa5be80ee (patch) | |
tree | 8d5e34b9e3a533c55e1eb251a50c53d4aab512d5 | |
parent | 0925a779e866b64899cdd6c7cf2dc042e865eac7 (diff) | |
download | qtlocation-mapboxgl-29a9f50287ba95463a829c096ef7b12fa5be80ee.tar.gz |
Implement Camera and Source functions using expressions
-rw-r--r-- | cmake/core-files.cmake | 4 | ||||
-rw-r--r-- | include/mbgl/style/expression/array_assertion.hpp | 128 | ||||
-rw-r--r-- | include/mbgl/style/expression/case.hpp | 174 | ||||
-rw-r--r-- | include/mbgl/style/expression/curve.hpp | 2 | ||||
-rw-r--r-- | include/mbgl/style/expression/expression.hpp | 79 | ||||
-rw-r--r-- | include/mbgl/style/expression/literal.hpp | 79 | ||||
-rw-r--r-- | include/mbgl/style/expression/match.hpp | 16 | ||||
-rw-r--r-- | include/mbgl/style/expression/parse.hpp | 19 | ||||
-rw-r--r-- | include/mbgl/style/expression/value.hpp | 27 | ||||
-rw-r--r-- | include/mbgl/style/function/camera_function.hpp | 21 | ||||
-rw-r--r-- | include/mbgl/style/function/convert.hpp | 217 | ||||
-rw-r--r-- | include/mbgl/style/function/source_function.hpp | 26 | ||||
-rw-r--r-- | include/mbgl/util/enum.hpp | 1 | ||||
-rw-r--r-- | src/mbgl/style/expression/compound_expression.cpp | 17 | ||||
-rw-r--r-- | src/mbgl/style/expression/match.cpp | 33 | ||||
-rw-r--r-- | src/mbgl/style/expression/value.cpp | 222 | ||||
-rw-r--r-- | src/mbgl/style/function/expression.cpp | 4 |
17 files changed, 926 insertions, 143 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 24d7fb7013..a8ece9f6cd 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -390,18 +390,21 @@ set(MBGL_CORE_FILES src/mbgl/style/conversion/stringify.hpp # style/expression + include/mbgl/style/expression/array_assertion.hpp include/mbgl/style/expression/case.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/let.hpp + include/mbgl/style/expression/literal.hpp include/mbgl/style/expression/match.hpp include/mbgl/style/expression/parse.hpp include/mbgl/style/expression/parsing_context.hpp include/mbgl/style/expression/type.hpp include/mbgl/style/expression/value.hpp src/mbgl/style/expression/compound_expression.cpp + src/mbgl/style/expression/match.cpp src/mbgl/style/expression/type.cpp src/mbgl/style/expression/value.cpp @@ -412,6 +415,7 @@ set(MBGL_CORE_FILES include/mbgl/style/function/composite_exponential_stops.hpp include/mbgl/style/function/composite_function.hpp include/mbgl/style/function/composite_interval_stops.hpp + include/mbgl/style/function/convert.hpp include/mbgl/style/function/exponential_stops.hpp include/mbgl/style/function/identity_stops.hpp include/mbgl/style/function/interval_stops.hpp diff --git a/include/mbgl/style/expression/array_assertion.hpp b/include/mbgl/style/expression/array_assertion.hpp new file mode 100644 index 0000000000..710e16f00b --- /dev/null +++ b/include/mbgl/style/expression/array_assertion.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include <vector> +#include <memory> +#include <mbgl/util/optional.hpp> +#include <mbgl/util/variant.hpp> +#include <mbgl/style/expression/expression.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 { + + +class TypedArrayAssertion : public TypedExpression { +public: + TypedArrayAssertion(type::Array type, std::unique_ptr<TypedExpression> input_) : + TypedExpression(type), + input(std::move(input_)) + {} + + EvaluationResult evaluate(const EvaluationParameters& params) const override { + auto result = input->evaluate(params); + if (!result) { + return result.error(); + } + auto error = matchType(getType(), typeOf(*result)); + if (error) { + return EvaluationError { *error }; + } + return *result; + } +private: + std::unique_ptr<TypedExpression> input; +}; + +class UntypedArrayAssertion : public UntypedExpression { +public: + UntypedArrayAssertion(std::string key_, + type::Type itemType_, + optional<std::size_t> length_, + std::unique_ptr<UntypedExpression> input_ + ) : UntypedExpression(key_), + itemType(itemType_), + length(length_), + input(std::move(input_)) + {} + + TypecheckResult typecheck(std::vector<CompileError>& errors) const override { + auto checkedInput = input->typecheck(errors); + if (!checkedInput) { + return TypecheckResult(); + } + return TypecheckResult(std::make_unique<TypedArrayAssertion>( + type::Array(itemType, length), std::move(*checkedInput)) + ); + } + + template <class V> + static ParseResult parse(const V& value, const 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) { + return CompileError { + "Expected 1, 2, or 3 arguments, but found " + std::to_string(length - 1) + " instead.", + ctx.key() + }; + } + + auto input = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, {length - 1}, {"array"})); + if (input.template is<CompileError>()) { + return input; + } + + optional<type::Type> itemType; + optional<std::size_t> N; + if (length > 2) { + auto type = toString(arrayMember(value, 2)); + if (!type || itemTypes.find(*type) == itemTypes.end()) { + return CompileError { + "The item type argument of \"array\" must be one of string, number, boolean", + ctx.key(2) + }; + } + itemType = itemTypes.at(*type); + } else { + itemType = {type::Value}; + } + + if (length > 3) { + auto n = toNumber(arrayMember(value, 3)); + if (!n || *n != ceilf(*n)) { + return CompileError { + "The length argument to \"array\" must be a positive integer literal.", + ctx.key(3) + }; + } + N = optional<std::size_t>(*n); + } + + return std::make_unique<UntypedArrayAssertion>( + ctx.key(), + *itemType, + N, + std::move(input.template get<std::unique_ptr<UntypedExpression>>()) + ); + } + +private: + + type::Type itemType; + optional<std::size_t> length; + std::unique_ptr<UntypedExpression> input; +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/case.hpp b/include/mbgl/style/expression/case.hpp index e69de29bb2..005bb475c6 100644 --- a/include/mbgl/style/expression/case.hpp +++ b/include/mbgl/style/expression/case.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +class TypedCase : public TypedExpression { +public: + using Case = std::pair<std::unique_ptr<TypedExpression>, std::unique_ptr<TypedExpression>>; + + TypedCase(std::vector<Case> cases_, + std::unique_ptr<TypedExpression> otherwise_ + ) : TypedExpression(otherwise_->getType()), + cases(std::move(cases_)), + otherwise(std::move(otherwise_)) + {} + + bool isFeatureConstant() const override { + if (!otherwise->isFeatureConstant()) { return false; } + for (const auto& pair : cases) { + if (!pair.first->isFeatureConstant() || !pair.second->isFeatureConstant()) { + return false; + } + } + return true; + } + + bool isZoomConstant() const override { + if (!otherwise->isZoomConstant()) { return false; } + for (const auto& pair : cases) { + if (!pair.first->isFeatureConstant() || !pair.second->isZoomConstant()) { + return false; + } + } + return true; + } + + EvaluationResult evaluate(const EvaluationParameters& params) const override { + for (const auto& casePair : cases) { + const auto& condition = casePair.first->template evaluate<bool>(params); + if (!condition) { + return condition.error(); + } + if (*condition) { + return casePair.second->evaluate(params); + } + } + + return otherwise->evaluate(params); + } + +private: + std::unique_ptr<TypedExpression> input; + std::vector<Case> cases; + std::unique_ptr<TypedExpression> otherwise; +}; + +class UntypedCase : public UntypedExpression { +public: + using Cases = std::vector<std::pair<std::unique_ptr<UntypedExpression>, std::unique_ptr<UntypedExpression>>>; + + UntypedCase(std::string key, + Cases cases_, + std::unique_ptr<UntypedExpression> otherwise_) + : UntypedExpression(key), + cases(std::move(cases_)), + otherwise(std::move(otherwise_)) + {} + + template <class V> + static ParseResult parse(const V& value, const ParsingContext& ctx) { + using namespace mbgl::style::conversion; + + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 4) { + return CompileError { + "Expected at least 3 arguments, but found only " + std::to_string(length - 1) + ".", + ctx.key() + }; + } + + // Expect even-length array: ["case", 2 * (n pairs)..., otherwise] + if (length % 2 != 0) { + return CompileError { + "Missing final output value for \"case\" expression.", + ctx.key() + }; + } + + auto otherwise = parseExpression(arrayMember(value, length - 1), ParsingContext(ctx, {length - 1}, {"case"})); + if (otherwise.template is<CompileError>()) { + return otherwise; + } + + Cases cases; + for (size_t i = 1; i + 1 < length; i += 2) { + auto condition = parseExpression(arrayMember(value, i), ParsingContext(ctx, {i}, {"case"})); + if (condition.template is<CompileError>()) { + return condition.template get<CompileError>(); + } + + auto output = parseExpression(arrayMember(value, i + 1), ParsingContext(ctx, {i + 1}, {"case"})); + if (output.template is<CompileError>()) { + return output.template get<CompileError>(); + } + + cases.push_back(std::make_pair( + std::move(condition.template get<std::unique_ptr<UntypedExpression>>()), + std::move(output.template get<std::unique_ptr<UntypedExpression>>())) + ); + } + + return std::make_unique<UntypedCase>(ctx.key(), + std::move(cases), + std::move(otherwise.template get<std::unique_ptr<UntypedExpression>>())); + } + + TypecheckResult typecheck(std::vector<CompileError>& errors) const override { + auto checkedOtherwise = otherwise->typecheck(errors); + if (!checkedOtherwise) { + return TypecheckResult(); + } + + optional<type::Type> outputType; + std::vector<TypedCase::Case> checkedCases; + for (const auto& pair : cases) { + auto condition = pair.first->typecheck(errors); + if (!condition) continue; + auto error = matchType(type::Boolean, (*condition)->getType()); + if (error) { + errors.push_back({ *error, pair.first->getKey() }); + } + + auto output = pair.second->typecheck(errors); + if (!output) continue; + if (!outputType) { + outputType = (*output)->getType(); + } else { + error = matchType(*outputType, (*output)->getType()); + if (error) { + errors.push_back({ *error, pair.second->getKey() }); + } + } + + if (errors.size() == 0) { + checkedCases.emplace_back(std::move(*condition), std::move(*output)); + } + } + + auto error = matchType(*outputType, (*checkedOtherwise)->getType()); + if (error) { + errors.push_back({ *error, otherwise->getKey() }); + } + + if (errors.size() > 0) { + return TypecheckResult(); + } + + return TypecheckResult(std::make_unique<TypedCase>(std::move(checkedCases), std::move(*checkedOtherwise))); + }; + +private: + Cases cases; + std::unique_ptr<UntypedExpression> otherwise; +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/curve.hpp b/include/mbgl/style/expression/curve.hpp index 00ea15eb46..e0c9fae534 100644 --- a/include/mbgl/style/expression/curve.hpp +++ b/include/mbgl/style/expression/curve.hpp @@ -284,7 +284,7 @@ public: checkedStops.emplace(stop.first, std::move(*checkedOutput)); } - if (stops.size() > 0) return TypecheckResult(); + if (errors.size() > 0) return TypecheckResult(); return interpolation.match( [&](const StepInterpolation&) -> TypecheckResult { diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp index 8b2c5c7e98..94f62054a8 100644 --- a/include/mbgl/style/expression/expression.hpp +++ b/include/mbgl/style/expression/expression.hpp @@ -24,8 +24,8 @@ struct EvaluationError { }; struct EvaluationParameters { - float zoom; - const GeometryTileFeature& feature; + optional<float> zoom; + GeometryTileFeature const * feature = nullptr; }; template<typename T> @@ -90,22 +90,22 @@ public: virtual EvaluationResult evaluate(const EvaluationParameters& params) const = 0; /* - Evaluate this expression to a particular value type T. (See expression/value.hpp for - possible types T.) + Evaluate this expression to a particular type T. + (See expression/value.xpp for possible types T.) */ template <typename T> Result<T> evaluate(const EvaluationParameters& params) { const auto& result = evaluate(params); if (!result) { return result.error(); } - return result->match( - [&] (const T& v) -> variant<EvaluationError, T> { return v; }, - [&] (const auto& v) -> variant<EvaluationError, T> { - return EvaluationError { - "Expected value to be of type " + toString(valueTypeToExpressionType<T>()) + - ", but found " + toString(typeOf(v)) + " instead." - }; - } - ); + optional<T> converted = fromExpressionValue<T>(*result); + if (converted) { + return *converted; + } else { + return EvaluationError { + "Expected value to be of type " + toString(valueTypeToExpressionType<T>()) + + ", but found " + toString(typeOf(*result)) + " instead." + }; + } } EvaluationResult evaluate(float z, const Feature& feature) const; @@ -133,59 +133,6 @@ using ParseResult = variant<CompileError, std::unique_ptr<UntypedExpression>>; template <class V> ParseResult parseExpression(const V& value, const ParsingContext& context); -class TypedLiteral : public TypedExpression { -public: - TypedLiteral(Value value_) : TypedExpression(typeOf(value_)), value(value_) {} - EvaluationResult evaluate(const EvaluationParameters&) const override { - return value; - } -private: - Value value; -}; - -class UntypedLiteral : public UntypedExpression { -public: - UntypedLiteral(std::string key_, Value value_) : UntypedExpression(key_), value(value_) {} - - TypecheckResult typecheck(std::vector<CompileError>&) const override { - return {std::make_unique<TypedLiteral>(value)}; - } - - template <class V> - static ParseResult parse(const V& value, const ParsingContext& ctx) { - const Value& parsedValue = parseValue(value); - return std::make_unique<UntypedLiteral>(ctx.key(), parsedValue); - } - -private: - template <class V> - static Value parseValue(const V& value) { - using namespace mbgl::style::conversion; - if (isUndefined(value)) return Null; - if (isObject(value)) { - std::unordered_map<std::string, Value> result; - eachMember(value, [&] (const std::string& k, const V& v) -> optional<conversion::Error> { - result.emplace(k, parseValue(v)); - return {}; - }); - return result; - } - if (isArray(value)) { - std::vector<Value> result; - const auto length = arrayLength(value); - for(std::size_t i = 0; i < length; i++) { - result.emplace_back(parseValue(arrayMember(value, i))); - } - return result; - } - - optional<mbgl::Value> v = toValue(value); - assert(v); - return convertValue(*v); - } - - Value value; -}; } // namespace expression } // namespace style diff --git a/include/mbgl/style/expression/literal.hpp b/include/mbgl/style/expression/literal.hpp new file mode 100644 index 0000000000..a7390c279c --- /dev/null +++ b/include/mbgl/style/expression/literal.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include <vector> +#include <memory> +#include <mbgl/util/optional.hpp> +#include <mbgl/util/variant.hpp> +#include <mbgl/util/color.hpp> +#include <mbgl/style/expression/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 { + +class TypedLiteral : public TypedExpression { +public: + TypedLiteral(Value value_) : TypedExpression(typeOf(value_)), value(value_) {} + EvaluationResult evaluate(const EvaluationParameters&) const override { + return value; + } +private: + Value value; +}; + +class UntypedLiteral : public UntypedExpression { +public: + UntypedLiteral(std::string key_, Value value_) : UntypedExpression(key_), value(value_) {} + + TypecheckResult typecheck(std::vector<CompileError>&) const override { + return {std::make_unique<TypedLiteral>(value)}; + } + + Value getValue() const { return value; } + + template <class V> + static ParseResult parse(const V& value, const ParsingContext& ctx) { + const Value& parsedValue = parseValue(value); + return std::make_unique<UntypedLiteral>(ctx.key(), parsedValue); + } + +private: + template <class V> + static Value parseValue(const V& value) { + using namespace mbgl::style::conversion; + if (isUndefined(value)) return Null; + if (isObject(value)) { + std::unordered_map<std::string, Value> result; + eachMember(value, [&] (const std::string& k, const V& v) -> optional<conversion::Error> { + result.emplace(k, parseValue(v)); + return {}; + }); + return result; + } + if (isArray(value)) { + std::vector<Value> result; + const auto length = arrayLength(value); + for(std::size_t i = 0; i < length; i++) { + result.emplace_back(parseValue(arrayMember(value, i))); + } + return result; + } + + optional<mbgl::Value> v = toValue(value); + assert(v); + + return Value(toExpressionValue(*v)); + } + + Value value; +}; + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/match.hpp b/include/mbgl/style/expression/match.hpp index 8bd2e1d238..bd0096e044 100644 --- a/include/mbgl/style/expression/match.hpp +++ b/include/mbgl/style/expression/match.hpp @@ -55,7 +55,7 @@ public: } EvaluationResult evaluate(const EvaluationParameters& params) const override { - const auto& inputValue = input->evaluate<T>(params); + const auto& inputValue = evaluateInput(params); if (!inputValue) { return inputValue.error(); } @@ -66,23 +66,13 @@ public: } private: + Result<T> evaluateInput(const EvaluationParameters& params) const; + std::unique_ptr<TypedExpression> input; std::unordered_map<T, std::shared_ptr<TypedExpression>> cases; std::unique_ptr<TypedExpression> otherwise; }; -template <> EvaluationResult TypedMatch<int64_t>::evaluate(const EvaluationParameters& params) const { - const auto& inputValue = input->evaluate<float>(params); - if (!inputValue) { - return inputValue.error(); - } - int64_t rounded = ceilf(*inputValue); - if (*inputValue != rounded || cases.find(rounded) == cases.end()) { - return otherwise->evaluate(params); - } - return cases.at(rounded)->evaluate(params); -} - class UntypedMatch : public UntypedExpression { public: using Cases = std::vector<std::pair<MatchKey, std::unique_ptr<UntypedExpression>>>; diff --git a/include/mbgl/style/expression/parse.hpp b/include/mbgl/style/expression/parse.hpp index 97ad880ae5..89d4354d76 100644 --- a/include/mbgl/style/expression/parse.hpp +++ b/include/mbgl/style/expression/parse.hpp @@ -1,13 +1,16 @@ #pragma once #include <memory> -#include <mbgl/style/expression/parsing_context.hpp> -#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/case.hpp> +#include <mbgl/style/expression/coalesce.hpp> #include <mbgl/style/expression/compound_expression.hpp> -#include <mbgl/style/expression/match.hpp> #include <mbgl/style/expression/curve.hpp> -#include <mbgl/style/expression/coalesce.hpp> -#include <mbgl/style/conversion.hpp> +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/parsing_context.hpp> namespace mbgl { namespace style { @@ -24,7 +27,7 @@ std::string getJSType(const V& value) { if (isArray(value) || isObject(value)) { return "object"; } - optional<mbgl::Value> v = toValue(value); + optional<mbgl::Value> v = conversion::toValue(value); assert(v); return v->match( [&] (std::string) { return "string"; }, @@ -74,6 +77,10 @@ ParseResult parseExpression(const V& value, const ParsingContext& context) return UntypedCurve::parse(value, context); } else if (*op == "coalesce") { return UntypedCoalesce::parse(value, context); + } else if (*op == "case") { + return UntypedCase::parse(value, context); + } else if (*op == "array") { + return UntypedArrayAssertion::parse(value, context); } return UntypedCompoundExpression::parse(value, context); diff --git a/include/mbgl/style/expression/value.hpp b/include/mbgl/style/expression/value.hpp index ffc31856e0..8a9cafcec1 100644 --- a/include/mbgl/style/expression/value.hpp +++ b/include/mbgl/style/expression/value.hpp @@ -1,9 +1,13 @@ #pragma once +#include <array> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/position.hpp> +#include <mbgl/style/types.hpp> #include <mbgl/util/color.hpp> -#include <mbgl/util/variant.hpp> +#include <mbgl/util/enum.hpp> #include <mbgl/util/feature.hpp> -#include <mbgl/style/expression/type.hpp> +#include <mbgl/util/variant.hpp> namespace mbgl { @@ -11,6 +15,7 @@ namespace style { namespace expression { struct Value; + using ValueBase = variant< NullValue, bool, @@ -24,9 +29,24 @@ struct Value : ValueBase { using ValueBase::ValueBase; }; +Value toExpressionValue(const Value&); + +template <typename T, typename Enable = std::enable_if_t< !std::is_convertible<T, Value>::value >> +Value toExpressionValue(const T& value); + +template <typename T> +std::enable_if_t< !std::is_convertible<T, Value>::value, +optional<T>> fromExpressionValue(const Value& v); + +template <typename T> +std::enable_if_t< std::is_convertible<T, Value>::value, +optional<T>> fromExpressionValue(const Value& v) +{ + return v.template is<T>() ? v.template get<T>() : optional<T>(); +} + constexpr NullValue Null = NullValue(); -Value convertValue(const mbgl::Value&); type::Type typeOf(const Value& value); std::string stringify(const Value& value); @@ -38,6 +58,7 @@ std::string stringify(const Value& value); template <typename T> type::Type valueTypeToExpressionType(); + } // 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 7fde365b3d..3d33c814cb 100644 --- a/include/mbgl/style/function/camera_function.hpp +++ b/include/mbgl/style/function/camera_function.hpp @@ -1,5 +1,6 @@ #pragma once +#include <mbgl/style/function/convert.hpp> #include <mbgl/style/function/exponential_stops.hpp> #include <mbgl/style/function/interval_stops.hpp> #include <mbgl/util/interpolate.hpp> @@ -20,13 +21,16 @@ public: IntervalStops<T>>>; CameraFunction(Stops stops_) - : stops(std::move(stops_)) { - } + : stops(std::move(stops_)), + expression(stops.match([&] (const auto& s) { + return expression::Convert::toExpression(s); + })) + {} T evaluate(float zoom) const { - return stops.match([&] (const auto& s) { - return s.evaluate(zoom).value_or(T()); - }); + auto result = expression->evaluate<T>(expression::EvaluationParameters { zoom }); + if (!result) return T(); + return *result; } friend bool operator==(const CameraFunction& lhs, @@ -34,8 +38,13 @@ public: return lhs.stops == rhs.stops; } - Stops stops; bool useIntegerZoom = false; + + // retained for compatibility with pre-expression function API + Stops stops; + +private: + std::shared_ptr<expression::TypedExpression> expression; }; } // namespace style diff --git a/include/mbgl/style/function/convert.hpp b/include/mbgl/style/function/convert.hpp new file mode 100644 index 0000000000..ae439faa54 --- /dev/null +++ b/include/mbgl/style/function/convert.hpp @@ -0,0 +1,217 @@ +#pragma once + +#include <mbgl/util/enum.hpp> +#include <mbgl/style/types.hpp> +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/case.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/function/exponential_stops.hpp> +#include <mbgl/style/function/interval_stops.hpp> +#include <mbgl/style/function/categorical_stops.hpp> +#include <mbgl/style/function/identity_stops.hpp> + +#include <string> + + +namespace mbgl { +namespace style { +namespace expression { + +// Create expressions representing 'classic' (i.e. stop-based) style functions + +struct Convert { + // TODO: Organize. Where should each of these actually go? + + template <typename T> + static std::unique_ptr<UntypedLiteral> makeLiteral(const T& value) { + return std::make_unique<UntypedLiteral>("", Value(toExpressionValue(value))); + } + + static std::unique_ptr<UntypedExpression> makeGet(const std::string& type, const std::string& property) { + UntypedCompoundExpression::Args getArgs; + getArgs.push_back(makeLiteral(property)); + auto get = std::make_unique<UntypedCompoundExpression>("", "get", std::move(getArgs)); + UntypedCompoundExpression::Args assertionArgs; + assertionArgs.push_back(std::move(get)); + return std::make_unique<UntypedCompoundExpression>("", type, std::move(assertionArgs)); + } + + static std::unique_ptr<UntypedExpression> makeZoom() { + return std::make_unique<UntypedCompoundExpression>("", "zoom", UntypedCompoundExpression::Args()); + } + + template <typename T> + static std::unique_ptr<UntypedExpression> makeCoalesceToDefault(std::unique_ptr<UntypedExpression> main, optional<T> defaultValue) { + if (!defaultValue) { + return main; + } + + UntypedCoalesce::Args args; + args.push_back(std::move(main)); + args.push_back(makeLiteral(*defaultValue)); + return(std::make_unique<UntypedCoalesce>("", std::move(args))); + } + + template <typename T> + static std::unique_ptr<TypedExpression> makeCurve(std::unique_ptr<UntypedExpression> input, + UntypedCurve::Interpolation interpolation, + const std::map<float, T>& stops, + optional<T> defaultValue) + { + UntypedCurve::Stops convertedStops; + for(const auto& stop : stops) { + convertedStops.push_back(std::make_pair( + stop.first, + makeLiteral(stop.second) + )); + } + + std::unique_ptr<UntypedExpression> curve = std::make_unique<UntypedCurve>("", std::move(input), std::move(convertedStops), interpolation); + + std::vector<CompileError> errors; + auto checked = makeCoalesceToDefault(std::move(curve), defaultValue)->typecheck(errors); + assert(checked); + return std::move(*checked); + } + + template <typename Key, typename T> + static std::unique_ptr<UntypedExpression> makeMatch(std::unique_ptr<UntypedExpression> input, + const CategoricalStops<T>& stops) { + // match expression + UntypedMatch::Cases cases; + optional<type::Type> inputType; + for(const auto& stop : stops.stops) { + if (!inputType) { + inputType = stop.first.template is<std::string>() ? + type::Type(type::String) : type::Type(type::Number); + } + assert(stop.first.template is<Key>()); + auto key = stop.first.template get<Key>(); + cases.push_back(std::make_pair( + std::move(key), + makeLiteral(stop.second) + )); + } + + return std::make_unique<UntypedMatch>("", + std::move(input), + std::move(cases), + makeLiteral(Null), + *inputType); + } + + template <typename T> + static std::unique_ptr<UntypedExpression> makeCase(std::unique_ptr<UntypedExpression> input, + const CategoricalStops<T>& stops) { + // case expression + UntypedCase::Cases cases; + auto true_case = stops.stops.find(true) == stops.stops.end() ? + makeLiteral(Null) : + makeLiteral(stops.stops.at(true)); + auto false_case = stops.stops.find(false) == stops.stops.end() ? + makeLiteral(Null) : + makeLiteral(stops.stops.at(false)); + cases.push_back(std::make_pair(std::move(input), std::move(true_case))); + return std::make_unique<UntypedCase>("", std::move(cases), std::move(false_case)); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const ExponentialStops<T>& stops) + { + return makeCurve(makeZoom(), ExponentialInterpolation{stops.base}, stops.stops, {}); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const IntervalStops<T>& stops) + { + return makeCurve(makeZoom(), StepInterpolation(), stops.stops, {}); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const std::string& property, + const ExponentialStops<T>& stops, + optional<T> defaultValue) + { + return makeCurve(makeGet("number", property), ExponentialInterpolation{stops.base}, stops.stops, defaultValue); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const std::string& property, + const IntervalStops<T>& stops, + optional<T> defaultValue) + { + return makeCurve(makeGet("number", property), StepInterpolation(), stops.stops, defaultValue); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const std::string& property, + const CategoricalStops<T>& stops, + optional<T> defaultValue) + { + assert(stops.stops.size() > 0); + + const auto& firstKey = stops.stops.begin()->first; + auto expr = firstKey.match( + [&](bool) { + auto input = makeGet("boolean", property); + return makeCase(std::move(input), stops); + }, + [&](const std::string&) { + auto input = makeGet("string", property); + return makeMatch<std::string>(std::move(input), stops); + }, + [&](int64_t) { + auto input = makeGet("number", property); + return makeMatch<int64_t>(std::move(input), stops); + } + + ); + + std::vector<CompileError> errors; + auto checked = makeCoalesceToDefault(std::move(expr), defaultValue)->typecheck(errors); + assert(checked); + return std::move(*checked); + } + + template <typename T> + static std::unique_ptr<TypedExpression> toExpression(const std::string& property, + const IdentityStops<T>&, + optional<T> defaultValue) + { + auto input = valueTypeToExpressionType<T>().match( + [&] (const type::StringType&) { + return makeGet("string", property); + }, + [&] (const type::NumberType&) { + return makeGet("number", property); + }, + [&] (const type::BooleanType&) { + return makeGet("boolean", property); + }, + [&] (const type::Array& arr) { + UntypedCompoundExpression::Args getArgs; + getArgs.push_back(makeLiteral(property)); + auto get = std::make_unique<UntypedCompoundExpression>("", "get", std::move(getArgs)); + return std::make_unique<UntypedArrayAssertion>("", arr.itemType, arr.N, std::move(get)); + }, + [&] (const auto&) -> std::unique_ptr<UntypedExpression> { + return makeLiteral(Null); + } + ); + + std::vector<CompileError> errors; + auto checked = makeCoalesceToDefault(std::move(input), defaultValue)->typecheck(errors); + assert(checked); + return std::move(*checked); + } +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp index 9c2ad101ec..a25fda0a91 100644 --- a/include/mbgl/style/function/source_function.hpp +++ b/include/mbgl/style/function/source_function.hpp @@ -1,5 +1,7 @@ #pragma once +#include <mbgl/style/function/convert.hpp> + #include <mbgl/style/function/exponential_stops.hpp> #include <mbgl/style/function/interval_stops.hpp> #include <mbgl/style/function/categorical_stops.hpp> @@ -30,18 +32,19 @@ public: SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {}) : property(std::move(property_)), stops(std::move(stops_)), - defaultValue(std::move(defaultValue_)) { - } + defaultValue(std::move(defaultValue_)), + expression(stops.match([&] (const auto& s) { + return expression::Convert::toExpression(property, s, defaultValue); + })) + {} template <class Feature> T evaluate(const Feature& feature, T finalDefaultValue) const { - optional<Value> v = feature.getValue(property); - if (!v) { - return defaultValue.value_or(finalDefaultValue); + auto result = expression->evaluate<T>(expression::EvaluationParameters { optional<float>(), &feature }); + if (!result) { + return finalDefaultValue; } - return stops.match([&] (const auto& s) -> T { - return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue)); - }); + return *result; } friend bool operator==(const SourceFunction& lhs, @@ -50,10 +53,15 @@ public: == std::tie(rhs.property, rhs.stops, rhs.defaultValue); } + bool useIntegerZoom = false; + + // retained for compatibility with pre-expression function API std::string property; Stops stops; optional<T> defaultValue; - bool useIntegerZoom = false; + +private: + std::shared_ptr<expression::TypedExpression> expression; }; } // namespace style diff --git a/include/mbgl/util/enum.hpp b/include/mbgl/util/enum.hpp index 369ca86bfd..608befd3c4 100644 --- a/include/mbgl/util/enum.hpp +++ b/include/mbgl/util/enum.hpp @@ -11,6 +11,7 @@ namespace mbgl { template <typename T> class Enum { public: + using Type = T; static const char * toString(T); static optional<T> toEnum(const std::string&); }; diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp index 6767842b9a..9134b769f9 100644 --- a/src/mbgl/style/expression/compound_expression.cpp +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -71,13 +71,16 @@ static Definition defineGet() { definition.push_back( std::make_unique<Signature<Result<Value> (const EvaluationParameters&, const std::string&)>>( [](const EvaluationParameters& params, const std::string& key) -> Result<Value> { - const auto propertyValue = params.feature.getValue(key); + optional<mbgl::Value> propertyValue; + if (params.feature) { + propertyValue = params.feature->getValue(key); + } if (!propertyValue) { return EvaluationError { "Property '" + key + "' not found in feature.properties" }; } - return convertValue(*propertyValue); + return Value(toExpressionValue(*propertyValue)); }, false ) @@ -117,7 +120,6 @@ std::unordered_map<std::string, CompoundExpression::Definition> CompoundExpressi define("number", assertion<float>), define("string", assertion<std::string>), define("boolean", assertion<bool>), - define("array", assertion<std::vector<Value>>), // TODO: [array, type, value], [array, type, length, value] define("to_string", [](const Value& v) -> Result<std::string> { return stringify(v); }), define("to_number", [](const Value& v) -> Result<float> { @@ -160,6 +162,15 @@ std::unordered_map<std::string, CompoundExpression::Definition> CompoundExpressi }; }), + define("zoom", [](const EvaluationParameters& params) -> Result<float> { + if (!params.zoom) { + return EvaluationError { + "The 'zoom' expression is unavailable in the current evaluation context." + }; + } + return *(params.zoom); + }), + std::pair<std::string, Definition>("get", defineGet()), define("+", [](const Varargs<float>& args) -> Result<float> { diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp new file mode 100644 index 0000000000..9286e96d50 --- /dev/null +++ b/src/mbgl/style/expression/match.cpp @@ -0,0 +1,33 @@ +#include <mbgl/style/expression/match.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template<> Result<std::string> TypedMatch<std::string>::evaluateInput(const EvaluationParameters& params) const { + const auto& inputValue = input->evaluate<std::string>(params); + if (!inputValue) { + return inputValue.error(); + } + return *inputValue; +} + +template<> Result<int64_t> TypedMatch<int64_t>::evaluateInput(const EvaluationParameters& params) const { + const auto& inputValue = input->evaluate<float>(params); + if (!inputValue) { + return inputValue.error(); + } + int64_t rounded = ceilf(*inputValue); + if (*inputValue == rounded) { + return rounded; + } else { + return EvaluationError { + "Input to \"match\" must be an integer value; found " + + std::to_string(*inputValue) + " instead ." + }; + } +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp index 4e670ec5dd..324fc06f17 100644 --- a/src/mbgl/style/expression/value.cpp +++ b/src/mbgl/style/expression/value.cpp @@ -4,38 +4,6 @@ namespace mbgl { namespace style { namespace expression { -struct ConvertValue { -// null_value_t, bool, uint64_t, int64_t, double, std::string, -// mapbox::util::recursive_wrapper<std::vector<value>>, -// mapbox::util::recursive_wrapper<std::unordered_map<std::string, value>> - Value operator()(const std::vector<mbgl::Value>& v) { - std::vector<Value> result; - for(const auto& item : v) { - result.emplace_back(convertValue(item)); - } - return result; - } - - Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) { - std::unordered_map<std::string, Value> result; - for(const auto& entry : v) { - result.emplace(entry.first, convertValue(entry.second)); - } - return result; - } - - Value operator()(const std::string& s) { return s; } - Value operator()(const bool& b) { return b; } - Value operator()(const mbgl::NullValue) { return Null; } - - template <typename T> - Value operator()(const T& v) { return *numericValue<float>(v); } -}; - -Value convertValue(const mbgl::Value& value) { - return mbgl::Value::visit(value, ConvertValue()); -} - type::Type typeOf(const Value& value) { return value.match( [&](bool) -> type::Type { return type::Boolean; }, @@ -92,6 +60,165 @@ std::string stringify(const Value& value) { ); } + +template <class T, class Enable = void> +struct Converter { + static Value toExpressionValue(const T& value) { + return Value(value); + } + static optional<T> fromExpressionValue(const Value& value) { + return value.template is<T>() ? value.template get<T>() : optional<T>(); + } +}; + +template<> +struct Converter<mbgl::Value> { + static Value toExpressionValue(const mbgl::Value& value) { + return mbgl::Value::visit(value, Converter<mbgl::Value>()); + } + + + // Double duty as a variant visitor for mbgl::Value: + Value operator()(const std::vector<mbgl::Value>& v) { + std::vector<Value> result; + for(const auto& item : v) { + result.emplace_back(toExpressionValue(item)); + } + return result; + } + + Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) { + std::unordered_map<std::string, Value> result; + for(const auto& entry : v) { + result.emplace(entry.first, toExpressionValue(entry.second)); + } + return result; + } + + Value operator()(const std::string& s) { return s; } + Value operator()(const bool& b) { return b; } + Value operator()(const mbgl::NullValue) { return Null; } + + template <typename T> + Value operator()(const T& v) { return *numericValue<float>(v); } +}; + +template <typename T, std::size_t N> +struct Converter<std::array<T, N>> { + static Value toExpressionValue(const std::array<T, N>& value) { + std::vector<Value> result; + std::copy_n(value.begin(), N, result.begin()); + return result; + } + + static optional<std::array<T, N>> fromExpressionValue(const Value& v) { + return v.match( + [&] (const std::vector<Value>& v) -> optional<std::array<T, N>> { + if (v.size() != N) return optional<std::array<T, N>>(); + std::array<T, N> result; + auto it = result.begin(); + for(const auto& item : v) { + if (!item.template is<T>()) { + return optional<std::array<T, N>>(); + } + *it = item.template get<T>(); + it = std::next(it); + } + return result; + }, + [&] (const auto&) { return optional<std::array<T, N>>(); } + ); + } + + static type::Type expressionType() { + return type::Array(valueTypeToExpressionType<T>(), N); + } +}; + +template <typename T> +struct Converter<std::vector<T>> { + static Value toExpressionValue(const std::vector<T>& value) { + std::vector<Value> v; + std::copy(value.begin(), value.end(), v.begin()); + return v; + } + + static optional<std::vector<T>> fromExpressionValue(const Value& v) { + return v.match( + [&] (const std::vector<Value>& v) -> optional<std::vector<T>> { + std::vector<T> result; + for(const auto& item : v) { + if (!item.template is<T>()) { + return optional<std::vector<T>>(); + } + result.push_back(item.template get<T>()); + } + return result; + }, + [&] (const auto&) { return optional<std::vector<T>>(); } + ); + } + + static type::Type expressionType() { + return type::Array(valueTypeToExpressionType<T>()); + } +}; + +template <> +struct Converter<Position> { + static Value toExpressionValue(const mbgl::style::Position& value) { + return Converter<std::array<float, 3>>::toExpressionValue(value.getSpherical()); + } + + static optional<Position> fromExpressionValue(const Value& v) { + auto pos = Converter<std::array<float, 3>>::fromExpressionValue(v); + return pos ? optional<Position>(Position(*pos)) : optional<Position>(); + } + + static type::Type expressionType() { + return type::Array(type::Number, 3); + } +}; + +template <typename T> +struct Converter<T, std::enable_if_t< std::is_enum<T>::value >> { + static Value toExpressionValue(const T& value) { + return std::string(Enum<T>::toString(value)); + } + + static optional<T> fromExpressionValue(const Value& v) { + return v.match( + [&] (const std::string& v) { return Enum<T>::toEnum(v); }, + [&] (const auto&) { return optional<T>(); } + ); + } + + static type::Type expressionType() { + return type::String; + } +}; + +Value toExpressionValue(const Value& v) { + return v; +} + +template <typename T, typename Enable> +Value toExpressionValue(const T& value) { + return Converter<T>::toExpressionValue(value); +} + +template <typename T> +std::enable_if_t< !std::is_convertible<T, Value>::value, +optional<T>> fromExpressionValue(const Value& v) +{ + return Converter<T>::fromExpressionValue(v); +} + +template <typename T> +type::Type valueTypeToExpressionType() { + return Converter<T>::expressionType(); +} + template <> type::Type valueTypeToExpressionType<Value>() { return type::Value; } template <> type::Type valueTypeToExpressionType<NullValue>() { return type::Null; } template <> type::Type valueTypeToExpressionType<bool>() { return type::Boolean; } @@ -99,10 +226,37 @@ template <> type::Type valueTypeToExpressionType<float>() { return type::Number; template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; } template <> type::Type valueTypeToExpressionType<mbgl::Color>() { return type::Color; } template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; } -template <> type::Type valueTypeToExpressionType<std::array<float, 2>>() { return type::Array(type::Number, 2); } -template <> type::Type valueTypeToExpressionType<std::array<float, 4>>() { return type::Array(type::Number, 4); } template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); } + +template Value toExpressionValue(const mbgl::Value&); + +// instantiate templates fromExpressionValue<T>, toExpressionValue<T>, and valueTypeToExpressionType<T> +template <typename T> +struct instantiate { + void noop(const T& t) { + fromExpressionValue<T>(toExpressionValue(t)); + valueTypeToExpressionType<T>(); + } +}; + +template struct instantiate<std::array<float, 2>>; +template struct instantiate<std::array<float, 4>>; +template struct instantiate<std::vector<float>>; +template struct instantiate<std::vector<std::string>>; +template struct instantiate<AlignmentType>; +template struct instantiate<CirclePitchScaleType>; +template struct instantiate<IconTextFitType>; +template struct instantiate<LineCapType>; +template struct instantiate<LineJoinType>; +template struct instantiate<SymbolPlacementType>; +template struct instantiate<TextAnchorType>; +template struct instantiate<TextJustifyType>; +template struct instantiate<TextTransformType>; +template struct instantiate<TranslateAnchorType>; +template struct instantiate<LightAnchorType>; +template struct instantiate<Position>; + } // namespace expression } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp index 76d4b3cd1f..4fef6e45d9 100644 --- a/src/mbgl/style/function/expression.cpp +++ b/src/mbgl/style/function/expression.cpp @@ -28,8 +28,8 @@ public: EvaluationResult TypedExpression::evaluate(float z, const Feature& feature) const { - std::unique_ptr<const GeometryTileFeature> f = std::make_unique<const GeoJSONFeature>(feature); - return this->evaluate(EvaluationParameters {z, *f}); + GeoJSONFeature f(feature); + return this->evaluate(EvaluationParameters {z, &f}); } } // namespace expression |