summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnand Thakker <github@anandthakker.net>2017-07-06 09:37:46 -0400
committerAnand Thakker <github@anandthakker.net>2017-08-11 21:54:48 -0400
commitfd2c1055a786c365b973a80b3f8cc36c9ba2fa8d (patch)
treef26f9fb9f1f1ef13259f7d937f1c3b70188acf76
parent25a0b7925c0d8c215ae7b02550cadf4df59022ac (diff)
downloadqtlocation-mapboxgl-fd2c1055a786c365b973a80b3f8cc36c9ba2fa8d.tar.gz
Add base Expression model
-rw-r--r--cmake/core-files.cmake15
-rw-r--r--cmake/node.cmake2
-rw-r--r--include/mbgl/style/conversion/expression.hpp27
-rw-r--r--include/mbgl/style/expression/definitions.hpp322
-rw-r--r--include/mbgl/style/expression/expression.hpp257
-rw-r--r--include/mbgl/style/expression/parse.hpp117
-rw-r--r--include/mbgl/style/expression/parsing_context.hpp40
-rw-r--r--include/mbgl/style/expression/type.hpp116
-rw-r--r--include/mbgl/style/expression/type_check.hpp14
-rw-r--r--include/mbgl/style/expression/value.hpp43
-rw-r--r--package.json2
-rw-r--r--platform/node/src/node_expression.cpp201
-rw-r--r--platform/node/src/node_expression.hpp40
-rw-r--r--platform/node/src/node_mapbox_gl_native.cpp2
-rw-r--r--platform/node/test/expression.test.js52
-rw-r--r--src/mbgl/style/expression/definitions.cpp355
-rw-r--r--src/mbgl/style/expression/type.cpp120
-rw-r--r--src/mbgl/style/expression/type_check.cpp121
-rw-r--r--src/mbgl/style/expression/value.cpp108
-rw-r--r--src/mbgl/style/function/expression.cpp50
20 files changed, 2003 insertions, 1 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 1a86da6f24..ee15bf5b91 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -371,6 +371,7 @@ 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
@@ -388,6 +389,19 @@ set(MBGL_CORE_FILES
src/mbgl/style/conversion/json.hpp
src/mbgl/style/conversion/stringify.hpp
+ # style/expression
+ include/mbgl/style/expression/definitions.hpp
+ include/mbgl/style/expression/expression.hpp
+ include/mbgl/style/expression/parse.hpp
+ include/mbgl/style/expression/parsing_context.hpp
+ include/mbgl/style/expression/type.hpp
+ include/mbgl/style/expression/type_check.hpp
+ include/mbgl/style/expression/value.hpp
+ src/mbgl/style/expression/definitions.cpp
+ src/mbgl/style/expression/type.cpp
+ src/mbgl/style/expression/type_check.cpp
+ src/mbgl/style/expression/value.cpp
+
# style/function
include/mbgl/style/function/camera_function.hpp
include/mbgl/style/function/categorical_stops.hpp
@@ -400,6 +414,7 @@ set(MBGL_CORE_FILES
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
diff --git a/cmake/node.cmake b/cmake/node.cmake
index 502edd8293..add66e7fbb 100644
--- a/cmake/node.cmake
+++ b/cmake/node.cmake
@@ -21,6 +21,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
)
diff --git a/include/mbgl/style/conversion/expression.hpp b/include/mbgl/style/conversion/expression.hpp
new file mode 100644
index 0000000000..bce7c91396
--- /dev/null
+++ b/include/mbgl/style/conversion/expression.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <memory>
+#include <mbgl/style/expression/parse.hpp>
+#include <mbgl/style/conversion.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+using namespace mbgl::style::expression;
+
+template<> struct Converter<std::unique_ptr<Expression>> {
+ template <class V>
+ optional<std::unique_ptr<Expression>> operator()(const V& value, Error& error) const {
+ auto parsed = parseExpression(value, ParsingContext());
+ if (parsed.template is<std::unique_ptr<Expression>>()) {
+ return std::move(parsed.template get<std::unique_ptr<Expression>>());
+ }
+ error = { parsed.template get<CompileError>().message };
+ return {};
+ };
+};
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/definitions.hpp b/include/mbgl/style/expression/definitions.hpp
new file mode 100644
index 0000000000..7557e9a682
--- /dev/null
+++ b/include/mbgl/style/expression/definitions.hpp
@@ -0,0 +1,322 @@
+#include <mbgl/style/expression/expression.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+
+// Concrete expression definitions
+class MathConstant : public LambdaExpression {
+public:
+ MathConstant(const std::string& key, const std::string& name, float value_) :
+ LambdaExpression(key, name, {}, type::Number, {{}}),
+ value(value_)
+ {}
+
+ EvaluationResult evaluate(const EvaluationParameters&) const override { return value; }
+
+ std::unique_ptr<Expression> applyInferredType(const type::Type&, Args) const override {
+ return std::make_unique<MathConstant>(getKey(), getName(), value);
+ }
+
+ // TODO: declaring these constants like `static constexpr double E = 2.718...` caused
+ // a puzzling link error.
+ static std::unique_ptr<Expression> ln2(const ParsingContext& ctx) {
+ return std::make_unique<MathConstant>(ctx.key(), "ln2", 0.693147180559945309417);
+ }
+ static std::unique_ptr<Expression> e(const ParsingContext& ctx) {
+ return std::make_unique<MathConstant>(ctx.key(), "e", 2.71828182845904523536);
+ }
+ static std::unique_ptr<Expression> pi(const ParsingContext& ctx) {
+ return std::make_unique<MathConstant>(ctx.key(), "pi", 3.14159265358979323846);
+ }
+private:
+ float value;
+};
+
+class TypeOf : public LambdaBase<TypeOf> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::StringType type() { return type::String; };
+ static std::vector<Params> signatures() {
+ return {{type::Value}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+// TODO: This doesn't work with CRTP. With:
+// `template <typename T> class Assertion : public LambdaBase<Assertion<T>>`,
+// there's an error referring to member 'args'.
+template <typename T>
+class Assertion : public LambdaExpression {
+public:
+ Assertion(const std::string& key, const std::string& name, Args args) :
+ LambdaExpression(key, name, std::move(args), Assertion::type(), Assertion::signatures())
+ {}
+ Assertion(const std::string& key, const std::string& name, const type::Type& type, Args args) :
+ LambdaExpression(key, name, std::move(args), type, Assertion::signatures())
+ {}
+
+ static type::Type type() { return valueTypeToExpressionType<T>(); }
+ static std::vector<LambdaExpression::Params> signatures() { return {{type::Value}}; };
+
+ std::unique_ptr<Expression> applyInferredType(const type::Type& type, Args args) const override {
+ return std::make_unique<Assertion>(getKey(), getName(), type, std::move(args));
+ }
+
+ EvaluationResult evaluate(const EvaluationParameters& params) const override {
+ const auto& result = args[0]->template evaluate<T>(params);
+ if (result) return *result;
+ return result.error();
+ };
+};
+
+class Array : public LambdaBase<Array> {
+public:
+ Array(const std::string& key, const std::string& name, type::Type type, Args args) :
+ LambdaBase(key, name, type, std::move(args))
+ {}
+
+ static std::vector<Params> signatures() { return {{type::Value}}; }
+
+ template <typename V>
+ static ParseResult parse(const V& value, const ParsingContext& ctx) {
+ assert(isArray(value));
+ auto length = arrayLength(value);
+ if (length < 2) return CompileError { "Expected at least one argument to \"array\"", ctx.key() };
+ if (length > 4) return CompileError {
+ "Expected one, two, or three arguments to \"array\", but found " + std::to_string(length - 1) + " instead.",
+ ctx.key()
+ };
+
+ const std::string& name = *toString(arrayMember(value, 0));
+
+ auto arg = parseExpression(arrayMember(value, 1), ParsingContext(ctx, {1}, {"array"}));
+ if (arg.template is<CompileError>()) return arg.template get<CompileError>();
+ Args args;
+ args.push_back(std::move(arg.template get<std::unique_ptr<Expression>>()));
+
+ // parse the optional item type and length arguments
+ optional<type::Type> itemType;
+ optional<size_t> N;
+ if (length > 2) {
+ const auto& itemTypeName = toString(arrayMember(value, 2));
+ if (itemTypeName && *itemTypeName == "string") {
+ itemType = {type::String};
+ } else if (itemTypeName && *itemTypeName == "number") {
+ itemType = {type::String};
+ } else if (itemTypeName && *itemTypeName == "boolean") {
+ itemType = {type::String};
+ } else {
+ return CompileError {
+ "The item type argument to \"array\" must be one of ${Object.keys(types).join(', ')}",
+ ctx.key(2)
+ };
+ }
+ }
+ if (length > 3) {
+ const auto& arrayLength = toNumber(arrayMember(value, 3));
+ if (!arrayLength) return CompileError {
+ "The length argument to \"array\" must be a number literal.",
+ ctx.key(3)
+ };
+ N = static_cast<size_t>(*arrayLength);
+ }
+ return std::make_unique<Array>(ctx.key(), name, type::Array(type::Value), std::move(args));
+ }
+
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class ToString : public LambdaBase<ToString> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::String; };
+ static std::vector<Params> signatures() { return {{ type::Value }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class ToNumber : public LambdaBase<ToNumber> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Number; };
+ static std::vector<Params> signatures() { return {{ type::Value }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class ToBoolean : public LambdaBase<ToBoolean> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Boolean; };
+ static std::vector<Params> signatures() { return {{ type::Value }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class ToRGBA : public LambdaBase<ToRGBA> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Array(type::Number, 4); };
+ static std::vector<Params> signatures() { return {{ type::Color }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class ParseColor : public LambdaBase<ParseColor> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Color; };
+ static std::vector<Params> signatures() { return {{ type::String }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class RGB : public LambdaBase<RGB> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Color; };
+ static std::vector<Params> signatures() { return {{ type::Number, type::Number, type::Number }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class RGBA : public LambdaBase<RGBA> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Color; };
+ static std::vector<Params> signatures() { return {{ type::Number, type::Number, type::Number, type::Number }}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Get : public LambdaBase<Get> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::ValueType type() { return type::Value; };
+ static std::vector<Params> signatures() {
+ return {{type::String, NArgs { {type::Object}, 1 }}};
+ };
+ bool isFeatureConstant() const override;
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Has : public LambdaBase<Has> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Boolean; };
+ static std::vector<Params> signatures() {
+ return {{type::String, NArgs { {type::Object}, 1 }}};
+ };
+ bool isFeatureConstant() const override;
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class At : public LambdaBase<At> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Typename("T"); };
+ static std::vector<Params> signatures() {
+ return {{type::Number, type::Array(type::Typename("T"))}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Length : public LambdaBase<Length> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {
+ {type::Array(type::Typename("T"))},
+ {type::String}
+ };
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Properties : public LambdaBase<Properties> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Object; };
+ static std::vector<Params> signatures() { return {{}}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+ bool isFeatureConstant() const override;
+};
+
+class Id : public LambdaBase<Id> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::Value; };
+ static std::vector<Params> signatures() { return {{}}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+ bool isFeatureConstant() const override;
+};
+
+class GeometryType : public LambdaBase<GeometryType> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::Type type() { return type::String; };
+ static std::vector<Params> signatures() { return {{}}; };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+ bool isFeatureConstant() const override;
+};
+
+class Plus : public LambdaBase<Plus> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{NArgs {{type::Number}, {}}}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Times : public LambdaBase<Times> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{NArgs {{type::Number}, {}}}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Minus : public LambdaBase<Minus> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{type::Number, type::Number}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Divide : public LambdaBase<Divide> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{type::Number, type::Number}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Mod : public LambdaBase<Mod> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{type::Number, type::Number}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+class Power : public LambdaBase<Power> {
+public:
+ using LambdaBase::LambdaBase;
+ static type::NumberType type() { return type::Number; };
+ static std::vector<Params> signatures() {
+ return {{type::Number, type::Number}};
+ };
+ EvaluationResult evaluate(const EvaluationParameters& params) const override;
+};
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp
new file mode 100644
index 0000000000..9c4d55784a
--- /dev/null
+++ b/include/mbgl/style/expression/expression.hpp
@@ -0,0 +1,257 @@
+#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>
+#include <mbgl/style/conversion.hpp>
+
+
+namespace mbgl {
+
+class GeometryTileFeature;
+
+namespace style {
+namespace expression {
+
+struct EvaluationError {
+ std::string message;
+};
+
+struct EvaluationParameters {
+ float zoom;
+ const GeometryTileFeature& feature;
+};
+
+template<typename T>
+class Result : private variant<EvaluationError, T> {
+public:
+ using variant<EvaluationError, T>::variant;
+
+ 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>();
+ }
+};
+
+using EvaluationResult = Result<Value>;
+
+struct CompileError {
+ std::string message;
+ std::string key;
+};
+
+class Expression {
+public:
+ Expression(std::string key_, type::Type type_) : key(key_), type(type_) {}
+ virtual ~Expression() {};
+
+ virtual EvaluationResult evaluate(const EvaluationParameters& params) const = 0;
+
+ /*
+ Evaluate this expression to a particular value type T. (See expression/value.hpp for
+ possible types T.)
+ */
+ template <typename T>
+ Result<T> evaluate(const EvaluationParameters& params) {
+ const auto& result = evaluate(params);
+ if (!result) { return result.error(); }
+ return result->match(
+ [&] (const T& v) -> variant<EvaluationError, T> { return v; },
+ [&] (const auto& v) -> variant<EvaluationError, T> {
+ return EvaluationError {
+ "Expected value to be of type " + toString(valueTypeToExpressionType<T>()) +
+ ", but found " + toString(typeOf(v)) + " instead."
+ };
+ }
+ );
+ }
+
+ EvaluationResult evaluate(float z, const Feature& feature) const;
+
+ type::Type getType() const { return type; }
+ std::string getKey() const { return key; }
+
+ virtual bool isFeatureConstant() const { return true; }
+ virtual bool isZoomConstant() const { return true; }
+
+protected:
+ void setType(type::Type newType) { type = newType; }
+
+private:
+ std::string key;
+ type::Type type;
+};
+
+
+using ParseResult = variant<CompileError, std::unique_ptr<Expression>>;
+template <class V>
+ParseResult parseExpression(const V& value, const ParsingContext& context);
+
+using TypecheckResult = variant<std::vector<CompileError>, std::unique_ptr<Expression>>;
+
+using namespace mbgl::style::conversion;
+
+class LiteralExpression : public Expression {
+public:
+ LiteralExpression(std::string key_, Value value_) : Expression(key_, typeOf(value_)), value(value_) {}
+
+ Value getValue() const { return value; }
+
+ EvaluationResult evaluate(const EvaluationParameters&) const override {
+ return value;
+ }
+
+ template <class V>
+ static ParseResult parse(const V& value, const ParsingContext& ctx) {
+ const Value& parsedValue = parseValue(value);
+ return std::make_unique<LiteralExpression>(ctx.key(), parsedValue);
+ }
+
+private:
+ template <class V>
+ static Value parseValue(const V& value) {
+ if (isUndefined(value)) return Null;
+ if (isObject(value)) {
+ std::unordered_map<std::string, Value> result;
+ eachMember(value, [&] (const std::string& k, const V& v) -> optional<conversion::Error> {
+ result.emplace(k, parseValue(v));
+ return {};
+ });
+ return result;
+ }
+ if (isArray(value)) {
+ std::vector<Value> result;
+ const auto length = arrayLength(value);
+ for(std::size_t i = 0; i < length; i++) {
+ result.emplace_back(parseValue(arrayMember(value, i)));
+ }
+ return result;
+ }
+
+ optional<mbgl::Value> v = toValue(value);
+ assert(v);
+ return convertValue(*v);
+ }
+
+ Value value;
+};
+
+struct NArgs {
+ std::vector<type::Type> types;
+ optional<std::size_t> N;
+};
+
+class LambdaExpression : public Expression {
+public:
+ using Params = std::vector<variant<type::Type, NArgs>>;
+ using Args = std::vector<std::unique_ptr<Expression>>;
+
+ LambdaExpression(std::string key_,
+ std::string name_,
+ Args args_,
+ type::Type type_,
+ std::vector<Params> signatures_) :
+ Expression(key_, type_),
+ args(std::move(args_)),
+ signatures(signatures_),
+ name(name_)
+ {}
+
+ std::string getName() const { return name; }
+
+ virtual bool isFeatureConstant() const override {
+ bool isFC = true;
+ for (const auto& arg : args) {
+ isFC = isFC && arg->isFeatureConstant();
+ }
+ return isFC;
+ }
+
+ virtual bool isZoomConstant() const override {
+ bool isZC = true;
+ for (const auto& arg : args) {
+ isZC = isZC && arg->isZoomConstant();
+ }
+ return isZC;
+ }
+
+ virtual std::unique_ptr<Expression> applyInferredType(const type::Type& type, Args args) const = 0;
+
+ friend TypecheckResult typecheck(const type::Type& expected, const std::unique_ptr<Expression>& e);
+
+ template <class Expr, class V>
+ static ParseResult parse(const V& value, const ParsingContext& ctx) {
+ assert(isArray(value));
+ auto length = arrayLength(value);
+ const std::string& name = *toString(arrayMember(value, 0));
+ Args args;
+ for(size_t i = 1; i < length; i++) {
+ const auto& arg = arrayMember(value, i);
+ auto parsedArg = parseExpression(arg, ParsingContext(ctx, i, {}));
+ if (parsedArg.template is<std::unique_ptr<Expression>>()) {
+ args.push_back(std::move(parsedArg.template get<std::unique_ptr<Expression>>()));
+ } else {
+ return parsedArg.template get<CompileError>();
+ }
+ }
+ return std::make_unique<Expr>(ctx.key(), name, std::move(args));
+ }
+
+protected:
+ Args args;
+private:
+ std::vector<Params> signatures;
+ std::string name;
+};
+
+template<class Expr>
+class LambdaBase : public LambdaExpression {
+public:
+ LambdaBase(const std::string& key, const std::string& name, Args args) :
+ LambdaExpression(key, name, std::move(args), Expr::type(), Expr::signatures())
+ {}
+ LambdaBase(const std::string& key, const std::string& name, const type::Type& type, Args args) :
+ LambdaExpression(key, name, std::move(args), type, Expr::signatures())
+ {}
+
+ std::unique_ptr<Expression> applyInferredType(const type::Type& type, Args args) const override {
+ return std::make_unique<Expr>(getKey(), getName(), type, std::move(args));
+ }
+};
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/parse.hpp b/include/mbgl/style/expression/parse.hpp
new file mode 100644
index 0000000000..9f04cef848
--- /dev/null
+++ b/include/mbgl/style/expression/parse.hpp
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <memory>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/definitions.hpp>
+#include <mbgl/style/conversion.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+using namespace mbgl::style;
+
+template <class V>
+std::string getJSType(const V& value) {
+ using namespace mbgl::style::conversion;
+ if (isUndefined(value)) {
+ return "undefined";
+ }
+ if (isArray(value) || isObject(value)) {
+ return "object";
+ }
+ optional<mbgl::Value> v = toValue(value);
+ assert(v);
+ return v->match(
+ [&] (std::string) { return "string"; },
+ [&] (bool) { return "boolean"; },
+ [&] (auto) { return "number"; }
+ );
+}
+
+using ParseResult = variant<CompileError, std::unique_ptr<Expression>>;
+
+template <class V>
+ParseResult parseExpression(const V& value, const ParsingContext& context)
+{
+ using namespace mbgl::style::conversion;
+
+ if (isArray(value)) {
+ const std::size_t length = arrayLength(value);
+ if (length == 0) {
+ CompileError error = {
+ "Expected an array with at least one element. If you wanted a literal array, use [\"literal\", []].",
+ context.key()
+ };
+ return error;
+ }
+
+ const optional<std::string>& op = toString(arrayMember(value, 0));
+ if (!op) {
+ CompileError error = {
+ "Expression name must be a string, but found " + getJSType(arrayMember(value, 0)) +
+ " instead. If you wanted a literal array, use [\"literal\", [...]].",
+ context.key(0)
+ };
+ return error;
+ }
+
+ if (*op == "literal") {
+ if (length != 2) return CompileError {
+ "'literal' expression requires exactly one argument, but found " + std::to_string(length - 1) + " instead.",
+ context.key()
+ };
+ return LiteralExpression::parse(arrayMember(value, 1), ParsingContext(context, {1}, {"literal"}));
+ }
+
+ if (*op == "e") return MathConstant::e(context);
+ if (*op == "pi") return MathConstant::pi(context);
+ if (*op == "ln2") return MathConstant::ln2(context);
+ if (*op == "typeof") return LambdaExpression::parse<TypeOf>(value, context);
+ if (*op == "string") return LambdaExpression::parse<Assertion<std::string>>(value, context);
+ if (*op == "number") return LambdaExpression::parse<Assertion<float>>(value, context);
+ if (*op == "boolean") return LambdaExpression::parse<Assertion<bool>>(value, context);
+ if (*op == "array") return Array::parse(value, context);
+ if (*op == "to_string") return LambdaExpression::parse<ToString>(value, context);
+ if (*op == "to_number") return LambdaExpression::parse<ToNumber>(value, context);
+ if (*op == "to_boolean") return LambdaExpression::parse<ToBoolean>(value, context);
+ if (*op == "to_rgba") return LambdaExpression::parse<ToRGBA>(value, context);
+ if (*op == "parse_color") return LambdaExpression::parse<ParseColor>(value, context);
+ if (*op == "rgba") return LambdaExpression::parse<RGBA>(value, context);
+ if (*op == "rgb") return LambdaExpression::parse<RGB>(value, context);
+ if (*op == "get") return LambdaExpression::parse<Get>(value, context);
+ if (*op == "has") return LambdaExpression::parse<Has>(value, context);
+ if (*op == "at") return LambdaExpression::parse<At>(value, context);
+ if (*op == "length") return LambdaExpression::parse<Length>(value, context);
+ if (*op == "properties") return LambdaExpression::parse<Properties>(value, context);
+ if (*op == "id") return LambdaExpression::parse<Id>(value, context);
+ if (*op == "geometry_type") return LambdaExpression::parse<GeometryType>(value, context);
+ if (*op == "+") return LambdaExpression::parse<Plus>(value, context);
+ if (*op == "-") return LambdaExpression::parse<Minus>(value, context);
+ if (*op == "*") return LambdaExpression::parse<Times>(value, context);
+ if (*op == "/") return LambdaExpression::parse<Divide>(value, context);
+ if (*op == "^") return LambdaExpression::parse<Power>(value, context);
+ if (*op == "%") return LambdaExpression::parse<Mod>(value, context);
+
+
+ return CompileError {
+ std::string("Unknown expression \"") + *op + "\". If you wanted a literal array, use [\"literal\", [...]].",
+ context.key(0)
+ };
+ }
+
+ if (isObject(value)) {
+ return CompileError {
+ "Bare objects invalid. Use [\"literal\", {...}] instead.",
+ context.key()
+ };
+ }
+
+ return LiteralExpression::parse(value, context);
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/parsing_context.hpp b/include/mbgl/style/expression/parsing_context.hpp
new file mode 100644
index 0000000000..7898da790d
--- /dev/null
+++ b/include/mbgl/style/expression/parsing_context.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <mbgl/util/optional.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class ParsingContext {
+public:
+ ParsingContext() {}
+ ParsingContext(ParsingContext previous,
+ optional<size_t> index,
+ optional<std::string> name) :
+ path(previous.path),
+ ancestors(previous.ancestors)
+ {
+ if (index) path.emplace_back(*index);
+ if (name) ancestors.emplace_back(*name);
+ }
+
+ std::string key() const {
+ std::string result;
+ for(auto const& index : path) { result += "[" + std::to_string(index) + "]"; }
+ return result;
+ }
+
+ std::string key(size_t lastIndex) const {
+ return key() + "[" + std::to_string(lastIndex) + "]";
+ }
+
+ std::vector<size_t> path;
+ std::vector<std::string> ancestors;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/type.hpp b/include/mbgl/style/expression/type.hpp
new file mode 100644
index 0000000000..995440dfde
--- /dev/null
+++ b/include/mbgl/style/expression/type.hpp
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <unordered_map>
+#include <vector>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/variant.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+namespace type {
+
+template <class T>
+std::string toString(const T& t);
+
+struct NullType {
+ constexpr NullType() {}
+ std::string getName() const { return "Null"; }
+};
+
+struct NumberType {
+ constexpr NumberType() {}
+ std::string getName() const { return "Number"; }
+};
+
+struct BooleanType {
+ constexpr BooleanType() {}
+ std::string getName() const { return "Boolean"; }
+};
+
+struct StringType {
+ constexpr StringType() {}
+ std::string getName() const { return "String"; }
+};
+
+struct ColorType {
+ constexpr ColorType() {}
+ std::string getName() const { return "Color"; }
+};
+
+struct ObjectType {
+ constexpr ObjectType() {}
+ std::string getName() const { return "Object"; }
+};
+
+struct ValueType {
+ constexpr ValueType() {}
+ std::string getName() const { return "Value"; }
+};
+
+constexpr NullType Null;
+constexpr NumberType Number;
+constexpr StringType String;
+constexpr BooleanType Boolean;
+constexpr ColorType Color;
+constexpr ValueType Value;
+constexpr ObjectType Object;
+
+class Typename {
+public:
+ Typename(std::string name_) : name(name_) {}
+ std::string getName() const { return name; }
+private:
+ std::string name;
+};
+
+struct Array;
+
+using Type = variant<
+ NullType,
+ NumberType,
+ BooleanType,
+ StringType,
+ ColorType,
+ ObjectType,
+ ValueType,
+ Typename,
+ mapbox::util::recursive_wrapper<Array>>;
+
+struct Array {
+ Array(Type itemType_) : itemType(itemType_) {}
+ Array(Type itemType_, std::size_t N_) : itemType(itemType_), N(N_) {}
+ Array(Type itemType_, optional<std::size_t> N_) : itemType(itemType_), N(N_) {}
+ std::string getName() const {
+ if (N) {
+ return "Array<" + toString(itemType) + ", " + std::to_string(*N) + ">";
+ } else if (toString(itemType) == "Value") {
+ return "Array";
+ } else {
+ return "Array<" + toString(itemType) + ">";
+ }
+ }
+
+ Type itemType;
+ optional<std::size_t> N;
+};
+
+template <class T>
+std::string toString(const T& t) { return t.match([&] (const auto& t) { return t.getName(); }); }
+
+bool isGeneric(const Type& t);
+Type resolveTypenamesIfPossible(const Type&, const std::unordered_map<std::string, Type>&);
+
+optional<std::string> matchType(const Type& expected, const Type& t);
+
+enum TypenameScope { expected, actual };
+optional<std::string> matchType(const Type& expected,
+ const Type& t,
+ std::unordered_map<std::string, Type>& typenames,
+ TypenameScope scope = TypenameScope::expected);
+
+
+} // namespace type
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/type_check.hpp b/include/mbgl/style/expression/type_check.hpp
new file mode 100644
index 0000000000..d30b7be002
--- /dev/null
+++ b/include/mbgl/style/expression/type_check.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/style/expression/expression.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+TypecheckResult typecheck(const type::Type& expected, const std::unique_ptr<Expression>& e);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/value.hpp b/include/mbgl/style/expression/value.hpp
new file mode 100644
index 0000000000..ffc31856e0
--- /dev/null
+++ b/include/mbgl/style/expression/value.hpp
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <mbgl/util/color.hpp>
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/feature.hpp>
+#include <mbgl/style/expression/type.hpp>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+struct Value;
+using ValueBase = variant<
+ NullValue,
+ bool,
+ // TODO: should we break up the numeric types here like mbgl::Value does?
+ float,
+ std::string,
+ mbgl::Color,
+ mapbox::util::recursive_wrapper<std::vector<Value>>,
+ mapbox::util::recursive_wrapper<std::unordered_map<std::string, Value>>>;
+struct Value : ValueBase {
+ using ValueBase::ValueBase;
+};
+
+constexpr NullValue Null = NullValue();
+
+Value convertValue(const mbgl::Value&);
+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();
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/package.json b/package.json
index 2f665e680a..7a9e5591b0 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
},
"license": "BSD-2-Clause",
"dependencies": {
- "nan": "^2.4.0",
+ "nan": "^2.6.2",
"node-pre-gyp": "^0.6.36"
},
"devDependencies": {
diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp
new file mode 100644
index 0000000000..5392f6c041
--- /dev/null
+++ b/platform/node/src/node_expression.cpp
@@ -0,0 +1,201 @@
+#include "node_conversion.hpp"
+#include "node_expression.hpp"
+
+#include <mbgl/style/expression/parse.hpp>
+#include <mbgl/style/expression/type_check.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());
+}
+
+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.");
+ }
+
+ auto expr = info[0];
+
+ try {
+ std::vector<CompileError> errors;
+ auto parsed = parseExpression(expr, ParsingContext());
+ if (parsed.template is<std::unique_ptr<Expression>>()) {
+ const auto& e = parsed.template get<std::unique_ptr<Expression>>();
+ auto checked = typecheck(e->getType(), e);
+ if (checked.template is<std::unique_ptr<Expression>>()) {
+ auto nodeExpr = new NodeExpression(std::move(checked.template get<std::unique_ptr<Expression>>()));
+ 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;
+ } else {
+ const auto& typeErrors = checked.template get<std::vector<CompileError>>();
+ errors.insert(errors.end(), typeErrors.begin(), typeErrors.end());
+ }
+ } else {
+ errors.emplace_back(parsed.template get<CompileError>());
+ }
+
+ v8::Local<v8::Array> result = Nan::New<v8::Array>();
+ for (std::size_t i = 0; i < errors.size(); i++) {
+ const auto& error = errors[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()(float 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> { color.r, color.g, color.b, 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 auto& expression = nodeExpr->expression;
+
+ if (info.Length() < 2) {
+ return Nan::ThrowTypeError("Requires arguments zoom and feature arguments.");
+ }
+
+ float zoom = info[0]->NumberValue();
+
+ // Pending https://github.com/mapbox/mapbox-gl-native/issues/5623,
+ // stringify the geojson feature in order to use convert<GeoJSON, string>()
+ Nan::JSON NanJSON;
+ Nan::MaybeLocal<v8::String> geojsonString = NanJSON.Stringify(Nan::To<v8::Object>(info[1]).ToLocalChecked());
+ if (geojsonString.IsEmpty()) {
+ return Nan::ThrowTypeError("couldn't stringify JSON");
+ }
+ conversion::Error conversionError;
+ mbgl::optional<mbgl::GeoJSON> geoJSON = conversion::convert<mbgl::GeoJSON, std::string>(*Nan::Utf8String(geojsonString.ToLocalChecked()), 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);
+ 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 auto& expression = nodeExpr->expression;
+
+ const auto& type = expression->getType();
+ const auto& 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 auto& expression = nodeExpr->expression;
+ info.GetReturnValue().Set(Nan::New(expression->isFeatureConstant()));
+}
+
+void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const auto& expression = nodeExpr->expression;
+ info.GetReturnValue().Set(Nan::New(expression->isZoomConstant()));
+}
+
+} // namespace node_mbgl
diff --git a/platform/node/src/node_expression.hpp b/platform/node/src/node_expression.hpp
new file mode 100644
index 0000000000..e977b58288
--- /dev/null
+++ b/platform/node/src/node_expression.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <exception>
+#include <memory>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/expression.hpp>
+
+#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
diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp
index cdcc982220..96e96e4298 100644
--- a/platform/node/src/node_mapbox_gl_native.cpp
+++ b/platform/node/src/node_mapbox_gl_native.cpp
@@ -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>();
diff --git a/platform/node/test/expression.test.js b/platform/node/test/expression.test.js
new file mode 100644
index 0000000000..842faa7350
--- /dev/null
+++ b/platform/node/test/expression.test.js
@@ -0,0 +1,52 @@
+'use strict';
+
+var suite = require('../../../mapbox-gl-js/test/integration').expression;
+var mbgl = require('../index');
+
+var tests;
+
+if (process.argv[1] === __filename && process.argv.length > 2) {
+ tests = process.argv.slice(2);
+}
+
+suite.run('native', {tests: tests}, (fixture) => {
+ const compileResult = {};
+ const testResult = {
+ compileResult
+ };
+
+ const expression = mbgl.Expression.parse(fixture.expression);
+
+ if (expression instanceof mbgl.Expression) {
+ compileResult.result = 'success';
+ compileResult.isFeatureConstant = expression.isFeatureConstant();
+ compileResult.isZoomConstant = expression.isZoomConstant();
+ compileResult.type = expression.getType();
+ if (fixture.evaluate) {
+ const evaluateResults = [];
+ for (const input of fixture.evaluate) {
+ const zoom = typeof input[0].zoom === 'number' ?
+ input[0].zoom : -1;
+
+ const feature = Object.assign({
+ type: 'Feature',
+ properties: {},
+ geometry: { type: 'Point', coordinates: [0, 0] }
+ }, input[1])
+
+ const output = expression.evaluate(zoom, feature);
+ evaluateResults.push(output);
+ }
+
+ if (evaluateResults.length) {
+ testResult.evaluateResults = evaluateResults;
+ }
+ }
+ } else {
+ compileResult.result = 'error';
+ compileResult.errors = expression;
+ }
+
+ return testResult;
+});
+
diff --git a/src/mbgl/style/expression/definitions.cpp b/src/mbgl/style/expression/definitions.cpp
new file mode 100644
index 0000000000..9fb82767e7
--- /dev/null
+++ b/src/mbgl/style/expression/definitions.cpp
@@ -0,0 +1,355 @@
+#include <mbgl/style/expression/definitions.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+
+Result<std::vector<Value>> evaluateArgs(const EvaluationParameters& params,
+ const LambdaExpression::Args& args)
+{
+ std::vector<Value> argValues;
+ for(const auto& arg : args) {
+ auto argValue = arg->evaluate(params);
+ if (!argValue) return argValue.error();
+ argValues.emplace_back(*argValue);
+ }
+ return argValues;
+}
+
+template<typename T, typename EvalFunc>
+EvaluationResult evaluateFromArgs(const EvaluationParameters& params,
+ const std::unique_ptr<Expression>& a0,
+ EvalFunc evaluate)
+{
+ const auto& a0value = a0->evaluate<T>(params);
+ if (!a0value) return a0value.error();
+ return evaluate(*a0value);
+}
+
+// TODO: get this working to replace all the overloads below
+
+//template <typename ...Ts>
+//struct restargs { using expression = std::unique_ptr<Expression>; };
+//
+//template <typename T, typename ...Ts, typename Eval>
+//EvaluationResult evaluateFromArgs(const EvaluationParameters& params,
+// const std::unique_ptr<Expression>& a0,
+// const typename restargs<Ts...>::expression& args,
+// Eval evaluate)
+//{
+// const auto& a0value = a0->evaluate<T>(params);
+// if (a0value.template is<EvaluationError>()) {
+// return a0value.template get<EvaluationError>();
+// }
+// return evaluateFromArgs<Ts...>(
+// params,
+// std::forward<const typename restargs<Ts...>::expression&>(args),
+// [&] (Ts... restValues) {
+// return evaluate(a0value.template get<T>(), std::forward<Ts...>(restValues)...);
+// }
+// );
+//}
+
+template<typename T, typename U, typename EvalFunc>
+EvaluationResult evaluateFromArgs(const EvaluationParameters& params,
+ const std::unique_ptr<Expression>& a0,
+ const std::unique_ptr<Expression>& a1,
+ EvalFunc evaluate)
+{
+ const auto& a0value = a0->evaluate<T>(params);
+ if (!a0value) return a0value.error();
+ const auto& a1value = a1->evaluate<U>(params);
+ if (!a1value) return a1value.error();
+ return evaluate(*a0value, *a1value);
+}
+
+template<typename T0, typename T1, typename T2, typename EvalFunc>
+EvaluationResult evaluateFromArgs(const EvaluationParameters& params,
+ const std::unique_ptr<Expression>& a0,
+ const std::unique_ptr<Expression>& a1,
+ const std::unique_ptr<Expression>& a2,
+ EvalFunc evaluate)
+{
+ const auto& a0value = a0->evaluate<T0>(params);
+ if (!a0value) return a0value.error();
+ const auto& a1value = a1->evaluate<T1>(params);
+ if (!a1value) return a1value.error();
+ const auto& a2value = a2->evaluate<T2>(params);
+ if (!a2value) return a2value.error();
+ return evaluate(*a0value, *a1value, *a2value);
+}
+
+template<typename T0, typename T1, typename T2, typename T3, typename EvalFunc>
+EvaluationResult evaluateFromArgs(const EvaluationParameters& params,
+ const std::unique_ptr<Expression>& a0,
+ const std::unique_ptr<Expression>& a1,
+ const std::unique_ptr<Expression>& a2,
+ const std::unique_ptr<Expression>& a3,
+ EvalFunc evaluate)
+{
+ const auto& a0value = a0->evaluate<T0>(params);
+ if (!a0value) return a0value.error();
+ const auto& a1value = a1->evaluate<T1>(params);
+ if (!a1value) return a1value.error();
+ const auto& a2value = a2->evaluate<T2>(params);
+ if (!a2value) return a2value.error();
+ const auto& a3value = a3->evaluate<T3>(params);
+ if (!a3value) return a3value.error();
+ return evaluate(*a0value, *a1value, *a2value, *a3value);
+}
+
+
+EvaluationResult TypeOf::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) return result.error();
+ return toString(typeOf(*result));
+}
+
+EvaluationResult Array::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) { return result.error(); }
+ const Value& v = *result;
+ const auto& expected = getType().get<type::Array>();
+ const auto& actual = typeOf(v);
+ if (actual.is<type::Array>()) {
+ const auto& arrayType = actual.get<type::Array>();
+ bool match = (!expected.N || expected.N == arrayType.N);
+ if (expected.itemType.is<type::ValueType>()) {
+ match = match && (arrayType.itemType.is<type::StringType>() ||
+ arrayType.itemType.is<type::NumberType>() ||
+ arrayType.itemType.is<type::BooleanType>());
+ } else {
+ match = match && (toString(expected.itemType) == toString(arrayType.itemType));
+ }
+
+ if (match) return EvaluationResult(v);
+ }
+
+ return EvaluationError {
+ "Expected value to be of type " + toString(getType()) +
+ ", but found " + toString(actual) + " instead."
+ };
+}
+
+EvaluationResult ToString::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) return result.error();
+ return result->match(
+ [&] (const std::string& s) -> EvaluationResult { return s; },
+ [&] (float v) -> EvaluationResult { return stringify(v); },
+ [&] (bool v) -> EvaluationResult { return stringify(v); },
+ [&] (const NullValue& v) -> EvaluationResult { return stringify(v); },
+ [&] (const auto& v) -> EvaluationResult {
+ return EvaluationError {
+ "Expected a primitive value in [\"string\", ...], but found " + toString(typeOf(v)) + " instead."
+ };
+ }
+ );
+}
+
+EvaluationResult ToNumber::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) return result.error();
+ if (result->is<float>()) { return result->get<float>(); }
+ if (result->is<std::string>()) {
+ const std::string& s = result->get<std::string>();
+ try {
+ return std::stof(s);
+ } catch(std::exception) {
+ }
+ }
+ return EvaluationError {
+ "Could not convert " + stringify(*result) + " to number."
+ };
+}
+
+EvaluationResult ToBoolean::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) return result.error();
+ return result->match(
+ [&] (float f) { return (bool)f; },
+ [&] (const std::string& s) { return s.length() > 0; },
+ [&] (bool b) { return b; },
+ [&] (const NullValue&) { return false; },
+ [&] (const auto&) { return true; }
+ );
+}
+
+EvaluationResult ToRGBA::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<mbgl::Color>(params, args[0], [&] (const mbgl::Color& color) {
+ return std::vector<Value> { color.r, color.g, color.b, color.a };
+ });
+}
+
+EvaluationResult ParseColor::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<std::string>(params, args[0], [&] (const std::string& colorString) -> EvaluationResult {
+ const auto& result = mbgl::Color::parse(colorString);
+ if (result) return EvaluationResult(*result);
+ return EvaluationError {
+ "Could not parse color from value '" + colorString + "'"
+ };
+ });
+}
+
+EvaluationResult RGB::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float, float>(params, args[0], args[1], args[2],
+ [&] (float r, float g, float b) -> EvaluationResult {
+ return mbgl::Color(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
+ }
+ );
+}
+
+EvaluationResult RGBA::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float, float, float>(params, args[0], args[1], args[2], args[3],
+ [&] (float r, float g, float b, float a) -> EvaluationResult {
+ return mbgl::Color(r / 255.0f, g / 255.0f, b / 255.0f, a);
+ }
+ );
+}
+
+bool Get::isFeatureConstant() const {
+ return args.size() == 1 ? false : LambdaExpression::isFeatureConstant();
+}
+EvaluationResult Get::evaluate(const EvaluationParameters& params) const {
+ if (args.size() == 1) {
+ return evaluateFromArgs<std::string>(params, args[0], [&] (const std::string& key) -> EvaluationResult {
+ const auto& value = params.feature.getValue(key);
+ if (!value) return EvaluationError { "Property '" + key + "' not found in feature.properties" };
+ return convertValue(*value);
+ });
+ } else {
+ return evaluateFromArgs<std::string, std::unordered_map<std::string, Value>>(
+ params,
+ args[0],
+ args[1],
+ [&] (const std::string& key, const std::unordered_map<std::string, Value>& object) -> EvaluationResult {
+ if (object.find(key) == object.end()) return EvaluationError { "Property '" + key + "' not found in object" };
+ return object.at(key);
+ }
+ );
+ }
+}
+
+bool Has::isFeatureConstant() const {
+ return args.size() == 1 ? false : LambdaExpression::isFeatureConstant();
+}
+EvaluationResult Has::evaluate(const EvaluationParameters& params) const {
+ if (args.size() == 1) {
+ return evaluateFromArgs<std::string>(params, args[0], [&] (const std::string& key) -> EvaluationResult {
+ const auto& value = params.feature.getValue(key);
+ return value ? true : false;
+ });
+ } else {
+ return evaluateFromArgs<std::string, std::unordered_map<std::string, Value>>(
+ params,
+ args[0],
+ args[1],
+ [&] (const std::string& key, const std::unordered_map<std::string, Value>& object) -> EvaluationResult {
+ return object.find(key) != object.end();
+ }
+ );
+ }
+}
+
+EvaluationResult At::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, std::vector<Value>>(
+ params,
+ args[0],
+ args[1],
+ [&] (float index, const std::vector<Value>& arr) -> EvaluationResult {
+ const size_t i = index;
+ if (index > arr.size()) {
+ return EvaluationError {
+ "Array index out of bounds: " + std::to_string(i) + " >= " + std::to_string(arr.size())
+ };
+ }
+ return arr[i];
+ }
+ );
+}
+
+EvaluationResult Length::evaluate(const EvaluationParameters& params) const {
+ const auto& result = args[0]->evaluate(params);
+ if (!result) return result.error();
+ return (float) result->match(
+ [&] (const std::string& s) { return s.size(); },
+ [&] (const std::vector<Value>& v) { return v.size(); },
+ [&] (const auto&) { assert(false); return -1; }
+ );
+}
+
+bool Properties::isFeatureConstant() const { return false; }
+EvaluationResult Properties::evaluate(const EvaluationParameters& params) const {
+ return convertValue(params.feature.getProperties());
+}
+
+bool Id::isFeatureConstant() const { return false; }
+EvaluationResult Id::evaluate(const EvaluationParameters& params) const {
+ const auto& id = params.feature.getID();
+ if (!id) return EvaluationError { "Property 'id' not found in feature" };
+ return id->match(
+ [&](const std::string& s) -> EvaluationResult { return s; },
+ [&](const auto& n) -> EvaluationResult { return *numericValue<float>(n); }
+ );
+}
+
+bool GeometryType::isFeatureConstant() const { return false; }
+EvaluationResult GeometryType::evaluate(const EvaluationParameters& params) const {
+ switch(params.feature.getType()) {
+ case FeatureType::Unknown: return std::string("Unknown");
+ case FeatureType::LineString: return std::string("LineString");
+ case FeatureType::Point: return std::string("Point");
+ case FeatureType::Polygon: return std::string("Polygon");
+ }
+}
+
+EvaluationResult Plus::evaluate(const EvaluationParameters& params) const {
+ const auto& evaluated = evaluateArgs(params, args);
+ if (!evaluated) return evaluated.error();
+ float sum = 0.0f;
+ for (const Value& v : *evaluated) {
+ sum += v.get<float>();
+ }
+ return sum;
+}
+
+EvaluationResult Times::evaluate(const EvaluationParameters& params) const {
+ const auto& evaluated = evaluateArgs(params, args);
+ if (!evaluated) return evaluated.error();
+ float product = 1.0f;
+ for (const Value& v : *evaluated) {
+ product *= v.get<float>();
+ }
+ return product;
+}
+
+EvaluationResult Minus::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float>(params, args[0], args[1], [&] (float a, float b) {
+ return a - b;
+ });
+}
+
+EvaluationResult Divide::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float>(params, args[0], args[1], [&] (float a, float b) {
+ return a / b;
+ });
+}
+
+EvaluationResult Mod::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float>(params, args[0], args[1], [&] (float a, float b) {
+ return (float) fmod(a, b);
+ });
+}
+
+EvaluationResult Power::evaluate(const EvaluationParameters& params) const {
+ return evaluateFromArgs<float, float>(params, args[0], args[1], [&] (float a, float b) {
+ return (float) pow(a, b);
+ });
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/type.cpp b/src/mbgl/style/expression/type.cpp
new file mode 100644
index 0000000000..2e4a6eb3f5
--- /dev/null
+++ b/src/mbgl/style/expression/type.cpp
@@ -0,0 +1,120 @@
+#include <mbgl/style/expression/type.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+namespace type {
+
+bool isGeneric(const Type& t) {
+ return t.match(
+ [&](const Array& arr) { return isGeneric(arr.itemType); },
+ [&](const Typename&) { return true; },
+ [&](const auto&) { return false; }
+ );
+}
+
+Type resolveTypenamesIfPossible(const Type& t, const std::unordered_map<std::string, Type>& map) {
+ if (!isGeneric(t)) return t;
+ return t.match(
+ [&](const Typename& t) -> Type {
+ if (map.find(t.getName()) == map.end()) {
+ return t;
+ } else {
+ return map.at(t.getName());
+ }
+ },
+ [&](const Array& arr) {
+ return Array(resolveTypenamesIfPossible(arr.itemType, map), arr.N);
+ },
+ [&](const auto&) { return t; }
+ );
+}
+
+inline std::string errorMessage(const Type& expected, const Type& t) {
+ return {"Expected " + toString(expected) + " but found " + toString(t) + " instead."};
+}
+
+optional<std::string> matchType(const Type& expected, const Type& t) {
+ // TODO this is wasteful; just make typenames param optional.
+ std::unordered_map<std::string, Type> typenames;
+ return matchType(expected, t, typenames);
+}
+
+optional<std::string> matchType(const Type& expected,
+ const Type& t,
+ std::unordered_map<std::string, Type>& typenames,
+ TypenameScope scope) {
+ if (expected.is<Typename>()) {
+ const auto& name = expected.get<Typename>().getName();
+ if (
+ scope == TypenameScope::expected &&
+ !isGeneric(t) &&
+ !t.is<NullType>() &&
+ typenames.find(name) == typenames.end()
+ ) {
+ typenames.emplace(name, t);
+ }
+ return {};
+ }
+
+ if (t.is<Typename>()) {
+ const auto& name = t.get<Typename>().getName();
+ if (
+ scope == TypenameScope::actual &&
+ !isGeneric(expected) &&
+ !expected.is<NullType>() &&
+ typenames.find(name) == typenames.end()
+ ) {
+ typenames.emplace(name, expected);
+ }
+ return {};
+ }
+
+ if (t.is<NullType>()) return {};
+
+ return expected.match(
+ [&] (const Array& expectedArray) -> optional<std::string> {
+ if (!t.is<Array>()) { return {errorMessage(expected, t)}; }
+ const auto& tArr = t.get<Array>();
+ const auto err = matchType(expectedArray.itemType, tArr.itemType, typenames);
+ if (err) return { errorMessage(expected, t) + " (" + (*err) + ")" };
+ if (expectedArray.N && expectedArray.N != tArr.N) return { errorMessage(expected, t) };
+ return {};
+ },
+ [&] (const ValueType&) -> optional<std::string> {
+ if (t.is<ValueType>()) return {};
+
+ const Type members[] = {
+ Null,
+ Boolean,
+ Number,
+ String,
+ Object,
+ Array(Value)
+ };
+
+ for (const auto& member : members) {
+ std::unordered_map<std::string, Type> memberTypenames;
+ const auto err = matchType(member, t, memberTypenames, scope);
+ if (!err) {
+ typenames.insert(memberTypenames.begin(), memberTypenames.end());
+ return {};
+ }
+ }
+ return { errorMessage(expected, t) };
+ },
+ [&] (const auto&) -> optional<std::string> {
+ // TODO silly to stringify for this; implement == / != operators instead.
+ if (toString(expected) != toString(t)) {
+ return { errorMessage(expected, t) };
+ }
+ return {};
+ }
+ );
+}
+
+
+} // namespace type
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/type_check.cpp b/src/mbgl/style/expression/type_check.cpp
new file mode 100644
index 0000000000..37bb3db823
--- /dev/null
+++ b/src/mbgl/style/expression/type_check.cpp
@@ -0,0 +1,121 @@
+#include <mbgl/style/expression/type_check.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+using namespace mbgl::style::expression::type;
+
+TypecheckResult typecheck(const Type& expected, const std::unique_ptr<Expression>& e) {
+ if (LiteralExpression* literal = dynamic_cast<LiteralExpression*>(e.get())) {
+ const auto& error = matchType(expected, literal->getType());
+ if (error) return std::vector<CompileError> {{ *error, literal->getKey() }};
+ return std::make_unique<LiteralExpression>(literal->getKey(), literal->getValue());
+ } else if (LambdaExpression* lambda = dynamic_cast<LambdaExpression*>(e.get())) {
+ // Check if the expected type matches the expression's output type;
+ // If expression's output type is generic and the expected type is concrete,
+ // pick up a typename binding
+ std::unordered_map<std::string, Type> initialTypenames;
+ const auto& resultMatchError = matchType(expected, lambda->getType(), initialTypenames, TypenameScope::actual);
+ if (resultMatchError) return std::vector<CompileError> {{ *resultMatchError, lambda->getKey() }};
+
+ std::vector<CompileError> errors;
+ for (const auto& params : lambda->signatures) {
+ std::unordered_map<std::string, Type> typenames(initialTypenames);
+ errors.clear();
+ // "Unroll" NArgs if present in the parameter list:
+ // argCount = nargType.type.length * n + nonNargParameterCount
+ // where n is the number of times the NArgs sequence must be
+ // repeated.
+ std::vector<Type> expandedParams;
+ for (const auto& param : params) {
+ param.match(
+ [&](const NArgs& nargs) {
+ size_t count = ceil(
+ float(lambda->args.size() - (params.size() - 1)) /
+ nargs.types.size()
+ );
+ if (nargs.N && *(nargs.N) < count) count = *(nargs.N);
+ while(count-- > 0) {
+ for(const auto& type : nargs.types) {
+ expandedParams.emplace_back(type);
+ }
+ }
+ },
+ [&](const Type& t) {
+ expandedParams.emplace_back(t);
+ }
+ );
+ }
+
+ if (expandedParams.size() != lambda->args.size()) {
+ errors.emplace_back(CompileError {
+ "Expected " + std::to_string(expandedParams.size()) +
+ " arguments, but found " + std::to_string(lambda->args.size()) + " instead.",
+ lambda->getKey()
+ });
+ continue;
+ }
+
+ // Iterate through arguments to:
+ // - match parameter type vs argument type, checking argument's result type only (don't recursively typecheck subexpressions at this stage)
+ // - collect typename mappings when ^ succeeds or type errors when it fails
+ for (size_t i = 0; i < lambda->args.size(); i++) {
+ const auto& param = expandedParams.at(i);
+ const auto& arg = lambda->args.at(i);
+// if (arg instanceof Reference) {
+// arg = scope.get(arg.name);
+// }
+
+ const auto& error = matchType(
+ resolveTypenamesIfPossible(param, typenames),
+ arg->getType(),
+ typenames
+ );
+ if (error) {
+ errors.emplace_back(CompileError{ *error, arg->getKey() });
+ }
+ }
+
+ const auto& resultType = resolveTypenamesIfPossible(expected, typenames);
+
+ if (isGeneric(resultType)) {
+ errors.emplace_back(CompileError {
+ "Could not resolve " + toString(lambda->getType()) + ". This expression must be wrapped in a type conversion, e.g. [\"string\", ${stringifyExpression(e)}].",
+ lambda->getKey()
+ });
+ };
+
+ // If we already have errors, return early so we don't get duplicates when
+ // we typecheck against the resolved argument types
+ if (errors.size()) continue;
+
+ std::vector<Type> resolvedParams;
+ std::vector<std::unique_ptr<Expression>> checkedArgs;
+ for (std::size_t i = 0; i < expandedParams.size(); i++) {
+ const auto& t = expandedParams.at(i);
+ const auto& expectedArgType = resolveTypenamesIfPossible(t, typenames);
+ auto checked = typecheck(expectedArgType, lambda->args.at(i));
+ if (checked.is<std::vector<CompileError>>()) {
+ const auto& errs = checked.get<std::vector<CompileError>>();
+ errors.insert(errors.end(), errs.begin(), errs.end());
+ } else if (checked.is<std::unique_ptr<Expression>>()) {
+ resolvedParams.emplace_back(expectedArgType);
+ checkedArgs.emplace_back(std::move(checked.get<std::unique_ptr<Expression>>()));
+ }
+ }
+
+ if (errors.size() == 0) {
+ return lambda->applyInferredType(resultType, std::move(checkedArgs));
+ }
+ }
+
+ return errors;
+ }
+
+ assert(false);
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
new file mode 100644
index 0000000000..4e670ec5dd
--- /dev/null
+++ b/src/mbgl/style/expression/value.cpp
@@ -0,0 +1,108 @@
+#include <mbgl/style/expression/value.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+struct ConvertValue {
+// null_value_t, bool, uint64_t, int64_t, double, std::string,
+// mapbox::util::recursive_wrapper<std::vector<value>>,
+// mapbox::util::recursive_wrapper<std::unordered_map<std::string, value>>
+ Value operator()(const std::vector<mbgl::Value>& v) {
+ std::vector<Value> result;
+ for(const auto& item : v) {
+ result.emplace_back(convertValue(item));
+ }
+ return result;
+ }
+
+ Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) {
+ std::unordered_map<std::string, Value> result;
+ for(const auto& entry : v) {
+ result.emplace(entry.first, convertValue(entry.second));
+ }
+ return result;
+ }
+
+ Value operator()(const std::string& s) { return s; }
+ Value operator()(const bool& b) { return b; }
+ Value operator()(const mbgl::NullValue) { return Null; }
+
+ template <typename T>
+ Value operator()(const T& v) { return *numericValue<float>(v); }
+};
+
+Value convertValue(const mbgl::Value& value) {
+ return mbgl::Value::visit(value, ConvertValue());
+}
+
+type::Type typeOf(const Value& value) {
+ return value.match(
+ [&](bool) -> type::Type { return type::Boolean; },
+ [&](float) -> type::Type { return type::Number; },
+ [&](const std::string&) -> type::Type { return type::String; },
+ [&](const mbgl::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 auto& t = typeOf(item);
+ const auto& tname = type::toString(t);
+ if (!itemType) {
+ itemType = {t};
+ } else if (type::toString(*itemType) == tname) {
+ continue;
+ } else {
+ itemType = {type::Value};
+ break;
+ }
+ }
+
+ if (!itemType) { itemType = {type::Value}; }
+
+ return type::Array(*itemType, arr.size());
+ }
+ );
+}
+
+std::string stringify(const Value& value) {
+ return value.match(
+ [&] (const NullValue&) { return std::string("null"); },
+ [&] (bool b) { return std::string(b ? "true" : "false"); },
+ [&] (float f) { return ceilf(f) == f ? std::to_string((int)f) : std::to_string(f); },
+ [&] (const std::string& s) { return "\"" + s + "\""; },
+ [&] (const mbgl::Color& c) { return c.stringify(); },
+ [&] (const std::vector<Value>& arr) {
+ std::string result = "[";
+ for(const auto& item : arr) {
+ if (result.size() > 1) result += ",";
+ result += stringify(item);
+ }
+ return result + "]";
+ },
+ [&] (const std::unordered_map<std::string, Value>& obj) {
+ std::string result = "{";
+ for(const auto& entry : obj) {
+ if (result.size() > 1) result += ",";
+ result += stringify(entry.first) + ":" + stringify(entry.second);
+ }
+ return result + "}";
+ }
+ );
+}
+
+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<float>() { return type::Number; }
+template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; }
+template <> type::Type valueTypeToExpressionType<mbgl::Color>() { return type::Color; }
+template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; }
+template <> type::Type valueTypeToExpressionType<std::array<float, 2>>() { return type::Array(type::Number, 2); }
+template <> type::Type valueTypeToExpressionType<std::array<float, 4>>() { return type::Array(type::Number, 4); }
+template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); }
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp
new file mode 100644
index 0000000000..e6760eb345
--- /dev/null
+++ b/src/mbgl/style/function/expression.cpp
@@ -0,0 +1,50 @@
+#include <mbgl/style/expression/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(float z, const Feature& feature) const {
+ std::unique_ptr<const GeometryTileFeature> f = std::make_unique<const GeoJSONFeature>(feature);
+ return this->evaluate(EvaluationParameters {z, *f});
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl