summaryrefslogtreecommitdiff
path: root/src/mbgl/style
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/style')
-rw-r--r--src/mbgl/style/conversion/constant.cpp94
-rw-r--r--src/mbgl/style/conversion/coordinate.cpp29
-rw-r--r--src/mbgl/style/conversion/filter.cpp248
-rw-r--r--src/mbgl/style/conversion/geojson.cpp18
-rw-r--r--src/mbgl/style/conversion/geojson_options.cpp85
-rw-r--r--src/mbgl/style/conversion/get_json_type.cpp34
-rw-r--r--src/mbgl/style/conversion/json.hpp2
-rw-r--r--src/mbgl/style/conversion/layer.cpp206
-rw-r--r--src/mbgl/style/conversion/light.cpp115
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp209
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp.ejs46
-rw-r--r--src/mbgl/style/conversion/position.cpp22
-rw-r--r--src/mbgl/style/conversion/property_setter.hpp70
-rw-r--r--src/mbgl/style/conversion/source.cpp175
-rw-r--r--src/mbgl/style/conversion/tileset.cpp73
-rw-r--r--src/mbgl/style/conversion/transition_options.cpp40
-rw-r--r--src/mbgl/style/expression/array_assertion.cpp85
-rw-r--r--src/mbgl/style/expression/assertion.cpp73
-rw-r--r--src/mbgl/style/expression/at.cpp63
-rw-r--r--src/mbgl/style/expression/boolean_operator.cpp87
-rw-r--r--src/mbgl/style/expression/case.cpp90
-rw-r--r--src/mbgl/style/expression/check_subtype.cpp60
-rw-r--r--src/mbgl/style/expression/coalesce.cpp62
-rw-r--r--src/mbgl/style/expression/coercion.cpp143
-rw-r--r--src/mbgl/style/expression/compound_expression.cpp571
-rw-r--r--src/mbgl/style/expression/find_zoom_curve.cpp76
-rw-r--r--src/mbgl/style/expression/get_covering_stops.cpp26
-rw-r--r--src/mbgl/style/expression/interpolate.cpp211
-rw-r--r--src/mbgl/style/expression/is_constant.cpp40
-rw-r--r--src/mbgl/style/expression/is_expression.cpp29
-rw-r--r--src/mbgl/style/expression/let.cpp91
-rw-r--r--src/mbgl/style/expression/literal.cpp108
-rw-r--r--src/mbgl/style/expression/match.cpp262
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp206
-rw-r--r--src/mbgl/style/expression/step.cpp151
-rw-r--r--src/mbgl/style/expression/util.cpp39
-rw-r--r--src/mbgl/style/expression/util.hpp14
-rw-r--r--src/mbgl/style/expression/value.cpp322
-rw-r--r--src/mbgl/style/function/expression.cpp38
-rw-r--r--src/mbgl/style/parser.cpp6
-rw-r--r--src/mbgl/style/rapidjson_conversion.hpp152
41 files changed, 4389 insertions, 82 deletions
diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp
new file mode 100644
index 0000000000..e837c4e70b
--- /dev/null
+++ b/src/mbgl/style/conversion/constant.cpp
@@ -0,0 +1,94 @@
+#include <mbgl/style/conversion/constant.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<bool> Converter<bool>::operator()(const Convertible& value, Error& error) const {
+ optional<bool> converted = toBool(value);
+ if (!converted) {
+ error = { "value must be a boolean" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<float> Converter<float>::operator()(const Convertible& value, Error& error) const {
+ optional<float> converted = toNumber(value);
+ if (!converted) {
+ error = { "value must be a number" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<std::string> Converter<std::string>::operator()(const Convertible& value, Error& error) const {
+ optional<std::string> converted = toString(value);
+ if (!converted) {
+ error = { "value must be a string" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<Color> Converter<Color>::operator()(const Convertible& value, Error& error) const {
+ optional<std::string> string = toString(value);
+ if (!string) {
+ error = { "value must be a string" };
+ return {};
+ }
+
+ optional<Color> color = Color::parse(*string);
+ if (!color) {
+ error = { "value must be a valid color" };
+ return {};
+ }
+
+ return *color;
+}
+
+optional<std::vector<float>> Converter<std::vector<float>>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "value must be an array" };
+ return {};
+ }
+
+ std::vector<float> result;
+ result.reserve(arrayLength(value));
+
+ for (std::size_t i = 0; i < arrayLength(value); ++i) {
+ optional<float> number = toNumber(arrayMember(value, i));
+ if (!number) {
+ error = { "value must be an array of numbers" };
+ return {};
+ }
+ result.push_back(*number);
+ }
+
+ return result;
+}
+
+optional<std::vector<std::string>> Converter<std::vector<std::string>>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "value must be an array" };
+ return {};
+ }
+
+ std::vector<std::string> result;
+ result.reserve(arrayLength(value));
+
+ for (std::size_t i = 0; i < arrayLength(value); ++i) {
+ optional<std::string> string = toString(arrayMember(value, i));
+ if (!string) {
+ error = { "value must be an array of strings" };
+ return {};
+ }
+ result.push_back(*string);
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/coordinate.cpp b/src/mbgl/style/conversion/coordinate.cpp
new file mode 100644
index 0000000000..9b2be3381e
--- /dev/null
+++ b/src/mbgl/style/conversion/coordinate.cpp
@@ -0,0 +1,29 @@
+#include <mbgl/style/conversion/coordinate.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<LatLng> Converter<LatLng>::operator() (const Convertible& value, Error& error) const {
+ if (!isArray(value) || arrayLength(value) < 2 ) {
+ error = { "coordinate array must contain numeric longitude and latitude values" };
+ return {};
+ }
+ //Style spec uses GeoJSON convention for specifying coordinates
+ optional<double> latitude = toDouble(arrayMember(value, 1));
+ optional<double> longitude = toDouble(arrayMember(value, 0));
+
+ if (!latitude || !longitude) {
+ error = { "coordinate array must contain numeric longitude and latitude values" };
+ return {};
+ }
+ if (*latitude < -90 || *latitude > 90 ){
+ error = { "coordinate latitude must be between -90 and 90" };
+ return {};
+ }
+ return LatLng(*latitude, *longitude);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp
new file mode 100644
index 0000000000..bb7bb6ea98
--- /dev/null
+++ b/src/mbgl/style/conversion/filter.cpp
@@ -0,0 +1,248 @@
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/util/geometry.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+static optional<Value> normalizeValue(const optional<Value>& value, Error& error) {
+ if (!value) {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ } else {
+ return *value;
+ }
+}
+
+static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) {
+ optional<std::string> type = toString(value);
+ if (!type) {
+ error = { "value for $type filter must be a string" };
+ return {};
+ } else if (*type == "Point") {
+ return FeatureType::Point;
+ } else if (*type == "LineString") {
+ return FeatureType::LineString;
+ } else if (*type == "Polygon") {
+ return FeatureType::Polygon;
+ } else {
+ error = { "value for $type filter must be Point, LineString, or Polygon" };
+ return {};
+ }
+}
+
+static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) {
+ optional<Value> identifier = toValue(value);
+ if (!identifier) {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ } else {
+ return (*identifier).match(
+ [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; },
+ [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; },
+ [] ( double t) -> optional<FeatureIdentifier> { return { t }; },
+ [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; },
+ [&] (const auto&) -> optional<FeatureIdentifier> {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ });
+ }
+}
+
+template <class FilterType, class IdentifierFilterType>
+optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 2) {
+ error = { "filter expression must have 2 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$id") {
+ return { IdentifierFilterType {} };
+ } else {
+ return { FilterType { *key } };
+ }
+}
+
+template <class FilterType, class TypeFilterType, class IdentifierFilterType>
+optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 3) {
+ error = { "filter expression must have 3 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$type") {
+ optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { TypeFilterType { *filterValue } };
+
+ } else if (*key == "$id") {
+ optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { IdentifierFilterType { *filterValue } };
+
+ } else {
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { FilterType { *key, *filterValue } };
+ }
+}
+
+template <class FilterType>
+optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 3) {
+ error = { "filter expression must have 3 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { FilterType { *key, *filterValue } };
+}
+
+template <class FilterType, class TypeFilterType, class IdentifierFilterType>
+optional<Filter> convertSetFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 2) {
+ error = { "filter expression must at least 2 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$type") {
+ std::vector<FeatureType> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { TypeFilterType { std::move(values) } };
+
+ } else if (*key == "$id") {
+ std::vector<FeatureIdentifier> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { IdentifierFilterType { std::move(values) } };
+
+ } else {
+ std::vector<Value> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, i)), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { FilterType { *key, std::move(values) } };
+ }
+}
+
+template <class FilterType>
+optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) {
+ std::vector<Filter> filters;
+ for (std::size_t i = 1; i < arrayLength(value); ++i) {
+ optional<Filter> element = convert<Filter>(arrayMember(value, i), error);
+ if (!element) {
+ return {};
+ }
+ filters.push_back(*element);
+ }
+
+ return { FilterType { std::move(filters) } };
+}
+
+optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "filter expression must be an array" };
+ return {};
+ }
+
+ if (arrayLength(value) < 1) {
+ error = { "filter expression must have at least 1 element" };
+ return {};
+ }
+
+ optional<std::string> op = toString(arrayMember(value, 0));
+ if (!op) {
+ error = { "filter operator must be a string" };
+ return {};
+ }
+
+ if (*op == "==") {
+ return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error);
+ } else if (*op == "!=") {
+ return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error);
+ } else if (*op == ">") {
+ return convertBinaryFilter<GreaterThanFilter>(value, error);
+ } else if (*op == ">=") {
+ return convertBinaryFilter<GreaterThanEqualsFilter>(value, error);
+ } else if (*op == "<") {
+ return convertBinaryFilter<LessThanFilter>(value, error);
+ } else if (*op == "<=") {
+ return convertBinaryFilter<LessThanEqualsFilter>(value, error);
+ } else if (*op == "in") {
+ return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error);
+ } else if (*op == "!in") {
+ return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error);
+ } else if (*op == "all") {
+ return convertCompoundFilter<AllFilter>(value, error);
+ } else if (*op == "any") {
+ return convertCompoundFilter<AnyFilter>(value, error);
+ } else if (*op == "none") {
+ return convertCompoundFilter<NoneFilter>(value, error);
+ } else if (*op == "has") {
+ return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error);
+ } else if (*op == "!has") {
+ return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error);
+ }
+
+ error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" };
+ return {};
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/geojson.cpp b/src/mbgl/style/conversion/geojson.cpp
index 8103e9014a..e39a1a80eb 100644
--- a/src/mbgl/style/conversion/geojson.cpp
+++ b/src/mbgl/style/conversion/geojson.cpp
@@ -1,26 +1,16 @@
#include <mbgl/style/conversion/geojson.hpp>
#include <mbgl/style/conversion/json.hpp>
-#include <mbgl/util/rapidjson.hpp>
-
-#include <mapbox/geojson.hpp>
-#include <mapbox/geojson/rapidjson.hpp>
namespace mbgl {
namespace style {
namespace conversion {
-optional<GeoJSON> Converter<GeoJSON>::operator()(const std::string& value, Error& error) const {
- return convertJSON<GeoJSON>(value, error);
+optional<GeoJSON> Converter<GeoJSON>::operator()(const Convertible& value, Error& error) const {
+ return toGeoJSON(value, error);
}
-template <>
-optional<GeoJSON> Converter<GeoJSON>::operator()(const JSValue& value, Error& error) const {
- try {
- return mapbox::geojson::convert(value);
- } catch (const std::exception& ex) {
- error = { ex.what() };
- return {};
- }
+optional<GeoJSON> parseGeoJSON(const std::string& value, Error& error) {
+ return convertJSON<GeoJSON>(value, error);
}
} // namespace conversion
diff --git a/src/mbgl/style/conversion/geojson_options.cpp b/src/mbgl/style/conversion/geojson_options.cpp
new file mode 100644
index 0000000000..a2c5ed8816
--- /dev/null
+++ b/src/mbgl/style/conversion/geojson_options.cpp
@@ -0,0 +1,85 @@
+#include <mbgl/style/conversion/geojson_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<GeoJSONOptions> Converter<GeoJSONOptions>::operator()(const Convertible& value, Error& error) const {
+ GeoJSONOptions options;
+
+ const auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ if (toNumber(*minzoomValue)) {
+ options.minzoom = static_cast<uint8_t>(*toNumber(*minzoomValue));
+ } else {
+ error = { "GeoJSON source minzoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ if (toNumber(*maxzoomValue)) {
+ options.maxzoom = static_cast<uint8_t>(*toNumber(*maxzoomValue));
+ } else {
+ error = { "GeoJSON source maxzoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto bufferValue = objectMember(value, "buffer");
+ if (bufferValue) {
+ if (toNumber(*bufferValue)) {
+ options.buffer = static_cast<uint16_t>(*toNumber(*bufferValue));
+ } else {
+ error = { "GeoJSON source buffer value must be a number" };
+ return {};
+ }
+ }
+
+ const auto toleranceValue = objectMember(value, "tolerance");
+ if (toleranceValue) {
+ if (toNumber(*toleranceValue)) {
+ options.tolerance = static_cast<double>(*toNumber(*toleranceValue));
+ } else {
+ error = { "GeoJSON source tolerance value must be a number" };
+ return {};
+ }
+ }
+
+ const auto clusterValue = objectMember(value, "cluster");
+ if (clusterValue) {
+ if (toBool(*clusterValue)) {
+ options.cluster = *toBool(*clusterValue);
+ } else {
+ error = { "GeoJSON source cluster value must be a boolean" };
+ return {};
+ }
+ }
+
+ const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom");
+ if (clusterMaxZoomValue) {
+ if (toNumber(*clusterMaxZoomValue)) {
+ options.clusterMaxZoom = static_cast<uint8_t>(*toNumber(*clusterMaxZoomValue));
+ } else {
+ error = { "GeoJSON source clusterMaxZoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto clusterRadiusValue = objectMember(value, "clusterRadius");
+ if (clusterRadiusValue) {
+ if (toNumber(*clusterRadiusValue)) {
+ options.clusterRadius = static_cast<double>(*toNumber(*clusterRadiusValue));
+ } else {
+ error = { "GeoJSON source clusterRadius value must be a number" };
+ return {};
+ }
+ }
+
+ return { options };
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/get_json_type.cpp b/src/mbgl/style/conversion/get_json_type.cpp
new file mode 100644
index 0000000000..cd3b4608b1
--- /dev/null
+++ b/src/mbgl/style/conversion/get_json_type.cpp
@@ -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
diff --git a/src/mbgl/style/conversion/json.hpp b/src/mbgl/style/conversion/json.hpp
index 0817ac09df..7dd2378f6b 100644
--- a/src/mbgl/style/conversion/json.hpp
+++ b/src/mbgl/style/conversion/json.hpp
@@ -20,7 +20,7 @@ optional<T> convertJSON(const std::string& json, Error& error, Args&&...args) {
return {};
}
- return convert<T, JSValue>(document, error, std::forward<Args>(args)...);
+ return convert<T>(document, error, std::forward<Args>(args)...);
}
} // namespace conversion
diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp
new file mode 100644
index 0000000000..0ca582f8dc
--- /dev/null
+++ b/src/mbgl/style/conversion/layer.cpp
@@ -0,0 +1,206 @@
+#include <mbgl/style/conversion/layer.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/style/conversion/make_property_setters.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+#include <mbgl/style/layers/circle_layer.hpp>
+#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/line_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value) {
+ static const auto setters = makeLayoutPropertySetters();
+ auto it = setters.find(name);
+ if (it == setters.end()) {
+ return Error { "property not found" };
+ }
+ return it->second(layer, value);
+}
+
+optional<Error> setPaintProperty(Layer& layer, const std::string& name, const Convertible& value) {
+ static const auto setters = makePaintPropertySetters();
+ auto it = setters.find(name);
+ if (it == setters.end()) {
+ return Error { "property not found" };
+ }
+ return it->second(layer, value);
+}
+
+optional<Error> setPaintProperties(Layer& layer, const Convertible& value) {
+ auto paintValue = objectMember(value, "paint");
+ if (!paintValue) {
+ return {};
+ }
+ return eachMember(*paintValue, [&] (const std::string& k, const Convertible& v) {
+ return setPaintProperty(layer, k, v);
+ });
+}
+
+template <class LayerType>
+optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const Convertible& value, Error& error) {
+ auto sourceValue = objectMember(value, "source");
+ if (!sourceValue) {
+ error = { "layer must have a source" };
+ return {};
+ }
+
+ optional<std::string> source = toString(*sourceValue);
+ if (!source) {
+ error = { "layer source must be a string" };
+ return {};
+ }
+
+ std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source);
+
+ auto sourceLayerValue = objectMember(value, "source-layer");
+ if (sourceLayerValue) {
+ optional<std::string> sourceLayer = toString(*sourceLayerValue);
+ if (!sourceLayer) {
+ error = { "layer source-layer must be a string" };
+ return {};
+ }
+ layer->setSourceLayer(*sourceLayer);
+ }
+
+ auto filterValue = objectMember(value, "filter");
+ if (filterValue) {
+ optional<Filter> filter = convert<Filter>(*filterValue, error);
+ if (!filter) {
+ return {};
+ }
+ layer->setFilter(*filter);
+ }
+
+ return { std::move(layer) };
+}
+
+static optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const Convertible& value, Error& error) {
+ auto sourceValue = objectMember(value, "source");
+ if (!sourceValue) {
+ error = { "layer must have a source" };
+ return {};
+ }
+
+ optional<std::string> source = toString(*sourceValue);
+ if (!source) {
+ error = { "layer source must be a string" };
+ return {};
+ }
+
+ return { std::make_unique<RasterLayer>(id, *source) };
+}
+
+static optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) {
+ return { std::make_unique<BackgroundLayer>(id) };
+}
+
+optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "layer must be an object" };
+ return {};
+ }
+
+ auto idValue = objectMember(value, "id");
+ if (!idValue) {
+ error = { "layer must have an id" };
+ return {};
+ }
+
+ optional<std::string> id = toString(*idValue);
+ if (!id) {
+ error = { "layer id must be a string" };
+ return {};
+ }
+
+ auto typeValue = objectMember(value, "type");
+ if (!typeValue) {
+ error = { "layer must have a type" };
+ return {};
+ }
+
+ optional<std::string> type = toString(*typeValue);
+ if (!type) {
+ error = { "layer type must be a string" };
+ return {};
+ }
+
+ optional<std::unique_ptr<Layer>> converted;
+
+ if (*type == "fill") {
+ converted = convertVectorLayer<FillLayer>(*id, value, error);
+ } else if (*type == "fill-extrusion") {
+ converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error);
+ } else if (*type == "line") {
+ converted = convertVectorLayer<LineLayer>(*id, value, error);
+ } else if (*type == "circle") {
+ converted = convertVectorLayer<CircleLayer>(*id, value, error);
+ } else if (*type == "symbol") {
+ converted = convertVectorLayer<SymbolLayer>(*id, value, error);
+ } else if (*type == "raster") {
+ converted = convertRasterLayer(*id, value, error);
+ } else if (*type == "background") {
+ converted = convertBackgroundLayer(*id, value, error);
+ } else {
+ error = { "invalid layer type" };
+ return {};
+ }
+
+ if (!converted) {
+ return converted;
+ }
+
+ std::unique_ptr<Layer> layer = std::move(*converted);
+
+ auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ optional<float> minzoom = toNumber(*minzoomValue);
+ if (!minzoom) {
+ error = { "minzoom must be numeric" };
+ return {};
+ }
+ layer->setMinZoom(*minzoom);
+ }
+
+ auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ optional<float> maxzoom = toNumber(*maxzoomValue);
+ if (!maxzoom) {
+ error = { "maxzoom must be numeric" };
+ return {};
+ }
+ layer->setMaxZoom(*maxzoom);
+ }
+
+ auto layoutValue = objectMember(value, "layout");
+ if (layoutValue) {
+ if (!isObject(*layoutValue)) {
+ error = { "layout must be an object" };
+ return {};
+ }
+ optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) {
+ return setLayoutProperty(*layer, k, v);
+ });
+ if (error_) {
+ error = *error_;
+ return {};
+ }
+ }
+
+ optional<Error> error_ = setPaintProperties(*layer, value);
+ if (error_) {
+ error = *error_;
+ return {};
+ }
+
+ return std::move(layer);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/light.cpp b/src/mbgl/style/conversion/light.cpp
new file mode 100644
index 0000000000..f521f74386
--- /dev/null
+++ b/src/mbgl/style/conversion/light.cpp
@@ -0,0 +1,115 @@
+#include <mbgl/style/conversion/light.hpp>
+#include <mbgl/style/conversion/position.hpp>
+#include <mbgl/style/conversion/property_value.hpp>
+#include <mbgl/style/conversion/transition_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Light> Converter<Light>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "light must be an object" };
+ return {};
+ }
+
+ Light light;
+
+ const auto anchor = objectMember(value, "anchor");
+ if (anchor) {
+ optional<PropertyValue<LightAnchorType>> convertedAnchor =
+ convert<PropertyValue<LightAnchorType>>(*anchor, error);
+
+ if (convertedAnchor) {
+ light.setAnchor(*convertedAnchor);
+ } else {
+ return {};
+ }
+ }
+
+ const auto anchorTransition = objectMember(value, "anchor-transition");
+ if (anchorTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*anchorTransition, error);
+ if (transition) {
+ light.setAnchorTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto color = objectMember(value, "color");
+ if (color) {
+ optional<PropertyValue<Color>> convertedColor =
+ convert<PropertyValue<Color>>(*color, error);
+
+ if (convertedColor) {
+ light.setColor(*convertedColor);
+ } else {
+ return {};
+ }
+ }
+
+ const auto colorTransition = objectMember(value, "color-transition");
+ if (colorTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*colorTransition, error);
+ if (transition) {
+ light.setColorTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto position = objectMember(value, "position");
+ if (position) {
+ optional<PropertyValue<Position>> convertedPosition =
+ convert<PropertyValue<Position>>(*position, error);
+
+ if (convertedPosition) {
+ light.setPosition(*convertedPosition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto positionTransition = objectMember(value, "position-transition");
+ if (positionTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*positionTransition, error);
+ if (transition) {
+ light.setPositionTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto intensity = objectMember(value, "intensity");
+ if (intensity) {
+ optional<PropertyValue<float>> convertedIntensity =
+ convert<PropertyValue<float>>(*intensity, error);
+
+ if (convertedIntensity) {
+ light.setIntensity(*convertedIntensity);
+ } else {
+ return {};
+ }
+ }
+
+ const auto intensityTransition = objectMember(value, "intensity-transition");
+ if (intensityTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*intensityTransition, error);
+ if (transition) {
+ light.setIntensityTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ return { std::move(light) };
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp
new file mode 100644
index 0000000000..074d7eb730
--- /dev/null
+++ b/src/mbgl/style/conversion/make_property_setters.hpp
@@ -0,0 +1,209 @@
+#pragma once
+
+// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
+
+#include <mbgl/style/conversion/property_setter.hpp>
+
+#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/line_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
+#include <mbgl/style/layers/circle_layer.hpp>
+#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+
+#include <unordered_map>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+inline auto makeLayoutPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["visibility"] = &setVisibility;
+
+
+ result["line-cap"] = &setProperty<LineLayer, PropertyValue<LineCapType>, &LineLayer::setLineCap>;
+ result["line-join"] = &setProperty<LineLayer, DataDrivenPropertyValue<LineJoinType>, &LineLayer::setLineJoin>;
+ result["line-miter-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineMiterLimit>;
+ result["line-round-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineRoundLimit>;
+
+ result["symbol-placement"] = &setProperty<SymbolLayer, PropertyValue<SymbolPlacementType>, &SymbolLayer::setSymbolPlacement>;
+ result["symbol-spacing"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setSymbolSpacing>;
+ result["symbol-avoid-edges"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setSymbolAvoidEdges>;
+ result["icon-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconAllowOverlap>;
+ result["icon-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconIgnorePlacement>;
+ result["icon-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconOptional>;
+ result["icon-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconRotationAlignment>;
+ result["icon-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconSize>;
+ result["icon-text-fit"] = &setProperty<SymbolLayer, PropertyValue<IconTextFitType>, &SymbolLayer::setIconTextFit>;
+ result["icon-text-fit-padding"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 4>>, &SymbolLayer::setIconTextFitPadding>;
+ result["icon-image"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setIconImage>;
+ result["icon-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconRotate>;
+ result["icon-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>;
+ result["icon-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>;
+ result["icon-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>;
+ result["icon-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>;
+ result["icon-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>;
+ result["text-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>;
+ result["text-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>;
+ result["text-field"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>;
+ result["text-font"] = &setProperty<SymbolLayer, PropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>;
+ result["text-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>;
+ result["text-max-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>;
+ result["text-line-height"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>;
+ result["text-letter-spacing"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>;
+ result["text-justify"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>;
+ result["text-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>;
+ result["text-max-angle"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>;
+ result["text-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>;
+ result["text-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>;
+ result["text-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextKeepUpright>;
+ result["text-transform"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextTransformType>, &SymbolLayer::setTextTransform>;
+ result["text-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setTextOffset>;
+ result["text-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextAllowOverlap>;
+ result["text-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextIgnorePlacement>;
+ result["text-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextOptional>;
+
+
+
+
+
+ return result;
+}
+
+inline auto makePaintPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["fill-antialias"] = &setProperty<FillLayer, PropertyValue<bool>, &FillLayer::setFillAntialias>;
+ result["fill-antialias-transition"] = &setTransition<FillLayer, &FillLayer::setFillAntialiasTransition>;
+ result["fill-opacity"] = &setProperty<FillLayer, DataDrivenPropertyValue<float>, &FillLayer::setFillOpacity>;
+ result["fill-opacity-transition"] = &setTransition<FillLayer, &FillLayer::setFillOpacityTransition>;
+ result["fill-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillColor>;
+ result["fill-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillColorTransition>;
+ result["fill-outline-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillOutlineColor>;
+ result["fill-outline-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillOutlineColorTransition>;
+ result["fill-translate"] = &setProperty<FillLayer, PropertyValue<std::array<float, 2>>, &FillLayer::setFillTranslate>;
+ result["fill-translate-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateTransition>;
+ result["fill-translate-anchor"] = &setProperty<FillLayer, PropertyValue<TranslateAnchorType>, &FillLayer::setFillTranslateAnchor>;
+ result["fill-translate-anchor-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateAnchorTransition>;
+ result["fill-pattern"] = &setProperty<FillLayer, PropertyValue<std::string>, &FillLayer::setFillPattern>;
+ result["fill-pattern-transition"] = &setTransition<FillLayer, &FillLayer::setFillPatternTransition>;
+
+ result["line-opacity"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOpacity>;
+ result["line-opacity-transition"] = &setTransition<LineLayer, &LineLayer::setLineOpacityTransition>;
+ result["line-color"] = &setProperty<LineLayer, DataDrivenPropertyValue<Color>, &LineLayer::setLineColor>;
+ result["line-color-transition"] = &setTransition<LineLayer, &LineLayer::setLineColorTransition>;
+ result["line-translate"] = &setProperty<LineLayer, PropertyValue<std::array<float, 2>>, &LineLayer::setLineTranslate>;
+ result["line-translate-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateTransition>;
+ result["line-translate-anchor"] = &setProperty<LineLayer, PropertyValue<TranslateAnchorType>, &LineLayer::setLineTranslateAnchor>;
+ result["line-translate-anchor-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateAnchorTransition>;
+ result["line-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineWidth>;
+ result["line-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineWidthTransition>;
+ result["line-gap-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineGapWidth>;
+ result["line-gap-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineGapWidthTransition>;
+ result["line-offset"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOffset>;
+ result["line-offset-transition"] = &setTransition<LineLayer, &LineLayer::setLineOffsetTransition>;
+ result["line-blur"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineBlur>;
+ result["line-blur-transition"] = &setTransition<LineLayer, &LineLayer::setLineBlurTransition>;
+ result["line-dasharray"] = &setProperty<LineLayer, PropertyValue<std::vector<float>>, &LineLayer::setLineDasharray>;
+ result["line-dasharray-transition"] = &setTransition<LineLayer, &LineLayer::setLineDasharrayTransition>;
+ result["line-pattern"] = &setProperty<LineLayer, PropertyValue<std::string>, &LineLayer::setLinePattern>;
+ result["line-pattern-transition"] = &setTransition<LineLayer, &LineLayer::setLinePatternTransition>;
+
+ result["icon-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconOpacity>;
+ result["icon-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconOpacityTransition>;
+ result["icon-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconColor>;
+ result["icon-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconColorTransition>;
+ result["icon-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconHaloColor>;
+ result["icon-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloColorTransition>;
+ result["icon-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloWidth>;
+ result["icon-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloWidthTransition>;
+ result["icon-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloBlur>;
+ result["icon-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloBlurTransition>;
+ result["icon-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setIconTranslate>;
+ result["icon-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateTransition>;
+ result["icon-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setIconTranslateAnchor>;
+ result["icon-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateAnchorTransition>;
+ result["text-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextOpacity>;
+ result["text-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextOpacityTransition>;
+ result["text-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextColor>;
+ result["text-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextColorTransition>;
+ result["text-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextHaloColor>;
+ result["text-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloColorTransition>;
+ result["text-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloWidth>;
+ result["text-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloWidthTransition>;
+ result["text-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloBlur>;
+ result["text-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloBlurTransition>;
+ result["text-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setTextTranslate>;
+ result["text-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateTransition>;
+ result["text-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setTextTranslateAnchor>;
+ result["text-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateAnchorTransition>;
+
+ result["circle-radius"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleRadius>;
+ result["circle-radius-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleRadiusTransition>;
+ result["circle-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleColor>;
+ result["circle-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleColorTransition>;
+ result["circle-blur"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleBlur>;
+ result["circle-blur-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleBlurTransition>;
+ result["circle-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleOpacity>;
+ result["circle-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleOpacityTransition>;
+ result["circle-translate"] = &setProperty<CircleLayer, PropertyValue<std::array<float, 2>>, &CircleLayer::setCircleTranslate>;
+ result["circle-translate-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateTransition>;
+ result["circle-translate-anchor"] = &setProperty<CircleLayer, PropertyValue<TranslateAnchorType>, &CircleLayer::setCircleTranslateAnchor>;
+ result["circle-translate-anchor-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateAnchorTransition>;
+ result["circle-pitch-scale"] = &setProperty<CircleLayer, PropertyValue<CirclePitchScaleType>, &CircleLayer::setCirclePitchScale>;
+ result["circle-pitch-scale-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchScaleTransition>;
+ result["circle-pitch-alignment"] = &setProperty<CircleLayer, PropertyValue<AlignmentType>, &CircleLayer::setCirclePitchAlignment>;
+ result["circle-pitch-alignment-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchAlignmentTransition>;
+ result["circle-stroke-width"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeWidth>;
+ result["circle-stroke-width-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeWidthTransition>;
+ result["circle-stroke-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleStrokeColor>;
+ result["circle-stroke-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeColorTransition>;
+ result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>;
+ result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>;
+
+ result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>;
+ result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>;
+ result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>;
+ result["fill-extrusion-color-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionColorTransition>;
+ result["fill-extrusion-translate"] = &setProperty<FillExtrusionLayer, PropertyValue<std::array<float, 2>>, &FillExtrusionLayer::setFillExtrusionTranslate>;
+ result["fill-extrusion-translate-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateTransition>;
+ result["fill-extrusion-translate-anchor"] = &setProperty<FillExtrusionLayer, PropertyValue<TranslateAnchorType>, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>;
+ result["fill-extrusion-translate-anchor-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateAnchorTransition>;
+ result["fill-extrusion-pattern"] = &setProperty<FillExtrusionLayer, PropertyValue<std::string>, &FillExtrusionLayer::setFillExtrusionPattern>;
+ result["fill-extrusion-pattern-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionPatternTransition>;
+ result["fill-extrusion-height"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionHeight>;
+ result["fill-extrusion-height-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionHeightTransition>;
+ result["fill-extrusion-base"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionBase>;
+ result["fill-extrusion-base-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionBaseTransition>;
+
+ result["raster-opacity"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterOpacity>;
+ result["raster-opacity-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterOpacityTransition>;
+ result["raster-hue-rotate"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterHueRotate>;
+ result["raster-hue-rotate-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterHueRotateTransition>;
+ result["raster-brightness-min"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMin>;
+ result["raster-brightness-min-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMinTransition>;
+ result["raster-brightness-max"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMax>;
+ result["raster-brightness-max-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMaxTransition>;
+ result["raster-saturation"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterSaturation>;
+ result["raster-saturation-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterSaturationTransition>;
+ result["raster-contrast"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterContrast>;
+ result["raster-contrast-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterContrastTransition>;
+ result["raster-fade-duration"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>;
+ result["raster-fade-duration-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterFadeDurationTransition>;
+
+ result["background-color"] = &setProperty<BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>;
+ result["background-color-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>;
+ result["background-pattern"] = &setProperty<BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>;
+ result["background-pattern-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundPatternTransition>;
+ result["background-opacity"] = &setProperty<BackgroundLayer, PropertyValue<float>, &BackgroundLayer::setBackgroundOpacity>;
+ result["background-opacity-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundOpacityTransition>;
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp.ejs b/src/mbgl/style/conversion/make_property_setters.hpp.ejs
new file mode 100644
index 0000000000..2975cb19f2
--- /dev/null
+++ b/src/mbgl/style/conversion/make_property_setters.hpp.ejs
@@ -0,0 +1,46 @@
+#pragma once
+
+// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
+
+#include <mbgl/style/conversion/property_setter.hpp>
+
+<% for (const layer of locals.layers) { -%>
+#include <mbgl/style/layers/<%- layer.type.replace('-', '_') %>_layer.hpp>
+<% } -%>
+
+#include <unordered_map>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+inline auto makeLayoutPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["visibility"] = &setVisibility;
+
+<% for (const layer of locals.layers) { -%>
+<% for (const property of layer.layoutProperties) { -%>
+ result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
+<% } -%>
+
+<% } -%>
+ return result;
+}
+
+inline auto makePaintPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+<% for (const layer of locals.layers) { -%>
+<% for (const property of layer.paintProperties) { -%>
+ result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
+ result["<%- property.name %>-transition"] = &setTransition<<%- camelize(layer.type) %>Layer, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>Transition>;
+<% } -%>
+
+<% } -%>
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/position.cpp b/src/mbgl/style/conversion/position.cpp
new file mode 100644
index 0000000000..702d250dbf
--- /dev/null
+++ b/src/mbgl/style/conversion/position.cpp
@@ -0,0 +1,22 @@
+#include <mbgl/style/conversion/position.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+
+#include <array>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Position> Converter<Position>::operator()(const Convertible& value, Error& error) const {
+ optional<std::array<float, 3>> spherical = convert<std::array<float, 3>>(value, error);
+
+ if (!spherical) {
+ return {};
+ }
+
+ return Position(*spherical);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp
new file mode 100644
index 0000000000..9e382b9c38
--- /dev/null
+++ b/src/mbgl/style/conversion/property_setter.hpp
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <mbgl/style/layer.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+#include <mbgl/style/conversion/property_value.hpp>
+#include <mbgl/style/conversion/data_driven_property_value.hpp>
+#include <mbgl/style/conversion/transition_options.hpp>
+
+#include <string>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+using PropertySetter = optional<Error> (*) (Layer&, const Convertible&);
+
+template <class L, class PropertyValue, void (L::*setter)(PropertyValue)>
+optional<Error> setProperty(Layer& layer, const Convertible& value) {
+ auto* typedLayer = layer.as<L>();
+ if (!typedLayer) {
+ return Error { "layer doesn't support this property" };
+ }
+
+ Error error;
+ optional<PropertyValue> typedValue = convert<PropertyValue>(value, error);
+ if (!typedValue) {
+ return error;
+ }
+
+ (typedLayer->*setter)(*typedValue);
+ return {};
+}
+
+template <class L, void (L::*setter)(const TransitionOptions&)>
+optional<Error> setTransition(Layer& layer, const Convertible& value) {
+ auto* typedLayer = layer.as<L>();
+ if (!typedLayer) {
+ return Error { "layer doesn't support this property" };
+ }
+
+ Error error;
+ optional<TransitionOptions> transition = convert<TransitionOptions>(value, error);
+ if (!transition) {
+ return error;
+ }
+
+ (typedLayer->*setter)(*transition);
+ return {};
+}
+
+inline optional<Error> setVisibility(Layer& layer, const Convertible& value) {
+ if (isUndefined(value)) {
+ layer.setVisibility(VisibilityType::Visible);
+ return {};
+ }
+
+ Error error;
+ optional<VisibilityType> visibility = convert<VisibilityType>(value, error);
+ if (!visibility) {
+ return error;
+ }
+
+ layer.setVisibility(*visibility);
+ return {};
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp
new file mode 100644
index 0000000000..c10d0babcf
--- /dev/null
+++ b/src/mbgl/style/conversion/source.cpp
@@ -0,0 +1,175 @@
+#include <mbgl/style/conversion/source.hpp>
+#include <mbgl/style/conversion/coordinate.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/style/conversion/geojson_options.hpp>
+#include <mbgl/style/conversion/tileset.hpp>
+#include <mbgl/style/sources/geojson_source.hpp>
+#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/style/sources/vector_source.hpp>
+#include <mbgl/style/sources/image_source.hpp>
+#include <mbgl/util/geo.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+// A tile source can either specify a URL to TileJSON, or inline TileJSON.
+static optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) {
+ auto urlVal = objectMember(value, "url");
+ if (!urlVal) {
+ optional<Tileset> tileset = convert<Tileset>(value, error);
+ if (!tileset) {
+ return {};
+ }
+ return { *tileset };
+ }
+
+ optional<std::string> url = toString(*urlVal);
+ if (!url) {
+ error = { "source url must be a string" };
+ return {};
+ }
+
+ return { *url };
+}
+
+static optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
+ if (!urlOrTileset) {
+ return {};
+ }
+
+ uint16_t tileSize = util::tileSize;
+ auto tileSizeValue = objectMember(value, "tileSize");
+ if (tileSizeValue) {
+ optional<float> size = toNumber(*tileSizeValue);
+ if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
+ error = { "invalid tileSize" };
+ return {};
+ }
+ tileSize = *size;
+ }
+
+ return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) };
+}
+
+static optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
+ if (!urlOrTileset) {
+ return {};
+ }
+
+ return { std::make_unique<VectorSource>(id, std::move(*urlOrTileset)) };
+}
+
+static optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ auto dataValue = objectMember(value, "data");
+ if (!dataValue) {
+ error = { "GeoJSON source must have a data value" };
+ return {};
+ }
+
+ optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error);
+ if (!options) {
+ return {};
+ }
+
+ auto result = std::make_unique<GeoJSONSource>(id, *options);
+
+ if (isObject(*dataValue)) {
+ optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error);
+ if (!geoJSON) {
+ return {};
+ }
+ result->setGeoJSON(std::move(*geoJSON));
+ } else if (toString(*dataValue)) {
+ result->setURL(*toString(*dataValue));
+ } else {
+ error = { "GeoJSON data must be a URL or an object" };
+ return {};
+ }
+
+ return { std::move(result) };
+}
+
+static optional<std::unique_ptr<Source>> convertImageSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ auto urlValue = objectMember(value, "url");
+ if (!urlValue) {
+ error = { "Image source must have a url value" };
+ return {};
+ }
+
+ auto urlString = toString(*urlValue);
+ if (!urlString) {
+ error = { "Image url must be a URL string" };
+ return {};
+ }
+
+ auto coordinatesValue = objectMember(value, "coordinates");
+ if (!coordinatesValue) {
+ error = { "Image source must have a coordinates values" };
+ return {};
+ }
+
+ if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) {
+ error = { "Image coordinates must be an array of four longitude latitude pairs" };
+ return {};
+ }
+
+ std::array<LatLng, 4> coordinates;
+ for (std::size_t i=0; i < 4; i++) {
+ auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error);
+ if (!latLng) {
+ return {};
+ }
+ coordinates[i] = *latLng;
+ }
+ auto result = std::make_unique<ImageSource>(id, coordinates);
+ result->setURL(*urlString);
+
+ return { std::move(result) };
+}
+
+optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value, Error& error, const std::string& id) const {
+ if (!isObject(value)) {
+ error = { "source must be an object" };
+ return {};
+ }
+
+ auto typeValue = objectMember(value, "type");
+ if (!typeValue) {
+ error = { "source must have a type" };
+ return {};
+ }
+
+ optional<std::string> type = toString(*typeValue);
+ if (!type) {
+ error = { "source type must be a string" };
+ return {};
+ }
+
+ if (*type == "raster") {
+ return convertRasterSource(id, value, error);
+ } else if (*type == "vector") {
+ return convertVectorSource(id, value, error);
+ } else if (*type == "geojson") {
+ return convertGeoJSONSource(id, value, error);
+ } else if (*type == "image") {
+ return convertImageSource(id, value, error);
+ } else {
+ error = { "invalid source type" };
+ return {};
+ }
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp
new file mode 100644
index 0000000000..b9383c41b8
--- /dev/null
+++ b/src/mbgl/style/conversion/tileset.cpp
@@ -0,0 +1,73 @@
+#include <mbgl/style/conversion/tileset.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const {
+ Tileset result;
+
+ auto tiles = objectMember(value, "tiles");
+ if (!tiles) {
+ error = { "source must have tiles" };
+ return {};
+ }
+
+ if (!isArray(*tiles)) {
+ error = { "source tiles must be an array" };
+ return {};
+ }
+
+ for (std::size_t i = 0; i < arrayLength(*tiles); i++) {
+ optional<std::string> urlTemplate = toString(arrayMember(*tiles, i));
+ if (!urlTemplate) {
+ error = { "source tiles member must be a string" };
+ return {};
+ }
+ result.tiles.push_back(std::move(*urlTemplate));
+ }
+
+ auto schemeValue = objectMember(value, "scheme");
+ if (schemeValue) {
+ optional<std::string> scheme = toString(*schemeValue);
+ if (scheme && *scheme == "tms") {
+ result.scheme = Tileset::Scheme::TMS;
+ }
+ }
+
+ auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ optional<float> minzoom = toNumber(*minzoomValue);
+ if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits<uint8_t>::max()) {
+ error = { "invalid minzoom" };
+ return {};
+ }
+ result.zoomRange.min = *minzoom;
+ }
+
+ auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ optional<float> maxzoom = toNumber(*maxzoomValue);
+ if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits<uint8_t>::max()) {
+ error = { "invalid maxzoom" };
+ return {};
+ }
+ result.zoomRange.max = *maxzoom;
+ }
+
+ auto attributionValue = objectMember(value, "attribution");
+ if (attributionValue) {
+ optional<std::string> attribution = toString(*attributionValue);
+ if (!attribution) {
+ error = { "source attribution must be a string" };
+ return {};
+ }
+ result.attribution = std::move(*attribution);
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/transition_options.cpp b/src/mbgl/style/conversion/transition_options.cpp
new file mode 100644
index 0000000000..8a60c5bfd8
--- /dev/null
+++ b/src/mbgl/style/conversion/transition_options.cpp
@@ -0,0 +1,40 @@
+#include <mbgl/style/conversion/transition_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<TransitionOptions> Converter<TransitionOptions>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "transition must be an object" };
+ return {};
+ }
+
+ TransitionOptions result;
+
+ auto duration = objectMember(value, "duration");
+ if (duration) {
+ auto number = toNumber(*duration);
+ if (!number) {
+ error = { "duration must be a number" };
+ return {};
+ }
+ result.duration = { std::chrono::milliseconds(int64_t(*number)) };
+ }
+
+ auto delay = objectMember(value, "delay");
+ if (delay) {
+ auto number = toNumber(*delay);
+ if (!number) {
+ error = { "delay must be a number" };
+ return {};
+ }
+ result.delay = { std::chrono::milliseconds(int64_t(*number)) };
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/array_assertion.cpp b/src/mbgl/style/expression/array_assertion.cpp
new file mode 100644
index 0000000000..a62f67fbb5
--- /dev/null
+++ b/src/mbgl/style/expression/array_assertion.cpp
@@ -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
diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp
new file mode 100644
index 0000000000..a17c53cf54
--- /dev/null
+++ b/src/mbgl/style/expression/assertion.cpp
@@ -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
+
+
diff --git a/src/mbgl/style/expression/at.cpp b/src/mbgl/style/expression/at.cpp
new file mode 100644
index 0000000000..d9beb63b52
--- /dev/null
+++ b/src/mbgl/style/expression/at.cpp
@@ -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
diff --git a/src/mbgl/style/expression/boolean_operator.cpp b/src/mbgl/style/expression/boolean_operator.cpp
new file mode 100644
index 0000000000..88797f965a
--- /dev/null
+++ b/src/mbgl/style/expression/boolean_operator.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/case.cpp b/src/mbgl/style/expression/case.cpp
new file mode 100644
index 0000000000..a435b71fc5
--- /dev/null
+++ b/src/mbgl/style/expression/case.cpp
@@ -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
diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp
new file mode 100644
index 0000000000..04a1643f0c
--- /dev/null
+++ b/src/mbgl/style/expression/check_subtype.cpp
@@ -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
diff --git a/src/mbgl/style/expression/coalesce.cpp b/src/mbgl/style/expression/coalesce.cpp
new file mode 100644
index 0000000000..bfde3c7581
--- /dev/null
+++ b/src/mbgl/style/expression/coalesce.cpp
@@ -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
diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp
new file mode 100644
index 0000000000..f2042ffd8f
--- /dev/null
+++ b/src/mbgl/style/expression/coercion.cpp
@@ -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
+
+
+
diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp
new file mode 100644
index 0000000000..c1e0639562
--- /dev/null
+++ b/src/mbgl/style/expression/compound_expression.cpp
@@ -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
diff --git a/src/mbgl/style/expression/find_zoom_curve.cpp b/src/mbgl/style/expression/find_zoom_curve.cpp
new file mode 100644
index 0000000000..5d39e0791e
--- /dev/null
+++ b/src/mbgl/style/expression/find_zoom_curve.cpp
@@ -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
diff --git a/src/mbgl/style/expression/get_covering_stops.cpp b/src/mbgl/style/expression/get_covering_stops.cpp
new file mode 100644
index 0000000000..c9f87d93ac
--- /dev/null
+++ b/src/mbgl/style/expression/get_covering_stops.cpp
@@ -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
diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp
new file mode 100644
index 0000000000..020aba9dce
--- /dev/null
+++ b/src/mbgl/style/expression/interpolate.cpp
@@ -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
diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp
new file mode 100644
index 0000000000..0ebb37faa9
--- /dev/null
+++ b/src/mbgl/style/expression/is_constant.cpp
@@ -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
diff --git a/src/mbgl/style/expression/is_expression.cpp b/src/mbgl/style/expression/is_expression.cpp
new file mode 100644
index 0000000000..77212f6a1e
--- /dev/null
+++ b/src/mbgl/style/expression/is_expression.cpp
@@ -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
diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp
new file mode 100644
index 0000000000..8e206d3582
--- /dev/null
+++ b/src/mbgl/style/expression/let.cpp
@@ -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
diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp
new file mode 100644
index 0000000000..fc11878bea
--- /dev/null
+++ b/src/mbgl/style/expression/literal.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp
new file mode 100644
index 0000000000..6336eba1e6
--- /dev/null
+++ b/src/mbgl/style/expression/match.cpp
@@ -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
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
new file mode 100644
index 0000000000..81cbdede59
--- /dev/null
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -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
diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp
new file mode 100644
index 0000000000..2720e9257a
--- /dev/null
+++ b/src/mbgl/style/expression/step.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/util.cpp b/src/mbgl/style/expression/util.cpp
new file mode 100644
index 0000000000..f198fb3e1b
--- /dev/null
+++ b/src/mbgl/style/expression/util.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/util.hpp b/src/mbgl/style/expression/util.hpp
new file mode 100644
index 0000000000..b6fc408ed9
--- /dev/null
+++ b/src/mbgl/style/expression/util.hpp
@@ -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
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
new file mode 100644
index 0000000000..b75f471ce3
--- /dev/null
+++ b/src/mbgl/style/expression/value.cpp
@@ -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
diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp
new file mode 100644
index 0000000000..d9dbbfa1d3
--- /dev/null
+++ b/src/mbgl/style/function/expression.cpp
@@ -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
diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp
index a83897dbf5..10fce33986 100644
--- a/src/mbgl/style/parser.cpp
+++ b/src/mbgl/style/parser.cpp
@@ -1,11 +1,13 @@
#include <mbgl/style/parser.hpp>
#include <mbgl/style/layer_impl.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/coordinate.hpp>
#include <mbgl/style/conversion/source.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/light.hpp>
+#include <mbgl/style/conversion/transition_options.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/string.hpp>
@@ -149,7 +151,7 @@ void Parser::parseSources(const JSValue& value) {
}
for (const auto& property : value.GetObject()) {
- std::string id = *conversion::toString(property.name);
+ std::string id { property.name.GetString(), property.name.GetStringLength() };
conversion::Error error;
optional<std::unique_ptr<Source>> source =
@@ -256,7 +258,7 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique
}
layer = reference->cloneRef(id);
- conversion::setPaintProperties(*layer, value);
+ conversion::setPaintProperties(*layer, conversion::Convertible(&value));
} else {
conversion::Error error;
optional<std::unique_ptr<Layer>> converted = conversion::convert<std::unique_ptr<Layer>>(value, error);
diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp
index 48a764ccb4..79bd9c928b 100644
--- a/src/mbgl/style/rapidjson_conversion.hpp
+++ b/src/mbgl/style/rapidjson_conversion.hpp
@@ -1,103 +1,125 @@
#pragma once
#include <mbgl/util/rapidjson.hpp>
-#include <mbgl/util/feature.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mapbox/geojson.hpp>
+#include <mapbox/geojson/rapidjson.hpp>
+
namespace mbgl {
namespace style {
namespace conversion {
-inline bool isUndefined(const JSValue& value) {
- return value.IsNull();
-}
-
-inline bool isArray(const JSValue& value) {
- return value.IsArray();
-}
+template <>
+class ConversionTraits<const JSValue*> {
+public:
+ static bool isUndefined(const JSValue* value) {
+ return value->IsNull();
+ }
-inline std::size_t arrayLength(const JSValue& value) {
- return value.Size();
-}
+ static bool isArray(const JSValue* value) {
+ return value->IsArray();
+ }
-inline const JSValue& arrayMember(const JSValue& value, std::size_t i) {
- return value[rapidjson::SizeType(i)];
-}
+ static std::size_t arrayLength(const JSValue* value) {
+ return value->Size();
+ }
-inline bool isObject(const JSValue& value) {
- return value.IsObject();
-}
+ static const JSValue* arrayMember(const JSValue* value, std::size_t i) {
+ return &(*value)[rapidjson::SizeType(i)];
+ }
-inline const JSValue* objectMember(const JSValue& value, const char * name) {
- if (!value.HasMember(name)) {
- return nullptr;
+ static bool isObject(const JSValue* value) {
+ return value->IsObject();
}
- return &value[name];
-}
-template <class Fn>
-optional<Error> eachMember(const JSValue& value, Fn&& fn) {
- assert(value.IsObject());
- for (const auto& property : value.GetObject()) {
- optional<Error> result =
- fn({ property.name.GetString(), property.name.GetStringLength() }, property.value);
- if (result) {
- return result;
+ static optional<const JSValue*> objectMember(const JSValue* value, const char * name) {
+ if (!value->HasMember(name)) {
+ return optional<const JSValue*>();
}
+ const JSValue* const& member = &(*value)[name];
+ return {member};
}
- return {};
-}
-inline optional<bool> toBool(const JSValue& value) {
- if (!value.IsBool()) {
+ template <class Fn>
+ static optional<Error> eachMember(const JSValue* value, Fn&& fn) {
+ assert(value->IsObject());
+ for (const auto& property : value->GetObject()) {
+ optional<Error> result =
+ fn({ property.name.GetString(), property.name.GetStringLength() }, &property.value);
+ if (result) {
+ return result;
+ }
+ }
return {};
}
- return value.GetBool();
-}
-inline optional<float> toNumber(const JSValue& value) {
- if (!value.IsNumber()) {
- return {};
+ static optional<bool> toBool(const JSValue* value) {
+ if (!value->IsBool()) {
+ return {};
+ }
+ return value->GetBool();
}
- return value.GetDouble();
-}
-inline optional<double> toDouble(const JSValue& value) {
- if (!value.IsNumber()) {
- return {};
+ static optional<float> toNumber(const JSValue* value) {
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->GetDouble();
}
- return value.GetDouble();
-}
-inline optional<std::string> toString(const JSValue& value) {
- if (!value.IsString()) {
- return {};
+ static optional<double> toDouble(const JSValue* value) {
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->GetDouble();
+ }
+
+ static optional<std::string> toString(const JSValue* value) {
+ if (!value->IsString()) {
+ return {};
+ }
+ return {{ value->GetString(), value->GetStringLength() }};
}
- return {{ value.GetString(), value.GetStringLength() }};
-}
-inline optional<Value> toValue(const JSValue& value) {
- switch (value.GetType()) {
- case rapidjson::kNullType:
- case rapidjson::kFalseType:
- return { false };
+ static optional<Value> toValue(const JSValue* value) {
+ switch (value->GetType()) {
+ case rapidjson::kNullType:
+ case rapidjson::kFalseType:
+ return { false };
- case rapidjson::kTrueType:
- return { true };
+ case rapidjson::kTrueType:
+ return { true };
- case rapidjson::kStringType:
- return { std::string { value.GetString(), value.GetStringLength() } };
+ case rapidjson::kStringType:
+ return { std::string { value->GetString(), value->GetStringLength() } };
- case rapidjson::kNumberType:
- if (value.IsUint64()) return { value.GetUint64() };
- if (value.IsInt64()) return { value.GetInt64() };
- return { value.GetDouble() };
+ case rapidjson::kNumberType:
+ if (value->IsUint64()) return { value->GetUint64() };
+ if (value->IsInt64()) return { value->GetInt64() };
+ return { value->GetDouble() };
- default:
+ default:
+ return {};
+ }
+ }
+
+ static optional<GeoJSON> toGeoJSON(const JSValue* value, Error& error) {
+ try {
+ return mapbox::geojson::convert(*value);
+ } catch (const std::exception& ex) {
+ error = { ex.what() };
return {};
+ }
}
+};
+
+template <class T, class...Args>
+optional<T> convert(const JSValue& value, Error& error, Args&&...args) {
+ return convert<T>(Convertible(&value), error, std::forward<Args>(args)...);
}
} // namespace conversion
} // namespace style
} // namespace mbgl
+