Files
MapLibreParticleLab/src/mbgl/style/expression/parsing_context.cpp
Chris Loer 9ff5d34ef2 [core] Introduce "collator" expressions
Cross platform parsing and evaluation code.
2018-07-03 10:03:05 -07:00

258 lines
9.5 KiB
C++

#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/at.hpp>
#include <mbgl/style/expression/array_assertion.hpp>
#include <mbgl/style/expression/assertion.hpp>
#include <mbgl/style/expression/boolean_operator.hpp>
#include <mbgl/style/expression/case.hpp>
#include <mbgl/style/expression/coalesce.hpp>
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/equals.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/length.hpp>
#include <mbgl/style/expression/let.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/style/expression/match.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/expression/find_zoom_curve.hpp>
#include <mbgl/style/conversion/get_json_type.hpp>
#include <mbgl/util/string.hpp>
namespace mbgl {
namespace style {
namespace expression {
bool isConstant(const Expression& expression) {
if (auto varExpression = dynamic_cast<const Var*>(&expression)) {
return isConstant(*varExpression->getBoundExpression());
}
if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
if (compound->getName() == "error") {
return false;
}
}
bool isTypeAnnotation = dynamic_cast<const Coercion*>(&expression) ||
dynamic_cast<const Assertion*>(&expression) ||
dynamic_cast<const ArrayAssertion*>(&expression);
bool childrenConstant = true;
expression.eachChild([&](const Expression& child) {
// We can _almost_ assume that if `expressions` children are constant,
// they would already have been evaluated to Literal values when they
// were parsed. Type annotations are the exception, because they might
// have been inferred and added after a child was parsed.
// So we recurse into isConstant() for the children of type annotations,
// but otherwise simply check whether they are Literals.
if (isTypeAnnotation) {
childrenConstant = childrenConstant && isConstant(child);
} else {
childrenConstant = childrenConstant && dynamic_cast<const Literal*>(&child);
}
});
if (!childrenConstant) {
return false;
}
return isFeatureConstant(expression) &&
isGlobalPropertyConstant(expression, std::array<std::string, 2>{{"zoom", "heatmap-density"}});
}
using namespace mbgl::style::conversion;
ParseResult ParsingContext::parse(const Convertible& value,
std::size_t index_,
optional<type::Type> expected_,
TypeAnnotationOption typeAnnotationOption) {
ParsingContext child(key + "[" + util::toString(index_) + "]",
errors,
std::move(expected_),
scope);
return child.parse(value, typeAnnotationOption);
}
ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_, optional<type::Type> expected_,
const std::map<std::string, std::shared_ptr<Expression>>& bindings) {
ParsingContext child(key + "[" + util::toString(index_) + "]",
errors,
std::move(expected_),
std::make_shared<detail::Scope>(bindings, scope));
return child.parse(value);
}
const ExpressionRegistry& getExpressionRegistry() {
static ExpressionRegistry registry {{
{"==", Equals::parse},
{"!=", Equals::parse},
{"all", All::parse},
{"any", Any::parse},
{"array", ArrayAssertion::parse},
{"at", At::parse},
{"boolean", Assertion::parse},
{"case", Case::parse},
{"coalesce", Coalesce::parse},
{"collator", CollatorExpression::parse},
{"interpolate", parseInterpolate},
{"length", Length::parse},
{"let", Let::parse},
{"literal", Literal::parse},
{"match", parseMatch},
{"number", Assertion::parse},
{"object", Assertion::parse},
{"step", Step::parse},
{"string", Assertion::parse},
{"to-color", Coercion::parse},
{"to-number", Coercion::parse},
{"var", Var::parse}
}};
return registry;
}
ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption typeAnnotationOption) {
ParseResult parsed;
if (isArray(value)) {
const std::size_t length = arrayLength(value);
if (length == 0) {
error(R"(Expected an array with at least one element. If you wanted a literal array, use ["literal", []].)");
return ParseResult();
}
const optional<std::string> op = toString(arrayMember(value, 0));
if (!op) {
error(
"Expression name must be a string, but found " + getJSONType(arrayMember(value, 0)) +
R"( instead. If you wanted a literal array, use ["literal", [...]].)",
0
);
return ParseResult();
}
const ExpressionRegistry& registry = getExpressionRegistry();
auto parseFunction = registry.find(*op);
if (parseFunction != registry.end()) {
parsed = parseFunction->second(value, *this);
} else {
parsed = parseCompoundExpression(*op, value, *this);
}
} else {
parsed = Literal::parse(value, *this);
}
if (!parsed) {
assert(errors->size() > 0);
return parsed;
}
auto array = [&](std::unique_ptr<Expression> expression) {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(std::move(expression));
return args;
};
if (expected) {
const type::Type actual = (*parsed)->getType();
if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean || *expected == type::Object) && actual == type::Value) {
if (typeAnnotationOption == includeTypeAnnotations) {
parsed = { std::make_unique<Assertion>(*expected, array(std::move(*parsed))) };
}
} else if (expected->is<type::Array>() && actual == type::Value) {
if (typeAnnotationOption == includeTypeAnnotations) {
parsed = { std::make_unique<ArrayAssertion>(expected->get<type::Array>(), std::move(*parsed)) };
}
} else if (*expected == type::Color && (actual == type::Value || actual == type::String)) {
if (typeAnnotationOption == includeTypeAnnotations) {
parsed = { std::make_unique<Coercion>(*expected, array(std::move(*parsed))) };
}
} else {
checkType((*parsed)->getType());
if (errors->size() > 0) {
return ParseResult();
}
}
}
// If an expression's arguments are all constant, we can evaluate
// it immediately and replace it with a literal value in the
// parsed result.
if (!dynamic_cast<Literal *>(parsed->get()) && isConstant(**parsed)) {
EvaluationContext params(nullptr);
EvaluationResult evaluated((*parsed)->evaluate(params));
if (!evaluated) {
error(evaluated.error().message);
return ParseResult();
}
const type::Type type = (*parsed)->getType();
if (type.is<type::Array>()) {
// keep the original expression's array type, even if the evaluated
// type is more specific.
return ParseResult(std::make_unique<Literal>(
type.get<type::Array>(),
evaluated->get<std::vector<Value>>())
);
} else {
return ParseResult(std::make_unique<Literal>(*evaluated));
}
}
return parsed;
}
ParseResult ParsingContext::parseExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) {
return parse(value, typeAnnotationOption);
}
ParseResult ParsingContext::parseLayerPropertyExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) {
ParseResult parsed = parse(value, typeAnnotationOption);
if (parsed && !isZoomConstant(**parsed)) {
optional<variant<const Interpolate*, const Step*, ParsingError>> zoomCurve = findZoomCurve(parsed->get());
if (!zoomCurve) {
error(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)");
return ParseResult();
} else if (zoomCurve->is<ParsingError>()) {
error(zoomCurve->get<ParsingError>().message);
return ParseResult();
}
}
return parsed;
}
const std::string ParsingContext::getCombinedErrors() const {
std::string combinedError;
for (const ParsingError& parsingError : *errors) {
if (combinedError.size() > 0) {
combinedError += "\n";
}
if (parsingError.key.size() > 0) {
combinedError += parsingError.key + ": ";
}
combinedError += parsingError.message;
}
return combinedError;
}
optional<std::string> ParsingContext::checkType(const type::Type& t) {
assert(expected);
optional<std::string> err = type::checkSubtype(*expected, t);
if (err) {
error(*err);
}
return err;
}
} // namespace expression
} // namespace style
} // namespace mbgl