#include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace style { namespace expression { /* Represents the parameter list for an expression that takes an arbitrary number of arguments (of a specific type). */ struct VarargsType { type::Type type; }; bool operator==(const VarargsType& lhs, const VarargsType& rhs) { return lhs.type == rhs.type; } template struct Varargs : std::vector { template Varargs(Args&&... args) : std::vector(std::forward(args)...) {} }; namespace detail { // Base class for the Signature structs that are used to determine // each CompoundExpression definition's type::Type data from the type of its // "evaluate" function. struct SignatureBase { using Args = std::vector>; SignatureBase(type::Type result_, variant, VarargsType> params_, std::string name_) : result(std::move(result_)), params(std::move(params_)), name(std::move(name_)) {} virtual ~SignatureBase() = default; virtual EvaluationResult apply(const EvaluationContext&, const Args&) const = 0; type::Type result; variant, VarargsType> params; std::string name; }; /* The Signature structs are wrappers around an "evaluate()" function whose purpose is to extract the necessary Type data from the evaluate function's type. There are three key (partial) specializations: Signature: Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result Signature&)>: Wraps an evaluate function that takes an arbitrary number of arguments (via a Varargs, which is just an alias for std::vector). Signature: Wraps an evaluate function that needs to access the expression evaluation parameters in addition to its subexpressions, i.e., (const EvaluationParams&, const T0&, const T1&, ...) -> Result. Needed for expressions like ["zoom"], ["get", key], etc. In each of the above evaluate signatures, T0, T1, etc. are the types of the successfully evaluated subexpressions. */ template struct Signature; // Simple evaluate function (const T0&, const T1&, ...) -> Result template struct Signature : SignatureBase { Signature(R (*evaluate_)(Params...), std::string name_) : SignatureBase( valueTypeToExpressionType>(), std::vector {valueTypeToExpressionType>()...}, std::move(name_) ), evaluate(evaluate_) {} EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override { return applyImpl(evaluationParameters, args, std::index_sequence_for{}); } R (*evaluate)(Params...); private: template EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence) const { std::array evaluated; for (std::size_t i = 0; i < sizeof...(Params); ++i) { const EvaluationResult evaluatedArg = args.at(i)->evaluate(evaluationParameters); if (!evaluatedArg) return evaluatedArg.error(); evaluated[i] = std::move(*evaluatedArg); } const R value = evaluate(*fromExpressionValue>(evaluated[I])...); if (!value) return value.error(); return *value; } }; // Varargs evaluate function (const Varargs&) -> Result template struct Signature&)> : SignatureBase { Signature(R (*evaluate_)(const Varargs&), std::string name_) : SignatureBase( valueTypeToExpressionType>(), VarargsType { valueTypeToExpressionType() }, std::move(name_) ), evaluate(evaluate_) {} EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override { Varargs evaluated; evaluated.reserve(args.size()); for (const auto& arg : args) { const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters); if(!evaluatedArg) return evaluatedArg.error(); evaluated.push_back(*fromExpressionValue>(*evaluatedArg)); } const R value = evaluate(evaluated); if (!value) return value.error(); return *value; } R (*evaluate)(const Varargs&); }; // Evaluate function needing parameter access, // (const EvaluationParams&, const T0&, const T1&, ...) -> Result template struct Signature : SignatureBase { Signature(R (*evaluate_)(const EvaluationContext&, Params...), std::string name_) : SignatureBase( valueTypeToExpressionType>(), std::vector {valueTypeToExpressionType>()...}, std::move(name_) ), evaluate(evaluate_) {} EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override { return applyImpl(evaluationParameters, args, std::index_sequence_for{}); } private: template EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence) const { std::array evaluated; for (std::size_t i = 0; i < sizeof...(Params); ++i) { const EvaluationResult evaluatedArg = args.at(i)->evaluate(evaluationParameters); if (!evaluatedArg) return evaluatedArg.error(); evaluated[i] = std::move(*evaluatedArg); } const R value = evaluate(evaluationParameters, *fromExpressionValue>(evaluated[I])...); if (!value) return value.error(); return *value; } R (*evaluate)(const EvaluationContext&, Params...); }; // Evaluate function needing EvaluationContext and Varargs // (const EvaluationContext&, const Varargs&) -> Result template struct Signature&)> : SignatureBase { Signature(R (*evaluate_)(const EvaluationContext&, const Varargs&), std::string name_) : SignatureBase( valueTypeToExpressionType>(), VarargsType { valueTypeToExpressionType() }, std::move(name_) ), evaluate(evaluate_) {} EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override { Varargs evaluated; evaluated.reserve(args.size()); for (const auto& arg : args) { const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters); if(!evaluatedArg) return evaluatedArg.error(); evaluated.push_back(*fromExpressionValue>(*evaluatedArg)); } const R value = evaluate(evaluationParameters, evaluated); if (!value) return value.error(); return *value; } R (*evaluate)(const EvaluationContext&, const Varargs&); }; // Machinery to pull out function type from lambdas. template struct SignatureType; template struct SignatureType { using Type = R (Params...); }; template struct SignatureType::value>> { using Type = typename SignatureType::Type; }; template static std::unique_ptr makeSignature(Fn evaluateFunction, std::string name) { return std::make_unique::Type>>(evaluateFunction, std::move(name)); } } // namespace detail using Definition = CompoundExpressionRegistry::Definition; Value featureIdAsExpressionValue(EvaluationContext params) { assert(params.feature); auto id = params.feature->getID(); if (!id) return Null; return id->match([](const auto& idid) { return toExpressionValue(mbgl::Value(idid)); }); }; optional featurePropertyAsExpressionValue(EvaluationContext params, const std::string& key) { assert(params.feature); auto property = params.feature->getValue(key); return property ? toExpressionValue(*property) : optional(); }; optional featureTypeAsString(FeatureType type) { switch(type) { case FeatureType::Point: return optional("Point"); case FeatureType::LineString: return optional("LineString"); case FeatureType::Polygon: return optional("Polygon"); case FeatureType::Unknown: return optional("Unknown"); default: return {}; } }; optional featurePropertyAsDouble(EvaluationContext params, const std::string& key) { assert(params.feature); auto property = params.feature->getValue(key); if (!property) return {}; return property->match( [](double value) { return value; }, [](uint64_t value) { return optional(static_cast(value)); }, [](int64_t value) { return optional(static_cast(value)); }, [](auto) { return optional(); } ); }; optional featurePropertyAsString(EvaluationContext params, const std::string& key) { assert(params.feature); auto property = params.feature->getValue(key); if (!property) return {}; return property->match( [](std::string value) { return value; }, [](auto) { return optional(); } ); }; optional featureIdAsDouble(EvaluationContext params) { assert(params.feature); auto id = params.feature->getID(); if (!id) return optional(); return id->match( [](double value) { return value; }, [](uint64_t value) { return optional(static_cast(value)); }, [](int64_t value) { return optional(static_cast(value)); }, [](auto) { return optional(); } ); }; optional featureIdAsString(EvaluationContext params) { assert(params.feature); auto id = params.feature->getID(); if (!id) return optional(); return id->match( [](std::string value) { return value; }, [](auto) { return optional(); } ); }; std::unordered_map initializeDefinitions() { std::unordered_map definitions; auto define = [&](std::string name, auto fn) { definitions[name].push_back(detail::makeSignature(fn, name)); }; define("e", []() -> Result { return 2.718281828459045; }); define("pi", []() -> Result { return 3.141592653589793; }); define("ln2", []() -> Result { return 0.6931471805599453; }); define("typeof", [](const Value& v) -> Result { return toString(typeOf(v)); }); define("to-string", [](const Value& value) -> Result { return value.match( [](const NullValue&) -> Result { return std::string(); }, [](const Color& c) -> Result { return c.stringify(); }, // avoid quoting [](const std::string& s) -> Result { return s; }, // avoid quoting [](const auto& v) -> Result { return stringify(v); } ); }); define("to-boolean", [](const Value& v) -> Result { return v.match( [&] (double f) { return static_cast(f); }, [&] (const std::string& s) { return s.length() > 0; }, [&] (bool b) { return b; }, [&] (const NullValue&) { return false; }, [&] (const auto&) { return true; } ); }); define("to-rgba", [](const Color& color) -> Result> { return color.toArray(); }); define("rgba", [](double r, double g, double b, double a) { return rgba(r, g, b, a); }); define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); }); define("zoom", [](const EvaluationContext& params) -> Result { if (!params.zoom) { return EvaluationError { "The 'zoom' expression is unavailable in the current evaluation context." }; } return *(params.zoom); }); define("heatmap-density", [](const EvaluationContext& params) -> Result { if (!params.colorRampParameter) { return EvaluationError { "The 'heatmap-density' expression is unavailable in the current evaluation context." }; } return *(params.colorRampParameter); }); define("has", [](const EvaluationContext& params, const std::string& key) -> Result { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } return params.feature->getValue(key) ? true : false; }); define("has", [](const std::string& key, const std::unordered_map& object) -> Result { return object.find(key) != object.end(); }); define("get", [](const EvaluationContext& params, const std::string& key) -> Result { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } auto propertyValue = params.feature->getValue(key); if (!propertyValue) { return Null; } return Value(toExpressionValue(*propertyValue)); }); define("get", [](const std::string& key, const std::unordered_map& object) -> Result { if (object.find(key) == object.end()) { return Null; } return object.at(key); }); define("properties", [](const EvaluationContext& params) -> Result> { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } std::unordered_map result; const PropertyMap properties = params.feature->getProperties(); for (const auto& entry : properties) { result[entry.first] = toExpressionValue(entry.second); } return result; }); define("geometry-type", [](const EvaluationContext& params) -> Result { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } auto type = params.feature->getType(); if (type == FeatureType::Point) { return "Point"; } else if (type == FeatureType::LineString) { return "LineString"; } else if (type == FeatureType::Polygon) { return "Polygon"; } else { return "Unknown"; } }); define("id", [](const EvaluationContext& params) -> Result { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } auto id = params.feature->getID(); if (!id) { return Null; } return id->match( [](const auto& idValue) { return toExpressionValue(mbgl::Value(idValue)); } ); }); define("+", [](const Varargs& args) -> Result { double sum = 0.0f; for (auto arg : args) { sum += arg; } return sum; }); define("-", [](double a, double b) -> Result { return a - b; }); define("-", [](double a) -> Result { return -a; }); define("*", [](const Varargs& args) -> Result { double prod = 1.0f; for (auto arg : args) { prod *= arg; } return prod; }); define("/", [](double a, double b) -> Result { return a / b; }); define("%", [](double a, double b) -> Result { return fmod(a, b); }); define("^", [](double a, double b) -> Result { return pow(a, b); }); define("sqrt", [](double x) -> Result { return sqrt(x); }); define("log10", [](double x) -> Result { return log10(x); }); define("ln", [](double x) -> Result { return log(x); }); define("log2", [](double x) -> Result { return log2(x); }); define("sin", [](double x) -> Result { return sin(x); }); define("cos", [](double x) -> Result { return cos(x); }); define("tan", [](double x) -> Result { return tan(x); }); define("asin", [](double x) -> Result { return asin(x); }); define("acos", [](double x) -> Result { return acos(x); }); define("atan", [](double x) -> Result { return atan(x); }); define("min", [](const Varargs& args) -> Result { double result = std::numeric_limits::infinity(); for (double arg : args) { result = fmin(arg, result); } return result; }); define("max", [](const Varargs& args) -> Result { double result = -std::numeric_limits::infinity(); for (double arg : args) { result = fmax(arg, result); } return result; }); define("round", [](double x) -> Result { return ::round(x); }); define("floor", [](double x) -> Result { return std::floor(x); }); define("ceil", [](double x) -> Result { return std::ceil(x); }); define("abs", [](double x) -> Result { return std::abs(x); }); define("!", [](bool e) -> Result { return !e; }); define("is-supported-script", [](const std::string& x) -> Result { return util::i18n::isStringInSupportedScript(x); }); define("upcase", [](const std::string& input) -> Result { return platform::uppercase(input); }); define("downcase", [](const std::string& input) -> Result { return platform::lowercase(input); }); define("concat", [](const Varargs& args) -> Result { std::string s; for (const std::string& arg : args) { s += arg; } return s; }); define("resolved-locale", [](const Collator& collator) -> Result { return collator.resolvedLocale(); }); define("error", [](const std::string& input) -> Result { return EvaluationError { input }; }); // Legacy Filters define("filter-==", [](const EvaluationContext& params, const std::string& key, const Value &lhs) -> Result { const auto rhs = featurePropertyAsExpressionValue(params, key); return rhs ? lhs == *rhs : false; }); define("filter-id-==", [](const EvaluationContext& params, const Value &lhs) -> Result { return lhs == featureIdAsExpressionValue(params); }); define("filter-type-==", [](const EvaluationContext& params, const std::string &lhs) -> Result { if (!params.feature) return false; return featureTypeAsString(params.feature->getType()) == lhs; }); define("filter-<", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result { auto rhs = featurePropertyAsDouble(params, key); return rhs ? rhs < lhs : false; }); define("filter-<", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result { auto rhs = featurePropertyAsString(params, key); return rhs ? rhs < lhs : false; }); define("filter-id-<", [](const EvaluationContext& params, double lhs) -> Result { auto rhs = featureIdAsDouble(params); return rhs ? rhs < lhs : false; }); define("filter-id-<", [](const EvaluationContext& params, std::string lhs) -> Result { auto rhs = featureIdAsString(params); return rhs ? rhs < lhs : false; }); define("filter->", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result { auto rhs = featurePropertyAsDouble(params, key); return rhs ? rhs > lhs : false; }); define("filter->", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result { auto rhs = featurePropertyAsString(params, key); return rhs ? rhs > lhs : false; }); define("filter-id->", [](const EvaluationContext& params, double lhs) -> Result { auto rhs = featureIdAsDouble(params); return rhs ? rhs > lhs : false; }); define("filter-id->", [](const EvaluationContext& params, std::string lhs) -> Result { auto rhs = featureIdAsString(params); return rhs ? rhs > lhs : false; }); define("filter-<=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result { auto rhs = featurePropertyAsDouble(params, key); return rhs ? rhs <= lhs : false; }); define("filter-<=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result { auto rhs = featurePropertyAsString(params, key); return rhs ? rhs <= lhs : false; }); define("filter-id-<=", [](const EvaluationContext& params, double lhs) -> Result { auto rhs = featureIdAsDouble(params); return rhs ? rhs <= lhs : false; }); define("filter-id-<=", [](const EvaluationContext& params, std::string lhs) -> Result { auto rhs = featureIdAsString(params); return rhs ? rhs <= lhs : false; }); define("filter->=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result { auto rhs = featurePropertyAsDouble(params, key); return rhs ? rhs >= lhs : false; }); define("filter->=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result { auto rhs = featurePropertyAsString(params, key); return rhs ? rhs >= lhs : false; }); define("filter-id->=", [](const EvaluationContext& params, double lhs) -> Result { auto rhs = featureIdAsDouble(params); return rhs ? rhs >= lhs : false; }); define("filter-id->=", [](const EvaluationContext& params, std::string lhs) -> Result { auto rhs = featureIdAsString(params); return rhs ? rhs >= lhs : false; }); define("filter-has", [](const EvaluationContext& params, const std::string& key) -> Result { assert(params.feature); return bool(params.feature->getValue(key)); }); define("filter-has-id", [](const EvaluationContext& params) -> Result { assert(params.feature); return bool(params.feature->getID()); }); define("filter-type-in", [](const EvaluationContext& params, const Varargs& types) -> Result { assert(params.feature); optional type = featureTypeAsString(params.feature->getType()); return std::find(types.begin(), types.end(), type) != types.end(); }); define("filter-id-in", [](const EvaluationContext& params, const Varargs& ids) -> Result { auto id = featureIdAsExpressionValue(params); return std::find(ids.begin(), ids.end(), id) != ids.end(); }); define("filter-in", [](const EvaluationContext& params, const Varargs& varargs) -> Result { if (varargs.size() < 2) return false; assert(varargs[0].is()); auto value = featurePropertyAsExpressionValue(params, varargs[0].get()); return value ? std::find(varargs.begin() + 1, varargs.end(), *value) != varargs.end() : false; }); return definitions; } std::unordered_map CompoundExpressionRegistry::definitions = initializeDefinitions(); using namespace mbgl::style::conversion; static ParseResult createCompoundExpression(const Definition& definition, std::vector> args, ParsingContext& ctx) { ParsingContext signatureContext(ctx.getKey()); for (const std::unique_ptr& signature : definition) { signatureContext.clearErrors(); if (signature->params.is>()) { const std::vector& params = signature->params.get>(); if (params.size() != args.size()) { signatureContext.error( "Expected " + util::toString(params.size()) + " arguments, but found " + util::toString(args.size()) + " instead." ); continue; } for (std::size_t i = 0; i < args.size(); i++) { const std::unique_ptr& arg = args[i]; optional err = type::checkSubtype(params.at(i), arg->getType()); if (err) { signatureContext.error(*err, i + 1); } } } else if (signature->params.is()) { const type::Type& paramType = signature->params.get().type; for (std::size_t i = 0; i < args.size(); i++) { const std::unique_ptr& arg = args[i]; optional err = type::checkSubtype(paramType, arg->getType()); if (err) { signatureContext.error(*err, i + 1); } } } if (signatureContext.getErrors().size() == 0) { return ParseResult(std::make_unique(*signature, std::move(args))); } } if (definition.size() == 1) { ctx.appendErrors(std::move(signatureContext)); } else { std::vector availableOverloads; // Only used if there are no overloads with matching number of args std::vector overloads; for (const auto& signature : definition) { signature->params.match( [&](const VarargsType& varargs) { std::string overload = "(" + toString(varargs.type) + ")"; overloads.push_back(overload); }, [&](const std::vector& params) { std::string overload = "("; bool first = true; for (const type::Type& param : params) { if (!first) overload += ", "; overload += toString(param); first = false; } overload += ")"; if (params.size() == args.size()) { overloads.push_back(overload); } else { availableOverloads.push_back(overload); } } ); } std::string signatures = overloads.empty() ? boost::algorithm::join(availableOverloads, " | ") : boost::algorithm::join(overloads, " | "); std::string actualTypes; for (const auto& arg : args) { if (actualTypes.size() > 0) { actualTypes += ", "; } actualTypes += toString(arg->getType()); } ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead."); } return ParseResult(); } ParseResult parseCompoundExpression(const std::string name, const Convertible& value, ParsingContext& ctx) { assert(isArray(value) && arrayLength(value) > 0); auto it = CompoundExpressionRegistry::definitions.find(name); if (it == CompoundExpressionRegistry::definitions.end()) { ctx.error( R"(Unknown expression ")" + name + R"(". If you wanted a literal array, use ["literal", [...]].)", 0 ); return ParseResult(); } const CompoundExpressionRegistry::Definition& definition = it->second; auto length = arrayLength(value); // Check if we have a single signature with the correct number of // parameters. If so, then use that signature's parameter types for parsing // (and inferring the types of) the arguments. optional singleMatchingSignature; for (std::size_t j = 0; j < definition.size(); j++) { const std::unique_ptr& signature = definition[j]; if ( signature->params.is() || signature->params.get>().size() == length - 1 ) { if (singleMatchingSignature) { singleMatchingSignature = {}; } else { singleMatchingSignature = j; } } } // parse subexpressions first std::vector> args; args.reserve(length - 1); for (std::size_t i = 1; i < length; i++) { optional expected; if (singleMatchingSignature) { expected = definition[*singleMatchingSignature]->params.match( [](const VarargsType& varargs) { return varargs.type; }, [&](const std::vector& params_) { return params_[i - 1]; } ); } auto parsed = ctx.parse(arrayMember(value, i), i, expected); if (!parsed) { return parsed; } args.push_back(std::move(*parsed)); } return createCompoundExpression(definition, std::move(args), ctx); } ParseResult createCompoundExpression(const std::string& name, std::vector> args, ParsingContext& ctx) { return createCompoundExpression(CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx); } CompoundExpression::CompoundExpression(const detail::SignatureBase& signature_, std::vector> args_) : Expression(Kind::CompoundExpression, signature_.result), signature(signature_), args(std::move(args_)) {} std::string CompoundExpression::getOperator() const { return signature.name; } EvaluationResult CompoundExpression::evaluate(const EvaluationContext& evaluationParams) const { return signature.apply(evaluationParams, args); } optional CompoundExpression::getParameterCount() const { return signature.params.match( [&](const VarargsType&) { return optional(); }, [&](const std::vector& p) -> optional { return p.size(); } ); } std::vector> CompoundExpression::possibleOutputs() const { return { nullopt }; } void CompoundExpression::eachChild(const std::function& visit) const { for (const std::unique_ptr& e : args) { visit(*e); } } bool CompoundExpression::operator==(const Expression& e) const { if (e.getKind() == Kind::CompoundExpression) { auto rhs = static_cast(&e); return signature.name == rhs->signature.name && signature.result == rhs->signature.result && signature.params == rhs->signature.params && Expression::childrenEqual(args, rhs->args); } return false; } } // namespace expression } // namespace style } // namespace mbgl