From af89318b1d3bef15e92e591887c9d65b10be54ce Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 13 Jul 2018 17:02:30 -0700 Subject: [PATCH] [core] Convert token strings to expressions --- .../function/composite_function.benchmark.cpp | 4 +- .../function/source_function.benchmark.cpp | 4 +- .../conversion/color_ramp_property_value.hpp | 2 +- .../conversion/data_driven_property_value.hpp | 17 +++- include/mbgl/style/conversion/function.hpp | 9 ++- .../mbgl/style/conversion/property_value.hpp | 8 +- .../mbgl/style/data_driven_property_value.hpp | 8 -- include/mbgl/style/expression/dsl.hpp | 12 ++- include/mbgl/style/property_expression.hpp | 14 +--- platform/darwin/src/MGLStyleValue_Private.h | 2 +- platform/glfw/glfw_view.cpp | 6 +- src/mbgl/annotation/annotation_manager.cpp | 4 +- src/mbgl/layout/symbol_layout.cpp | 30 +------ src/mbgl/style/conversion/function.cpp | 80 ++++++++++++++++--- src/mbgl/style/conversion/light.cpp | 8 +- .../conversion/make_property_setters.hpp | 4 +- .../conversion/make_property_setters.hpp.ejs | 2 +- src/mbgl/style/conversion/property_setter.hpp | 5 +- src/mbgl/style/expression/dsl.cpp | 28 +++---- test/style/conversion/function.test.cpp | 20 ++++- 20 files changed, 166 insertions(+), 101 deletions(-) diff --git a/benchmark/function/composite_function.benchmark.cpp b/benchmark/function/composite_function.benchmark.cpp index 77258669a..d495a518c 100644 --- a/benchmark/function/composite_function.benchmark.cpp +++ b/benchmark/function/composite_function.benchmark.cpp @@ -33,7 +33,7 @@ static void Parse_CompositeFunction(benchmark::State& state) { state.PauseTiming(); auto doc = createFunctionJSON(stopCount); state.ResumeTiming(); - optional> result = conversion::convertJSON>(doc, error); + optional> result = conversion::convertJSON>(doc, error, false); if (!result) { state.SkipWithError(error.message.c_str()); } @@ -45,7 +45,7 @@ static void Evaluate_CompositeFunction(benchmark::State& state) { size_t stopCount = state.range(0); auto doc = createFunctionJSON(stopCount); conversion::Error error; - optional> function = conversion::convertJSON>(doc, error); + optional> function = conversion::convertJSON>(doc, error, false); if (!function) { state.SkipWithError(error.message.c_str()); } diff --git a/benchmark/function/source_function.benchmark.cpp b/benchmark/function/source_function.benchmark.cpp index 402c9b89d..a5d2f9548 100644 --- a/benchmark/function/source_function.benchmark.cpp +++ b/benchmark/function/source_function.benchmark.cpp @@ -28,7 +28,7 @@ static void Parse_SourceFunction(benchmark::State& state) { state.PauseTiming(); auto doc = createFunctionJSON(stopCount); state.ResumeTiming(); - optional> result = conversion::convertJSON>(doc, error); + optional> result = conversion::convertJSON>(doc, error, false); if (!result) { state.SkipWithError(error.message.c_str()); } @@ -40,7 +40,7 @@ static void Evaluate_SourceFunction(benchmark::State& state) { size_t stopCount = state.range(0); auto doc = createFunctionJSON(stopCount); conversion::Error error; - optional> function = conversion::convertJSON>(doc, error); + optional> function = conversion::convertJSON>(doc, error, false); if (!function) { state.SkipWithError(error.message.c_str()); } diff --git a/include/mbgl/style/conversion/color_ramp_property_value.hpp b/include/mbgl/style/conversion/color_ramp_property_value.hpp index 9394daa05..290ee6a56 100644 --- a/include/mbgl/style/conversion/color_ramp_property_value.hpp +++ b/include/mbgl/style/conversion/color_ramp_property_value.hpp @@ -14,7 +14,7 @@ namespace conversion { template <> struct Converter { - optional operator()(const Convertible& value, Error& error) const { + optional operator()(const Convertible& value, Error& error, bool /* convertTokens */ = false) const { using namespace mbgl::style::expression; if (isUndefined(value)) { return ColorRampPropertyValue(); diff --git a/include/mbgl/style/conversion/data_driven_property_value.hpp b/include/mbgl/style/conversion/data_driven_property_value.hpp index 363134bd3..f1bd1bdbb 100644 --- a/include/mbgl/style/conversion/data_driven_property_value.hpp +++ b/include/mbgl/style/conversion/data_driven_property_value.hpp @@ -16,7 +16,7 @@ namespace conversion { template struct Converter> { - optional> operator()(const Convertible& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error, bool convertTokens) const { using namespace mbgl::style::expression; if (isUndefined(value)) { @@ -34,13 +34,13 @@ struct Converter> { } expression = PropertyExpression(std::move(*parsed)); } else if (isObject(value)) { - expression = convertFunctionToExpression(value, error); + expression = convertFunctionToExpression(value, error, convertTokens); } else { optional constant = convert(value, error); if (!constant) { return {}; } - return DataDrivenPropertyValue(*constant); + return convertTokens ? maybeConvertTokens(*constant) : DataDrivenPropertyValue(*constant); } if (!expression) { @@ -56,6 +56,17 @@ struct Converter> { return DataDrivenPropertyValue(*constant); } } + + template + DataDrivenPropertyValue maybeConvertTokens(const S& t) const { + return DataDrivenPropertyValue(t); + }; + + DataDrivenPropertyValue maybeConvertTokens(const std::string& t) const { + return hasTokens(t) + ? DataDrivenPropertyValue(PropertyExpression(convertTokenStringToExpression(t))) + : DataDrivenPropertyValue(t); + } }; } // namespace conversion diff --git a/include/mbgl/style/conversion/function.hpp b/include/mbgl/style/conversion/function.hpp index 6bc75d714..8799e9faa 100644 --- a/include/mbgl/style/conversion/function.hpp +++ b/include/mbgl/style/conversion/function.hpp @@ -10,11 +10,14 @@ namespace mbgl { namespace style { namespace conversion { -optional> convertFunctionToExpression(expression::type::Type, const Convertible&, Error&); +bool hasTokens(const std::string&); +std::unique_ptr convertTokenStringToExpression(const std::string&); + +optional> convertFunctionToExpression(expression::type::Type, const Convertible&, Error&, bool convertTokens); template -optional> convertFunctionToExpression(const Convertible& value, Error& error) { - auto expression = convertFunctionToExpression(expression::valueTypeToExpressionType(), value, error); +optional> convertFunctionToExpression(const Convertible& value, Error& error, bool convertTokens) { + auto expression = convertFunctionToExpression(expression::valueTypeToExpressionType(), value, error, convertTokens); if (!expression) { return {}; } diff --git a/include/mbgl/style/conversion/property_value.hpp b/include/mbgl/style/conversion/property_value.hpp index 2256cfffb..db23074c5 100644 --- a/include/mbgl/style/conversion/property_value.hpp +++ b/include/mbgl/style/conversion/property_value.hpp @@ -16,9 +16,13 @@ namespace conversion { template struct Converter> { - optional> operator()(const Convertible& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error, bool convertTokens = false) const { using namespace mbgl::style::expression; + // Only icon-image and text-field support tokens, and they are both data-driven. + assert(!convertTokens); + (void)convertTokens; + if (isUndefined(value)) { return PropertyValue(); } @@ -34,7 +38,7 @@ struct Converter> { } expression = PropertyExpression(std::move(*parsed)); } else if (isObject(value)) { - expression = convertFunctionToExpression(value, error); + expression = convertFunctionToExpression(value, error, false); } else { optional constant = convert(value, error); if (!constant) { diff --git a/include/mbgl/style/data_driven_property_value.hpp b/include/mbgl/style/data_driven_property_value.hpp index a95eaa4ea..baea861f6 100644 --- a/include/mbgl/style/data_driven_property_value.hpp +++ b/include/mbgl/style/data_driven_property_value.hpp @@ -52,14 +52,6 @@ public: ); } - bool isExpression() const { - return value.match( - [] (const Undefined&) { return false; }, - [] (const T&) { return false; }, - [] (const PropertyExpression& fn) { return fn.isExpression; } - ); - } - const T & asConstant() const { return value.template get< T >(); } const PropertyExpression& asExpression() const { return value.template get>(); } diff --git a/include/mbgl/style/expression/dsl.hpp b/include/mbgl/style/expression/dsl.hpp index 22278b097..e9de20de1 100644 --- a/include/mbgl/style/expression/dsl.hpp +++ b/include/mbgl/style/expression/dsl.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,13 @@ namespace dsl { // This convenience API does little to no expression validation or type-checking, and is intended for // use only by test and other non-production code. +template +std::vector> vec(Args... args) { + std::vector> result; + util::ignore({ (result.push_back(std::move(args)), 0)... }); + return result; +} + std::unique_ptr error(std::string); std::unique_ptr literal(const char* value); @@ -27,8 +35,8 @@ std::unique_ptr number(std::unique_ptr); std::unique_ptr string(std::unique_ptr); std::unique_ptr boolean(std::unique_ptr); -std::unique_ptr toColor(const char* value); std::unique_ptr toColor(std::unique_ptr); +std::unique_ptr toString(std::unique_ptr); std::unique_ptr get(const char* value); std::unique_ptr get(std::unique_ptr); @@ -68,6 +76,8 @@ std::unique_ptr interpolate(Interpolator interpolator, double input2, std::unique_ptr output2, double input3, std::unique_ptr output3); +std::unique_ptr concat(std::vector> inputs); + } // namespace dsl } // namespace expression } // namespace style diff --git a/include/mbgl/style/property_expression.hpp b/include/mbgl/style/property_expression.hpp index 16f154cdf..b198de02b 100644 --- a/include/mbgl/style/property_expression.hpp +++ b/include/mbgl/style/property_expression.hpp @@ -14,16 +14,9 @@ namespace style { template class PropertyExpression { public: - PropertyExpression(std::unique_ptr expression_) - : isExpression(true), - expression(std::move(expression_)), - zoomCurve(expression::findZoomCurveChecked(expression.get())) { - } - - // To be used only for conversions from legacy functions. - PropertyExpression(std::unique_ptr expression_, optional defaultValue_) - : isExpression(false), - expression(std::move(expression_)), + // Second parameter to be used only for conversions from legacy functions. + PropertyExpression(std::unique_ptr expression_, optional defaultValue_ = {}) + : expression(std::move(expression_)), defaultValue(std::move(defaultValue_)), zoomCurve(expression::findZoomCurveChecked(expression.get())) { } @@ -108,7 +101,6 @@ public: const expression::Expression& getExpression() const { return *expression; } bool useIntegerZoom = false; - bool isExpression; friend bool operator==(const PropertyExpression& lhs, const PropertyExpression& rhs) { diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index c1958fc03..d6e2edf3e 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -86,7 +86,7 @@ public: mbgl::style::conversion::Error valueError; auto value = mbgl::style::conversion::convert( - mbgl::style::conversion::makeConvertible(jsonExpression), valueError); + mbgl::style::conversion::makeConvertible(jsonExpression), valueError, false); if (!value) { [NSException raise:NSInvalidArgumentException format:@"Invalid property value: %@", @(valueError.message.c_str())]; diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index d7cabf624..f1cbdbbae 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -639,9 +639,9 @@ void GLFWView::toggle3DExtrusions(bool visible) { extrusionLayer->setFilter(Filter(eq(get("extrude"), literal("true")))); extrusionLayer->setFillExtrusionColor(PropertyExpression( interpolate(linear(), number(get("height")), - 0.f, toColor("#160e23"), - 50.f, toColor("#00615f"), - 100.f, toColor("#55e9ff")))); + 0.f, toColor(literal("#160e23")), + 50.f, toColor(literal("#00615f")), + 100.f, toColor(literal("#55e9ff"))))); extrusionLayer->setFillExtrusionOpacity(0.6f); extrusionLayer->setFillExtrusionHeight(PropertyExpression(get("height"))); extrusionLayer->setFillExtrusionBase(PropertyExpression(get("min_height"))); diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index b94b0a1bc..41eedf17d 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -160,8 +161,9 @@ void AnnotationManager::updateStyle() { std::unique_ptr layer = std::make_unique(PointLayerID, SourceID); + using namespace expression::dsl; layer->setSourceLayer(PointLayerID); - layer->setIconImage({SourceID + ".{sprite}"}); + layer->setIconImage(PropertyExpression(concat(vec(literal(SourceID + "."), toString(get("sprite")))))); layer->setIconAllowOverlap(true); layer->setIconIgnorePlacement(true); diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index e71cfb93b..d4df58b67 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -106,31 +105,10 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, ft.index = i; - auto getValue = [&ft](const std::string& key) -> std::string { - auto value = ft.getValue(key); - if (!value) - return std::string(); - if (value->is()) - return value->get(); - if (value->is()) - return value->get() ? "true" : "false"; - if (value->is()) - return util::toString(value->get()); - if (value->is()) - return util::toString(value->get()); - if (value->is()) - return util::toString(value->get()); - return "null"; - }; - if (hasText) { std::string u8string = layout.evaluate(zoom, ft); - if (layout.get().isConstant() && !leader.layout.get().isExpression()) { - u8string = util::replaceTokens(u8string, getValue); - } - - auto textTransform = layout.evaluate(zoom, ft); + auto textTransform = layout.evaluate(zoom, ft); if (textTransform == TextTransformType::Uppercase) { u8string = platform::uppercase(u8string); } else if (textTransform == TextTransformType::Lowercase) { @@ -157,11 +135,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, } if (hasIcon) { - std::string icon = layout.evaluate(zoom, ft); - if (layout.get().isConstant() && !leader.layout.get().isExpression()) { - icon = util::replaceTokens(icon, getValue); - } - ft.icon = icon; + ft.icon = layout.evaluate(zoom, ft); imageDependencies.insert(*ft.icon); } diff --git a/src/mbgl/style/conversion/function.cpp b/src/mbgl/style/conversion/function.cpp index 39e061d75..61b45fcbe 100644 --- a/src/mbgl/style/conversion/function.cpp +++ b/src/mbgl/style/conversion/function.cpp @@ -16,6 +16,60 @@ namespace conversion { using namespace expression; using namespace expression::dsl; +const static std::string tokenReservedChars = "{}"; + +bool hasTokens(const std::string& source) { + auto pos = source.begin(); + const auto end = source.end(); + + while (pos != end) { + auto brace = std::find(pos, end, '{'); + if (brace == end) + return false; + for (brace++; brace != end && tokenReservedChars.find(*brace) == std::string::npos; brace++); + if (brace != end && *brace == '}') { + return true; + } + pos = brace; + } + + return false; +} + +std::unique_ptr convertTokenStringToExpression(const std::string& source) { + std::vector> inputs; + + auto pos = source.begin(); + const auto end = source.end(); + + while (pos != end) { + auto brace = std::find(pos, end, '{'); + if (pos != brace) { + inputs.push_back(literal(std::string(pos, brace))); + } + pos = brace; + if (pos != end) { + for (brace++; brace != end && tokenReservedChars.find(*brace) == std::string::npos; brace++); + if (brace != end && *brace == '}') { + inputs.push_back(toString(get(literal(std::string(pos + 1, brace))))); + pos = brace + 1; + } else { + inputs.push_back(literal(std::string(pos, brace))); + pos = brace; + } + } + } + + switch (inputs.size()) { + case 0: + return literal(source); + case 1: + return std::move(inputs[0]); + default: + return concat(std::move(inputs)); + } +} + // Ad-hoc Converters for double and int64_t. We should replace float with double wholesale, // and promote the int64_t Converter to general use (and it should check that the input is // an integer). @@ -66,7 +120,7 @@ static bool interpolatable(type::Type type) { ); } -static optional> convertLiteral(type::Type type, const Convertible& value, Error& error) { +static optional> convertLiteral(type::Type type, const Convertible& value, Error& error, bool convertTokens = false) { return type.match( [&] (const type::NumberType&) -> optional> { auto result = convert(value, error); @@ -87,7 +141,7 @@ static optional> convertLiteral(type::Type type, con if (!result) { return {}; } - return literal(*result); + return convertTokens ? convertTokenStringToExpression(*result) : literal(*result); }, [&] (const type::ColorType&) -> optional> { auto result = convert(value, error); @@ -163,7 +217,8 @@ static optional> convertLiteral(type::Type type, con static optional>> convertStops(type::Type type, const Convertible& value, - Error& error) { + Error& error, + bool convertTokens) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { error = { "function value must specify stops" }; @@ -199,7 +254,7 @@ static optional>> convertStops(type return {}; } - optional> e = convertLiteral(type, arrayMember(stopValue, 1), error); + optional> e = convertLiteral(type, arrayMember(stopValue, 1), error, convertTokens); if (!e) { return {}; } @@ -320,8 +375,9 @@ std::unique_ptr categorical(type::Type type, const std::string static optional> convertIntervalFunction(type::Type type, const Convertible& value, Error& error, - std::unique_ptr input) { - auto stops = convertStops(type, value, error); + std::unique_ptr input, + bool convertTokens = false) { + auto stops = convertStops(type, value, error, convertTokens); if (!stops) { return {}; } @@ -331,8 +387,9 @@ static optional> convertIntervalFunction(type::Type static optional> convertExponentialFunction(type::Type type, const Convertible& value, Error& error, - std::unique_ptr input) { - auto stops = convertStops(type, value, error); + std::unique_ptr input, + bool convertTokens = false) { + auto stops = convertStops(type, value, error, convertTokens); if (!stops) { return {}; } @@ -486,7 +543,8 @@ optional> composite(type::Type type, optional> convertFunctionToExpression(type::Type type, const Convertible& value, - Error& err) { + Error& err, + bool convertTokens) { if (!isObject(value)) { err = { "function must be an object" }; return {}; @@ -515,9 +573,9 @@ optional> convertFunctionToExpression(type::Type typ // Camera function. switch (functionType) { case FunctionType::Interval: - return convertIntervalFunction(type, value, err, zoom()); + return convertIntervalFunction(type, value, err, zoom(), convertTokens); case FunctionType::Exponential: - return convertExponentialFunction(type, value, err, zoom()); + return convertExponentialFunction(type, value, err, zoom(), convertTokens); default: err = { "unsupported function type" }; return {}; diff --git a/src/mbgl/style/conversion/light.cpp b/src/mbgl/style/conversion/light.cpp index f521f7438..57b61eb34 100644 --- a/src/mbgl/style/conversion/light.cpp +++ b/src/mbgl/style/conversion/light.cpp @@ -18,7 +18,7 @@ optional Converter::operator()(const Convertible& value, Error& er const auto anchor = objectMember(value, "anchor"); if (anchor) { optional> convertedAnchor = - convert>(*anchor, error); + convert>(*anchor, error, false); if (convertedAnchor) { light.setAnchor(*convertedAnchor); @@ -41,7 +41,7 @@ optional Converter::operator()(const Convertible& value, Error& er const auto color = objectMember(value, "color"); if (color) { optional> convertedColor = - convert>(*color, error); + convert>(*color, error, false); if (convertedColor) { light.setColor(*convertedColor); @@ -64,7 +64,7 @@ optional Converter::operator()(const Convertible& value, Error& er const auto position = objectMember(value, "position"); if (position) { optional> convertedPosition = - convert>(*position, error); + convert>(*position, error, false); if (convertedPosition) { light.setPosition(*convertedPosition); @@ -87,7 +87,7 @@ optional Converter::operator()(const Convertible& value, Error& er const auto intensity = objectMember(value, "intensity"); if (intensity) { optional> convertedIntensity = - convert>(*intensity, error); + convert>(*intensity, error, false); if (convertedIntensity) { light.setIntensity(*convertedIntensity); diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp index 96aa1b641..64826106f 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -41,7 +41,7 @@ inline auto makeLayoutPropertySetters() { result["icon-size"] = &setProperty, &SymbolLayer::setIconSize>; result["icon-text-fit"] = &setProperty, &SymbolLayer::setIconTextFit>; result["icon-text-fit-padding"] = &setProperty>, &SymbolLayer::setIconTextFitPadding>; - result["icon-image"] = &setProperty, &SymbolLayer::setIconImage>; + result["icon-image"] = &setProperty, &SymbolLayer::setIconImage, true>; result["icon-rotate"] = &setProperty, &SymbolLayer::setIconRotate>; result["icon-padding"] = &setProperty, &SymbolLayer::setIconPadding>; result["icon-keep-upright"] = &setProperty, &SymbolLayer::setIconKeepUpright>; @@ -50,7 +50,7 @@ inline auto makeLayoutPropertySetters() { result["icon-pitch-alignment"] = &setProperty, &SymbolLayer::setIconPitchAlignment>; result["text-pitch-alignment"] = &setProperty, &SymbolLayer::setTextPitchAlignment>; result["text-rotation-alignment"] = &setProperty, &SymbolLayer::setTextRotationAlignment>; - result["text-field"] = &setProperty, &SymbolLayer::setTextField>; + result["text-field"] = &setProperty, &SymbolLayer::setTextField, true>; result["text-font"] = &setProperty>, &SymbolLayer::setTextFont>; result["text-size"] = &setProperty, &SymbolLayer::setTextSize>; result["text-max-width"] = &setProperty, &SymbolLayer::setTextMaxWidth>; diff --git a/src/mbgl/style/conversion/make_property_setters.hpp.ejs b/src/mbgl/style/conversion/make_property_setters.hpp.ejs index 2975cb19f..2b8925817 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp.ejs +++ b/src/mbgl/style/conversion/make_property_setters.hpp.ejs @@ -21,7 +21,7 @@ inline auto makeLayoutPropertySetters() { <% for (const layer of locals.layers) { -%> <% for (const property of layer.layoutProperties) { -%> - result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; + result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %><%- property.name === 'icon-image' || property.name === 'text-field' ? ', true' : '' %>>; <% } -%> <% } -%> diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp index e13338f62..8791e36e1 100644 --- a/src/mbgl/style/conversion/property_setter.hpp +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -16,7 +17,7 @@ namespace conversion { using PropertySetter = optional (*) (Layer&, const Convertible&); -template +template optional setProperty(Layer& layer, const Convertible& value) { auto* typedLayer = layer.as(); if (!typedLayer) { @@ -24,7 +25,7 @@ optional setProperty(Layer& layer, const Convertible& value) { } Error error; - optional typedValue = convert(value, error); + optional typedValue = convert(value, error, convertTokens); if (!typedValue) { return error; } diff --git a/src/mbgl/style/expression/dsl.cpp b/src/mbgl/style/expression/dsl.cpp index 5532e0a52..a851d82e1 100644 --- a/src/mbgl/style/expression/dsl.cpp +++ b/src/mbgl/style/expression/dsl.cpp @@ -7,26 +7,22 @@ #include #include #include -#include namespace mbgl { namespace style { namespace expression { namespace dsl { -template -static std::vector> vec(Args... args) { - std::vector> result; - util::ignore({ (result.push_back(std::move(args)), 0)... }); - return result; +static std::unique_ptr compound(const char* op, std::vector> args) { + ParsingContext ctx; + ParseResult result = createCompoundExpression(op, std::move(args), ctx); + assert(result); + return std::move(*result); } template static std::unique_ptr compound(const char* op, Args... args) { - ParsingContext ctx; - ParseResult result = createCompoundExpression(op, vec(std::move(args)...), ctx); - assert(result); - return std::move(*result); + return compound(op, vec(std::move(args)...)); } std::unique_ptr error(std::string message) { @@ -69,14 +65,14 @@ std::unique_ptr boolean(std::unique_ptr value) { return std::make_unique(type::Boolean, vec(std::move(value))); } -std::unique_ptr toColor(const char* value) { - return toColor(literal(value)); -} - std::unique_ptr toColor(std::unique_ptr value) { return std::make_unique(type::Color, vec(std::move(value))); } +std::unique_ptr toString(std::unique_ptr value) { + return compound("to-string", std::move(value)); +} + std::unique_ptr get(const char* value) { return get(literal(value)); } @@ -177,6 +173,10 @@ std::unique_ptr interpolate(Interpolator interpolator, return std::move(*result); } +std::unique_ptr concat(std::vector> inputs) { + return compound("concat", std::move(inputs)); +} + } // namespace dsl } // namespace expression } // namespace style diff --git a/test/style/conversion/function.test.cpp b/test/style/conversion/function.test.cpp index fa89576af..4612147f7 100644 --- a/test/style/conversion/function.test.cpp +++ b/test/style/conversion/function.test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace mbgl; using namespace mbgl::style; @@ -56,7 +57,7 @@ TEST(StyleConversion, CompositeFunctionExpression) { Error error; auto parseFunction = [&](const std::string& json) { - return convertJSON>(json, error); + return convertJSON>(json, error, false); }; auto fn1 = parseFunction(R"(["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10])"); @@ -75,3 +76,20 @@ TEST(StyleConversion, CompositeFunctionExpression) { ASSERT_FALSE(fn5); ASSERT_EQ(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", error.message); } + +TEST(StyleConversion, TokenStrings) { + ASSERT_FALSE(hasTokens("")); + ASSERT_FALSE(hasTokens("{")); + ASSERT_FALSE(hasTokens("{token")); + ASSERT_TRUE(hasTokens("{token}")); + ASSERT_TRUE(hasTokens("token {token}")); + ASSERT_TRUE(hasTokens("{token} {token}")); + + using namespace mbgl::style::expression::dsl; + ASSERT_EQ(*convertTokenStringToExpression("{token}"), *toString(get(literal("token")))); + ASSERT_EQ(*convertTokenStringToExpression("token {token}"), *concat(vec(literal("token "), toString(get(literal("token")))))); + ASSERT_EQ(*convertTokenStringToExpression("{token} token"), *concat(vec(toString(get(literal("token"))), literal(" token")))); + ASSERT_EQ(*convertTokenStringToExpression("{token} {token}"), *concat(vec(toString(get(literal("token"))), literal(" "), toString(get(literal("token")))))); + ASSERT_EQ(*convertTokenStringToExpression("{token} {token"), *concat(vec(toString(get(literal("token"))), literal(" "), literal("{token")))); + ASSERT_EQ(*convertTokenStringToExpression("{token {token}"), *concat(vec(literal("{token "), toString(get(literal("token")))))); +}