diff options
Diffstat (limited to 'src/mbgl/style/expression')
-rw-r--r-- | src/mbgl/style/expression/check_subtype.cpp | 1 | ||||
-rw-r--r-- | src/mbgl/style/expression/coercion.cpp | 30 | ||||
-rw-r--r-- | src/mbgl/style/expression/dsl.cpp | 15 | ||||
-rw-r--r-- | src/mbgl/style/expression/format_expression.cpp | 175 | ||||
-rw-r--r-- | src/mbgl/style/expression/formatted.cpp | 61 | ||||
-rw-r--r-- | src/mbgl/style/expression/parsing_context.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/style/expression/value.cpp | 35 |
7 files changed, 316 insertions, 5 deletions
diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp index 73f7d18a34..73e6e3bff7 100644 --- a/src/mbgl/style/expression/check_subtype.cpp +++ b/src/mbgl/style/expression/check_subtype.cpp @@ -40,6 +40,7 @@ optional<std::string> checkSubtype(const Type& expected, const Type& t) { String, Object, Color, + Formatted, Array(Value) }; diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp index 4a17895991..75a6056081 100644 --- a/src/mbgl/style/expression/coercion.cpp +++ b/src/mbgl/style/expression/coercion.cpp @@ -82,6 +82,10 @@ EvaluationResult toColor(const Value& colorValue) { ); } +EvaluationResult toFormatted(const Value& formattedValue) { + return Formatted(toString(formattedValue).c_str()); +} + Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) : Expression(Kind::Coercion, std::move(type_)), inputs(std::move(inputs_)) @@ -96,11 +100,26 @@ Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> in coerceSingleValue = toNumber; } else if (t.is<type::StringType>()) { coerceSingleValue = [] (const Value& v) -> EvaluationResult { return toString(v); }; + } else if (t.is<type::FormattedType>()) { + coerceSingleValue = toFormatted; } else { assert(false); } } +mbgl::Value Coercion::serialize() const { + if (getType().is<type::FormattedType>()) { + // Since there's no explicit "to-formatted" coercion, the only coercions should be created + // by string expressions that get implicitly coerced to "formatted". + std::vector<mbgl::Value> serialized{{ std::string("format") }}; + serialized.push_back(inputs[0]->serialize()); + serialized.push_back(std::unordered_map<std::string, mbgl::Value>()); + return serialized; + } else { + return Expression::serialize(); + } +}; + std::string Coercion::getOperator() const { return getType().match( [](const type::BooleanType&) { return "to-boolean"; }, @@ -129,10 +148,16 @@ ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) { auto it = types.find(*toString(arrayMember(value, 0))); assert(it != types.end()); - if ((it->second == type::Boolean || it->second == type::String) && length != 2) { + if ((it->second == type::Boolean || it->second == type::String || it->second == type::Formatted) && length != 2) { ctx.error("Expected one argument."); return ParseResult(); } + + /** + * Special form for error-coalescing coercion expressions "to-number", + * "to-color". Since these coercions can fail at runtime, they accept multiple + * arguments, only evaluating one at a time until one succeeds. + */ std::vector<std::unique_ptr<Expression>> parsed; parsed.reserve(length - 1); @@ -186,6 +211,3 @@ std::vector<optional<Value>> Coercion::possibleOutputs() const { } // namespace expression } // namespace style } // namespace mbgl - - - diff --git a/src/mbgl/style/expression/dsl.cpp b/src/mbgl/style/expression/dsl.cpp index c6318fb637..f5ff83a9e7 100644 --- a/src/mbgl/style/expression/dsl.cpp +++ b/src/mbgl/style/expression/dsl.cpp @@ -7,6 +7,7 @@ #include <mbgl/style/expression/step.hpp> #include <mbgl/style/expression/interpolate.hpp> #include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/format_expression.hpp> namespace mbgl { namespace style { @@ -77,6 +78,10 @@ std::unique_ptr<Expression> toString(std::unique_ptr<Expression> value) { return std::make_unique<Coercion>(type::String, vec(std::move(value))); } +std::unique_ptr<Expression> toFormatted(std::unique_ptr<Expression> value) { + return std::make_unique<Coercion>(type::Formatted, vec(std::move(value))); +} + std::unique_ptr<Expression> get(const char* value) { return get(literal(value)); } @@ -181,6 +186,16 @@ std::unique_ptr<Expression> concat(std::vector<std::unique_ptr<Expression>> inpu return compound("concat", std::move(inputs)); } +std::unique_ptr<Expression> format(const char* value) { + return std::make_unique<Literal>(Formatted(value)); +} + +std::unique_ptr<Expression> format(std::unique_ptr<Expression> input) { + std::vector<FormatExpressionSection> sections; + sections.emplace_back(std::move(input), nullopt, nullopt); + return std::make_unique<FormatExpression>(sections); +} + } // namespace dsl } // namespace expression } // namespace style diff --git a/src/mbgl/style/expression/format_expression.cpp b/src/mbgl/style/expression/format_expression.cpp new file mode 100644 index 0000000000..144df4b160 --- /dev/null +++ b/src/mbgl/style/expression/format_expression.cpp @@ -0,0 +1,175 @@ +#include <mbgl/style/conversion_impl.hpp> +#include <mbgl/style/expression/format_expression.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/util/font_stack.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +FormatExpressionSection::FormatExpressionSection(std::unique_ptr<Expression> text_, + optional<std::unique_ptr<Expression>> fontScale_, + optional<std::unique_ptr<Expression>> textFont_) + : text(std::move(text_)) +{ + if (fontScale_) { + fontScale = std::shared_ptr<Expression>(std::move(*fontScale_)); + } + if (textFont_) { + textFont = std::shared_ptr<Expression>(std::move(*textFont_)); + } +} + +FormatExpression::FormatExpression(std::vector<FormatExpressionSection> sections_) + : Expression(Kind::FormatExpression, type::Formatted) + , sections(std::move(sections_)) +{} + +using namespace mbgl::style::conversion; + +ParseResult FormatExpression::parse(const Convertible& value, ParsingContext& ctx) { + std::size_t argsLength = arrayLength(value); + if (argsLength < 3) { + ctx.error("Expected at least two arguments."); + return ParseResult(); + } + + if ((argsLength - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + std::vector<FormatExpressionSection> sections; + for (std::size_t i = 1; i < argsLength - 1; i += 2) { + auto textArg = arrayMember(value, i); + ParseResult text = ctx.parse(textArg, 1, {type::Value}); + if (!text) { + return ParseResult(); + } + auto options = arrayMember(value, i + 1); + if (!isObject(options)) { + ctx.error("Format options argument must be an object."); + return ParseResult(); + } + + const optional<Convertible> fontScaleOption = objectMember(options, "font-scale"); + ParseResult fontScale; + if (fontScaleOption) { + fontScale = ctx.parse(*fontScaleOption, 1, {type::Number}); + if (!fontScale) { + return ParseResult(); + } + } + + const optional<Convertible> textFontOption = objectMember(options, "text-font"); + ParseResult textFont; + if (textFontOption) { + textFont = ctx.parse(*textFontOption, 1, {type::Array(type::String)}); + if (!textFont) { + return ParseResult(); + } + } + sections.emplace_back(std::move(*text), std::move(fontScale), std::move(textFont)); + } + + return ParseResult(std::make_unique<FormatExpression>(std::move(sections))); +} + +void FormatExpression::eachChild(const std::function<void(const Expression&)>& fn) const { + for (auto& section : sections) { + fn(*section.text); + if (section.fontScale) { + fn(**section.fontScale); + } + if (section.textFont) { + fn(**section.textFont); + } + } +} + +bool FormatExpression::operator==(const Expression& e) const { + if (e.getKind() == Kind::FormatExpression) { + auto rhs = static_cast<const FormatExpression*>(&e); + if (sections.size() != rhs->sections.size()) { + return false; + } + for (std::size_t i = 0; i < sections.size(); i++) { + const auto& lhsSection = sections.at(i); + const auto& rhsSection = rhs->sections.at(i); + if (*lhsSection.text != *rhsSection.text) { + return false; + } + if ((lhsSection.fontScale && (!rhsSection.fontScale || **lhsSection.fontScale != **rhsSection.fontScale)) || + (!lhsSection.fontScale && rhsSection.fontScale)) { + return false; + } + if ((lhsSection.textFont && (!rhsSection.textFont || **lhsSection.textFont != **rhsSection.textFont)) || + (!lhsSection.textFont && rhsSection.textFont)) { + return false; + } + } + return true; + } + return false; +} + +mbgl::Value FormatExpression::serialize() const { + std::vector<mbgl::Value> serialized{{ std::string("format") }}; + for (const auto& section : sections) { + serialized.push_back(section.text->serialize()); + std::unordered_map<std::string, mbgl::Value> options; + if (section.fontScale) { + options.emplace("font-scale", (*section.fontScale)->serialize()); + } + if (section.textFont) { + options.emplace("text-font", (*section.textFont)->serialize()); + } + serialized.push_back(options); + } + return serialized; +} + +EvaluationResult FormatExpression::evaluate(const EvaluationContext& params) const { + std::vector<FormattedSection> evaluatedSections; + for (const auto& section : sections) { + auto textResult = section.text->evaluate(params); + if (!textResult) { + return textResult.error(); + } + + optional<std::string> evaluatedText = toString(*textResult); + if (!evaluatedText) { + return EvaluationError({ "Could not coerce format expression text input to string." }); + } + + optional<double> evaluatedFontScale; + if (section.fontScale) { + auto fontScaleResult = (*section.fontScale)->evaluate(params); + if (!fontScaleResult) { + return fontScaleResult.error(); + } + evaluatedFontScale = fontScaleResult->get<double>(); + } + + optional<FontStack> evaluatedTextFont; + if (section.textFont) { + auto textFontResult = (*section.textFont)->evaluate(params); + if (!textFontResult) { + return textFontResult.error(); + } + auto textFontValue = ValueConverter<std::vector<std::string>>::fromExpressionValue(*textFontResult); + if (!textFontValue) { + return EvaluationError { "Format text-font option must evaluate to an array of strings" }; + } + evaluatedTextFont = *textFontValue; + } + evaluatedSections.emplace_back(*evaluatedText, evaluatedFontScale, evaluatedTextFont); + } + return Formatted(evaluatedSections); +} + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/formatted.cpp b/src/mbgl/style/expression/formatted.cpp new file mode 100644 index 0000000000..6eb106dfec --- /dev/null +++ b/src/mbgl/style/expression/formatted.cpp @@ -0,0 +1,61 @@ +#include <mbgl/style/expression/formatted.hpp> +#include <mbgl/style/conversion_impl.hpp> +#include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/is_expression.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/boolean_operator.hpp> + +namespace mbgl { +namespace style { + +namespace expression { + +bool Formatted::operator==(const Formatted& other) const { + if (other.sections.size() != sections.size()) { + return false; + } + for (std::size_t i = 0; i < sections.size(); i++) { + const auto& thisSection = sections.at(i); + const auto& otherSection = other.sections.at(i); + if (thisSection.text != otherSection.text || + thisSection.fontScale != otherSection.fontScale || + thisSection.fontStack != otherSection.fontStack) { + return false; + } + } + return true; +} + + +std::string Formatted::toString() const { + std::string result; + for (const auto& section : sections) { + result += section.text; + } + return result; +} + +} // namespace expression + +namespace conversion { + +using namespace mbgl::style::expression; + +optional<Formatted> Converter<Formatted>::operator()(const Convertible& value, Error&) const { + using namespace mbgl::style::expression; + + auto result = toString(value); + if (result) { + return Formatted(result->c_str()); + } else { + return nullopt; + } +} + +} // namespace conversion +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp index 0373b9721f..34fbc5c380 100644 --- a/src/mbgl/style/expression/parsing_context.cpp +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -13,6 +13,7 @@ #include <mbgl/style/expression/coercion.hpp> #include <mbgl/style/expression/compound_expression.hpp> #include <mbgl/style/expression/comparison.hpp> +#include <mbgl/style/expression/format_expression.hpp> #include <mbgl/style/expression/interpolate.hpp> #include <mbgl/style/expression/length.hpp> #include <mbgl/style/expression/let.hpp> @@ -110,6 +111,7 @@ const ExpressionRegistry& getExpressionRegistry() { {"case", Case::parse}, {"coalesce", Coalesce::parse}, {"collator", CollatorExpression::parse}, + {"format", FormatExpression::parse}, {"interpolate", parseInterpolate}, {"length", Length::parse}, {"let", Let::parse}, @@ -183,7 +185,7 @@ ParseResult ParsingContext::parse(const Convertible& value, optional<TypeAnnotat const type::Type actual = (*parsed)->getType(); if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean || *expected == type::Object || expected->is<type::Array>()) && actual == type::Value) { parsed = { annotate(std::move(*parsed), *expected, typeAnnotationOption.value_or(TypeAnnotationOption::assert)) }; - } else if (*expected == type::Color && (actual == type::Value || actual == type::String)) { + } else if ((*expected == type::Color || *expected == type::Formatted) && (actual == type::Value || actual == type::String)) { parsed = { annotate(std::move(*parsed), *expected, typeAnnotationOption.value_or(TypeAnnotationOption::coerce)) }; } else { checkType((*parsed)->getType()); diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp index 4bac8116c2..436ed83ecd 100644 --- a/src/mbgl/style/expression/value.cpp +++ b/src/mbgl/style/expression/value.cpp @@ -1,6 +1,7 @@ #include <rapidjson/writer.h> #include <rapidjson/stringbuffer.h> #include <mbgl/style/expression/value.hpp> +#include <mbgl/style/conversion/stringify.hpp> namespace mbgl { namespace style { @@ -13,6 +14,7 @@ type::Type typeOf(const Value& value) { [&](const std::string&) -> type::Type { return type::String; }, [&](const Color&) -> type::Type { return type::Color; }, [&](const Collator&) -> type::Type { return type::Collator; }, + [&](const Formatted&) -> type::Type { return type::Formatted; }, [&](const NullValue&) -> type::Type { return type::Null; }, [&](const std::unordered_map<std::string, Value>&) -> type::Type { return type::Object; }, [&](const std::vector<Value>& arr) -> type::Type { @@ -38,6 +40,7 @@ std::string toString(const Value& value) { return value.match( [](const NullValue&) { return std::string(); }, [](const Color& c) { return c.stringify(); }, // avoid quoting + [](const Formatted& f) { return f.toString(); }, [](const std::string& s) { return s; }, // avoid quoting [](const auto& v_) { return stringify(v_); } ); @@ -58,6 +61,12 @@ void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value& // for them so there shouldn't be any way to serialize this value. assert(false); }, + [&] (const Formatted& f) { + // `stringify` in turns calls ValueConverter::fromExpressionValue below + // Serialization strategy for Formatted objects is to return the constant + // expression that would generate them. + mbgl::style::conversion::stringify(writer, f); + }, [&] (const std::vector<Value>& arr) { writer.StartArray(); for(const auto& item : arr) { @@ -136,6 +145,31 @@ mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value) assert(false); return mbgl::Value(); }, + [&](const Formatted& formatted)->mbgl::Value { + // Serialization strategy for Formatted objects is to return the constant + // expression that would generate them. + std::vector<mbgl::Value> serialized; + static std::string formatOperator("format"); + serialized.emplace_back(formatOperator); + for (const auto& section : formatted.sections) { + serialized.emplace_back(section.text); + std::unordered_map<std::string, mbgl::Value> options; + + if (section.fontScale) { + options.emplace("font-scale", *section.fontScale); + } + + if (section.fontStack) { + std::vector<mbgl::Value> fontStack; + for (const auto& font : *section.fontStack) { + fontStack.emplace_back(font); + } + options.emplace("text-font", std::vector<mbgl::Value>{ std::string("literal"), fontStack }); + } + serialized.push_back(options); + } + return serialized; + }, [&](const std::vector<Value>& values)->mbgl::Value { std::vector<mbgl::Value> converted; converted.reserve(values.size()); @@ -262,6 +296,7 @@ template <> type::Type valueTypeToExpressionType<double>() { return type::Number template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; } template <> type::Type valueTypeToExpressionType<Color>() { return type::Color; } template <> type::Type valueTypeToExpressionType<Collator>() { return type::Collator; } +template <> type::Type valueTypeToExpressionType<Formatted>() { return type::Formatted; } template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; } template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); } |