#include #include #include #include #include #include #include #include #include namespace mbgl { namespace style { 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). template <> struct Converter { optional operator()(const Convertible& value, Error& error) const { auto converted = convert(value, error); if (!converted) { return nullopt; } return *converted; } }; template <> struct Converter { optional operator()(const Convertible& value, Error& error) const { auto converted = convert(value, error); if (!converted) { return nullopt; } return *converted; } }; enum class FunctionType { Interval, Exponential, Categorical, Identity, Invalid }; static bool interpolatable(type::Type type) { return type.match( [&] (const type::NumberType&) { return true; }, [&] (const type::ColorType&) { return true; }, [&] (const type::Array& array) { return array.N && array.itemType == type::Number; }, [&] (const auto&) { return false; } ); } 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); if (!result) { return nullopt; } return literal(double(*result)); }, [&] (const type::BooleanType&) -> optional> { auto result = convert(value, error); if (!result) { return nullopt; } return literal(*result); }, [&] (const type::StringType&) -> optional> { auto result = convert(value, error); if (!result) { return nullopt; } return convertTokens ? convertTokenStringToExpression(*result) : literal(*result); }, [&] (const type::ColorType&) -> optional> { auto result = convert(value, error); if (!result) { return nullopt; } return literal(*result); }, [&] (const type::Array& array) -> optional> { if (!isArray(value)) { error.message = "value must be an array"; return nullopt; } if (array.N && arrayLength(value) != *array.N) { error.message = "value must be an array of length " + util::toString(*array.N); return nullopt; } return array.itemType.match( [&] (const type::NumberType&) -> optional> { std::vector result; result.reserve(arrayLength(value)); for (std::size_t i = 0; i < arrayLength(value); ++i) { optional number = toNumber(arrayMember(value, i)); if (!number) { error.message = "value must be an array of numbers"; return nullopt; } result.push_back(double(*number)); } return literal(result); }, [&] (const type::StringType&) -> optional> { std::vector result; result.reserve(arrayLength(value)); for (std::size_t i = 0; i < arrayLength(value); ++i) { optional string = toString(arrayMember(value, i)); if (!string) { error.message = "value must be an array of strings"; return nullopt; } result.push_back(*string); } return literal(result); }, [&] (const auto&) -> optional> { assert(false); // No properties use this type. return nullopt; } ); }, [&] (const type::NullType&) -> optional> { assert(false); // No properties use this type. return nullopt; }, [&] (const type::ObjectType&) -> optional> { assert(false); // No properties use this type. return nullopt; }, [&] (const type::ErrorType&) -> optional> { assert(false); // No properties use this type. return nullopt; }, [&] (const type::ValueType&) -> optional> { assert(false); // No properties use this type. return nullopt; }, [&] (const type::CollatorType&) -> optional> { assert(false); // No properties use this type. return nullopt; } ); } static optional>> convertStops(type::Type type, const Convertible& value, Error& error, bool convertTokens) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { error.message = "function value must specify stops"; return nullopt; } if (!isArray(*stopsValue)) { error.message = "function stops must be an array"; return nullopt; } if (arrayLength(*stopsValue) == 0) { error.message = "function must have at least one stop"; return nullopt; } std::map> stops; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); if (!isArray(stopValue)) { error.message = "function stop must be an array"; return nullopt; } if (arrayLength(stopValue) != 2) { error.message = "function stop must have two elements"; return nullopt; } optional t = convert(arrayMember(stopValue, 0), error); if (!t) { return nullopt; } optional> e = convertLiteral(type, arrayMember(stopValue, 1), error, convertTokens); if (!e) { return nullopt; } stops.emplace(*t, std::move(*e)); } return { std::move(stops) }; } template optional>> convertBranches(type::Type type, const Convertible& value, Error& error) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { error.message = "function value must specify stops"; return nullopt; } if (!isArray(*stopsValue)) { error.message = "function stops must be an array"; return nullopt; } if (arrayLength(*stopsValue) == 0) { error.message = "function must have at least one stop"; return nullopt; } std::map> stops; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); if (!isArray(stopValue)) { error.message = "function stop must be an array"; return nullopt; } if (arrayLength(stopValue) != 2) { error.message = "function stop must have two elements"; return nullopt; } optional t = convert(arrayMember(stopValue, 0), error); if (!t) { return nullopt; } optional> e = convertLiteral(type, arrayMember(stopValue, 1), error); if (!e) { return nullopt; } stops.emplace(*t, std::move(*e)); } return { std::move(stops) }; } static optional convertBase(const Convertible& value, Error& error) { auto baseValue = objectMember(value, "base"); if (!baseValue) { return 1.0; } auto base = toNumber(*baseValue); if (!base) { error.message = "function base must be a number"; return nullopt; } return *base; } static std::unique_ptr step(type::Type type, std::unique_ptr input, std::map> stops) { return std::make_unique(type, std::move(input), std::move(stops)); } static std::unique_ptr interpolate(type::Type type, Interpolator interpolator, std::unique_ptr input, std::map> stops) { ParsingContext ctx; auto result = createInterpolate(type, std::move(interpolator), std::move(input), std::move(stops), ctx); if (!result) { assert(false); return {}; } return std::move(*result); } template std::unique_ptr categorical(type::Type type, const std::string& property, std::map> branches) { std::unordered_map> convertedBranches; for (auto& b : branches) { convertedBranches[b.first] = std::move(b.second); } return std::make_unique>(type, get(literal(property)), std::move(convertedBranches), error("replaced with default")); } template <> std::unique_ptr categorical(type::Type type, const std::string& property, std::map> branches) { auto it = branches.find(true); std::unique_ptr trueCase = it == branches.end() ? error("replaced with default") : std::move(it->second); it = branches.find(false); std::unique_ptr falseCase = it == branches.end() ? error("replaced with default") : std::move(it->second); std::vector trueBranch; trueBranch.emplace_back(get(literal(property)), std::move(trueCase)); return std::make_unique(type, std::move(trueBranch), std::move(falseCase)); } static optional> convertIntervalFunction(type::Type type, const Convertible& value, Error& error, std::unique_ptr input, bool convertTokens = false) { auto stops = convertStops(type, value, error, convertTokens); if (!stops) { return nullopt; } return step(type, std::move(input), std::move(*stops)); } static optional> convertExponentialFunction(type::Type type, const Convertible& value, Error& error, std::unique_ptr input, bool convertTokens = false) { auto stops = convertStops(type, value, error, convertTokens); if (!stops) { return nullopt; } auto base = convertBase(value, error); if (!base) { return nullopt; } return interpolate(type, exponential(*base), std::move(input), std::move(*stops)); } static optional> convertCategoricalFunction(type::Type type, const Convertible& value, Error& err, const std::string& property) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { err.message = "function value must specify stops"; return nullopt; } if (!isArray(*stopsValue)) { err.message = "function stops must be an array"; return nullopt; } if (arrayLength(*stopsValue) == 0) { err.message = "function must have at least one stop"; return nullopt; } const auto& first = arrayMember(*stopsValue, 0); if (!isArray(first)) { err.message = "function stop must be an array"; return nullopt; } if (arrayLength(first) != 2) { err.message = "function stop must have two elements"; return nullopt; } if (toBool(arrayMember(first, 0))) { auto branches = convertBranches(type, value, err); if (!branches) { return nullopt; } return categorical(type, property, std::move(*branches)); } if (toNumber(arrayMember(first, 0))) { auto branches = convertBranches(type, value, err); if (!branches) { return nullopt; } return categorical(type, property, std::move(*branches)); } if (toString(arrayMember(first, 0))) { auto branches = convertBranches(type, value, err); if (!branches) { return nullopt; } return categorical(type, property, std::move(*branches)); } err.message = "stop domain value must be a number, string, or boolean"; return nullopt; } template optional> composite(type::Type type, const Convertible& value, Error& error, const Fn& makeInnerExpression) { auto base = convertBase(value, error); if (!base) { return nullopt; } auto stopsValue = objectMember(value, "stops"); // Checked by caller. assert(stopsValue); assert(isArray(*stopsValue)); std::map>> map; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); if (!isArray(stopValue)) { error.message = "function stop must be an array"; return nullopt; } if (arrayLength(stopValue) != 2) { error.message = "function stop must have two elements"; return nullopt; } const auto& stopInput = arrayMember(stopValue, 0); if (!isObject(stopInput)) { error.message = "stop input must be an object"; return nullopt; } auto zoomValue = objectMember(stopInput, "zoom"); if (!zoomValue) { error.message = "stop input must specify zoom"; return nullopt; } auto sourceValue = objectMember(stopInput, "value"); if (!sourceValue) { error.message = "stop input must specify value"; return nullopt; } optional z = convert(*zoomValue, error); if (!z) { return nullopt; } optional d = convert(*sourceValue, error); if (!d) { return nullopt; } optional> r = convertLiteral(type, arrayMember(stopValue, 1), error); if (!r) { return nullopt; } map[*z].emplace(*d, std::move(*r)); } std::map> stops; for (auto& e : map) { stops.emplace(e.first, makeInnerExpression(type, *base, std::move(e.second))); } if (interpolatable(type)) { return interpolate(type, linear(), zoom(), std::move(stops)); } else { return step(type, zoom(), std::move(stops)); } } optional> convertFunctionToExpression(type::Type type, const Convertible& value, Error& err, bool convertTokens) { if (!isObject(value)) { err.message = "function must be an object"; return nullopt; } FunctionType functionType = FunctionType::Invalid; auto typeValue = objectMember(value, "type"); if (!typeValue) { functionType = interpolatable(type) ? FunctionType::Exponential : FunctionType::Interval; } else { optional string = toString(*typeValue); if (string) { if (*string == "interval") functionType = FunctionType::Interval; if (*string == "exponential" && interpolatable(type)) functionType = FunctionType::Exponential; if (*string == "categorical") functionType = FunctionType::Categorical; if (*string == "identity") functionType = FunctionType::Identity; } } if (!objectMember(value, "property")) { // Camera function. switch (functionType) { case FunctionType::Interval: return convertIntervalFunction(type, value, err, zoom(), convertTokens); case FunctionType::Exponential: return convertExponentialFunction(type, value, err, zoom(), convertTokens); default: err.message = "unsupported function type"; return nullopt; } } auto propertyValue = objectMember(value, "property"); if (!propertyValue) { err.message = "function must specify property"; return nullopt; } auto property = toString(*propertyValue); if (!property) { err.message = "function property must be a string"; return nullopt; } if (functionType == FunctionType::Identity) { return type.match( [&] (const type::StringType&) -> optional> { return string(get(literal(*property))); }, [&] (const type::NumberType&) -> optional> { return number(get(literal(*property))); }, [&] (const type::BooleanType&) -> optional> { return boolean(get(literal(*property))); }, [&] (const type::ColorType&) -> optional> { return toColor(get(literal(*property))); }, [&] (const type::Array& array) -> optional> { return std::unique_ptr( std::make_unique(array, get(literal(*property)))); }, [&] (const auto&) -> optional> { assert(false); // No properties use this type. return nullopt; } ); } auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { err.message = "function value must specify stops"; return nullopt; } if (!isArray(*stopsValue)) { err.message = "function stops must be an array"; return nullopt; } if (arrayLength(*stopsValue) == 0) { err.message = "function must have at least one stop"; return nullopt; } const auto& first = arrayMember(*stopsValue, 0); if (!isArray(first)) { err.message = "function stop must be an array"; return nullopt; } if (arrayLength(first) != 2) { err.message = "function stop must have two elements"; return nullopt; } const auto& stop = arrayMember(first, 0); if (!isObject(stop)) { // Source function. switch (functionType) { case FunctionType::Interval: return convertIntervalFunction(type, value, err, number(get(literal(*property)))); case FunctionType::Exponential: return convertExponentialFunction(type, value, err, number(get(literal(*property)))); case FunctionType::Categorical: return convertCategoricalFunction(type, value, err, *property); default: err.message = "unsupported function type"; return nullopt; } } else { // Composite function. auto sourceValue = objectMember(stop, "value"); if (!sourceValue) { err.message = "stop must specify value"; return nullopt; } if (toBool(*sourceValue)) { switch (functionType) { case FunctionType::Categorical: return composite(type, value, err, [&] (type::Type type_, double, std::map> stops) { return categorical(type_, *property, std::move(stops)); }); default: err.message = "unsupported function type"; return nullopt; } } if (toNumber(*sourceValue)) { switch (functionType) { case FunctionType::Interval: return composite(type, value, err, [&] (type::Type type_, double, std::map> stops) { return step(type_, number(get(literal(*property))), std::move(stops)); }); case FunctionType::Exponential: return composite(type, value, err, [&] (type::Type type_, double base, std::map> stops) { return interpolate(type_, exponential(base), number(get(literal(*property))), std::move(stops)); }); case FunctionType::Categorical: return composite(type, value, err, [&] (type::Type type_, double, std::map> stops) { return categorical(type_, *property, std::move(stops)); }); default: err.message = "unsupported function type"; return nullopt; } } if (toString(*sourceValue)) { switch (functionType) { case FunctionType::Categorical: return composite(type, value, err, [&] (type::Type type_, double, std::map> stops) { return categorical(type_, *property, std::move(stops)); }); default: err.message = "unsupported function type"; return nullopt; } } err.message = "stop domain value must be a number, string, or boolean"; return nullopt; } } } // namespace conversion } // namespace style } // namespace mbgl