Implement Expressions (#9439)

Ports https://github.com/mapbox/mapbox-gl-js/pull/4777 (and its several follow-ups)
This commit is contained in:
Anand Thakker
2017-11-08 12:34:02 -05:00
committed by GitHub
parent 9aac976104
commit f648cfeef6
167 changed files with 6474 additions and 189 deletions

View File

@@ -365,10 +365,12 @@ set(MBGL_CORE_FILES
include/mbgl/style/conversion/constant.hpp
include/mbgl/style/conversion/coordinate.hpp
include/mbgl/style/conversion/data_driven_property_value.hpp
include/mbgl/style/conversion/expression.hpp
include/mbgl/style/conversion/filter.hpp
include/mbgl/style/conversion/function.hpp
include/mbgl/style/conversion/geojson.hpp
include/mbgl/style/conversion/geojson_options.hpp
include/mbgl/style/conversion/get_json_type.hpp
include/mbgl/style/conversion/layer.hpp
include/mbgl/style/conversion/light.hpp
include/mbgl/style/conversion/position.hpp
@@ -381,6 +383,7 @@ set(MBGL_CORE_FILES
src/mbgl/style/conversion/filter.cpp
src/mbgl/style/conversion/geojson.cpp
src/mbgl/style/conversion/geojson_options.cpp
src/mbgl/style/conversion/get_json_type.cpp
src/mbgl/style/conversion/json.hpp
src/mbgl/style/conversion/layer.cpp
src/mbgl/style/conversion/light.cpp
@@ -392,6 +395,52 @@ set(MBGL_CORE_FILES
src/mbgl/style/conversion/tileset.cpp
src/mbgl/style/conversion/transition_options.cpp
# style/expression
include/mbgl/style/expression/array_assertion.hpp
include/mbgl/style/expression/assertion.hpp
include/mbgl/style/expression/at.hpp
include/mbgl/style/expression/boolean_operator.hpp
include/mbgl/style/expression/case.hpp
include/mbgl/style/expression/check_subtype.hpp
include/mbgl/style/expression/coalesce.hpp
include/mbgl/style/expression/coercion.hpp
include/mbgl/style/expression/compound_expression.hpp
include/mbgl/style/expression/expression.hpp
include/mbgl/style/expression/find_zoom_curve.hpp
include/mbgl/style/expression/get_covering_stops.hpp
include/mbgl/style/expression/interpolate.hpp
include/mbgl/style/expression/is_constant.hpp
include/mbgl/style/expression/is_expression.hpp
include/mbgl/style/expression/let.hpp
include/mbgl/style/expression/literal.hpp
include/mbgl/style/expression/match.hpp
include/mbgl/style/expression/parsing_context.hpp
include/mbgl/style/expression/step.hpp
include/mbgl/style/expression/type.hpp
include/mbgl/style/expression/value.hpp
src/mbgl/style/expression/array_assertion.cpp
src/mbgl/style/expression/assertion.cpp
src/mbgl/style/expression/at.cpp
src/mbgl/style/expression/boolean_operator.cpp
src/mbgl/style/expression/case.cpp
src/mbgl/style/expression/check_subtype.cpp
src/mbgl/style/expression/coalesce.cpp
src/mbgl/style/expression/coercion.cpp
src/mbgl/style/expression/compound_expression.cpp
src/mbgl/style/expression/find_zoom_curve.cpp
src/mbgl/style/expression/get_covering_stops.cpp
src/mbgl/style/expression/interpolate.cpp
src/mbgl/style/expression/is_constant.cpp
src/mbgl/style/expression/is_expression.cpp
src/mbgl/style/expression/let.cpp
src/mbgl/style/expression/literal.cpp
src/mbgl/style/expression/match.cpp
src/mbgl/style/expression/parsing_context.cpp
src/mbgl/style/expression/step.cpp
src/mbgl/style/expression/util.cpp
src/mbgl/style/expression/util.hpp
src/mbgl/style/expression/value.cpp
# style/function
include/mbgl/style/function/camera_function.hpp
include/mbgl/style/function/categorical_stops.hpp
@@ -399,11 +448,13 @@ set(MBGL_CORE_FILES
include/mbgl/style/function/composite_exponential_stops.hpp
include/mbgl/style/function/composite_function.hpp
include/mbgl/style/function/composite_interval_stops.hpp
include/mbgl/style/function/convert.hpp
include/mbgl/style/function/exponential_stops.hpp
include/mbgl/style/function/identity_stops.hpp
include/mbgl/style/function/interval_stops.hpp
include/mbgl/style/function/source_function.hpp
src/mbgl/style/function/categorical_stops.cpp
src/mbgl/style/function/expression.cpp
src/mbgl/style/function/identity_stops.cpp
# style/layers

View File

@@ -22,6 +22,8 @@ target_sources(mbgl-node
PRIVATE platform/node/src/node_feature.cpp
PRIVATE platform/node/src/node_thread_pool.hpp
PRIVATE platform/node/src/node_thread_pool.cpp
PRIVATE platform/node/src/node_expression.hpp
PRIVATE platform/node/src/node_expression.cpp
PRIVATE platform/node/src/util/async_queue.hpp
)
@@ -91,6 +93,17 @@ xcode_create_scheme(
"test"
)
xcode_create_scheme(
TARGET mbgl-node
TYPE node
NAME "node expression tests"
ARGS
"platform/node/test/expression.test.js"
OPTIONAL_ARGS
"group"
"test"
)
xcode_create_scheme(
TARGET mbgl-node
TYPE node

View File

@@ -16,6 +16,7 @@ target_link_libraries(mbgl-render
target_add_mason_package(mbgl-render PRIVATE boost)
target_add_mason_package(mbgl-render PRIVATE boost_libprogram_options)
target_add_mason_package(mbgl-render PRIVATE geojson)
mbgl_platform_render()

View File

@@ -88,6 +88,10 @@ set(MBGL_TEST_FILES
test/style/conversion/light.test.cpp
test/style/conversion/stringify.test.cpp
# style/expression
test/style/expression/expression.test.cpp
test/style/expression/util.test.cpp
# style
test/style/filter.test.cpp

View File

@@ -4,6 +4,13 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/function.hpp>
#include <mbgl/style/conversion/expression.hpp>
#include <mbgl/style/expression/is_expression.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/expression/find_zoom_curve.hpp>
#include <unordered_set>
namespace mbgl {
namespace style {
@@ -11,9 +18,27 @@ namespace conversion {
template <class T>
struct Converter<DataDrivenPropertyValue<T>> {
optional<DataDrivenPropertyValue<T>> operator()(const Convertible& value, Error& error) const {
if (isUndefined(value)) {
return DataDrivenPropertyValue<T>();
} else if (expression::isExpression(value)) {
optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(
value,
error,
valueTypeToExpressionType<T>());
if (!expression) {
return {};
}
if (isFeatureConstant(**expression)) {
return DataDrivenPropertyValue<T>(CameraFunction<T>(std::move(*expression)));
} else if (isZoomConstant(**expression)) {
return DataDrivenPropertyValue<T>(SourceFunction<T>(std::move(*expression)));
} else {
return DataDrivenPropertyValue<T>(CompositeFunction<T>(std::move(*expression)));
}
} else if (!isObject(value)) {
optional<T> constant = convert<T>(value, error);
if (!constant) {

View File

@@ -0,0 +1,39 @@
#pragma once
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace conversion {
using namespace mbgl::style::expression;
template<> struct Converter<std::unique_ptr<Expression>> {
optional<std::unique_ptr<Expression>> operator()(const Convertible& value, Error& error, type::Type expected) const {
ParsingContext ctx(optional<type::Type> {expected});
ParseResult parsed = ctx.parse(value);
if (parsed) {
return std::move(*parsed);
}
std::string combinedError;
for (const ParsingError& parsingError : ctx.getErrors()) {
if (combinedError.size() > 0) {
combinedError += "\n";
}
if (parsingError.key.size() > 0) {
combinedError += parsingError.key + ": ";
}
combinedError += parsingError.message;
}
error = { combinedError };
return {};
};
};
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,14 @@
#pragma once
#include <mbgl/style/conversion.hpp>
#include <string>
namespace mbgl {
namespace style {
namespace conversion {
std::string getJSONType(const Convertible& value);
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -4,6 +4,11 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/function.hpp>
#include <mbgl/style/conversion/expression.hpp>
#include <mbgl/style/expression/value.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/expression/is_expression.hpp>
#include <mbgl/style/expression/find_zoom_curve.hpp>
namespace mbgl {
namespace style {
@@ -14,6 +19,17 @@ struct Converter<PropertyValue<T>> {
optional<PropertyValue<T>> operator()(const Convertible& value, Error& error) const {
if (isUndefined(value)) {
return PropertyValue<T>();
} else if (isExpression(value)) {
optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, valueTypeToExpressionType<T>());
if (!expression) {
return {};
}
if (isFeatureConstant(**expression)) {
return { CameraFunction<T>(std::move(*expression)) };
} else {
error = { "property expressions not supported" };
return {};
}
} else if (isObject(value)) {
optional<CameraFunction<T>> function = convert<CameraFunction<T>>(value, error);
if (!function) {

View File

@@ -0,0 +1,39 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class ArrayAssertion : public Expression {
public:
ArrayAssertion(type::Array type_, std::unique_ptr<Expression> input_) :
Expression(type_),
input(std::move(input_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const ArrayAssertion*>(&e)) {
return getType() == rhs->getType() && *input == *(rhs->input);
}
return false;
}
private:
std::unique_ptr<Expression> input;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <memory>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
class Assertion : public Expression {
public:
Assertion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) :
Expression(type_),
inputs(std::move(inputs_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
private:
std::vector<std::unique_ptr<Expression>> inputs;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class At : public Expression {
public:
At(std::unique_ptr<Expression> index_, std::unique_ptr<Expression> input_) :
Expression(input_->getType().get<type::Array>().itemType),
index(std::move(index_)),
input(std::move(input_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>&) const override;
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const At*>(&e)) {
return *index == *(rhs->index) && *input == *(rhs->input);
}
return false;
}
private:
std::unique_ptr<Expression> index;
std::unique_ptr<Expression> input;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,49 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class Any : public Expression {
public:
Any(std::vector<std::unique_ptr<Expression>> inputs_) :
Expression(type::Boolean),
inputs(std::move(inputs_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
private:
std::vector<std::unique_ptr<Expression>> inputs;
};
class All : public Expression {
public:
All(std::vector<std::unique_ptr<Expression>> inputs_) :
Expression(type::Boolean),
inputs(std::move(inputs_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
private:
std::vector<std::unique_ptr<Expression>> inputs;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <memory>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
class Case : public Expression {
public:
using Branch = std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>;
Case(type::Type type_, std::vector<Branch> branches_, std::unique_ptr<Expression> otherwise_)
: Expression(type_), branches(std::move(branches_)), otherwise(std::move(otherwise_)) {
}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
private:
std::vector<Branch> branches;
std::unique_ptr<Expression> otherwise;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,17 @@
#pragma once
#include <mbgl/style/expression/type.hpp>
#include <mbgl/util/optional.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
namespace type {
optional<std::string> checkSubtype(const Type& expected, const Type& t);
} // namespace type
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <memory>
#include <map>
namespace mbgl {
namespace style {
namespace expression {
class Coalesce : public Expression {
public:
using Args = std::vector<std::unique_ptr<Expression>>;
Coalesce(const type::Type& type_, Args args_) :
Expression(type_),
args(std::move(args_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
std::size_t getLength() const {
return args.size();
}
Expression* getChild(std::size_t i) const {
return args.at(i).get();
}
private:
Args args;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
/**
* 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.
*/
class Coercion : public Expression {
public:
Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_);
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
private:
EvaluationResult (*coerceSingleValue) (const Value& v);
std::vector<std::unique_ptr<Expression>> inputs;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,138 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/expression/value.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/variant.hpp>
#include <memory>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
/*
CompoundExpression provides a mechanism for implementing an expression
simply by providing a list of pure functions of the form
(const T0& arg0, const T1& arg1, ...) -> Result<U> where T0, T1, ..., U are
member types of mbgl::style::expression::Value.
The majority of expressions specified in the style-spec are implemented in
this fashion (see compound_expression.cpp).
*/
/*
Represents the parameter list for an expression that takes an arbitrary
number of arguments (of a specific type).
*/
struct VarargsType { type::Type type; };
template <typename T>
struct Varargs : std::vector<T> { using std::vector<T>::vector; };
namespace detail {
// Base class for the Signature<Fn> structs that are used to determine the
// each CompoundExpression definition's type::Type data from the type of its
// "evaluate" function.
struct SignatureBase {
SignatureBase(type::Type result_, variant<std::vector<type::Type>, VarargsType> params_) :
result(std::move(result_)),
params(std::move(params_))
{}
virtual ~SignatureBase() = default;
virtual std::unique_ptr<Expression> makeExpression(const std::string& name, std::vector<std::unique_ptr<Expression>>) const = 0;
type::Type result;
variant<std::vector<type::Type>, VarargsType> params;
};
} // namespace detail
/*
Common base class for CompoundExpression<Signature> instances. Used to
allow downcasting (and access to things like name & parameter list) during
an Expression tree traversal.
*/
class CompoundExpressionBase : public Expression {
public:
CompoundExpressionBase(std::string name_, const detail::SignatureBase& signature) :
Expression(signature.result),
name(std::move(name_)),
params(signature.params)
{}
std::string getName() const { return name; }
optional<std::size_t> getParameterCount() const {
return params.match(
[&](const VarargsType&) { return optional<std::size_t>(); },
[&](const std::vector<type::Type>& p) -> optional<std::size_t> { return p.size(); }
);
}
private:
std::string name;
variant<std::vector<type::Type>, VarargsType> params;
};
template <typename Signature>
class CompoundExpression : public CompoundExpressionBase {
public:
using Args = typename Signature::Args;
CompoundExpression(const std::string& name_,
Signature signature_,
typename Signature::Args args_) :
CompoundExpressionBase(name_, signature_),
signature(signature_),
args(std::move(args_))
{}
EvaluationResult evaluate(const EvaluationContext& evaluationParams) const override {
return signature.apply(evaluationParams, args);
}
void eachChild(const std::function<void(const Expression&)>& visit) const override {
for (const std::unique_ptr<Expression>& e : args) {
visit(*e);
}
}
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const CompoundExpression*>(&e)) {
return getName() == rhs->getName() && Expression::childrenEqual(args, rhs->args);
}
return false;
}
private:
Signature signature;
typename Signature::Args args;
};
/*
Holds the map of expression name => implementation (which is just one or
more evaluation functions, each wrapped in a Signature struct).
*/
struct CompoundExpressionRegistry {
using Definition = std::vector<std::unique_ptr<detail::SignatureBase>>;
static std::unordered_map<std::string, Definition> definitions;
};
ParseResult parseCompoundExpression(const std::string name, const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
ParseResult createCompoundExpression(const std::string& name,
const CompoundExpressionRegistry::Definition& definition,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx);
ParseResult createCompoundExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,169 @@
#pragma once
#include <array>
#include <vector>
#include <memory>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/expression/value.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
namespace mbgl {
class GeometryTileFeature;
namespace style {
namespace expression {
class EvaluationError {
public:
std::string message;
};
class EvaluationContext {
public:
EvaluationContext(float zoom_) : zoom(zoom_), feature(nullptr) {}
EvaluationContext(GeometryTileFeature const * feature_) : zoom(optional<float>()), feature(feature_) {}
EvaluationContext(float zoom_, GeometryTileFeature const * feature_) :
zoom(zoom_), feature(feature_)
{}
EvaluationContext(optional<float> zoom_, GeometryTileFeature const * feature_, optional<double> heatmapDensity_) :
zoom(std::move(zoom_)), feature(feature_), heatmapDensity(std::move(heatmapDensity_))
{}
optional<float> zoom;
GeometryTileFeature const * feature;
optional<double> heatmapDensity;
};
template<typename T>
class Result : private variant<EvaluationError, T> {
public:
using variant<EvaluationError, T>::variant;
using Value = T;
explicit operator bool () const {
return this->template is<T>();
}
// optional does some type trait magic for this one, so this might
// be problematic as is.
const T* operator->() const {
assert(this->template is<T>());
return std::addressof(this->template get<T>());
}
T* operator->() {
assert(this->template is<T>());
return std::addressof(this->template get<T>());
}
T& operator*() {
assert(this->template is<T>());
return this->template get<T>();
}
const T& operator*() const {
assert(this->template is<T>());
return this->template get<T>();
}
const EvaluationError& error() const {
assert(this->template is<EvaluationError>());
return this->template get<EvaluationError>();
}
};
class EvaluationResult : public Result<Value> {
public:
using Result::Result; // NOLINT
EvaluationResult(const std::array<double, 4>& arr) :
Result(toExpressionValue(arr))
{}
// used only for the special (private) "error" expression
EvaluationResult(const type::ErrorType&) {
assert(false);
}
};
/*
Expression is an abstract class that serves as an interface and base class
for particular expression implementations.
CompoundExpression implements the majority of expressions in the spec by
inferring the argument and output from a simple function (const T0& arg0,
const T1& arg1, ...) -> Result<U> where T0, T1, ..., U are member types of
mbgl::style::expression::Value.
The other Expression subclasses (Let, Curve, Match, etc.) exist in order to
implement expressions that need specialized parsing, type checking, or
evaluation logic that can't be handled by CompoundExpression's inference
mechanism.
Each Expression subclass also provides a static
ParseResult ExpressionClass::parse(const V&, ParsingContext),
which handles parsing a style-spec JSON representation of the expression.
*/
class Expression {
public:
Expression(type::Type type_) : type(std::move(type_)) {}
virtual ~Expression() = default;
virtual EvaluationResult evaluate(const EvaluationContext& params) const = 0;
virtual void eachChild(const std::function<void(const Expression&)>&) const = 0;
virtual bool operator==(const Expression&) const = 0;
bool operator!=(const Expression& rhs) const {
return !operator==(rhs);
}
type::Type getType() const { return type; };
EvaluationResult evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const;
protected:
template <typename T>
static bool childrenEqual(const T& lhs, const T& rhs) {
if (lhs.size() != rhs.size()) return false;
for (auto leftChild = lhs.begin(), rightChild = rhs.begin();
leftChild != lhs.end();
leftChild++, rightChild++)
{
if (!Expression::childEqual(*leftChild, *rightChild)) return false;
}
return true;
}
static bool childEqual(const std::unique_ptr<Expression>& lhs, const std::unique_ptr<Expression>& rhs) {
return *lhs == *rhs;
}
template <typename T>
static bool childEqual(const std::pair<T, std::unique_ptr<Expression>>& lhs,
const std::pair<T, std::unique_ptr<Expression>>& rhs) {
return lhs.first == rhs.first && *(lhs.second) == *(rhs.second);
}
template <typename T>
static bool childEqual(const std::pair<T, std::shared_ptr<Expression>>& lhs,
const std::pair<T, std::shared_ptr<Expression>>& rhs) {
return lhs.first == rhs.first && *(lhs.second) == *(rhs.second);
}
static bool childEqual(const std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>& lhs,
const std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>& rhs) {
return *(lhs.first) == *(rhs.first) && *(lhs.second) == *(rhs.second);
}
private:
type::Type type;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,20 @@
#pragma once
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/optional.hpp>
namespace mbgl {
namespace style {
namespace expression {
optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e);
variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,18 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/util/range.hpp>
#include <memory>
#include <map>
namespace mbgl {
namespace style {
namespace expression {
// Return the smallest range of stops that covers the interval [lower, upper]
Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops,
const double lower, const double upper);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,177 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/get_covering_stops.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/util/interpolate.hpp>
#include <mbgl/util/range.hpp>
#include <mbgl/util/unitbezier.hpp>
#include <memory>
#include <map>
namespace mbgl {
namespace style {
namespace expression {
class ExponentialInterpolator {
public:
ExponentialInterpolator(double base_) : base(base_) {}
double base;
double interpolationFactor(const Range<double>& inputLevels, const double input) const {
return util::interpolationFactor(base,
Range<float> {
static_cast<float>(inputLevels.min),
static_cast<float>(inputLevels.max)
},
input);
}
bool operator==(const ExponentialInterpolator& rhs) const {
return base == rhs.base;
}
};
class CubicBezierInterpolator {
public:
CubicBezierInterpolator(double x1_, double y1_, double x2_, double y2_) : ub(x1_, y1_, x2_, y2_) {}
double interpolationFactor(const Range<double>& inputLevels, const double input) const {
return ub.solve(input / (inputLevels.max - inputLevels.min), 1e-6);
}
bool operator==(const CubicBezierInterpolator& rhs) const {
return ub == rhs.ub;
}
util::UnitBezier ub;
};
ParseResult parseInterpolate(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
class InterpolateBase : public Expression {
public:
using Interpolator = variant<ExponentialInterpolator, CubicBezierInterpolator>;
InterpolateBase(const type::Type& type_,
Interpolator interpolator_,
std::unique_ptr<Expression> input_,
std::map<double, std::unique_ptr<Expression>> stops_
) : Expression(type_),
interpolator(std::move(interpolator_)),
input(std::move(input_)),
stops(std::move(stops_))
{}
const std::unique_ptr<Expression>& getInput() const { return input; }
void eachChild(const std::function<void(const Expression&)>& visit) const override {
visit(*input);
for (const std::pair<const double, const std::unique_ptr<Expression>&>& stop : stops) {
visit(*stop.second);
}
}
// Return the smallest range of stops that covers the interval [lower, upper]
Range<float> getCoveringStops(const double lower, const double upper) const {
return ::mbgl::style::expression::getCoveringStops(stops, lower, upper);
}
double interpolationFactor(const Range<double>& inputLevels, const double inputValue) const {
return interpolator.match(
[&](const auto& interp) { return interp.interpolationFactor(inputLevels, inputValue); }
);
}
protected:
const Interpolator interpolator;
const std::unique_ptr<Expression> input;
const std::map<double, std::unique_ptr<Expression>> stops;
};
template <typename T>
class Interpolate : public InterpolateBase {
public:
Interpolate(type::Type type_,
Interpolator interpolator_,
std::unique_ptr<Expression> input_,
std::map<double, std::unique_ptr<Expression>> stops_
) : InterpolateBase(std::move(type_), std::move(interpolator_), std::move(input_), std::move(stops_))
{
static_assert(util::Interpolatable<T>::value, "Interpolate expression requires an interpolatable value type.");
}
EvaluationResult evaluate(const EvaluationContext& params) const override {
const EvaluationResult evaluatedInput = input->evaluate(params);
if (!evaluatedInput) { return evaluatedInput.error(); }
float x = *fromExpressionValue<float>(*evaluatedInput);
if (stops.empty()) {
return EvaluationError { "No stops in exponential curve." };
}
auto it = stops.upper_bound(x);
if (it == stops.end()) {
return stops.rbegin()->second->evaluate(params);
} else if (it == stops.begin()) {
return stops.begin()->second->evaluate(params);
} else {
float t = interpolationFactor({ std::prev(it)->first, it->first }, x);
if (t == 0.0f) {
return std::prev(it)->second->evaluate(params);
}
if (t == 1.0f) {
return it->second->evaluate(params);
}
EvaluationResult lower = std::prev(it)->second->evaluate(params);
if (!lower) {
return lower.error();
}
EvaluationResult upper = it->second->evaluate(params);
if (!upper) {
return upper.error();
}
if (!lower->is<T>()) {
return EvaluationError {
"Expected value to be of type " + toString(valueTypeToExpressionType<T>()) +
", but found " + toString(typeOf(*lower)) + " instead."
};
}
if (!upper->is<T>()) {
return EvaluationError {
"Expected value to be of type " + toString(valueTypeToExpressionType<T>()) +
", but found " + toString(typeOf(*upper)) + " instead."
};
}
return util::interpolate(lower->get<T>(), upper->get<T>(), t);
}
}
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const Interpolate*>(&e)) {
if (interpolator != rhs->interpolator ||
*input != *(rhs->input) ||
stops.size() != rhs->stops.size())
{
return false;
}
return Expression::childrenEqual(stops, rhs->stops);
}
return false;
}
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
namespace mbgl {
namespace style {
namespace expression {
template <typename T>
bool isGlobalPropertyConstant(const Expression& expression, const T& properties) {
if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
for (const std::string& property : properties) {
if (e->getName() == property) {
return false;
}
}
}
bool isConstant = true;
expression.eachChild([&](const Expression& e) {
if (isConstant && !isGlobalPropertyConstant(e, properties)) {
isConstant = false;
}
});
return isConstant;
};
bool isFeatureConstant(const Expression& expression);
bool isZoomConstant(const Expression& e);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,13 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
namespace mbgl {
namespace style {
namespace expression {
bool isExpression(const conversion::Convertible& value);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,72 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
#include <map>
namespace mbgl {
namespace style {
namespace expression {
class Let : public Expression {
public:
using Bindings = std::map<std::string, std::shared_ptr<Expression>>;
Let(Bindings bindings_, std::unique_ptr<Expression> result_) :
Expression(result_->getType()),
bindings(std::move(bindings_)),
result(std::move(result_))
{}
static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>&) const override;
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const Let*>(&e)) {
return *result == *(rhs->result);
}
return false;
}
Expression* getResult() const {
return result.get();
}
private:
Bindings bindings;
std::unique_ptr<Expression> result;
};
class Var : public Expression {
public:
Var(std::string name_, std::shared_ptr<Expression> value_) :
Expression(value_->getType()),
name(std::move(name_)),
value(value_)
{}
static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>&) const override;
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const Var*>(&e)) {
return *value == *(rhs->value);
}
return false;
}
private:
std::string name;
std::shared_ptr<Expression> value;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class Literal : public Expression {
public:
Literal(Value value_) : Expression(typeOf(value_)), value(value_) {}
Literal(type::Array type_, std::vector<Value> value_) : Expression(type_), value(value_) {}
EvaluationResult evaluate(const EvaluationContext&) const override {
return value;
}
static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
void eachChild(const std::function<void(const Expression&)>&) const override {}
bool operator==(const Expression& e) const override {
if (auto rhs = dynamic_cast<const Literal*>(&e)) {
return value == rhs->value;
}
return false;
}
private:
Value value;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
template <typename T>
class Match : public Expression {
public:
using Branches = std::unordered_map<T, std::shared_ptr<Expression>>;
Match(type::Type type_,
std::unique_ptr<Expression> input_,
Branches branches_,
std::unique_ptr<Expression> otherwise_
) : Expression(type_),
input(std::move(input_)),
branches(std::move(branches_)),
otherwise(std::move(otherwise_))
{}
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
EvaluationResult evaluate(const EvaluationContext& params) const override;
private:
std::unique_ptr<Expression> input;
Branches branches;
std::unique_ptr<Expression> otherwise;
};
ParseResult parseMatch(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,147 @@
#pragma once
#include <mbgl/util/optional.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/conversion.hpp>
#include <map>
#include <string>
#include <vector>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class Expression;
struct ParsingError {
std::string message;
std::string key;
bool operator==(const ParsingError& rhs) const { return message == rhs.message && key == rhs.key; }
};
using ParseResult = optional<std::unique_ptr<Expression>>;
namespace detail {
class Scope {
public:
Scope(const std::map<std::string, std::shared_ptr<Expression>>& bindings_, std::shared_ptr<Scope> parent_ = nullptr) :
bindings(bindings_),
parent(std::move(parent_))
{}
const std::map<std::string, std::shared_ptr<Expression>>& bindings;
std::shared_ptr<Scope> parent;
optional<std::shared_ptr<Expression>> get(const std::string& name) {
auto it = bindings.find(name);
if (it != bindings.end()) {
return {it->second};
} else if (parent) {
return parent->get(name);
} else {
return optional<std::shared_ptr<Expression>>();
}
}
};
} // namespace detail
class ParsingContext {
public:
ParsingContext() : errors(std::make_shared<std::vector<ParsingError>>()) {}
ParsingContext(std::string key_) : key(std::move(key_)), errors(std::make_shared<std::vector<ParsingError>>()) {}
explicit ParsingContext(optional<type::Type> expected_)
: expected(std::move(expected_)),
errors(std::make_shared<std::vector<ParsingError>>())
{}
ParsingContext(ParsingContext&&) = default;
ParsingContext(const ParsingContext&) = delete;
ParsingContext& operator=(const ParsingContext&) = delete;
std::string getKey() const { return key; }
optional<type::Type> getExpected() const { return expected; }
const std::vector<ParsingError>& getErrors() const { return *errors; }
/*
Parse the given style-spec JSON value into an Expression object.
Specifically, this function is responsible for determining the expression
type (either Literal, or the one named in value[0]) and dispatching to the
appropriate ParseXxxx::parse(const V&, ParsingContext) method.
*/
ParseResult parse(const mbgl::style::conversion::Convertible& value);
/*
Parse a child expression.
*/
ParseResult parse(const mbgl::style::conversion::Convertible&,
std::size_t,
optional<type::Type> = {});
/*
Parse a child expression.
*/
ParseResult parse(const mbgl::style::conversion::Convertible&,
std::size_t index,
optional<type::Type>,
const std::map<std::string, std::shared_ptr<Expression>>&);
/*
Check whether `t` is a subtype of `expected`, collecting an error if not.
*/
optional<std::string> checkType(const type::Type& t);
optional<std::shared_ptr<Expression>> getBinding(const std::string name) {
if (!scope) return optional<std::shared_ptr<Expression>>();
return scope->get(name);
}
void error(std::string message) {
errors->push_back({message, key});
}
void error(std::string message, std::size_t child) {
errors->push_back({message, key + "[" + std::to_string(child) + "]"});
}
void error(std::string message, std::size_t child, std::size_t grandchild) {
errors->push_back({message, key + "[" + std::to_string(child) + "][" + std::to_string(grandchild) + "]"});
}
void appendErrors(ParsingContext&& ctx) {
errors->reserve(errors->size() + ctx.errors->size());
std::move(ctx.errors->begin(), ctx.errors->end(), std::inserter(*errors, errors->end()));
ctx.errors->clear();
}
void clearErrors() {
errors->clear();
}
private:
ParsingContext(std::string key_,
std::shared_ptr<std::vector<ParsingError>> errors_,
optional<type::Type> expected_,
std::shared_ptr<detail::Scope> scope_)
: key(std::move(key_)),
expected(std::move(expected_)),
scope(std::move(scope_)),
errors(std::move(errors_))
{}
std::string key;
optional<type::Type> expected;
std::shared_ptr<detail::Scope> scope;
std::shared_ptr<std::vector<ParsingError>> errors;
};
using ParseFunction = ParseResult (*)(const conversion::Convertible&, ParsingContext&);
using ExpressionRegistry = std::unordered_map<std::string, ParseFunction>;
const ExpressionRegistry& getExpressionRegistry();
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/util/range.hpp>
#include <memory>
#include <map>
namespace mbgl {
namespace style {
namespace expression {
class Step : public Expression {
public:
Step(const type::Type& type_,
std::unique_ptr<Expression> input_,
std::map<double, std::unique_ptr<Expression>> stops_
) : Expression(type_),
input(std::move(input_)),
stops(std::move(stops_))
{}
EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
const std::unique_ptr<Expression>& getInput() const { return input; }
Range<float> getCoveringStops(const double lower, const double upper) const;
bool operator==(const Expression& e) const override;
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
private:
const std::unique_ptr<Expression> input;
const std::map<double, std::unique_ptr<Expression>> stops;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,111 @@
#pragma once
#include <mbgl/util/optional.hpp>
#include <mbgl/util/variant.hpp>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
namespace type {
template <class T>
std::string toString(const T& t);
struct NullType {
constexpr NullType() = default;
std::string getName() const { return "null"; }
bool operator==(const NullType&) const { return true; }
};
struct NumberType {
constexpr NumberType() = default;
std::string getName() const { return "number"; }
bool operator==(const NumberType&) const { return true; }
};
struct BooleanType {
constexpr BooleanType() = default;
std::string getName() const { return "boolean"; }
bool operator==(const BooleanType&) const { return true; }
};
struct StringType {
constexpr StringType() = default;
std::string getName() const { return "string"; }
bool operator==(const StringType&) const { return true; }
};
struct ColorType {
constexpr ColorType() = default;
std::string getName() const { return "color"; }
bool operator==(const ColorType&) const { return true; }
};
struct ObjectType {
constexpr ObjectType() = default;
std::string getName() const { return "object"; }
bool operator==(const ObjectType&) const { return true; }
};
struct ErrorType {
constexpr ErrorType() = default;
std::string getName() const { return "error"; }
bool operator==(const ErrorType&) const { return true; }
};
struct ValueType {
constexpr ValueType() = default;
std::string getName() const { return "value"; }
bool operator==(const ValueType&) const { return true; }
};
constexpr NullType Null;
constexpr NumberType Number;
constexpr StringType String;
constexpr BooleanType Boolean;
constexpr ColorType Color;
constexpr ValueType Value;
constexpr ObjectType Object;
constexpr ErrorType Error;
struct Array;
using Type = variant<
NullType,
NumberType,
BooleanType,
StringType,
ColorType,
ObjectType,
ValueType,
mapbox::util::recursive_wrapper<Array>,
ErrorType>;
struct Array {
explicit Array(Type itemType_) : itemType(std::move(itemType_)) {}
Array(Type itemType_, std::size_t N_) : itemType(std::move(itemType_)), N(N_) {}
Array(Type itemType_, optional<std::size_t> N_) : itemType(std::move(itemType_)), N(std::move(N_)) {}
std::string getName() const {
if (N) {
return "array<" + toString(itemType) + ", " + std::to_string(*N) + ">";
} else if (itemType == Value) {
return "array";
} else {
return "array<" + toString(itemType) + ">";
}
}
bool operator==(const Array& rhs) const { return itemType == rhs.itemType && N == rhs.N; }
Type itemType;
optional<std::size_t> N;
};
template <class T>
std::string toString(const T& type) { return type.match([&] (const auto& t) { return t.getName(); }); }
} // namespace type
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,153 @@
#pragma once
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/position.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/util/variant.hpp>
#include <array>
#include <vector>
namespace mbgl {
namespace style {
namespace expression {
struct Value;
using ValueBase = variant<
NullValue,
bool,
double,
std::string,
Color,
mapbox::util::recursive_wrapper<std::vector<Value>>,
mapbox::util::recursive_wrapper<std::unordered_map<std::string, Value>>>;
struct Value : ValueBase {
using ValueBase::ValueBase;
// Javascript's Number.MAX_SAFE_INTEGER
static uint64_t maxSafeInteger() { return 9007199254740991ULL; }
static bool isSafeInteger(uint64_t x) { return x <= maxSafeInteger(); };
static bool isSafeInteger(int64_t x) {
return static_cast<uint64_t>(x > 0 ? x : -x) <= maxSafeInteger();
}
static bool isSafeInteger(double x) {
return static_cast<uint64_t>(x > 0 ? x : -x) <= maxSafeInteger();
}
};
constexpr NullValue Null = NullValue();
type::Type typeOf(const Value& value);
std::string stringify(const Value& value);
/*
Returns a Type object representing the expression type that corresponds to
the value type T. (Specialized for primitives and specific array types in
the .cpp.)
*/
template <typename T>
type::Type valueTypeToExpressionType();
/*
Conversions between style value types and expression::Value
*/
// no-op overloads
Value toExpressionValue(const Value&);
// T = Value (just wrap in optional)
template <typename T>
std::enable_if_t<std::is_same<T, Value>::value,
optional<T>> fromExpressionValue(const Value& v)
{
return optional<T>(v);
}
// T = member type of Value
template <typename T>
std::enable_if_t< std::is_convertible<T, Value>::value && !std::is_same<T, Value>::value,
optional<T>> fromExpressionValue(const Value& v)
{
return v.template is<T>() ? v.template get<T>() : optional<T>();
}
// real conversions
template <typename T, typename Enable = std::enable_if_t< !std::is_convertible<T, Value>::value >>
Value toExpressionValue(const T& value);
template <typename T>
std::enable_if_t< !std::is_convertible<T, Value>::value,
optional<T>> fromExpressionValue(const Value& v);
template <class T, class Enable = void>
struct ValueConverter {
using ExpressionType = T;
static Value toExpressionValue(const T& value) {
return Value(value);
}
static optional<T> fromExpressionValue(const Value& value) {
return value.template is<T>() ? value.template get<T>() : optional<T>();
}
};
template <>
struct ValueConverter<float> {
using ExpressionType = double;
static type::Type expressionType() { return type::Number; }
static Value toExpressionValue(const float value);
static optional<float> fromExpressionValue(const Value& value);
};
template<>
struct ValueConverter<mbgl::Value> {
static Value toExpressionValue(const mbgl::Value& value);
};
template <typename T, std::size_t N>
struct ValueConverter<std::array<T, N>> {
using ExpressionType = std::vector<Value>;
static type::Type expressionType() {
return type::Array(valueTypeToExpressionType<T>(), N);
}
static Value toExpressionValue(const std::array<T, N>& value);
static optional<std::array<T, N>> fromExpressionValue(const Value& value);
};
template <typename T>
struct ValueConverter<std::vector<T>> {
using ExpressionType = std::vector<Value>;
static type::Type expressionType() {
return type::Array(valueTypeToExpressionType<T>());
}
static Value toExpressionValue(const std::vector<T>& value);
static optional<std::vector<T>> fromExpressionValue(const Value& value);
};
template <>
struct ValueConverter<Position> {
using ExpressionType = std::vector<Value>;
static type::Type expressionType() { return type::Array(type::Number, 3); }
static Value toExpressionValue(const mbgl::style::Position& value);
static optional<Position> fromExpressionValue(const Value& v);
};
template <typename T>
struct ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >> {
using ExpressionType = std::string;
static type::Type expressionType() { return type::String; }
static Value toExpressionValue(const T& value);
static optional<T> fromExpressionValue(const Value& value);
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -1,10 +1,18 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/expression/find_zoom_curve.hpp>
#include <mbgl/style/expression/value.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/exponential_stops.hpp>
#include <mbgl/style/function/interval_stops.hpp>
#include <mbgl/util/interpolate.hpp>
#include <mbgl/util/variant.hpp>
namespace mbgl {
namespace style {
@@ -18,24 +26,60 @@ public:
IntervalStops<T>>,
variant<
IntervalStops<T>>>;
CameraFunction(Stops stops_)
: stops(std::move(stops_)) {
CameraFunction(std::unique_ptr<expression::Expression> expression_)
: expression(std::move(expression_)),
zoomCurve(expression::findZoomCurveChecked(expression.get()))
{
assert(!expression::isZoomConstant(*expression));
assert(expression::isFeatureConstant(*expression));
}
CameraFunction(Stops stops_)
: stops(std::move(stops_)),
expression(stops.match([&] (const auto& s) {
return expression::Convert::toExpression(s);
})),
zoomCurve(expression::findZoomCurveChecked(expression.get()))
{}
T evaluate(float zoom) const {
return stops.match([&] (const auto& s) {
return s.evaluate(zoom).value_or(T());
});
const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(zoom, nullptr));
if (result) {
const optional<T> typed = expression::fromExpressionValue<T>(*result);
return typed ? *typed : T();
}
return T();
}
float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const {
return zoomCurve.match(
[&](const expression::InterpolateBase* z) {
return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue);
},
[&](const expression::Step*) { return 0.0f; }
);
}
Range<float> getCoveringStops(const float lower, const float upper) const {
return zoomCurve.match(
[&](auto z) { return z->getCoveringStops(lower, upper); }
);
}
friend bool operator==(const CameraFunction& lhs,
const CameraFunction& rhs) {
return lhs.stops == rhs.stops;
return *lhs.expression == *rhs.expression;
}
Stops stops;
bool useIntegerZoom = false;
// retained for compatibility with pre-expression function API
Stops stops;
private:
std::shared_ptr<expression::Expression> expression;
const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve;
};
} // namespace style

View File

@@ -1,5 +1,12 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/expression/find_zoom_curve.hpp>
#include <mbgl/style/expression/value.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/composite_exponential_stops.hpp>
#include <mbgl/style/function/composite_interval_stops.hpp>
#include <mbgl/style/function/composite_categorical_stops.hpp>
@@ -43,110 +50,71 @@ public:
CompositeIntervalStops<T>,
CompositeCategoricalStops<T>>>;
CompositeFunction(std::unique_ptr<expression::Expression> expression_)
: expression(std::move(expression_)),
zoomCurve(expression::findZoomCurveChecked(expression.get()))
{
assert(!expression::isZoomConstant(*expression));
assert(!expression::isFeatureConstant(*expression));
}
CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
: property(std::move(property_)),
stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)) {
}
: property(std::move(property_)),
stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)),
expression(stops.match([&] (const auto& s) {
return expression::Convert::toExpression(property, s);
})),
zoomCurve(expression::findZoomCurveChecked(expression.get()))
{}
struct CoveringRanges {
float zoom;
Range<float> coveringZoomRange;
Range<InnerStops> coveringStopsRange;
};
// Return the relevant stop zoom values and inner stops that bracket a given zoom level. This
// is the first step toward evaluating the function, and is used for in the course of both partial
// evaluation of data-driven paint properties, and full evaluation of data-driven layout properties.
CoveringRanges coveringRanges(float zoom) const {
return stops.match(
[&] (const auto& s) {
assert(!s.stops.empty());
auto minIt = s.stops.lower_bound(zoom);
auto maxIt = s.stops.upper_bound(zoom);
// lower_bound yields first element >= zoom, but we want the *last*
// element <= zoom, so if we found a stop > zoom, back up by one.
if (minIt != s.stops.begin() && minIt != s.stops.end() && minIt->first > zoom) {
minIt--;
}
return CoveringRanges {
zoom,
Range<float> {
minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first,
maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first
},
Range<InnerStops> {
s.innerStops(minIt == s.stops.end() ? s.stops.rbegin()->second : minIt->second),
s.innerStops(maxIt == s.stops.end() ? s.stops.rbegin()->second : maxIt->second)
}
};
}
);
}
// Given a range of zoom values (typically two adjacent integer zoom levels, e.g. 5.0 and 6.0),
// return the covering ranges for both. This is used in the course of partial evaluation for
// data-driven paint properties.
Range<CoveringRanges> rangeOfCoveringRanges(Range<float> zoomRange) {
return Range<CoveringRanges> {
coveringRanges(zoomRange.min),
coveringRanges(zoomRange.max)
};
}
// Given the covering ranges for range of zoom values (typically two adjacent integer zoom levels,
// e.g. 5.0 and 6.0), and a feature, return the results of fully evaluating the function for that
// feature at each of the two zoom levels. These two results are what go into the paint vertex buffers
// for vertices associated with this feature. The shader will interpolate between them at render time.
// Return the range obtained by evaluating the function at each of the zoom levels in zoomRange
template <class Feature>
Range<T> evaluate(const Range<CoveringRanges>& ranges, const Feature& feature, T finalDefaultValue) {
optional<Value> value = feature.getValue(property);
if (!value) {
return Range<T> {
defaultValue.value_or(finalDefaultValue),
defaultValue.value_or(finalDefaultValue)
};
}
Range<T> evaluate(const Range<float>& zoomRange, const Feature& feature, T finalDefaultValue) {
return Range<T> {
evaluateFinal(ranges.min, *value, finalDefaultValue),
evaluateFinal(ranges.max, *value, finalDefaultValue)
evaluate(zoomRange.min, feature, finalDefaultValue),
evaluate(zoomRange.max, feature, finalDefaultValue)
};
}
// Fully evaluate the function for a zoom value and feature. This is used when evaluating data-driven
// layout properties.
template <class Feature>
T evaluate(float zoom, const Feature& feature, T finalDefaultValue) const {
optional<Value> value = feature.getValue(property);
if (!value) {
return defaultValue.value_or(finalDefaultValue);
const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext({zoom}, &feature));
if (result) {
const optional<T> typed = expression::fromExpressionValue<T>(*result);
return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue;
}
return evaluateFinal(coveringRanges(zoom), *value, finalDefaultValue);
return defaultValue ? *defaultValue : finalDefaultValue;
}
float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const {
return zoomCurve.match(
[&](const expression::InterpolateBase* z) {
return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue);
},
[&](const expression::Step*) { return 0.0f; }
);
}
Range<float> getCoveringStops(const float lower, const float upper) const {
return zoomCurve.match(
[&](auto z) { return z->getCoveringStops(lower, upper); }
);
}
friend bool operator==(const CompositeFunction& lhs,
const CompositeFunction& rhs) {
return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
== std::tie(rhs.property, rhs.stops, rhs.defaultValue);
return *lhs.expression == *rhs.expression;
}
std::string property;
Stops stops;
optional<T> defaultValue;
bool useIntegerZoom = false;
private:
T evaluateFinal(const CoveringRanges& ranges, const Value& value, T finalDefaultValue) const {
auto eval = [&] (const auto& s) {
return s.evaluate(value).value_or(defaultValue.value_or(finalDefaultValue));
};
return util::interpolate(
ranges.coveringStopsRange.min.match(eval),
ranges.coveringStopsRange.max.match(eval),
util::interpolationFactor(1.0f, ranges.coveringZoomRange, ranges.zoom));
}
std::shared_ptr<expression::Expression> expression;
const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve;
};
} // namespace style

View File

@@ -0,0 +1,351 @@
#pragma once
#include <mbgl/style/expression/array_assertion.hpp>
#include <mbgl/style/expression/assertion.hpp>
#include <mbgl/style/expression/case.hpp>
#include <mbgl/style/expression/coalesce.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/style/expression/match.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/function/exponential_stops.hpp>
#include <mbgl/style/function/interval_stops.hpp>
#include <mbgl/style/function/categorical_stops.hpp>
#include <mbgl/style/function/composite_exponential_stops.hpp>
#include <mbgl/style/function/composite_interval_stops.hpp>
#include <mbgl/style/function/composite_categorical_stops.hpp>
#include <mbgl/style/function/identity_stops.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/style/types.hpp>
#include <string>
namespace mbgl {
namespace style {
namespace expression {
namespace detail {
class ErrorExpression : public Expression {
public:
ErrorExpression(std::string message_) : Expression(type::Error), message(std::move(message_)) {}
void eachChild(const std::function<void(const Expression&)>&) const override {}
bool operator==(const Expression& e) const override {
return dynamic_cast<const ErrorExpression*>(&e);
}
EvaluationResult evaluate(const EvaluationContext&) const override {
return EvaluationError{message};
}
private:
std::string message;
};
} // namespace detail
// Create expressions representing 'classic' (i.e. stop-based) style functions
struct Convert {
template <typename T>
static std::unique_ptr<Literal> makeLiteral(const T& value) {
return std::make_unique<Literal>(Value(toExpressionValue(value)));
}
static std::unique_ptr<Expression> makeGet(type::Type type, const std::string& property) {
ParsingContext ctx;
std::vector<std::unique_ptr<Expression>> getArgs;
getArgs.push_back(makeLiteral(property));
ParseResult get = createCompoundExpression("get", std::move(getArgs), ctx);
assert(get);
assert(ctx.getErrors().size() == 0);
std::vector<std::unique_ptr<Expression>> assertionArgs;
assertionArgs.push_back(std::move(*get));
return std::make_unique<Assertion>(type, std::move(assertionArgs));
}
static std::unique_ptr<Expression> makeZoom() {
ParsingContext ctx;
ParseResult zoom = createCompoundExpression("zoom", std::vector<std::unique_ptr<Expression>>(), ctx);
assert(zoom);
assert(ctx.getErrors().size() == 0);
return std::move(*zoom);
}
static std::unique_ptr<Expression> makeError(std::string message) {
return std::make_unique<detail::ErrorExpression>(message);
}
template <typename OutputType>
static ParseResult makeInterpolate(type::Type type,
std::unique_ptr<Expression> input,
std::map<double, std::unique_ptr<Expression>> convertedStops,
typename Interpolate<OutputType>::Interpolator interpolator)
{
ParseResult curve = ParseResult(std::make_unique<Interpolate<OutputType>>(
std::move(type),
std::move(interpolator),
std::move(input),
std::move(convertedStops)
));
assert(curve);
return std::move(*curve);
}
template <typename Key>
static ParseResult makeMatch(type::Type type,
std::unique_ptr<Expression> input,
std::map<CategoricalValue, std::unique_ptr<Expression>> stops) {
// match expression
typename Match<Key>::Branches branches;
for(auto it = stops.begin(); it != stops.end(); it++) {
assert(it->first.template is<Key>());
Key key = it->first.template get<Key>();
branches.emplace(
std::move(key),
std::move(it->second)
);
}
return ParseResult(std::make_unique<Match<Key>>(std::move(type),
std::move(input),
std::move(branches),
makeError("No matching label")));
}
static ParseResult makeCase(type::Type type,
std::unique_ptr<Expression> input,
std::map<CategoricalValue, std::unique_ptr<Expression>> stops) {
// case expression
std::vector<typename Case::Branch> branches;
auto it = stops.find(true);
std::unique_ptr<Expression> true_case = it == stops.end() ?
makeError("No matching label") :
std::move(it->second);
it = stops.find(false);
std::unique_ptr<Expression> false_case = it == stops.end() ?
makeError("No matching label") :
std::move(it->second);
branches.push_back(std::make_pair(std::move(input), std::move(true_case)));
return ParseResult(std::make_unique<Case>(std::move(type), std::move(branches), std::move(false_case)));
}
template <typename T>
static ParseResult fromCategoricalStops(std::map<CategoricalValue, T> stops, const std::string& property) {
assert(stops.size() > 0);
std::map<CategoricalValue, std::unique_ptr<Expression>> convertedStops;
for(const std::pair<CategoricalValue, T>& stop : stops) {
convertedStops.emplace(
stop.first,
makeLiteral(stop.second)
);
}
type::Type type = valueTypeToExpressionType<T>();
const CategoricalValue& firstKey = stops.begin()->first;
return firstKey.match(
[&](bool) {
return makeCase(type, makeGet(type::Boolean, property), std::move(convertedStops));
},
[&](const std::string&) {
return makeMatch<std::string>(type, makeGet(type::String, property), std::move(convertedStops));
},
[&](int64_t) {
return makeMatch<int64_t>(type, makeGet(type::Number, property), std::move(convertedStops));
}
);
}
template <typename T>
static std::map<double, std::unique_ptr<Expression>> convertStops(const std::map<float, T>& stops) {
std::map<double, std::unique_ptr<Expression>> convertedStops;
for(const std::pair<float, T>& stop : stops) {
convertedStops.emplace(
stop.first,
makeLiteral(stop.second)
);
}
return convertedStops;
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const ExponentialStops<T>& stops)
{
ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>(
valueTypeToExpressionType<T>(),
makeZoom(),
convertStops(stops.stops),
ExponentialInterpolator(stops.base));
assert(e);
return std::move(*e);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const IntervalStops<T>& stops)
{
ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(),
makeZoom(),
convertStops(stops.stops)));
assert(e);
return std::move(*e);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const ExponentialStops<T>& stops)
{
ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
makeGet(type::Number, property),
convertStops(stops.stops),
ExponentialInterpolator(stops.base));
assert(e);
return std::move(*e);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const IntervalStops<T>& stops)
{
std::unique_ptr<Expression> get = makeGet(type::Number, property);
ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(),
std::move(get),
convertStops(stops.stops)));
assert(e);
return std::move(*e);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const CategoricalStops<T>& stops)
{
ParseResult expr = fromCategoricalStops(stops.stops, property);
assert(expr);
return std::move(*expr);
}
// interpolatable zoom curve
template <typename T>
static typename std::enable_if_t<util::Interpolatable<T>::value,
ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) {
return makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
makeZoom(),
std::move(stops),
ExponentialInterpolator(1.0));
}
// non-interpolatable zoom curve
template <typename T>
static typename std::enable_if_t<!util::Interpolatable<T>::value,
ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) {
return ParseResult(std::make_unique<Step>(valueTypeToExpressionType<T>(), makeZoom(), std::move(stops)));
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const CompositeExponentialStops<T>& stops)
{
std::map<double, std::unique_ptr<Expression>> outerStops;
for (const std::pair<float, std::map<float, T>>& stop : stops.stops) {
std::unique_ptr<Expression> get = makeGet(type::Number, property);
ParseResult innerInterpolate = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
std::move(get),
convertStops(stop.second),
ExponentialInterpolator(stops.base));
assert(innerInterpolate);
outerStops.emplace(stop.first, std::move(*innerInterpolate));
}
ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
assert(zoomCurve);
return std::move(*zoomCurve);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const CompositeIntervalStops<T>& stops)
{
std::map<double, std::unique_ptr<Expression>> outerStops;
for (const std::pair<float, std::map<float, T>>& stop : stops.stops) {
std::unique_ptr<Expression> get = makeGet(type::Number, property);
ParseResult innerInterpolate(std::make_unique<Step>(valueTypeToExpressionType<T>(),
std::move(get),
convertStops(stop.second)));
assert(innerInterpolate);
outerStops.emplace(stop.first, std::move(*innerInterpolate));
}
ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
assert(zoomCurve);
return std::move(*zoomCurve);
}
template <typename T>
static std::unique_ptr<Expression> toExpression(const std::string& property,
const CompositeCategoricalStops<T>& stops)
{
std::map<double, std::unique_ptr<Expression>> outerStops;
for (const std::pair<float, std::map<CategoricalValue, T>>& stop : stops.stops) {
ParseResult innerInterpolate = fromCategoricalStops(stop.second, property);
assert(innerInterpolate);
outerStops.emplace(stop.first, std::move(*innerInterpolate));
}
ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
assert(zoomCurve);
return std::move(*zoomCurve);
}
static std::unique_ptr<Expression> fromIdentityFunction(type::Type type, const std::string& property)
{
std::unique_ptr<Expression> input = type.match(
[&] (const type::StringType&) {
return makeGet(type::String, property);
},
[&] (const type::NumberType&) {
return makeGet(type::Number, property);
},
[&] (const type::BooleanType&) {
return makeGet(type::Boolean, property);
},
[&] (const type::ColorType&) {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(makeGet(type::String, property));
return std::make_unique<Coercion>(type::Color, std::move(args));
},
[&] (const type::Array& arr) {
std::vector<std::unique_ptr<Expression>> getArgs;
getArgs.push_back(makeLiteral(property));
ParsingContext ctx;
ParseResult get = createCompoundExpression("get", std::move(getArgs), ctx);
assert(get);
assert(ctx.getErrors().size() == 0);
return std::make_unique<ArrayAssertion>(arr, std::move(*get));
},
[&] (const auto&) -> std::unique_ptr<Expression> {
return makeLiteral(Null);
}
);
return input;
}
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -1,5 +1,7 @@
#pragma once
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/exponential_stops.hpp>
#include <mbgl/style/function/interval_stops.hpp>
#include <mbgl/style/function/categorical_stops.hpp>
@@ -27,33 +29,48 @@ public:
CategoricalStops<T>,
IdentityStops<T>>>;
SourceFunction(std::unique_ptr<expression::Expression> expression_)
: expression(std::move(expression_))
{
assert(expression::isZoomConstant(*expression));
assert(!expression::isFeatureConstant(*expression));
}
SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
: property(std::move(property_)),
stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)) {
}
defaultValue(std::move(defaultValue_)),
expression(stops.match([&] (const IdentityStops<T>&) {
return expression::Convert::fromIdentityFunction(expression::valueTypeToExpressionType<T>(), property);
}, [&] (const auto& s) {
return expression::Convert::toExpression(property, s);
}))
{}
template <class Feature>
T evaluate(const Feature& feature, T finalDefaultValue) const {
optional<Value> v = feature.getValue(property);
if (!v) {
return defaultValue.value_or(finalDefaultValue);
const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(&feature));
if (result) {
const optional<T> typed = expression::fromExpressionValue<T>(*result);
return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue;
}
return stops.match([&] (const auto& s) -> T {
return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue));
});
return defaultValue ? *defaultValue : finalDefaultValue;
}
friend bool operator==(const SourceFunction& lhs,
const SourceFunction& rhs) {
return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
== std::tie(rhs.property, rhs.stops, rhs.defaultValue);
return *lhs.expression == *rhs.expression;
}
bool useIntegerZoom = false;
// retained for compatibility with pre-expression function API
std::string property;
Stops stops;
optional<T> defaultValue;
bool useIntegerZoom = false;
private:
std::shared_ptr<expression::Expression> expression;
};
} // namespace style

View File

@@ -11,6 +11,7 @@ namespace mbgl {
template <typename T>
class Enum {
public:
using Type = T;
static const char * toString(T);
static optional<T> toEnum(const std::string&);
};

View File

@@ -3,6 +3,7 @@
#include <mbgl/util/color.hpp>
#include <mbgl/util/range.hpp>
#include <mbgl/style/position.hpp>
#include <mbgl/style/expression/value.hpp>
#include <array>
#include <vector>
@@ -47,6 +48,36 @@ public:
}
};
// In order to accept Array<Number, N> as an output value for Curve
// expressions, we need to have an interpolatable std::vector type.
// However, style properties like line-dasharray are represented using
// std::vector<float>, and should NOT be considered interpolatable.
// So, we use std::vector<Value> to represent expression array values,
// asserting that (a) the vectors are the same size, and (b) they contain
// only numeric values. (These invariants should be relatively safe,
// being enforced by the expression type system.)
template<>
struct Interpolator<std::vector<style::expression::Value>> {
std::vector<style::expression::Value> operator()(const std::vector<style::expression::Value>& a,
const std::vector<style::expression::Value>& b,
const double t) const {
assert(a.size() == b.size());
if (a.size() == 0) return {};
std::vector<style::expression::Value> result;
for (std::size_t i = 0; i < a.size(); i++) {
assert(a[i].template is<double>());
assert(b[i].template is<double>());
style::expression::Value item = interpolate(
a[i].template get<double>(),
b[i].template get<double>(),
t);
result.push_back(item);
}
return result;
}
};
template <>
struct Interpolator<style::Position> {
public:
@@ -101,5 +132,7 @@ struct Interpolatable
std::true_type,
std::false_type> {};
} // namespace util
} // namespace mbgl

View File

@@ -26,6 +26,7 @@
#pragma once
#include <cmath>
#include <tuple>
namespace mbgl {
namespace util {
@@ -102,6 +103,11 @@ struct UnitBezier {
double solve(double x, double epsilon) const {
return sampleCurveY(solveCurveX(x, epsilon));
}
bool operator==(const UnitBezier& rhs) const {
return std::tie(cx, bx, ax, cy, by, ay) ==
std::tie(rhs.cx, rhs.bx, rhs.ax, rhs.cy, rhs.by, rhs.ay);
}
private:
const double cx;

View File

@@ -13,7 +13,7 @@
},
"license": "BSD-2-Clause",
"dependencies": {
"nan": "^2.4.0",
"nan": "^2.6.2",
"node-pre-gyp": "^0.6.37",
"npm-run-all": "^4.0.2"
},
@@ -23,6 +23,7 @@
"ejs": "^2.4.1",
"express": "^4.11.1",
"flow-remove-types": "^1.2.1",
"json-stringify-pretty-compact": "^1.0.4",
"lodash": "^4.16.4",
"mapbox-gl-styles": "2.0.2",
"pixelmatch": "^4.0.2",
@@ -38,7 +39,8 @@
"install": "node-pre-gyp install --fallback-to-build=false || make node",
"test": "tape platform/node/test/js/**/*.test.js",
"test-memory": "node --expose-gc platform/node/test/memory.test.js",
"test-suite": "run-s test-render test-query",
"test-suite": "run-s test-render test-query test-expressions",
"test-expressions": "node platform/node/test/expression.test.js",
"test-render": "node platform/node/test/render.test.js",
"test-query": "node platform/node/test/query.test.js"
},

View File

@@ -0,0 +1,230 @@
#include "node_conversion.hpp"
#include "node_expression.hpp"
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/is_constant.hpp>
#include <mbgl/style/conversion/geojson.hpp>
#include <mbgl/util/geojson.hpp>
#include <nan.h>
using namespace mbgl::style;
using namespace mbgl::style::expression;
namespace node_mbgl {
Nan::Persistent<v8::Function> NodeExpression::constructor;
void NodeExpression::Init(v8::Local<v8::Object> target) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("Expression").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1); // what is this doing?
Nan::SetPrototypeMethod(tpl, "evaluate", Evaluate);
Nan::SetPrototypeMethod(tpl, "getType", GetType);
Nan::SetPrototypeMethod(tpl, "isFeatureConstant", IsFeatureConstant);
Nan::SetPrototypeMethod(tpl, "isZoomConstant", IsZoomConstant);
Nan::SetMethod(tpl, "parse", Parse);
constructor.Reset(tpl->GetFunction()); // what is this doing?
Nan::Set(target, Nan::New("Expression").ToLocalChecked(), tpl->GetFunction());
}
type::Type parseType(v8::Local<v8::Object> type) {
static std::unordered_map<std::string, type::Type> types = {
{"string", type::String},
{"number", type::Number},
{"noolean", type::Boolean},
{"object", type::Object},
{"color", type::Color},
{"value", type::Value}
};
v8::Local<v8::Value> v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked();
std::string kind(*v8::String::Utf8Value(v8kind));
if (kind == "array") {
type::Type itemType = parseType(Nan::Get(type, Nan::New("itemType").ToLocalChecked()).ToLocalChecked()->ToObject());
mbgl::optional<std::size_t> N;
v8::Local<v8::String> Nkey = Nan::New("N").ToLocalChecked();
if (Nan::Has(type, Nkey).FromMaybe(false)) {
N = Nan::Get(type, Nkey).ToLocalChecked()->ToInt32()->Value();
}
return type::Array(itemType, N);
}
return types[kind];
}
void NodeExpression::Parse(const Nan::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Function> cons = Nan::New(constructor);
if (info.Length() < 1 || info[0]->IsUndefined()) {
return Nan::ThrowTypeError("Requires a JSON style expression argument.");
}
mbgl::optional<type::Type> expected;
if (info.Length() > 1 && info[1]->IsObject()) {
expected = parseType(info[1]->ToObject());
}
auto expr = info[0];
try {
ParsingContext ctx(expected);
ParseResult parsed = ctx.parse(mbgl::style::conversion::Convertible(expr));
if (parsed) {
assert(ctx.getErrors().size() == 0);
auto nodeExpr = new NodeExpression(std::move(*parsed));
const int argc = 0;
v8::Local<v8::Value> argv[0] = {};
auto wrapped = Nan::NewInstance(cons, argc, argv).ToLocalChecked();
nodeExpr->Wrap(wrapped);
info.GetReturnValue().Set(wrapped);
return;
}
v8::Local<v8::Array> result = Nan::New<v8::Array>();
for (std::size_t i = 0; i < ctx.getErrors().size(); i++) {
const auto& error = ctx.getErrors()[i];
v8::Local<v8::Object> err = Nan::New<v8::Object>();
Nan::Set(err,
Nan::New("key").ToLocalChecked(),
Nan::New(error.key.c_str()).ToLocalChecked());
Nan::Set(err,
Nan::New("error").ToLocalChecked(),
Nan::New(error.message.c_str()).ToLocalChecked());
Nan::Set(result, Nan::New((uint32_t)i), err);
}
info.GetReturnValue().Set(result);
} catch(std::exception &ex) {
return Nan::ThrowError(ex.what());
}
}
void NodeExpression::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Use the new operator to create new Expression objects");
}
info.GetReturnValue().Set(info.This());
}
struct ToValue {
v8::Local<v8::Value> operator()(mbgl::NullValue) {
Nan::EscapableHandleScope scope;
return scope.Escape(Nan::Null());
}
v8::Local<v8::Value> operator()(bool t) {
Nan::EscapableHandleScope scope;
return scope.Escape(Nan::New(t));
}
v8::Local<v8::Value> operator()(double t) {
Nan::EscapableHandleScope scope;
return scope.Escape(Nan::New(t));
}
v8::Local<v8::Value> operator()(const std::string& t) {
Nan::EscapableHandleScope scope;
return scope.Escape(Nan::New(t).ToLocalChecked());
}
v8::Local<v8::Value> operator()(const std::vector<Value>& array) {
Nan::EscapableHandleScope scope;
v8::Local<v8::Array> result = Nan::New<v8::Array>();
for (unsigned int i = 0; i < array.size(); i++) {
result->Set(i, toJS(array[i]));
}
return scope.Escape(result);
}
v8::Local<v8::Value> operator()(const mbgl::Color& color) {
return operator()(std::vector<Value> {
static_cast<double>(color.r),
static_cast<double>(color.g),
static_cast<double>(color.b),
static_cast<double>(color.a)
});
}
v8::Local<v8::Value> operator()(const std::unordered_map<std::string, Value>& map) {
Nan::EscapableHandleScope scope;
v8::Local<v8::Object> result = Nan::New<v8::Object>();
for (const auto& entry : map) {
Nan::Set(result, Nan::New(entry.first).ToLocalChecked(), toJS(entry.second));
}
return scope.Escape(result);
}
};
v8::Local<v8::Value> toJS(const Value& value) {
return Value::visit(value, ToValue());
}
void NodeExpression::Evaluate(const Nan::FunctionCallbackInfo<v8::Value>& info) {
NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
const std::unique_ptr<Expression>& expression = nodeExpr->expression;
if (info.Length() < 2 || !info[0]->IsObject()) {
return Nan::ThrowTypeError("Requires globals and feature arguments.");
}
mbgl::optional<float> zoom;
v8::Local<v8::Value> v8zoom = Nan::Get(info[0]->ToObject(), Nan::New("zoom").ToLocalChecked()).ToLocalChecked();
if (v8zoom->IsNumber()) zoom = v8zoom->NumberValue();
mbgl::optional<double> heatmapDensity;
v8::Local<v8::Value> v8heatmapDensity = Nan::Get(info[0]->ToObject(), Nan::New("heatmapDensity").ToLocalChecked()).ToLocalChecked();
if (v8heatmapDensity->IsNumber()) heatmapDensity = v8heatmapDensity->NumberValue();
Nan::JSON NanJSON;
conversion::Error conversionError;
mbgl::optional<mbgl::GeoJSON> geoJSON = conversion::convert<mbgl::GeoJSON>(info[1], conversionError);
if (!geoJSON) {
Nan::ThrowTypeError(conversionError.message.c_str());
return;
}
try {
mapbox::geojson::feature feature = geoJSON->get<mapbox::geojson::feature>();
auto result = expression->evaluate(zoom, feature, heatmapDensity);
if (result) {
info.GetReturnValue().Set(toJS(*result));
} else {
v8::Local<v8::Object> res = Nan::New<v8::Object>();
Nan::Set(res,
Nan::New("error").ToLocalChecked(),
Nan::New(result.error().message.c_str()).ToLocalChecked());
info.GetReturnValue().Set(res);
}
} catch(std::exception &ex) {
return Nan::ThrowTypeError(ex.what());
}
}
void NodeExpression::GetType(const Nan::FunctionCallbackInfo<v8::Value>& info) {
NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
const std::unique_ptr<Expression>& expression = nodeExpr->expression;
const type::Type type = expression->getType();
const std::string name = type.match([&] (const auto& t) { return t.getName(); });
info.GetReturnValue().Set(Nan::New(name.c_str()).ToLocalChecked());
}
void NodeExpression::IsFeatureConstant(const Nan::FunctionCallbackInfo<v8::Value>& info) {
NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
const std::unique_ptr<Expression>& expression = nodeExpr->expression;
info.GetReturnValue().Set(Nan::New(isFeatureConstant(*expression)));
}
void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>& info) {
NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
const std::unique_ptr<Expression>& expression = nodeExpr->expression;
info.GetReturnValue().Set(Nan::New(isZoomConstant(*expression)));
}
} // namespace node_mbgl

View File

@@ -0,0 +1,40 @@
#pragma once
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <exception>
#include <memory>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#include <nan.h>
#pragma GCC diagnostic pop
using namespace mbgl::style::expression;
namespace node_mbgl {
v8::Local<v8::Value> toJS(const Value&);
class NodeExpression : public Nan::ObjectWrap {
public:
static void Init(v8::Local<v8::Object>);
private:
NodeExpression(std::unique_ptr<Expression> expression_) :
expression(std::move(expression_))
{};
static void New(const Nan::FunctionCallbackInfo<v8::Value>&);
static void Parse(const Nan::FunctionCallbackInfo<v8::Value>&);
static void Evaluate(const Nan::FunctionCallbackInfo<v8::Value>&);
static void GetType(const Nan::FunctionCallbackInfo<v8::Value>&);
static void IsFeatureConstant(const Nan::FunctionCallbackInfo<v8::Value>&);
static void IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>&);
static Nan::Persistent<v8::Function> constructor;
std::unique_ptr<Expression> expression;
};
} // namespace node_mbgl

View File

@@ -10,6 +10,7 @@
#include "node_map.hpp"
#include "node_logging.hpp"
#include "node_request.hpp"
#include "node_expression.hpp"
void RegisterModule(v8::Local<v8::Object> target, v8::Local<v8::Object> module) {
// This has the effect of:
@@ -20,6 +21,7 @@ void RegisterModule(v8::Local<v8::Object> target, v8::Local<v8::Object> module)
node_mbgl::NodeMap::Init(target);
node_mbgl::NodeRequest::Init();
node_mbgl::NodeExpression::Init(target);
// Exports Resource constants.
v8::Local<v8::Object> resource = Nan::New<v8::Object>();

View File

@@ -0,0 +1,71 @@
'use strict';
var suite = require('../../../mapbox-gl-js/test/integration').expression;
var mbgl = require('../index');
var ignores = require('./ignores.json');
var tests;
if (process.argv[1] === __filename && process.argv.length > 2) {
tests = process.argv.slice(2);
}
function getExpectedType(spec) {
if (spec.type === 'array') {
const itemType = getExpectedType({ type: spec.value });
const array = {
kind: 'array',
itemType: itemType || { kind: 'value' },
};
if (typeof spec.length === 'number') {
array.N = spec.length;
}
return array;
}
if (spec.type === 'enum') {
return { kind: 'string' };
}
return typeof spec.type === 'string' ? {kind: spec.type} : null;
}
suite.run('native', {ignores: ignores, tests: tests}, (fixture) => {
const compiled = {};
const result = {
compiled
};
const spec = fixture.propertySpec || {};
const expression = mbgl.Expression.parse(fixture.expression, getExpectedType(spec));
if (expression instanceof mbgl.Expression) {
compiled.result = 'success';
compiled.isFeatureConstant = expression.isFeatureConstant();
compiled.isZoomConstant = expression.isZoomConstant();
compiled.type = expression.getType();
const evaluate = fixture.inputs || [];
const evaluateResults = [];
for (const input of evaluate) {
const feature = Object.assign({
type: 'Feature',
properties: {},
geometry: { type: 'Point', coordinates: [0, 0] }
}, input[1])
const output = expression.evaluate(input[0], feature);
evaluateResults.push(output);
}
if (fixture.inputs) {
result.outputs = evaluateResults;
}
} else {
compiled.result = 'error';
compiled.errors = expression;
}
return result;
});

View File

@@ -1,62 +1,39 @@
{
"expression-tests/curve/step": "https://github.com/mapbox/mapbox-gl-js/issues/5580",
"expression-tests/curve/interpolate": "https://github.com/mapbox/mapbox-gl-js/issues/5580",
"query-tests/circle-stroke-width/inside": "https://github.com/mapbox/mapbox-gl-native/issues/10307",
"query-tests/geometry/multilinestring": "needs investigation",
"query-tests/geometry/multipolygon": "needs investigation",
"query-tests/geometry/polygon": "needs investigation",
"query-tests/regressions/mapbox-gl-js#3534": "https://github.com/mapbox/mapbox-gl-native/issues/8193",
"query-tests/regressions/mapbox-gl-js#4417": "https://github.com/mapbox/mapbox-gl-native/issues/8007",
"query-tests/regressions/mapbox-gl-js#5554": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"query-tests/symbol/panned-after-insert": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"query-tests/symbol/rotated-after-insert": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"query-tests/symbol/rotated-inside": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"query-tests/symbol-features-in/pitched-screen": "https://github.com/mapbox/mapbox-gl-native/issues/6817",
"query-tests/symbol-features-in/tilted-inside": "https://github.com/mapbox/mapbox-gl-native/issues/5056",
"query-tests/symbol-features-in/tilted-outside": "https://github.com/mapbox/mapbox-gl-native/issues/9435",
"query-tests/world-wrapping/box": "skip - needs issue",
"query-tests/world-wrapping/point": "skip - needs issue",
"render-tests/debug/collision-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/tile-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-lines": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/debug/collision-lines-pitched": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/debug/collision-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/tile": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/tile-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/extent/1024-circle": "needs investigation",
"render-tests/extent/1024-symbol": "needs investigation",
"render-tests/fill-extrusion-pattern/@2x": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/function-2": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/function": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/function-2": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/literal": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/missing": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary",
"render-tests/geojson/inline-polygon-symbol": "behavior needs reconciliation with gl-js",
"render-tests/icon-size/composite-function-high-base-plain": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
"render-tests/icon-size/composite-function-high-base-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
"render-tests/icon-text-fit/both-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/both": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/height": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/width-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/width": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/line-width/property-function": "https://github.com/mapbox/mapbox-gl-js/issues/3682#issuecomment-264348200",
"render-tests/line-join/property-function": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-join/property-function-dasharray": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-opacity/step-curve": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195",
"render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927",
"render-tests/regressions/mapbox-gl-js#3682": "https://github.com/mapbox/mapbox-gl-js/issues/3682",
"render-tests/regressions/mapbox-gl-js#5370": "skip - https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357",
"render-tests/runtime-styling/image-add-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/9847",
"render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
"render-tests/runtime-styling/set-style-paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
"render-tests/symbol-placement/line": "needs issue",
"render-tests/text-font/camera-function": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/map-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/viewport-overzoomed": "https://github.com/mapbox/mapbox-gl-js/issues/5095",
"render-tests/text-pitch-alignment/viewport-overzoomed-single-glyph": "https://github.com/mapbox/mapbox-gl-js/issues/5095",
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-size/composite-expression": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601",
"render-tests/heatmap-color/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/heatmap-color/expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/heatmap-color/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
@@ -73,5 +50,50 @@
"render-tests/heatmap-radius/pitch30": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/heatmap-weight/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/heatmap-weight/identity-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146"
"render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/icon-size/composite-function-high-base-plain": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
"render-tests/icon-size/composite-function-high-base-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
"render-tests/icon-text-fit/both": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/both-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/height": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/placement-line": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/icon-text-fit/width": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/width-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/line-join/property-function": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-join/property-function-dasharray": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-opacity/step-curve": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/line-width/property-function": "https://github.com/mapbox/mapbox-gl-js/issues/3682#issuecomment-264348200",
"render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195",
"render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927",
"render-tests/regressions/mapbox-gl-js#3682": "https://github.com/mapbox/mapbox-gl-js/issues/3682",
"render-tests/regressions/mapbox-gl-js#4647": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/regressions/mapbox-gl-js#5370": "skip - https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/regressions/mapbox-gl-js#5599": "https://github.com/mapbox/mapbox-gl-native/issues/10399",
"render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357",
"render-tests/runtime-styling/image-add-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/9847",
"render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
"render-tests/runtime-styling/set-style-glyphs": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/runtime-styling/set-style-paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
"render-tests/symbol-placement/line": "needs issue",
"render-tests/symbol-placement/line-overscaled": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/symbol-placement/point": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/symbol-spacing/line-close": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/symbol-spacing/line-far": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/symbol-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/text-font/camera-function": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/text-font/chinese": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/map-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/viewport-overzoomed": "https://github.com/mapbox/mapbox-gl-js/issues/5095",
"render-tests/text-pitch-alignment/viewport-overzoomed-single-glyph": "https://github.com/mapbox/mapbox-gl-js/issues/5095",
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-size/composite-expression": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
"render-tests/text-tile-edge-clipping/default": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/text-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601"
}

View File

@@ -128,23 +128,6 @@ public:
}
};
// Return the smallest range of stops that covers the interval [lowerZoom, upperZoom]
template <class Stops>
Range<float> getCoveringStops(Stops s, float lowerZoom, float upperZoom) {
assert(!s.stops.empty());
auto minIt = s.stops.lower_bound(lowerZoom);
auto maxIt = s.stops.lower_bound(upperZoom);
// lower_bound yields first element >= lowerZoom, but we want the *last*
// element <= lowerZoom, so if we found a stop > lowerZoom, back up by one.
if (minIt != s.stops.begin() && minIt != s.stops.end() && minIt->first > lowerZoom) {
minIt--;
}
return Range<float> {
minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first,
maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first
};
}
class ConstantSymbolSizeBinder final : public SymbolSizeBinder {
public:
@@ -155,19 +138,12 @@ public:
: layoutSize(defaultValue) {}
ConstantSymbolSizeBinder(const float tileZoom, const style::CameraFunction<float>& function_, const float /*defaultValue*/)
: layoutSize(function_.evaluate(tileZoom + 1)) {
function_.stops.match(
[&] (const style::ExponentialStops<float>& stops) {
const auto& zoomLevels = getCoveringStops(stops, tileZoom, tileZoom + 1);
coveringRanges = std::make_tuple(
zoomLevels,
Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) }
);
functionInterpolationBase = stops.base;
},
[&] (const style::IntervalStops<float>&) {
function = function_;
}
: layoutSize(function_.evaluate(tileZoom + 1)),
function(function_) {
const Range<float> zoomLevels = function_.getCoveringStops(tileZoom, tileZoom + 1);
coveringRanges = std::make_tuple(
zoomLevels,
Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) }
);
}
@@ -185,7 +161,7 @@ public:
const Range<float>& zoomLevels = std::get<0>(*coveringRanges);
const Range<float>& sizeLevels = std::get<1>(*coveringRanges);
float t = util::clamp(
util::interpolationFactor(*functionInterpolationBase, zoomLevels, currentZoom),
function->interpolationFactor(zoomLevels, currentZoom),
0.0f, 1.0f
);
size = sizeLevels.min + t * (sizeLevels.max - sizeLevels.min);
@@ -198,10 +174,7 @@ public:
}
float layoutSize;
// used for exponential functions
optional<std::tuple<Range<float>, Range<float>>> coveringRanges;
optional<float> functionInterpolationBase;
// used for interval functions
optional<style::CameraFunction<float>> function;
};
@@ -226,7 +199,7 @@ public:
return { true, false, unused, unused, unused };
}
const style::SourceFunction<float>& function;
style::SourceFunction<float> function;
const float defaultValue;
};
@@ -237,9 +210,7 @@ public:
: function(function_),
defaultValue(defaultValue_),
layoutZoom(tileZoom + 1),
coveringZoomStops(function.stops.match(
[&] (const auto& stops) {
return getCoveringStops(stops, tileZoom, tileZoom + 1); }))
coveringZoomStops(function.getCoveringStops(tileZoom, tileZoom + 1))
{}
Range<float> getVertexSizeData(const GeometryTileFeature& feature) override {
@@ -251,7 +222,7 @@ public:
ZoomEvaluatedSize evaluateForZoom(float currentZoom) const override {
float sizeInterpolationT = util::clamp(
util::interpolationFactor(1.0f, coveringZoomStops, currentZoom),
function.interpolationFactor(coveringZoomStops, currentZoom),
0.0f, 1.0f
);
@@ -259,7 +230,7 @@ public:
return { false, false, sizeInterpolationT, unused, unused };
}
const style::CompositeFunction<float>& function;
style::CompositeFunction<float> function;
const float defaultValue;
float layoutZoom;
Range<float> coveringZoomStops;

View File

@@ -190,11 +190,11 @@ public:
CompositeFunctionPaintPropertyBinder(style::CompositeFunction<T> function_, float zoom, T defaultValue_)
: function(std::move(function_)),
defaultValue(std::move(defaultValue_)),
rangeOfCoveringRanges(function.rangeOfCoveringRanges({zoom, zoom + 1})) {
zoomRange({zoom, zoom + 1}) {
}
void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) override {
Range<T> range = function.evaluate(rangeOfCoveringRanges, feature, defaultValue);
Range<T> range = function.evaluate(zoomRange, feature, defaultValue);
this->statistics.add(range.min);
this->statistics.add(range.max);
AttributeValue value = zoomInterpolatedAttributeValue(
@@ -219,9 +219,9 @@ public:
float interpolationFactor(float currentZoom) const override {
if (function.useIntegerZoom) {
return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, std::floor(currentZoom));
return function.interpolationFactor(zoomRange, std::floor(currentZoom));
} else {
return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, currentZoom);
return function.interpolationFactor(zoomRange, currentZoom);
}
}
@@ -237,8 +237,7 @@ public:
private:
style::CompositeFunction<T> function;
T defaultValue;
using CoveringRanges = typename style::CompositeFunction<T>::CoveringRanges;
Range<CoveringRanges> rangeOfCoveringRanges;
Range<float> zoomRange;
gl::VertexVector<Vertex> vertexVector;
optional<gl::VertexBuffer<Vertex>> vertexBuffer;
};

View File

@@ -0,0 +1,34 @@
#include <mbgl/style/conversion/get_json_type.hpp>
#include <mbgl/util/feature.hpp>
namespace mbgl {
namespace style {
namespace conversion {
std::string getJSONType(const Convertible& value) {
if (isUndefined(value)) {
return "null";
}
if (isArray(value)) {
return "array";
}
if (isObject(value)) {
return "object";
}
optional<mbgl::Value> v = toValue(value);
// Since we've already checked the non-atomic types above, value must then
// be a string, number, or boolean -- thus, assume that the toValue()
// conversion succeeds.
assert(v);
return v->match(
[&] (const std::string&) { return "string"; },
[&] (bool) { return "boolean"; },
[&] (auto) { return "number"; }
);
}
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,85 @@
#include <mbgl/style/expression/array_assertion.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult ArrayAssertion::evaluate(const EvaluationContext& params) const {
auto result = input->evaluate(params);
if (!result) {
return result.error();
}
type::Type expected = getType();
type::Type actual = typeOf(*result);
if (checkSubtype(expected, actual)) {
return EvaluationError {
"Expected value to be of type " + toString(expected) +
", but found " + toString(actual) + " instead."
};
}
return *result;
}
void ArrayAssertion::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*input);
}
using namespace mbgl::style::conversion;
ParseResult ArrayAssertion::parse(const Convertible& value, ParsingContext& ctx) {
static std::unordered_map<std::string, type::Type> itemTypes {
{"string", type::String},
{"number", type::Number},
{"boolean", type::Boolean}
};
auto length = arrayLength(value);
if (length < 2 || length > 4) {
ctx.error("Expected 1, 2, or 3 arguments, but found " + std::to_string(length - 1) + " instead.");
return ParseResult();
}
optional<type::Type> itemType;
optional<std::size_t> N;
if (length > 2) {
optional<std::string> itemTypeName = toString(arrayMember(value, 1));
auto it = itemTypeName ? itemTypes.find(*itemTypeName) : itemTypes.end();
if (it == itemTypes.end()) {
ctx.error(
R"(The item type argument of "array" must be one of string, number, boolean)",
1
);
return ParseResult();
}
itemType = it->second;
} else {
itemType = {type::Value};
}
if (length > 3) {
auto n = toNumber(arrayMember(value, 2));
if (!n || *n != std::floor(*n)) {
ctx.error(
R"(The length argument to "array" must be a positive integer literal.)",
2
);
return ParseResult();
}
N = optional<std::size_t>(*n);
}
auto input = ctx.parse(arrayMember(value, length - 1), length - 1, {type::Value});
if (!input) {
return input;
}
return ParseResult(std::make_unique<ArrayAssertion>(
type::Array(*itemType, N),
std::move(*input)
));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,73 @@
#include <mbgl/style/expression/assertion.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
namespace mbgl {
namespace style {
namespace expression {
using namespace mbgl::style::conversion;
ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) {
static std::unordered_map<std::string, type::Type> types {
{"string", type::String},
{"number", type::Number},
{"boolean", type::Boolean},
{"object", type::Object}
};
std::size_t length = arrayLength(value);
if (length < 2) {
ctx.error("Expected at least one argument.");
return ParseResult();
}
auto it = types.find(*toString(arrayMember(value, 0)));
assert(it != types.end());
std::vector<std::unique_ptr<Expression>> parsed;
parsed.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value});
if (!input) return ParseResult();
parsed.push_back(std::move(*input));
}
return ParseResult(std::make_unique<Assertion>(it->second, std::move(parsed)));
}
EvaluationResult Assertion::evaluate(const EvaluationContext& params) const {
for (std::size_t i = 0; i < inputs.size(); i++) {
EvaluationResult value = inputs[i]->evaluate(params);
if (!value) return value;
if (!type::checkSubtype(getType(), typeOf(*value))) {
return value;
} else if (i == inputs.size() - 1) {
return EvaluationError {
"Expected value to be of type " + toString(getType()) +
", but found " + toString(typeOf(*value)) + " instead."
};
}
}
assert(false);
return EvaluationError { "Unreachable" };
};
void Assertion::eachChild(const std::function<void(const Expression&)>& visit) const {
for(const std::unique_ptr<Expression>& input : inputs) {
visit(*input);
}
};
bool Assertion::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Assertion*>(&e)) {
return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs);
}
return false;
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,63 @@
#include <mbgl/style/expression/at.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult At::evaluate(const EvaluationContext& params) const {
const EvaluationResult evaluatedIndex = index->evaluate(params);
const EvaluationResult evaluatedInput = input->evaluate(params);
if (!evaluatedIndex) {
return evaluatedIndex.error();
}
if (!evaluatedInput) {
return evaluatedInput.error();
}
const auto i = evaluatedIndex->get<double>();
const auto inputArray = evaluatedInput->get<std::vector<Value>>();
if (i < 0 || i >= inputArray.size()) {
return EvaluationError {
"Array index out of bounds: " + stringify(i) +
" > " + std::to_string(inputArray.size()) + "."
};
}
if (i != std::floor(i)) {
return EvaluationError {
"Array index must be an integer, but found " + stringify(i) + " instead."
};
}
return inputArray[static_cast<std::size_t>(i)];
}
void At::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*index);
visit(*input);
}
using namespace mbgl::style::conversion;
ParseResult At::parse(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
std::size_t length = arrayLength(value);
if (length != 3) {
ctx.error("Expected 2 arguments, but found " + std::to_string(length - 1) + " instead.");
return ParseResult();
}
ParseResult index = ctx.parse(arrayMember(value, 1), 1, {type::Number});
type::Type inputType = type::Array(ctx.getExpected() ? *ctx.getExpected() : type::Value);
ParseResult input = ctx.parse(arrayMember(value, 2), 2, {inputType});
if (!index || !input) return ParseResult();
return ParseResult(std::make_unique<At>(std::move(*index), std::move(*input)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,87 @@
#include <mbgl/style/expression/boolean_operator.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult Any::evaluate(const EvaluationContext& params) const {
for (auto it = inputs.begin(); it != inputs.end(); it++) {
const EvaluationResult result = (*it)->evaluate(params);
if (!result) return result;
if (result->get<bool>()) return EvaluationResult(true);
}
return EvaluationResult(false);
}
void Any::eachChild(const std::function<void(const Expression&)>& visit) const {
for (const std::unique_ptr<Expression>& input : inputs) {
visit(*input);
}
}
bool Any::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Any*>(&e)) {
return Expression::childrenEqual(inputs, rhs->inputs);
}
return false;
}
EvaluationResult All::evaluate(const EvaluationContext& params) const {
for (auto it = inputs.begin(); it != inputs.end(); it++) {
const EvaluationResult result = (*it)->evaluate(params);
if (!result) return result;
if (!result->get<bool>()) return EvaluationResult(false);
}
return EvaluationResult(true);
}
void All::eachChild(const std::function<void(const Expression&)>& visit) const {
for (const std::unique_ptr<Expression>& input : inputs) {
visit(*input);
}
}
bool All::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const All*>(&e)) {
return Expression::childrenEqual(inputs, rhs->inputs);
}
return false;
}
using namespace mbgl::style::conversion;
template <class T>
ParseResult parseBooleanOp(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
auto length = arrayLength(value);
std::vector<std::unique_ptr<Expression>> parsedInputs;
parsedInputs.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
auto parsed = ctx.parse(arrayMember(value, i), i, {type::Boolean});
if (!parsed) {
return parsed;
}
parsedInputs.push_back(std::move(*parsed));
}
return ParseResult(std::make_unique<T>(std::move(parsedInputs)));
}
ParseResult Any::parse(const Convertible& value, ParsingContext& ctx) {
return parseBooleanOp<Any>(value, ctx);
}
ParseResult All::parse(const Convertible& value, ParsingContext& ctx) {
return parseBooleanOp<All>(value, ctx);
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,90 @@
#include <mbgl/style/expression/case.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult Case::evaluate(const EvaluationContext& params) const {
for (const auto& branch : branches) {
const EvaluationResult evaluatedTest = branch.first->evaluate(params);
if (!evaluatedTest) {
return evaluatedTest.error();
}
if (evaluatedTest->get<bool>()) {
return branch.second->evaluate(params);
}
}
return otherwise->evaluate(params);
}
void Case::eachChild(const std::function<void(const Expression&)>& visit) const {
for (const Branch& branch : branches) {
visit(*branch.first);
visit(*branch.second);
}
visit(*otherwise);
}
bool Case::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Case*>(&e)) {
return *otherwise == *(rhs->otherwise) && Expression::childrenEqual(branches, rhs->branches);
}
return false;
}
using namespace mbgl::style::conversion;
ParseResult Case::parse(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
auto length = arrayLength(value);
if (length < 4) {
ctx.error("Expected at least 3 arguments, but found only " + std::to_string(length - 1) + ".");
return ParseResult();
}
// Expect even-length array: ["case", 2 * (n pairs)..., otherwise]
if (length % 2 != 0) {
ctx.error("Expected an odd number of arguments");
return ParseResult();
}
optional<type::Type> outputType;
if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
outputType = ctx.getExpected();
}
std::vector<Case::Branch> branches;
branches.reserve((length - 2) / 2);
for (size_t i = 1; i + 1 < length; i += 2) {
auto test = ctx.parse(arrayMember(value, i), i, {type::Boolean});
if (!test) {
return test;
}
auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
if (!output) {
return output;
}
if (!outputType) {
outputType = (*output)->getType();
}
branches.push_back(std::make_pair(std::move(*test), std::move(*output)));
}
assert(outputType);
auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType);
if (!otherwise) {
return otherwise;
}
return ParseResult(std::make_unique<Case>(*outputType,
std::move(branches),
std::move(*otherwise)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,60 @@
#include <string>
#include <mbgl/style/expression/check_subtype.hpp>
namespace mbgl {
namespace style {
namespace expression {
namespace type {
std::string errorMessage(const Type& expected, const Type& t) {
return {"Expected " + toString(expected) + " but found " + toString(t) + " instead."};
}
optional<std::string> checkSubtype(const Type& expected, const Type& t) {
if (t.is<ErrorType>()) return {};
optional<std::string> result = expected.match(
[&] (const Array& expectedArray) -> optional<std::string> {
if (!t.is<Array>()) { return {errorMessage(expected, t)}; }
const auto& actualArray = t.get<Array>();
const auto err = checkSubtype(expectedArray.itemType, actualArray.itemType);
if (err) return { errorMessage(expected, t) };
if (expectedArray.N && expectedArray.N != actualArray.N) return { errorMessage(expected, t) };
return {};
},
[&] (const ValueType&) -> optional<std::string> {
if (t.is<ValueType>()) return {};
const Type members[] = {
Null,
Boolean,
Number,
String,
Object,
Color,
Array(Value)
};
for (const auto& member : members) {
const auto err = checkSubtype(member, t);
if (!err) {
return {};
}
}
return { errorMessage(expected, t) };
},
[&] (const auto&) -> optional<std::string> {
if (expected != t) {
return { errorMessage(expected, t) };
}
return {};
}
);
return result;
}
} // namespace type
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,62 @@
#include <mbgl/style/expression/coalesce.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult Coalesce::evaluate(const EvaluationContext& params) const {
EvaluationResult result = Null;
for (const auto& arg : args) {
result = arg->evaluate(params);
if (!result || *result != Null) break;
}
return result;
}
void Coalesce::eachChild(const std::function<void(const Expression&)>& visit) const {
for (const std::unique_ptr<Expression>& arg : args) {
visit(*arg);
}
}
bool Coalesce::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Coalesce*>(&e)) {
return Expression::childrenEqual(args, rhs->args);
}
return false;
}
using namespace mbgl::style::conversion;
ParseResult Coalesce::parse(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
auto length = arrayLength(value);
if (length < 2) {
ctx.error("Expected at least one argument.");
return ParseResult();
}
optional<type::Type> outputType;
if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
outputType = ctx.getExpected();
}
Coalesce::Args args;
args.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
auto parsed = ctx.parse(arrayMember(value, i), i, outputType);
if (!parsed) {
return parsed;
}
if (!outputType) {
outputType = (*parsed)->getType();
}
args.push_back(std::move(*parsed));
}
assert(outputType);
return ParseResult(std::make_unique<Coalesce>(*outputType, std::move(args)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,143 @@
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
#include <mbgl/style/expression/util.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult toNumber(const Value& v) {
optional<double> result = v.match(
[](const double f) -> optional<double> { return f; },
[](const std::string& s) -> optional<double> {
try {
return std::stof(s);
} catch(std::exception) {
return optional<double>();
}
},
[](const auto&) { return optional<double>(); }
);
if (!result) {
return EvaluationError {
"Could not convert " + stringify(v) + " to number."
};
}
return *result;
}
EvaluationResult toColor(const Value& colorValue) {
return colorValue.match(
[&](const std::string& colorString) -> EvaluationResult {
const optional<Color> result = Color::parse(colorString);
if (result) {
return *result;
} else {
return EvaluationError{
"Could not parse color from value '" + colorString + "'"
};
}
},
[&](const std::vector<Value>& components) -> EvaluationResult {
std::size_t len = components.size();
bool isNumeric = std::all_of(components.begin(), components.end(), [](const Value& item) {
return item.template is<double>();
});
if ((len == 3 || len == 4) && isNumeric) {
Result<Color> c = {rgba(
components[0].template get<double>(),
components[1].template get<double>(),
components[2].template get<double>(),
len == 4 ? components[3].template get<double>() : 1.0
)};
if (!c) return c.error();
return *c;
} else {
return EvaluationError{
"Invalid rbga value " + stringify(colorValue) + ": expected an array containing either three or four numeric values."
};
}
},
[&](const auto&) -> EvaluationResult {
return EvaluationError{
"Could not parse color from value '" + stringify(colorValue) + "'"
};
}
);
}
Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) :
Expression(std::move(type_)),
inputs(std::move(inputs_))
{
type::Type t = getType();
if (t.is<type::NumberType>()) {
coerceSingleValue = toNumber;
} else if (t.is<type::ColorType>()) {
coerceSingleValue = toColor;
} else {
assert(false);
}
}
using namespace mbgl::style::conversion;
ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
static std::unordered_map<std::string, type::Type> types {
{"to-number", type::Number},
{"to-color", type::Color}
};
std::size_t length = arrayLength(value);
if (length < 2) {
ctx.error("Expected at least one argument.");
return ParseResult();
}
auto it = types.find(*toString(arrayMember(value, 0)));
assert(it != types.end());
std::vector<std::unique_ptr<Expression>> parsed;
parsed.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value});
if (!input) return ParseResult();
parsed.push_back(std::move(*input));
}
return ParseResult(std::make_unique<Coercion>(it->second, std::move(parsed)));
}
EvaluationResult Coercion::evaluate(const EvaluationContext& params) const {
for (std::size_t i = 0; i < inputs.size(); i++) {
EvaluationResult value = inputs[i]->evaluate(params);
if (!value) return value;
EvaluationResult coerced = coerceSingleValue(*value);
if (coerced || i == inputs.size() - 1) {
return coerced;
}
}
assert(false);
return EvaluationError { "Unreachable" };
};
void Coercion::eachChild(const std::function<void(const Expression&)>& visit) const {
for(const std::unique_ptr<Expression>& input : inputs) {
visit(*input);
}
};
bool Coercion::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Coercion*>(&e)) {
return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs);
}
return false;
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,571 @@
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
#include <mbgl/style/expression/util.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/util/ignore.hpp>
namespace mbgl {
namespace style {
namespace expression {
namespace detail {
/*
The Signature<Fn> 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<R (Params...)>:
Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U>
Signature<R (const Varargs<T>&)>:
Wraps an evaluate function that takes an arbitrary number of arguments (via
a Varargs<T>, which is just an alias for std::vector).
Signature<R (const EvaluationContext&, Params...)>:
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<U>. 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 <class, class Enable = void>
struct Signature;
// Simple evaluate function (const T0&, const T1&, ...) -> Result<U>
template <class R, class... Params>
struct Signature<R (Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
Signature(R (*evaluate_)(Params...)) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
),
evaluate(evaluate_)
{}
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
}
std::unique_ptr<Expression> makeExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray));
}
R (*evaluate)(Params...);
private:
template <std::size_t ...I>
EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}};
for (const auto& arg : evaluated) {
if(!arg) return arg.error();
}
const R value = evaluate(*fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...);
if (!value) return value.error();
return *value;
}
};
// Varargs evaluate function (const Varargs<T>&) -> Result<U>
template <class R, typename T>
struct Signature<R (const Varargs<T>&)> : SignatureBase {
using Args = std::vector<std::unique_ptr<Expression>>;
Signature(R (*evaluate_)(const Varargs<T>&)) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
VarargsType { valueTypeToExpressionType<T>() }
),
evaluate(evaluate_)
{}
std::unique_ptr<Expression> makeExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args) const override {
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args));
};
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
Varargs<T> 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<std::decay_t<T>>(*evaluatedArg));
}
const R value = evaluate(evaluated);
if (!value) return value.error();
return *value;
}
R (*evaluate)(const Varargs<T>&);
};
// Evaluate function needing parameter access,
// (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>
template <class R, class... Params>
struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
Signature(R (*evaluate_)(const EvaluationContext&, Params...)) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
),
evaluate(evaluate_)
{}
std::unique_ptr<Expression> makeExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray));
}
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
}
private:
template <std::size_t ...I>
EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}};
for (const auto& arg : evaluated) {
if(!arg) return arg.error();
}
// TODO: assert correct runtime type of each arg value
const R value = evaluate(evaluationParameters, *fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...);
if (!value) return value.error();
return *value;
}
R (*evaluate)(const EvaluationContext&, Params...);
};
// Machinery to pull out function types from class methods, lambdas, etc.
template <class R, class... Params>
struct Signature<R (*)(Params...)>
: Signature<R (Params...)>
{ using Signature<R (Params...)>::Signature; };
template <class T, class R, class... Params>
struct Signature<R (T::*)(Params...) const>
: Signature<R (Params...)>
{ using Signature<R (Params...)>::Signature; };
template <class T, class R, class... Params>
struct Signature<R (T::*)(Params...)>
: Signature<R (Params...)>
{ using Signature<R (Params...)>::Signature; };
template <class Lambda>
struct Signature<Lambda, std::enable_if_t<std::is_class<Lambda>::value>>
: Signature<decltype(&Lambda::operator())>
{ using Signature<decltype(&Lambda::operator())>::Signature; };
} // namespace detail
using Definition = CompoundExpressionRegistry::Definition;
template <typename T>
Result<bool> equal(const T& lhs, const T& rhs) { return lhs == rhs; }
template <typename T>
Result<bool> notEqual(const T& lhs, const T& rhs) { return lhs != rhs; }
template <typename Fn>
static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction) {
return std::make_unique<detail::Signature<Fn>>(evaluateFunction);
}
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() {
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions;
auto define = [&](std::string name, auto fn) {
definitions[name].push_back(makeSignature(fn));
};
define("e", []() -> Result<double> { return 2.718281828459045; });
define("pi", []() -> Result<double> { return 3.141592653589793; });
define("ln2", []() -> Result<double> { return 0.6931471805599453; });
define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); });
define("to-string", [](const Value& value) -> Result<std::string> {
return value.match(
[](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting
[](const std::string& s) -> Result<std::string> { return s; }, // avoid quoting
[](const auto& v) -> Result<std::string> { return stringify(v); }
);
});
define("to-boolean", [](const Value& v) -> Result<bool> {
return v.match(
[&] (double f) { return (bool)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<std::array<double, 4>> {
return std::array<double, 4> {{ color.r, color.g, color.b, color.a }};
});
define("rgba", rgba);
define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); });
define("zoom", [](const EvaluationContext& params) -> Result<double> {
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<double> {
if (!params.heatmapDensity) {
return EvaluationError {
"The 'heatmap-density' expression is unavailable in the current evaluation context."
};
}
return *(params.heatmapDensity);
});
define("has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> {
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<std::string, Value>& object) -> Result<bool> {
return object.find(key) != object.end();
});
define("get", [](const EvaluationContext& params, const std::string& key) -> Result<Value> {
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<std::string, Value>& object) -> Result<Value> {
if (object.find(key) == object.end()) {
return Null;
}
return object.at(key);
});
define("length", [](const std::vector<Value>& arr) -> Result<double> {
return arr.size();
});
define("length", [] (const std::string s) -> Result<double> {
return s.size();
});
define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> {
if (!params.feature) {
return EvaluationError {
"Feature data is unavailable in the current evaluation context."
};
}
std::unordered_map<std::string, Value> 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<std::string> {
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<Value> {
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<double>& args) -> Result<double> {
double sum = 0.0f;
for (auto arg : args) {
sum += arg;
}
return sum;
});
define("-", [](double a, double b) -> Result<double> { return a - b; });
define("-", [](double a) -> Result<double> { return -a; });
define("*", [](const Varargs<double>& args) -> Result<double> {
double prod = 1.0f;
for (auto arg : args) {
prod *= arg;
}
return prod;
});
define("/", [](double a, double b) -> Result<double> { return a / b; });
define("%", [](double a, double b) -> Result<double> { return fmod(a, b); });
define("^", [](double a, double b) -> Result<double> { return pow(a, b); });
define("sqrt", [](double x) -> Result<double> { return sqrt(x); });
define("log10", [](double x) -> Result<double> { return log10(x); });
define("ln", [](double x) -> Result<double> { return log(x); });
define("log2", [](double x) -> Result<double> { return log2(x); });
define("sin", [](double x) -> Result<double> { return sin(x); });
define("cos", [](double x) -> Result<double> { return cos(x); });
define("tan", [](double x) -> Result<double> { return tan(x); });
define("asin", [](double x) -> Result<double> { return asin(x); });
define("acos", [](double x) -> Result<double> { return acos(x); });
define("atan", [](double x) -> Result<double> { return atan(x); });
define("min", [](const Varargs<double>& args) -> Result<double> {
double result = std::numeric_limits<double>::infinity();
for (double arg : args) {
result = fmin(arg, result);
}
return result;
});
define("max", [](const Varargs<double>& args) -> Result<double> {
double result = -std::numeric_limits<double>::infinity();
for (double arg : args) {
result = fmax(arg, result);
}
return result;
});
define("==", equal<double>);
define("==", equal<const std::string&>);
define("==", equal<bool>);
define("==", equal<NullValue>);
define("!=", notEqual<double>);
define("!=", notEqual<const std::string&>);
define("!=", notEqual<bool>);
define("!=", notEqual<NullValue>);
define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; });
define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; });
define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; });
define(">=",[](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs >= rhs; });
define("<", [](double lhs, double rhs) -> Result<bool> { return lhs < rhs; });
define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; });
define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; });
define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; });
define("!", [](bool e) -> Result<bool> { return !e; });
define("upcase", [](const std::string& input) -> Result<std::string> {
std::string s = input;
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return std::toupper(c); });
return s;
});
define("downcase", [](const std::string& input) -> Result<std::string> {
std::string s = input;
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return std::tolower(c); });
return s;
});
define("concat", [](const Varargs<std::string>& args) -> Result<std::string> {
std::string s;
for (const std::string& arg : args) {
s += arg;
}
return s;
});
define("error", [](const std::string& input) -> Result<type::ErrorType> {
return EvaluationError { input };
});
return definitions;
}
std::unordered_map<std::string, Definition> CompoundExpressionRegistry::definitions = initializeDefinitions();
using namespace mbgl::style::conversion;
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<std::size_t> singleMatchingSignature;
for (std::size_t j = 0; j < definition.size(); j++) {
const std::unique_ptr<detail::SignatureBase>& signature = definition[j];
if (
signature->params.is<VarargsType>() ||
signature->params.get<std::vector<type::Type>>().size() == length - 1
) {
if (singleMatchingSignature) {
singleMatchingSignature = {};
} else {
singleMatchingSignature = j;
}
}
}
// parse subexpressions first
std::vector<std::unique_ptr<Expression>> args;
args.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
optional<type::Type> expected;
if (singleMatchingSignature) {
expected = definition[*singleMatchingSignature]->params.match(
[](const VarargsType& varargs) { return varargs.type; },
[&](const std::vector<type::Type>& 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(name, definition, std::move(args), ctx);
}
ParseResult createCompoundExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx)
{
return createCompoundExpression(name, CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx);
}
ParseResult createCompoundExpression(const std::string& name,
const Definition& definition,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx)
{
ParsingContext signatureContext(ctx.getKey());
for (const std::unique_ptr<detail::SignatureBase>& signature : definition) {
signatureContext.clearErrors();
if (signature->params.is<std::vector<type::Type>>()) {
const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>();
if (params.size() != args.size()) {
signatureContext.error(
"Expected " + std::to_string(params.size()) +
" arguments, but found " + std::to_string(args.size()) + " instead."
);
continue;
}
for (std::size_t i = 0; i < args.size(); i++) {
const std::unique_ptr<Expression>& arg = args[i];
optional<std::string> err = type::checkSubtype(params.at(i), arg->getType());
if (err) {
signatureContext.error(*err, i + 1);
}
}
} else if (signature->params.is<VarargsType>()) {
const type::Type& paramType = signature->params.get<VarargsType>().type;
for (std::size_t i = 0; i < args.size(); i++) {
const std::unique_ptr<Expression>& arg = args[i];
optional<std::string> err = type::checkSubtype(paramType, arg->getType());
if (err) {
signatureContext.error(*err, i + 1);
}
}
}
if (signatureContext.getErrors().size() == 0) {
return ParseResult(signature->makeExpression(name, std::move(args)));
}
}
if (definition.size() == 1) {
ctx.appendErrors(std::move(signatureContext));
} else {
std::string signatures;
for (const auto& signature : definition) {
signatures += (signatures.size() > 0 ? " | " : "");
signature->params.match(
[&](const VarargsType& varargs) {
signatures += "(" + toString(varargs.type) + ")";
},
[&](const std::vector<type::Type>& params) {
signatures += "(";
bool first = true;
for (const type::Type& param : params) {
if (!first) signatures += ", ";
signatures += toString(param);
first = false;
}
signatures += ")";
}
);
}
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();
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,76 @@
#include <mbgl/style/expression/find_zoom_curve.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/let.hpp>
#include <mbgl/style/expression/coalesce.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/optional.hpp>
namespace mbgl {
namespace style {
namespace expression {
optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e) {
optional<variant<const InterpolateBase*, const Step*, ParsingError>> result;
if (auto let = dynamic_cast<const Let*>(e)) {
result = findZoomCurve(let->getResult());
} else if (auto coalesce = dynamic_cast<const Coalesce*>(e)) {
std::size_t length = coalesce->getLength();
for (std::size_t i = 0; i < length; i++) {
result = findZoomCurve(coalesce->getChild(i));
if (result) {
break;
}
}
} else if (auto curve = dynamic_cast<const InterpolateBase*>(e)) {
auto z = dynamic_cast<CompoundExpressionBase*>(curve->getInput().get());
if (z && z->getName() == "zoom") {
result = {curve};
}
} else if (auto step = dynamic_cast<const Step*>(e)) {
auto z = dynamic_cast<CompoundExpressionBase*>(step->getInput().get());
if (z && z->getName() == "zoom") {
result = {step};
}
}
if (result && result->is<ParsingError>()) {
return result;
}
e->eachChild([&](const Expression& child) {
optional<variant<const InterpolateBase*, const Step*, ParsingError>> childResult(findZoomCurve(&child));
if (childResult) {
if (childResult->is<ParsingError>()) {
result = childResult;
} else if (!result && childResult) {
result = {ParsingError {
R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", ""
}};
} else if (result && childResult && result != childResult) {
result = {ParsingError {
R"(Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.)", ""
}};
}
}
});
return result;
}
variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e) {
return findZoomCurve(e)->match(
[](const ParsingError&) -> variant<const InterpolateBase*, const Step*> {
assert(false);
return {};
},
[](auto zoomCurve) -> variant<const InterpolateBase*, const Step*> {
return {std::move(zoomCurve)};
}
);
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,26 @@
#include <mbgl/style/expression/get_covering_stops.hpp>
namespace mbgl {
namespace style {
namespace expression {
Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops,
const double lower, const double upper) {
assert(!stops.empty());
auto minIt = stops.lower_bound(lower);
auto maxIt = stops.lower_bound(upper);
// lower_bound yields first element >= lowerZoom, but we want the *last*
// element <= lowerZoom, so if we found a stop > lowerZoom, back up by one.
if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) {
minIt--;
}
return Range<float> {
static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first),
static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first)
};
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,211 @@
#include <mbgl/style/expression/interpolate.hpp>
namespace mbgl {
namespace style {
namespace expression {
using Interpolator = variant<ExponentialInterpolator,
CubicBezierInterpolator>;
using namespace mbgl::style::conversion;
ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
auto length = arrayLength(value);
if (length < 2) {
ctx.error("Expected an interpolation type expression.");
return ParseResult();
}
const Convertible& interp = arrayMember(value, 1);
if (!isArray(interp) || arrayLength(interp) == 0) {
ctx.error("Expected an interpolation type expression.");
return ParseResult();
}
optional<Interpolator> interpolator;
const optional<std::string> interpName = toString(arrayMember(interp, 0));
if (interpName && *interpName == "linear") {
interpolator = {ExponentialInterpolator(1.0)};
} else if (interpName && *interpName == "exponential") {
optional<double> base;
if (arrayLength(interp) == 2) {
base = toDouble(arrayMember(interp, 1));
}
if (!base) {
ctx.error("Exponential interpolation requires a numeric base.", 1, 1);
return ParseResult();
}
interpolator = {ExponentialInterpolator(*base)};
} else if (interpName && *interpName == "cubic-bezier") {
optional<double> x1;
optional<double> y1;
optional<double> x2;
optional<double> y2;
if (arrayLength(interp) == 5) {
x1 = toDouble(arrayMember(interp, 1));
y1 = toDouble(arrayMember(interp, 2));
x2 = toDouble(arrayMember(interp, 3));
y2 = toDouble(arrayMember(interp, 4));
}
if (
!x1 || !y1 || !x2 || !y2 ||
*x1 < 0 || *x1 > 1 ||
*y1 < 0 || *y1 > 1 ||
*x2 < 0 || *x2 > 1 ||
*y2 < 0 || *y2 > 1
) {
ctx.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.", 1);
return ParseResult();
}
interpolator = {CubicBezierInterpolator(*x1, *y1, *x2, *y2)};
}
if (!interpolator) {
ctx.error("Unknown interpolation type " + (interpName ? *interpName : ""), 1, 0);
return ParseResult();
}
std::size_t minArgs = 4;
if (length - 1 < minArgs) {
ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + ".");
return ParseResult();
}
// [interpolation, interp_type, input, 2 * (n pairs)...]
if ((length - 1) % 2 != 0) {
ctx.error("Expected an even number of arguments.");
return ParseResult();
}
ParseResult input = ctx.parse(arrayMember(value, 2), 2, {type::Number});
if (!input) {
return input;
}
std::map<double, std::unique_ptr<Expression>> stops;
optional<type::Type> outputType;
if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
outputType = ctx.getExpected();
}
double previous = - std::numeric_limits<double>::infinity();
for (std::size_t i = 3; i + 1 < length; i += 2) {
const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i));
optional<double> label;
optional<std::string> labelError;
if (labelValue) {
labelValue->match(
[&](uint64_t n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](int64_t n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](double n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](const auto&) {}
);
}
if (!label) {
ctx.error(labelError ? *labelError :
R"(Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.)",
i);
return ParseResult();
}
if (*label <= previous) {
ctx.error(
R"(Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.)",
i
);
return ParseResult();
}
previous = *label;
auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
if (!output) {
return ParseResult();
}
if (!outputType) {
outputType = (*output)->getType();
}
stops.emplace(*label, std::move(*output));
}
assert(outputType);
if (
*outputType != type::Number &&
*outputType != type::Color &&
!(
outputType->is<type::Array>() &&
outputType->get<type::Array>().itemType == type::Number &&
outputType->get<type::Array>().N
)
)
{
ctx.error("Type " + toString(*outputType) + " is not interpolatable.");
return ParseResult();
}
return outputType->match(
[&](const type::NumberType&) -> ParseResult {
return interpolator->match([&](const auto& interpolator_) {
return ParseResult(std::make_unique<Interpolate<double>>(
*outputType, interpolator_, std::move(*input), std::move(stops)
));
});
},
[&](const type::ColorType&) -> ParseResult {
return interpolator->match([&](const auto& interpolator_) {
return ParseResult(std::make_unique<Interpolate<Color>>(
*outputType, interpolator_, std::move(*input), std::move(stops)
));
});
},
[&](const type::Array& arrayType) -> ParseResult {
return interpolator->match(
[&](const auto& continuousInterpolator) {
if (arrayType.itemType != type::Number || !arrayType.N) {
assert(false); // interpolability already checked above.
return ParseResult();
}
return ParseResult(std::make_unique<Interpolate<std::vector<Value>>>(
*outputType, continuousInterpolator, std::move(*input), std::move(stops)
));
}
);
},
[&](const auto&) {
// unreachable: Null, Boolean, String, Object, Value output types
// are not interpolatable, and interpolability was already checked above
assert(false);
return ParseResult();
}
);
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,40 @@
#include <mbgl/style/expression/is_constant.hpp>
namespace mbgl {
namespace style {
namespace expression {
bool isFeatureConstant(const Expression& expression) {
if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
const std::string name = e->getName();
optional<std::size_t> parameterCount = e->getParameterCount();
if (name == "get" && parameterCount && *parameterCount == 1) {
return false;
} else if (name == "has" && parameterCount && *parameterCount == 1) {
return false;
} else if (
name == "properties" ||
name == "geometry-type" ||
name == "id"
) {
return false;
}
}
bool featureConstant = true;
expression.eachChild([&](const Expression& e) {
if (featureConstant && !isFeatureConstant(e)) {
featureConstant = false;
}
});
return featureConstant;
}
bool isZoomConstant(const Expression& e) {
return isGlobalPropertyConstant(e, std::array<std::string, 1>{{"zoom"}});
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,29 @@
#include <mbgl/style/expression/is_expression.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <unordered_set>
namespace mbgl {
namespace style {
namespace expression {
using namespace mbgl::style::conversion;
bool isExpression(const Convertible& value) {
const ExpressionRegistry& registry = getExpressionRegistry();
if (!isArray(value) || arrayLength(value) == 0) return false;
optional<std::string> name = toString(arrayMember(value, 0));
if (!name) return false;
return (registry.find(*name) != registry.end()) ||
(CompoundExpressionRegistry::definitions.find(*name) != CompoundExpressionRegistry::definitions.end());
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,91 @@
#include <mbgl/style/expression/let.hpp>
#include <mbgl/style/conversion/get_json_type.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult Let::evaluate(const EvaluationContext& params) const {
return result->evaluate(params);
}
void Let::eachChild(const std::function<void(const Expression&)>& visit) const {
for (auto it = bindings.begin(); it != bindings.end(); it++) {
visit(*it->second);
}
visit(*result);
}
using namespace mbgl::style::conversion;
ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
std::size_t length = arrayLength(value);
if (length < 4) {
ctx.error("Expected at least 3 arguments, but found " + std::to_string(length - 1) + " instead.");
return ParseResult();
}
std::map<std::string, std::shared_ptr<Expression>> bindings_;
for(std::size_t i = 1; i < length - 1; i += 2) {
optional<std::string> name = toString(arrayMember(value, i));
if (!name) {
ctx.error("Expected string, but found " + getJSONType(arrayMember(value, i)) + " instead.", i);
return ParseResult();
}
bool isValidName = std::all_of(name->begin(), name->end(), [](unsigned char c) {
return std::isalnum(c) || c == '_';
});
if (!isValidName) {
ctx.error("Variable names must contain only alphanumeric characters or '_'.", 1);
return ParseResult();
}
ParseResult bindingValue = ctx.parse(arrayMember(value, i + 1), i + 1);
if (!bindingValue) {
return ParseResult();
}
bindings_.emplace(*name, std::move(*bindingValue));
}
ParseResult result_ = ctx.parse(arrayMember(value, length - 1), length - 1, ctx.getExpected(), bindings_);
if (!result_) {
return ParseResult();
}
return ParseResult(std::make_unique<Let>(std::move(bindings_), std::move(*result_)));
}
EvaluationResult Var::evaluate(const EvaluationContext& params) const {
return value->evaluate(params);
}
void Var::eachChild(const std::function<void(const Expression&)>&) const {}
ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) {
assert(isArray(value_));
if (arrayLength(value_) != 2 || !toString(arrayMember(value_, 1))) {
ctx.error("'var' expression requires exactly one string literal argument.");
return ParseResult();
}
std::string name_ = *toString(arrayMember(value_, 1));
optional<std::shared_ptr<Expression>> bindingValue = ctx.getBinding(name_);
if (!bindingValue) {
ctx.error(R"(Unknown variable ")" + name_ + R"(". Make sure ")" +
name_ + R"(" has been bound in an enclosing "let" expression before using it.)", 1);
return ParseResult();
}
return ParseResult(std::make_unique<Var>(name_, std::move(*bindingValue)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,108 @@
#include <mbgl/style/expression/literal.hpp>
namespace mbgl {
namespace style {
namespace expression {
template <typename T>
optional<Value> checkNumber(T n) {
if (n > std::numeric_limits<double>::max()) {
return {std::numeric_limits<double>::infinity()};
} else {
return {static_cast<double>(n)};
}
}
using namespace mbgl::style::conversion;
optional<Value> parseValue(const Convertible& value, ParsingContext& ctx) {
if (isUndefined(value)) return {Null};
if (isObject(value)) {
std::unordered_map<std::string, Value> result;
bool error = false;
eachMember(value, [&] (const std::string& k, const mbgl::style::conversion::Convertible& v) -> optional<conversion::Error> {
if (!error) {
optional<Value> memberValue = parseValue(v, ctx);
if (memberValue) {
result.emplace(k, *memberValue);
} else {
error = true;
}
}
return {};
});
return error ? optional<Value>() : optional<Value>(result);
}
if (isArray(value)) {
std::vector<Value> result;
const auto length = arrayLength(value);
for(std::size_t i = 0; i < length; i++) {
optional<Value> item = parseValue(arrayMember(value, i), ctx);
if (item) {
result.emplace_back(*item);
} else {
return optional<Value>();
}
}
return optional<Value>(result);
}
optional<mbgl::Value> v = toValue(value);
// since value represents a JSON value, if it's not undefined, object, or
// array, it must be convertible to mbgl::Value
assert(v);
return v->match(
[&](uint64_t n) { return checkNumber(n); },
[&](int64_t n) { return checkNumber(n); },
[&](double n) { return checkNumber(n); },
[&](const auto&) {
return optional<Value>(toExpressionValue(*v));
}
);
}
ParseResult Literal::parse(const Convertible& value, ParsingContext& ctx) {
if (isObject(value)) {
ctx.error(R"(Bare objects invalid. Use ["literal", {...}] instead.)");
return ParseResult();
} else if (isArray(value)) {
// object or array value, quoted with ["literal", value]
if (arrayLength(value) != 2) {
ctx.error("'literal' expression requires exactly one argument, but found " + std::to_string(arrayLength(value) - 1) + " instead.");
return ParseResult();
}
const optional<Value> parsedValue = parseValue(arrayMember(value, 1), ctx);
if (!parsedValue) {
return ParseResult();
}
// special case: infer the item type if possible for zero-length arrays
if (
ctx.getExpected() &&
ctx.getExpected()->template is<type::Array>() &&
parsedValue->template is<std::vector<Value>>()
) {
auto type = typeOf(*parsedValue).template get<type::Array>();
auto expected = ctx.getExpected()->template get<type::Array>();
if (
type.N && (*type.N == 0) &&
(!expected.N || (*expected.N == 0))
) {
return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>()));
}
}
return ParseResult(std::make_unique<Literal>(*parsedValue));
} else {
// bare primitive value (string, number, boolean, null)
const optional<Value> parsedValue = parseValue(value, ctx);
return ParseResult(std::make_unique<Literal>(*parsedValue));
}
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,262 @@
#include <mbgl/style/expression/match.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
namespace mbgl {
namespace style {
namespace expression {
template <typename T>
void Match<T>::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*input);
for (const std::pair<T, std::shared_ptr<Expression>>& branch : branches) {
visit(*branch.second);
}
visit(*otherwise);
}
template <typename T>
bool Match<T>::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Match*>(&e)) {
return (*input == *(rhs->input) &&
*otherwise == *(rhs->otherwise) &&
Expression::childrenEqual(branches, rhs->branches));
}
return false;
}
template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext& params) const {
const EvaluationResult inputValue = input->evaluate(params);
if (!inputValue) {
return inputValue.error();
}
auto it = branches.find(inputValue->get<std::string>());
if (it != branches.end()) {
return (*it).second->evaluate(params);
}
return otherwise->evaluate(params);
}
template<> EvaluationResult Match<int64_t>::evaluate(const EvaluationContext& params) const {
const EvaluationResult inputValue = input->evaluate(params);
if (!inputValue) {
return inputValue.error();
}
const auto numeric = inputValue->get<double>();
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<int64_t>;
template class Match<std::string>;
using InputType = variant<int64_t, std::string>;
using namespace mbgl::style::conversion;
optional<InputType> parseInputValue(const Convertible& input, ParsingContext& parentContext, std::size_t index, optional<type::Type>& inputType) {
using namespace mbgl::style::conversion;
optional<InputType> result;
optional<type::Type> 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 " + std::to_string(Value::maxSafeInteger()) + ".", index);
} else {
type = {type::Number};
result = {static_cast<int64_t>(n)};
}
},
[&] (int64_t n) {
if (!Value::isSafeInteger(n)) {
parentContext.error("Branch labels must be integers no larger than " + std::to_string(Value::maxSafeInteger()) + ".", index);
} else {
type = {type::Number};
result = {n};
}
},
[&] (double n) {
if (!Value::isSafeInteger(n)) {
parentContext.error("Branch labels must be integers no larger than " + std::to_string(Value::maxSafeInteger()) + ".", index);
} else if (n != std::floor(n)) {
parentContext.error("Numeric branch labels must be integer values.", index);
} else {
type = {type::Number};
result = {static_cast<int64_t>(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<std::string> err = type::checkSubtype(*inputType, *type);
if (err) {
parentContext.error(*err, index);
return optional<InputType>();
}
}
return result;
}
template <typename T>
static ParseResult create(type::Type outputType,
std::unique_ptr<Expression>input,
std::vector<std::pair<std::vector<InputType>,
std::unique_ptr<Expression>>> branches,
std::unique_ptr<Expression> otherwise,
ParsingContext& ctx) {
typename Match<T>::Branches typedBranches;
std::size_t index = 2;
typedBranches.reserve(branches.size());
for (std::pair<std::vector<InputType>,
std::unique_ptr<Expression>>& pair : branches) {
std::shared_ptr<Expression> result = std::move(pair.second);
for (const InputType& label : pair.first) {
const auto& typedLabel = label.template get<T>();
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<Match<T>>(
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 " + std::to_string(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<type::Type> inputType;
optional<type::Type> outputType;
if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
outputType = ctx.getExpected();
}
std::vector<std::pair<std::vector<InputType>,
std::unique_ptr<Expression>>> branches;
branches.reserve((length - 3) / 2);
for (size_t i = 2; i + 1 < length; i += 2) {
const auto& label = arrayMember(value, i);
std::vector<InputType> 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<InputType> inputValue = parseInputValue(arrayMember(label, j), ctx, i, inputType);
if (!inputValue) {
return ParseResult();
}
labels.push_back(*inputValue);
}
} else {
const optional<InputType> 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, inputType);
if (!input) {
return ParseResult();
}
auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType);
if (!otherwise) {
return ParseResult();
}
assert(inputType && outputType);
return inputType->match(
[&](const type::NumberType&) {
return create<int64_t>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx);
},
[&](const type::StringType&) {
return create<std::string>(*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

View File

@@ -0,0 +1,206 @@
#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/interpolate.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>
namespace mbgl {
namespace style {
namespace expression {
bool isConstant(const Expression& expression) {
if (dynamic_cast<const Var*>(&expression)) {
return false;
}
if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
if (compound->getName() == "error") {
return false;
}
}
bool literalArgs = true;
expression.eachChild([&](const Expression& child) {
if (!dynamic_cast<const Literal*>(&child)) {
literalArgs = false;
}
});
if (!literalArgs) {
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_) {
ParsingContext child(key + "[" + std::to_string(index_) + "]",
errors,
std::move(expected_),
scope);
return child.parse(value);
}
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 + "[" + std::to_string(index_) + "]",
errors,
std::move(expected_),
std::make_shared<detail::Scope>(bindings, scope));
return child.parse(value);
}
const ExpressionRegistry& getExpressionRegistry() {
static ExpressionRegistry registry {{
{"all", All::parse},
{"any", Any::parse},
{"array", ArrayAssertion::parse},
{"at", At::parse},
{"boolean", Assertion::parse},
{"case", Case::parse},
{"coalesce", Coalesce::parse},
{"interpolate", parseInterpolate},
{"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)
{
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);
} else if (expected) {
auto wrapForType = [&](const type::Type& target, std::unique_ptr<Expression> expression) -> std::unique_ptr<Expression> {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(std::move(expression));
if (target == type::Color) {
return std::make_unique<Coercion>(target, std::move(args));
} else {
return std::make_unique<Assertion>(target, std::move(args));
}
};
const type::Type actual = (*parsed)->getType();
if (*expected == type::Color && (actual == type::String || actual == type::Value)) {
parsed = wrapForType(type::Color, std::move(*parsed));
} else if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean) && actual == type::Value) {
parsed = wrapForType(*expected, std::move(*parsed));
}
checkType((*parsed)->getType());
if (errors->size() > 0) {
return ParseResult();
}
}
// If an expression's arguments are all literals, we can evaluate
// it immediately and replace it with a literal value in the
// parsed result.
if (parsed && !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));
}
}
// if this is the root expression, enforce constraints on the use ["zoom"].
if (key.size() == 0 && parsed && !isZoomConstant(**parsed)) {
optional<variant<const InterpolateBase*, 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;
}
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

View File

@@ -0,0 +1,151 @@
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/expression/get_covering_stops.hpp>
namespace mbgl {
namespace style {
namespace expression {
EvaluationResult Step::evaluate(const EvaluationContext& params) const {
const EvaluationResult evaluatedInput = input->evaluate(params);
if (!evaluatedInput) { return evaluatedInput.error(); }
float x = *fromExpressionValue<float>(*evaluatedInput);
if (stops.empty()) {
return EvaluationError { "No stops in step curve." };
}
auto it = stops.upper_bound(x);
if (it == stops.end()) {
return stops.rbegin()->second->evaluate(params);
} else if (it == stops.begin()) {
return stops.begin()->second->evaluate(params);
} else {
return std::prev(it)->second->evaluate(params);
}
}
void Step::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*input);
for (auto it = stops.begin(); it != stops.end(); it++) {
visit(*it->second);
}
}
bool Step::operator==(const Expression& e) const {
if (auto rhs = dynamic_cast<const Step*>(&e)) {
return *input == *(rhs->input) && Expression::childrenEqual(stops, rhs->stops);
}
return false;
}
Range<float> Step::getCoveringStops(const double lower, const double upper) const {
return ::mbgl::style::expression::getCoveringStops(stops, lower, upper);
}
ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx) {
assert(isArray(value));
auto length = arrayLength(value);
if (length - 1 < 4) {
ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + ".");
return ParseResult();
}
// [step, input, firstOutput_value, 2 * (n pairs)...]
if ((length - 1) % 2 != 0) {
ctx.error("Expected an even number of arguments.");
return ParseResult();
}
ParseResult input = ctx.parse(arrayMember(value, 1), 1, {type::Number});
if (!input) {
return input;
}
std::map<double, std::unique_ptr<Expression>> stops;
optional<type::Type> outputType;
if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
outputType = ctx.getExpected();
}
double previous = - std::numeric_limits<double>::infinity();
// consume the first output value, which doesn't have a corresponding input value,
// before proceeding into the "stops" loop below.
auto firstOutput = ctx.parse(arrayMember(value, 2), 2, outputType);
if (!firstOutput) {
return ParseResult();
}
if (!outputType) {
outputType = (*firstOutput)->getType();
}
stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*firstOutput));
for (std::size_t i = 3; i + 1 < length; i += 2) {
const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i));
optional<double> label;
if (labelValue) {
labelValue->match(
[&](uint64_t n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](int64_t n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](double n) {
if (n > std::numeric_limits<double>::max()) {
label = {std::numeric_limits<double>::infinity()};
} else {
label = {static_cast<double>(n)};
}
},
[&](const auto&) {}
);
}
if (!label) {
ctx.error(R"(Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", i);
return ParseResult();
}
if (*label <= previous) {
ctx.error(
R"(Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.)",
i
);
return ParseResult();
}
previous = *label;
auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
if (!output) {
return ParseResult();
}
if (!outputType) {
outputType = (*output)->getType();
}
stops.emplace(*label, std::move(*output));
}
assert(outputType);
return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,39 @@
#include <mbgl/style/expression/util.hpp>
#include <mbgl/style/expression/value.hpp>
namespace mbgl {
namespace style {
namespace expression {
std::string stringifyColor(double r, double g, double b, double a) {
return stringify(r) + ", " +
stringify(g) + ", " +
stringify(b) + ", " +
stringify(a);
}
Result<Color> rgba(double r, double g, double b, double a) {
if (
r < 0 || r > 255 ||
g < 0 || g > 255 ||
b < 0 || b > 255
) {
return EvaluationError {
"Invalid rgba value [" + stringifyColor(r, g, b, a) +
"]: 'r', 'g', and 'b' must be between 0 and 255."
};
}
if (a < 0 || a > 1) {
return EvaluationError {
"Invalid rgba value [" + stringifyColor(r, g, b, a) +
"]: 'a' must be between 0 and 1."
};
}
return Color(r / 255, g / 255, b / 255, a);
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,14 @@
#pragma once
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/util/color.hpp>
namespace mbgl {
namespace style {
namespace expression {
Result<Color> rgba(double r, double g, double b, double a);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,322 @@
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <mbgl/style/expression/value.hpp>
namespace mbgl {
namespace style {
namespace expression {
type::Type typeOf(const Value& value) {
return value.match(
[&](bool) -> type::Type { return type::Boolean; },
[&](double) -> type::Type { return type::Number; },
[&](const std::string&) -> type::Type { return type::String; },
[&](const Color&) -> type::Type { return type::Color; },
[&](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 {
optional<type::Type> itemType;
for (const auto& item : arr) {
const type::Type t = typeOf(item);
if (!itemType) {
itemType = {t};
} else if (*itemType == t) {
continue;
} else {
itemType = {type::Value};
break;
}
}
return type::Array(itemType.value_or(type::Value), arr.size());
}
);
}
void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value& value) {
value.match(
[&] (const NullValue&) { writer.Null(); },
[&] (bool b) { writer.Bool(b); },
[&] (double f) {
// make sure integer values are stringified without trailing ".0".
f == std::floor(f) ? writer.Int(f) : writer.Double(f);
},
[&] (const std::string& s) { writer.String(s); },
[&] (const Color& c) { writer.String(c.stringify()); },
[&] (const std::vector<Value>& arr) {
writer.StartArray();
for(const auto& item : arr) {
writeJSON(writer, item);
}
writer.EndArray();
},
[&] (const std::unordered_map<std::string, Value>& obj) {
writer.StartObject();
for(const auto& entry : obj) {
writer.Key(entry.first.c_str());
writeJSON(writer, entry.second);
}
writer.EndObject();
}
);
}
std::string stringify(const Value& value) {
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
writeJSON(writer, value);
return buffer.GetString();
}
struct FromMBGLValue {
Value operator()(const std::vector<mbgl::Value>& v) {
std::vector<Value> result;
result.reserve(v.size());
for(const auto& item : v) {
result.emplace_back(toExpressionValue(item));
}
return result;
}
Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) {
std::unordered_map<std::string, Value> result;
result.reserve(v.size());
for(const auto& entry : v) {
result.emplace(entry.first, toExpressionValue(entry.second));
}
return result;
}
Value operator()(const std::string& s) { return s; }
Value operator()(const bool b) { return b; }
Value operator()(const NullValue) { return Null; }
Value operator()(const double v) { return v; }
Value operator()(const uint64_t& v) {
return static_cast<double>(v);
}
Value operator()(const int64_t& v) {
return static_cast<double>(v);
}
};
Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) {
return mbgl::Value::visit(value, FromMBGLValue());
}
Value ValueConverter<float>::toExpressionValue(const float value) {
return static_cast<double>(value);
}
optional<float> ValueConverter<float>::fromExpressionValue(const Value& value) {
if (value.template is<double>()) {
double v = value.template get<double>();
if (v <= std::numeric_limits<float>::max()) {
return static_cast<float>(v);
}
}
return optional<float>();
}
template <typename T, typename Container>
std::vector<Value> toArrayValue(const Container& value) {
std::vector<Value> result;
result.reserve(value.size());
for (const T& item : value) {
result.push_back(ValueConverter<T>::toExpressionValue(item));
}
return result;
}
template <typename T, std::size_t N>
Value ValueConverter<std::array<T, N>>::toExpressionValue(const std::array<T, N>& value) {
return toArrayValue<T>(value);
}
template <typename T, std::size_t N>
optional<std::array<T, N>> ValueConverter<std::array<T, N>>::fromExpressionValue(const Value& value) {
return value.match(
[&] (const std::vector<Value>& v) -> optional<std::array<T, N>> {
if (v.size() != N) return optional<std::array<T, N>>();
std::array<T, N> result;
auto it = result.begin();
for(const Value& item : v) {
optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item);
if (!convertedItem) {
return optional<std::array<T, N>>();
}
*it = *convertedItem;
it = std::next(it);
}
return result;
},
[&] (const auto&) { return optional<std::array<T, N>>(); }
);
}
template <typename T>
Value ValueConverter<std::vector<T>>::toExpressionValue(const std::vector<T>& value) {
return toArrayValue<T>(value);
}
template <typename T>
optional<std::vector<T>> ValueConverter<std::vector<T>>::fromExpressionValue(const Value& value) {
return value.match(
[&] (const std::vector<Value>& v) -> optional<std::vector<T>> {
std::vector<T> result;
result.reserve(v.size());
for(const Value& item : v) {
optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item);
if (!convertedItem) {
return optional<std::vector<T>>();
}
result.push_back(*convertedItem);
}
return result;
},
[&] (const auto&) { return optional<std::vector<T>>(); }
);
}
Value ValueConverter<Position>::toExpressionValue(const mbgl::style::Position& value) {
return ValueConverter<std::array<float, 3>>::toExpressionValue(value.getSpherical());
}
optional<Position> ValueConverter<Position>::fromExpressionValue(const Value& v) {
auto pos = ValueConverter<std::array<float, 3>>::fromExpressionValue(v);
return pos ? optional<Position>(Position(*pos)) : optional<Position>();
}
template <typename T>
Value ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::toExpressionValue(const T& value) {
return std::string(Enum<T>::toString(value));
}
template <typename T>
optional<T> ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::fromExpressionValue(const Value& value) {
return value.match(
[&] (const std::string& v) { return Enum<T>::toEnum(v); },
[&] (const auto&) { return optional<T>(); }
);
}
Value toExpressionValue(const Value& v) {
return v;
}
template <typename T, typename Enable>
Value toExpressionValue(const T& value) {
return ValueConverter<T>::toExpressionValue(value);
}
optional<Value> fromExpressionValue(const Value& v) {
return optional<Value>(v);
}
template <typename T>
std::enable_if_t< !std::is_convertible<T, Value>::value,
optional<T>> fromExpressionValue(const Value& v)
{
return ValueConverter<T>::fromExpressionValue(v);
}
template <typename T>
type::Type valueTypeToExpressionType() {
return ValueConverter<T>::expressionType();
}
template <> type::Type valueTypeToExpressionType<Value>() { return type::Value; }
template <> type::Type valueTypeToExpressionType<NullValue>() { return type::Null; }
template <> type::Type valueTypeToExpressionType<bool>() { return type::Boolean; }
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<std::unordered_map<std::string, Value>>() { return type::Object; }
template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); }
// used only for the special (and private) "error" expression
template <> type::Type valueTypeToExpressionType<type::ErrorType>() { return type::Error; }
template Value toExpressionValue(const mbgl::Value&);
// for to_rgba expression
template type::Type valueTypeToExpressionType<std::array<double, 4>>();
template optional<std::array<double, 4>> fromExpressionValue<std::array<double, 4>>(const Value&);
template Value toExpressionValue(const std::array<double, 4>&);
// layout/paint property types
template type::Type valueTypeToExpressionType<float>();
template optional<float> fromExpressionValue<float>(const Value&);
template Value toExpressionValue(const float&);
template type::Type valueTypeToExpressionType<std::array<float, 2>>();
template optional<std::array<float, 2>> fromExpressionValue<std::array<float, 2>>(const Value&);
template Value toExpressionValue(const std::array<float, 2>&);
template type::Type valueTypeToExpressionType<std::array<float, 4>>();
template optional<std::array<float, 4>> fromExpressionValue<std::array<float, 4>>(const Value&);
template Value toExpressionValue(const std::array<float, 4>&);
template type::Type valueTypeToExpressionType<std::vector<float>>();
template optional<std::vector<float>> fromExpressionValue<std::vector<float>>(const Value&);
template Value toExpressionValue(const std::vector<float>&);
template type::Type valueTypeToExpressionType<std::vector<std::string>>();
template optional<std::vector<std::string>> fromExpressionValue<std::vector<std::string>>(const Value&);
template Value toExpressionValue(const std::vector<std::string>&);
template type::Type valueTypeToExpressionType<AlignmentType>();
template optional<AlignmentType> fromExpressionValue<AlignmentType>(const Value&);
template Value toExpressionValue(const AlignmentType&);
template type::Type valueTypeToExpressionType<CirclePitchScaleType>();
template optional<CirclePitchScaleType> fromExpressionValue<CirclePitchScaleType>(const Value&);
template Value toExpressionValue(const CirclePitchScaleType&);
template type::Type valueTypeToExpressionType<IconTextFitType>();
template optional<IconTextFitType> fromExpressionValue<IconTextFitType>(const Value&);
template Value toExpressionValue(const IconTextFitType&);
template type::Type valueTypeToExpressionType<LineCapType>();
template optional<LineCapType> fromExpressionValue<LineCapType>(const Value&);
template Value toExpressionValue(const LineCapType&);
template type::Type valueTypeToExpressionType<LineJoinType>();
template optional<LineJoinType> fromExpressionValue<LineJoinType>(const Value&);
template Value toExpressionValue(const LineJoinType&);
template type::Type valueTypeToExpressionType<SymbolPlacementType>();
template optional<SymbolPlacementType> fromExpressionValue<SymbolPlacementType>(const Value&);
template Value toExpressionValue(const SymbolPlacementType&);
template type::Type valueTypeToExpressionType<SymbolAnchorType>();
template optional<SymbolAnchorType> fromExpressionValue<SymbolAnchorType>(const Value&);
template Value toExpressionValue(const SymbolAnchorType&);
template type::Type valueTypeToExpressionType<TextJustifyType>();
template optional<TextJustifyType> fromExpressionValue<TextJustifyType>(const Value&);
template Value toExpressionValue(const TextJustifyType&);
template type::Type valueTypeToExpressionType<TextTransformType>();
template optional<TextTransformType> fromExpressionValue<TextTransformType>(const Value&);
template Value toExpressionValue(const TextTransformType&);
template type::Type valueTypeToExpressionType<TranslateAnchorType>();
template optional<TranslateAnchorType> fromExpressionValue<TranslateAnchorType>(const Value&);
template Value toExpressionValue(const TranslateAnchorType&);
template type::Type valueTypeToExpressionType<LightAnchorType>();
template optional<LightAnchorType> fromExpressionValue<LightAnchorType>(const Value&);
template Value toExpressionValue(const LightAnchorType&);
template type::Type valueTypeToExpressionType<Position>();
template optional<Position> fromExpressionValue<Position>(const Value&);
template Value toExpressionValue(const Position&);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,38 @@
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
namespace mbgl {
namespace style {
namespace expression {
class GeoJSONFeature : public GeometryTileFeature {
public:
const Feature& feature;
GeoJSONFeature(const Feature& feature_) : feature(feature_) {}
FeatureType getType() const override {
return apply_visitor(ToFeatureType(), feature.geometry);
}
PropertyMap getProperties() const override { return feature.properties; }
optional<FeatureIdentifier> getID() const override { return feature.id; }
GeometryCollection getGeometries() const override { return {}; }
optional<mbgl::Value> getValue(const std::string& key) const override {
auto it = feature.properties.find(key);
if (it != feature.properties.end()) {
return optional<mbgl::Value>(it->second);
}
return optional<mbgl::Value>();
}
};
EvaluationResult Expression::evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const {
GeoJSONFeature f(feature);
return this->evaluate(EvaluationContext(zoom, &f, heatmapDensity));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,4 @@
[
"acos",
0.5
]

View File

@@ -0,0 +1,4 @@
[
"acos",
1.5
]

View File

@@ -0,0 +1,17 @@
[
"all",
[
"boolean",
[
"get",
"x"
]
],
[
"boolean",
[
"get",
"y"
]
]
]

View File

@@ -0,0 +1,17 @@
[
"all",
[
"boolean",
[
"get",
"x"
]
],
[
"boolean",
[
"get",
"y_other"
]
]
]

View File

@@ -0,0 +1,17 @@
[
"any",
[
"boolean",
[
"get",
"x"
]
],
[
"boolean",
[
"get",
"y"
]
]
]

View File

@@ -0,0 +1,17 @@
[
"any",
[
"boolean",
[
"get",
"x"
]
],
[
"boolean",
[
"get",
"y_other"
]
]
]

View File

@@ -0,0 +1,11 @@
[
"array",
[
"literal",
[
1,
2,
3
]
]
]

View File

@@ -0,0 +1,11 @@
[
"array",
[
"literal",
[
1,
2,
4
]
]
]

View File

@@ -0,0 +1,4 @@
[
"asin",
0.5
]

View File

@@ -0,0 +1,4 @@
[
"asin",
1.5
]

View File

@@ -0,0 +1,20 @@
[
"number",
[
"at",
[
"number",
[
"get",
"i"
]
],
[
"array",
[
"get",
"arr"
]
]
]
]

View File

@@ -0,0 +1,20 @@
[
"number",
[
"at",
[
"number",
[
"get",
"i"
]
],
[
"array",
[
"get",
"arr_other"
]
]
]
]

View File

@@ -0,0 +1,4 @@
[
"atan",
1
]

View File

@@ -0,0 +1,4 @@
[
"atan",
2
]

View File

@@ -0,0 +1,7 @@
[
"boolean",
[
"get",
"x"
]
]

View File

@@ -0,0 +1,7 @@
[
"boolean",
[
"get",
"x_other"
]
]

View File

@@ -0,0 +1,14 @@
[
"case",
[
"get",
"x"
],
"x",
[
"get",
"y"
],
"y",
"otherwise"
]

View File

@@ -0,0 +1,14 @@
[
"case",
[
"get",
"x"
],
"x",
[
"get",
"y"
],
"y",
"otherwise_other"
]

View File

@@ -0,0 +1,16 @@
[
"coalesce",
[
"get",
"x"
],
[
"get",
"y"
],
[
"get",
"z"
],
0
]

View File

@@ -0,0 +1,16 @@
[
"coalesce",
[
"get",
"x"
],
[
"get",
"y"
],
[
"get",
"z"
],
1
]

View File

@@ -0,0 +1,6 @@
[
"concat",
"a",
"b",
"c"
]

View File

@@ -0,0 +1,6 @@
[
"concat",
"a",
"b",
"c_other"
]

View File

@@ -0,0 +1,4 @@
[
"cos",
0
]

View File

@@ -0,0 +1,4 @@
[
"cos",
1
]

View File

@@ -0,0 +1,5 @@
[
"/",
10,
5
]

View File

@@ -0,0 +1,5 @@
[
"/",
10,
6
]

View File

@@ -0,0 +1,4 @@
[
"downcase",
"StRiNg"
]

View File

@@ -0,0 +1,4 @@
[
"downcase",
"StRiNg_other"
]

View File

@@ -0,0 +1,7 @@
[
"number",
[
"get",
"x"
]
]

View File

@@ -0,0 +1,7 @@
[
"number",
[
"get",
"x_other"
]
]

Some files were not shown because too many files have changed in this diff Show More