#include #include #include #include #include namespace mbgl { namespace style { namespace expression { template void Match::eachChild(const std::function& visit) const { visit(*input); for (const std::pair>& branch : branches) { visit(*branch.second); } visit(*otherwise); } template bool Match::operator==(const Expression& e) const { if (e.getKind() == Kind::Match) { auto rhs = static_cast(&e); return (*input == *(rhs->input) && *otherwise == *(rhs->otherwise) && Expression::childrenEqual(branches, rhs->branches)); } return false; } template std::vector> Match::possibleOutputs() const { std::vector> result; for (const auto& branch : branches) { for (auto& output : branch.second->possibleOutputs()) { result.push_back(std::move(output)); } } for (auto& output : otherwise->possibleOutputs()) { result.push_back(std::move(output)); } return result; } template mbgl::Value Match::serialize() const { std::vector serialized; serialized.emplace_back(getOperator()); serialized.emplace_back(input->serialize()); // Sort so serialization has an arbitrary defined order, even though branch order doesn't affect evaluation std::map> sortedBranches(branches.begin(), branches.end()); // Group branches by unique match expression to support condensed serializations // of the form [case1, case2, ...] -> matchExpression std::map outputLookup; std::vector>> groupedByOutput; for (auto& entry : sortedBranches) { auto outputIndex = outputLookup.find(entry.second.get()); if (outputIndex == outputLookup.end()) { // First time seeing this output, add it to the end of the grouped list outputLookup[entry.second.get()] = groupedByOutput.size(); groupedByOutput.emplace_back(entry.second.get(), std::vector{{entry.first}}); } else { // We've seen this expression before, add the label to that output's group groupedByOutput[outputIndex->second].second.emplace_back(entry.first); } }; for (auto& entry : groupedByOutput) { entry.second.size() == 1 ? serialized.emplace_back(entry.second[0]) // Only a single label matches this output expression : serialized.emplace_back(entry.second); // Array of literal labels pointing to this output expression serialized.emplace_back(entry.first->serialize()); // The output expression itself } serialized.emplace_back(otherwise->serialize()); return serialized; } template<> EvaluationResult Match::evaluate(const EvaluationContext& params) const { const EvaluationResult inputValue = input->evaluate(params); if (!inputValue) { return inputValue.error(); } if (!inputValue->is()) { return otherwise->evaluate(params); } auto it = branches.find(inputValue->get()); if (it != branches.end()) { return (*it).second->evaluate(params); } return otherwise->evaluate(params); } template<> EvaluationResult Match::evaluate(const EvaluationContext& params) const { const EvaluationResult inputValue = input->evaluate(params); if (!inputValue) { return inputValue.error(); } if (!inputValue->is()) { return otherwise->evaluate(params); } const auto numeric = inputValue->get(); int64_t rounded = std::floor(numeric); if (numeric == rounded) { auto it = branches.find(rounded); if (it != branches.end()) { return (*it).second->evaluate(params); } } return otherwise->evaluate(params); } template class Match; template class Match; using InputType = variant; using namespace mbgl::style::conversion; optional parseInputValue(const Convertible& input, ParsingContext& parentContext, std::size_t index, optional& inputType) { using namespace mbgl::style::conversion; optional result; optional type; auto value = toValue(input); if (value) { value->match( [&] (uint64_t n) { if (!Value::isSafeInteger(n)) { parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); } else { type = {type::Number}; result = optional{static_cast(n)}; } }, [&] (int64_t n) { if (!Value::isSafeInteger(n)) { parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); } else { type = {type::Number}; result = optional{n}; } }, [&] (double n) { if (!Value::isSafeInteger(n)) { parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); } else if (n != std::floor(n)) { parentContext.error("Numeric branch labels must be integer values.", index); } else { type = {type::Number}; result = optional{static_cast(n)}; } }, [&] (const std::string& s) { type = {type::String}; result = {s}; }, [&] (const auto&) { parentContext.error("Branch labels must be numbers or strings.", index); } ); } else { parentContext.error("Branch labels must be numbers or strings.", index); } if (!type) { return result; } if (!inputType) { inputType = type; } else { optional err = type::checkSubtype(*inputType, *type); if (err) { parentContext.error(*err, index); return optional(); } } return result; } template static ParseResult create(type::Type outputType, std::unique_ptrinput, std::vector, std::unique_ptr>> branches, std::unique_ptr otherwise, ParsingContext& ctx) { typename Match::Branches typedBranches; std::size_t index = 2; typedBranches.reserve(branches.size()); for (std::pair, std::unique_ptr>& pair : branches) { std::shared_ptr result = std::move(pair.second); for (const InputType& label : pair.first) { const auto& typedLabel = label.template get(); if (typedBranches.find(typedLabel) != typedBranches.end()) { ctx.error("Branch labels must be unique.", index); return ParseResult(); } typedBranches.emplace(typedLabel, result); } index += 2; } return ParseResult(std::make_unique>( outputType, std::move(input), std::move(typedBranches), std::move(otherwise) )); } ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) { assert(isArray(value)); auto length = arrayLength(value); if (length < 5) { ctx.error( "Expected at least 4 arguments, but found only " + util::toString(length - 1) + "." ); return ParseResult(); } // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise] if (length % 2 != 1) { ctx.error("Expected an even number of arguments."); return ParseResult(); } optional inputType; optional outputType; if (ctx.getExpected() && *ctx.getExpected() != type::Value) { outputType = ctx.getExpected(); } std::vector, std::unique_ptr>> branches; branches.reserve((length - 3) / 2); for (size_t i = 2; i + 1 < length; i += 2) { const auto& label = arrayMember(value, i); std::vector labels; // Match pair inputs are provided as either a literal value or a // raw JSON array of string / number / boolean values. if (isArray(label)) { auto groupLength = arrayLength(label); if (groupLength == 0) { ctx.error("Expected at least one branch label.", i); return ParseResult(); } labels.reserve(groupLength); for (size_t j = 0; j < groupLength; j++) { const optional inputValue = parseInputValue(arrayMember(label, j), ctx, i, inputType); if (!inputValue) { return ParseResult(); } labels.push_back(*inputValue); } } else { const optional inputValue = parseInputValue(label, ctx, i, inputType); if (!inputValue) { return ParseResult(); } labels.push_back(*inputValue); } ParseResult output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); if (!output) { return ParseResult(); } if (!outputType) { outputType = (*output)->getType(); } branches.push_back(std::make_pair(std::move(labels), std::move(*output))); } auto input = ctx.parse(arrayMember(value, 1), 1, {type::Value}); if (!input) { return ParseResult(); } auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType); if (!otherwise) { return ParseResult(); } assert(inputType && outputType); optional err; if ((*input)->getType() != type::Value && (err = type::checkSubtype(*inputType, (*input)->getType()))) { ctx.error(*err, 1); return ParseResult(); } return inputType->match( [&](const type::NumberType&) { return create(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); }, [&](const type::StringType&) { return create(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); }, [&](const auto&) { // unreachable: inputType is set by parseInputValue(), which only // accepts string and (integer) numeric values. assert(false); return ParseResult(); } ); } } // namespace expression } // namespace style } // namespace mbgl