summaryrefslogtreecommitdiff
path: root/platform/darwin/src/NSExpression+MGLAdditions.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/darwin/src/NSExpression+MGLAdditions.mm')
-rw-r--r--platform/darwin/src/NSExpression+MGLAdditions.mm585
1 files changed, 583 insertions, 2 deletions
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm
index a7759cda9d..5ad565c398 100644
--- a/platform/darwin/src/NSExpression+MGLAdditions.mm
+++ b/platform/darwin/src/NSExpression+MGLAdditions.mm
@@ -1,13 +1,19 @@
-#import "NSExpression+MGLAdditions.h"
+#import "NSExpression+MGLPrivateAdditions.h"
#import "MGLTypes.h"
#if TARGET_OS_IPHONE
#import "UIColor+MGLAdditions.h"
+ #define MGLEdgeInsets UIEdgeInsets
#else
#import "NSColor+MGLAdditions.h"
+ #define MGLEdgeInsets NSEdgeInsets
#endif
+#import "NSPredicate+MGLAdditions.h"
+#import "NSValue+MGLStyleAttributeAdditions.h"
-@implementation NSExpression (MGLAdditions)
+#import <mbgl/style/expression/expression.hpp>
+
+@implementation NSExpression (MGLPrivateAdditions)
- (std::vector<mbgl::Value>)mgl_aggregateMBGLValue {
if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) {
@@ -154,3 +160,578 @@
}
@end
+
+@implementation NSObject (MGLExpressionAdditions)
+
+- (NSNumber *)mgl_number {
+ return nil;
+}
+
+- (NSNumber *)mgl_numberWithFallbackValues:(id)fallbackValue, ... {
+ if (self.mgl_number) {
+ return self.mgl_number;
+ }
+
+ va_list fallbackValues;
+ va_start(fallbackValues, fallbackValue);
+ for (id value = fallbackValue; value; value = va_arg(fallbackValues, id)) {
+ if ([value mgl_number]) {
+ return [value mgl_number];
+ }
+ }
+
+ return nil;
+}
+
+@end
+
+@implementation NSNull (MGLExpressionAdditions)
+
+- (id)mgl_jsonExpressionObject {
+ return self;
+}
+
+@end
+
+@implementation NSString (MGLExpressionAdditions)
+
+- (id)mgl_jsonExpressionObject {
+ return self;
+}
+
+- (NSNumber *)mgl_number {
+ if (self.doubleValue || ![[NSDecimalNumber decimalNumberWithString:self] isEqual:[NSDecimalNumber notANumber]]) {
+ return @(self.doubleValue);
+ }
+
+ return nil;
+}
+
+@end
+
+@implementation NSNumber (MGLExpressionAdditions)
+
+- (id)mgl_interpolateWithCurveType:(NSString *)curveType
+ parameters:(NSArray *)parameters
+ stops:(NSDictionary<NSNumber *, id> *)stops {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Interpolation expressions lack underlying Objective-C implementations."];
+ return nil;
+}
+
+- (id)mgl_stepWithMinimum:(id)minimum stops:(NSDictionary<NSNumber *, id> *)stops {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Interpolation expressions lack underlying Objective-C implementations."];
+ return nil;
+}
+
+- (NSNumber *)mgl_number {
+ return self;
+}
+
+- (id)mgl_jsonExpressionObject {
+ if ([self isEqualToNumber:@(M_E)]) {
+ return @[@"e"];
+ } else if ([self isEqualToNumber:@(M_PI)]) {
+ return @[@"pi"];
+ }
+ return self;
+}
+
+@end
+
+@implementation MGLColor (MGLExpressionAdditions)
+
+- (id)mgl_jsonExpressionObject {
+ auto color = [self mgl_color];
+ if (color.a == 1) {
+ return @[@"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255)];
+ }
+ return @[@"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a)];
+}
+
+@end
+
+@implementation NSArray (MGLExpressionAdditions)
+
+- (id)mgl_jsonExpressionObject {
+ return [self valueForKeyPath:@"mgl_jsonExpressionObject"];
+}
+
+@end
+
+@implementation NSDictionary (MGLExpressionAdditions)
+
+- (id)mgl_jsonExpressionObject {
+ NSMutableDictionary *expressionObject = [NSMutableDictionary dictionaryWithCapacity:self.count];
+ [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
+ expressionObject[[key mgl_jsonExpressionObject]] = [obj mgl_jsonExpressionObject];
+ }];
+
+ return expressionObject;
+}
+
+@end
+
+@implementation NSExpression (MGLExpressionAdditions)
+
+- (NSExpression *)mgl_expressionWithContext:(NSDictionary<NSString *, NSExpression *> *)context {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"Assignment expressions lack underlying Objective-C implementations."];
+ return self;
+}
+
+@end
+
+@implementation NSExpression (MGLAdditions)
+
+static NSDictionary<NSString *, NSString *> *MGLFunctionNamesByExpressionOperator;
+static NSDictionary<NSString *, NSString *> *MGLExpressionOperatorsByFunctionNames;
+
+NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
+ NSMutableArray *subexpressions = [NSMutableArray arrayWithCapacity:objects.count];
+ for (id object in objects) {
+ NSExpression *expression = [NSExpression mgl_expressionWithJSONObject:object];
+ [subexpressions addObject:expression];
+ }
+ return subexpressions;
+}
+
++ (instancetype)mgl_expressionWithJSONObject:(id)object {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ MGLFunctionNamesByExpressionOperator = @{
+ @"+": @"add:to:",
+ @"-": @"from:subtract:",
+ @"*": @"multiply:by:",
+ @"/": @"divide:by:",
+ @"%": @"modulus:by:",
+ @"sqrt": @"sqrt:",
+ @"log10": @"log:",
+ @"ln": @"ln:",
+ @"^": @"raise:toPower:",
+ @"upcase": @"uppercase:",
+ @"downcase": @"lowercase:",
+ };
+ });
+
+ if (!object || object == [NSNull null]) {
+ return [NSExpression expressionForConstantValue:nil];
+ }
+
+ if ([object isKindOfClass:[NSString class]] ||
+ [object isKindOfClass:[NSNumber class]] ||
+ [object isKindOfClass:[NSValue class]] ||
+ [object isKindOfClass:[MGLColor class]]) {
+ return [NSExpression expressionForConstantValue:object];
+ }
+
+ if ([object isKindOfClass:[NSDictionary class]]) {
+ NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[object count]];
+ [object enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
+ dictionary[key] = [NSExpression mgl_expressionWithJSONObject:obj];
+ }];
+ return [NSExpression expressionForConstantValue:dictionary];
+ }
+
+ if ([object isKindOfClass:[NSArray class]]) {
+ NSArray *array = (NSArray *)object;
+ NSString *op = array.firstObject;
+
+ NSArray *argumentObjects = [array subarrayWithRange:NSMakeRange(1, array.count - 1)];
+
+ NSString *functionName = MGLFunctionNamesByExpressionOperator[op];
+ if (functionName) {
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ if ([op isEqualToString:@"+"] && argumentObjects.count > 2) {
+ NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions];
+ return [NSExpression expressionForFunction:@"sum:"
+ arguments:@[subexpression]];
+ } else if ([op isEqualToString:@"^"] && [argumentObjects.firstObject isEqual:@[@"e"]]) {
+ functionName = @"exp:";
+ subexpressions = [subexpressions subarrayWithRange:NSMakeRange(1, subexpressions.count - 1)];
+ }
+
+ return [NSExpression expressionForFunction:functionName
+ arguments:subexpressions];
+ } else if ([op isEqualToString:@"literal"]) {
+ if ([argumentObjects.firstObject isKindOfClass:[NSArray class]]) {
+ return [NSExpression expressionForAggregate:MGLSubexpressionsWithJSONObjects(argumentObjects.firstObject)];
+ }
+ return [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ } else if ([op isEqualToString:@"to-boolean"]) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ return [NSExpression expressionForFunction:operand selectorName:@"boolValue" arguments:@[]];
+ } else if ([op isEqualToString:@"to-number"]) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)];
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ return [NSExpression expressionForFunction:operand selectorName:@"mgl_numberWithFallbackValues:" arguments:subexpressions];
+ } else if ([op isEqualToString:@"to-string"]) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ return [NSExpression expressionForFunction:operand selectorName:@"stringValue" arguments:@[]];
+ } else if ([op isEqualToString:@"get"]) {
+ if (argumentObjects.count == 2) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.lastObject];
+ if ([argumentObjects.firstObject isKindOfClass:[NSString class]]) {
+ return [NSExpression expressionWithFormat:@"%@.%K", operand, argumentObjects.firstObject];
+ }
+ NSExpression *key = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ return [NSExpression expressionWithFormat:@"%@.%@", operand, key];
+ }
+ return [NSExpression expressionForKeyPath:argumentObjects.firstObject];
+ } else if ([op isEqualToString:@"length"]) {
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ return [NSExpression expressionForFunction:@"count:" arguments:@[subexpressions.firstObject]];
+ } else if ([op isEqualToString:@"min"]) {
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions];
+ return [NSExpression expressionForFunction:@"min:" arguments:@[subexpression]];
+ } else if ([op isEqualToString:@"max"]) {
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions];
+ return [NSExpression expressionForFunction:@"max:" arguments:@[subexpression]];
+ } else if ([op isEqualToString:@"e"]) {
+ return [NSExpression expressionForConstantValue:@(M_E)];
+ } else if ([op isEqualToString:@"pi"]) {
+ return [NSExpression expressionForConstantValue:@(M_PI)];
+ } else if ([op isEqualToString:@"concat"]) {
+ NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects);
+ NSExpression *operand = subexpressions.firstObject;
+ subexpressions = [subexpressions subarrayWithRange:NSMakeRange(1, subexpressions.count - 1)];
+ return [NSExpression expressionForFunction:operand selectorName:@"stringByAppendingString:" arguments:subexpressions];
+ } else if ([op isEqualToString:@"interpolate"]) {
+ NSArray *interpolationOptions = argumentObjects.firstObject;
+ NSString *curveType = interpolationOptions.firstObject;
+ NSExpression *curveTypeExpression = [NSExpression mgl_expressionWithJSONObject:curveType];
+ id curveParameters;
+ if ([curveType isEqual:@"exponential"]) {
+ curveParameters = interpolationOptions[1];
+ } else if ([curveType isEqualToString:@"cubic-bezier"]) {
+ curveParameters = @[@"literal", [interpolationOptions subarrayWithRange:NSMakeRange(1, 4)]];
+ }
+ NSExpression *curveParameterExpression = [NSExpression mgl_expressionWithJSONObject:curveParameters];
+ argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)];
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject];
+ NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)];
+ NSMutableDictionary *stops = [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2];
+ NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator;
+ while (NSNumber *key = stopEnumerator.nextObject) {
+ NSExpression *valueExpression = stopEnumerator.nextObject;
+ stops[key] = [NSExpression mgl_expressionWithJSONObject:valueExpression];
+ }
+ NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops];
+ return [NSExpression expressionForFunction:operand
+ selectorName:@"mgl_interpolateWithCurveType:parameters:stops:"
+ arguments:@[curveTypeExpression, curveParameterExpression, stopExpression]];
+ } else if ([op isEqualToString:@"step"]) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects[0]];
+ NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)];
+ NSExpression *minimum;
+ if (stopExpressions.count % 2) {
+ minimum = [NSExpression mgl_expressionWithJSONObject:stopExpressions.firstObject];
+ stopExpressions = [stopExpressions subarrayWithRange:NSMakeRange(1, stopExpressions.count - 1)];
+ }
+ NSMutableDictionary *stops = [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2];
+ NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator;
+ while (NSNumber *key = stopEnumerator.nextObject) {
+ NSExpression *valueExpression = stopEnumerator.nextObject;
+ if (minimum) {
+ stops[key] = [NSExpression mgl_expressionWithJSONObject:valueExpression];
+ } else {
+ minimum = [NSExpression mgl_expressionWithJSONObject:valueExpression];
+ }
+ }
+ NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops];
+ return [NSExpression expressionForFunction:operand
+ selectorName:@"mgl_stepWithMinimum:stops:"
+ arguments:@[minimum, stopExpression]];
+ } else if ([op isEqualToString:@"zoom"]) {
+ return [NSExpression expressionForVariable:@"zoomLevel"];
+ } else if ([op isEqualToString:@"heatmap-density"]) {
+ return [NSExpression expressionForVariable:@"heatmapDensity"];
+ } else if ([op isEqualToString:@"let"]) {
+ NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.lastObject];
+ NSArray *bindingObjects = [argumentObjects subarrayWithRange:NSMakeRange(0, argumentObjects.count - 1)];
+ NSMutableDictionary *context = [NSMutableDictionary dictionaryWithCapacity:bindingObjects.count / 2];
+ NSEnumerator *bindingEnumerator = bindingObjects.objectEnumerator;
+ while (NSString *key = bindingEnumerator.nextObject) {
+ context[key] = [NSExpression mgl_expressionWithJSONObject:bindingEnumerator.nextObject];
+ }
+ return [NSExpression expressionForFunction:operand
+ selectorName:@"mgl_expressionWithContext:"
+ arguments:@[[NSExpression expressionForConstantValue:context]]];
+ } else if ([op isEqualToString:@"var"]) {
+ return [NSExpression expressionForVariable:argumentObjects.firstObject];
+ } else if ([op isEqualToString:@"case"]) {
+ NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:argumentObjects.firstObject];
+ NSExpression *trueExpression = [NSExpression mgl_expressionWithJSONObject:argumentObjects[1]];
+ NSExpression *falseExpression;
+ if (argumentObjects.count > 3) {
+ NSArray *falseObjects = [@[@"case"] arrayByAddingObjectsFromArray:
+ [argumentObjects subarrayWithRange:NSMakeRange(2, argumentObjects.count - 2)]];
+ falseExpression = [NSExpression mgl_expressionWithJSONObject:falseObjects];
+ } else {
+ falseExpression = [NSExpression mgl_expressionWithJSONObject:argumentObjects[2]];
+ }
+ return [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression];
+ } else {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Expression operator %@ not yet implemented.", op];
+ }
+ }
+
+ [NSException raise:NSInvalidArgumentException
+ format:@"Unable to convert JSON object %@ to an NSExpression.", object];
+
+ return nil;
+}
+
+- (id)mgl_jsonExpressionObject {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ MGLExpressionOperatorsByFunctionNames = @{
+ @"add:to:": @"+",
+ @"from:subtract:": @"-",
+ @"multiply:by:": @"*",
+ @"divide:by:": @"/",
+ @"modulus:by:": @"%",
+ @"sqrt:": @"sqrt",
+ @"log:": @"log10",
+ @"ln:": @"ln",
+ @"raise:toPower:": @"^",
+ @"uppercase:": @"upcase",
+ @"lowercase:": @"downcase",
+ };
+ });
+
+ switch (self.expressionType) {
+ case NSVariableExpressionType: {
+ if ([self.variable isEqualToString:@"heatmapDensity"]) {
+ return @[@"heatmap-density"];
+ }
+ if ([self.variable isEqualToString:@"zoomLevel"]) {
+ return @[@"zoom"];
+ }
+ return @[@"var", self.variable];
+ }
+
+ case NSConstantValueExpressionType: {
+ id constantValue = self.constantValue;
+ if (!constantValue || constantValue == [NSNull null]) {
+ return [NSNull null];
+ }
+ if ([constantValue isEqual:@(M_E)]) {
+ return @[@"e"];
+ }
+ if ([constantValue isEqual:@(M_PI)]) {
+ return @[@"pi"];
+ }
+ if ([constantValue isKindOfClass:[NSArray class]] ||
+ [constantValue isKindOfClass:[NSDictionary class]]) {
+ NSArray *collection = [constantValue mgl_jsonExpressionObject];
+ return @[@"literal", collection];
+ }
+ if ([constantValue isKindOfClass:[MGLColor class]]) {
+ auto color = [constantValue mgl_color];
+ if (color.a == 1) {
+ return @[@"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255)];
+ }
+ return @[@"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a)];
+ }
+ if ([constantValue isKindOfClass:[NSValue class]]) {
+ const auto boxedValue = (NSValue *)constantValue;
+ if (strcmp([boxedValue objCType], @encode(CGVector)) == 0) {
+ // offset [x, y]
+ std::array<float, 2> mglValue = boxedValue.mgl_offsetArrayValue;
+ return @[@"literal", @[@(mglValue[0]), @(mglValue[1])]];
+ }
+ if (strcmp([boxedValue objCType], @encode(MGLEdgeInsets)) == 0) {
+ // padding [x, y]
+ std::array<float, 4> mglValue = boxedValue.mgl_paddingArrayValue;
+ return @[@"literal", @[@(mglValue[0]), @(mglValue[1]), @(mglValue[2]), @(mglValue[3])]];
+ }
+ }
+ return self.constantValue;
+ }
+
+ case NSKeyPathExpressionType: {
+ NSArray *expressionObject;
+ for (NSString *pathComponent in self.keyPath.pathComponents.reverseObjectEnumerator) {
+ if (expressionObject) {
+ expressionObject = @[@"get", pathComponent, expressionObject];
+ } else {
+ expressionObject = @[@"get", pathComponent];
+ }
+ }
+ return expressionObject;
+ }
+
+ case NSFunctionExpressionType: {
+ NSString *function = self.function;
+ NSString *op = MGLExpressionOperatorsByFunctionNames[function];
+ if (op) {
+ NSArray *arguments = self.arguments.mgl_jsonExpressionObject;
+ return [@[op] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"valueForKeyPath:"]) {
+ return @[@"get", self.arguments.firstObject.mgl_jsonExpressionObject, self.operand.mgl_jsonExpressionObject];
+ } else if ([function isEqualToString:@"average:"]) {
+ NSExpression *sum = [NSExpression expressionForFunction:@"sum:" arguments:self.arguments];
+ NSExpression *count = [NSExpression expressionForFunction:@"count:" arguments:self.arguments];
+ return [NSExpression expressionForFunction:@"divide:by:" arguments:@[sum, count]].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"sum:"]) {
+ NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ return [@[@"+"] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"count:"]) {
+ NSArray *arguments = self.arguments.firstObject.mgl_jsonExpressionObject;
+ return @[@"length", arguments];
+ } else if ([function isEqualToString:@"min:"]) {
+ NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ return [@[@"min"] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"max:"]) {
+ NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ return [@[@"max"] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"exp:"]) {
+ return [NSExpression expressionForFunction:@"raise:toPower:" arguments:@[@(M_E), self.arguments.firstObject]].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"ceiling:"]) {
+ return [NSExpression expressionWithFormat:@"trunc:(%@) + TERNARY(modulus:by:(%@, 1) > 0, 1, 0)",
+ self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"trunc:"]) {
+ return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)",
+ self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"abs:"]) {
+ return [NSExpression expressionWithFormat:@"%@ * TERNARY(%@ > 0, 1, -1)",
+ self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"floor:"]) {
+ return [NSExpression expressionWithFormat:@"trunc:(%@) - TERNARY(modulus:by:(%@, 1) < 0, 1, 0)",
+ self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"stringByAppendingString:"]) {
+ NSArray *arguments = self.arguments.mgl_jsonExpressionObject;
+ return [@[@"concat", self.operand.mgl_jsonExpressionObject] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"boolValue"]) {
+ return @[@"to-boolean", self.operand.mgl_jsonExpressionObject];
+ } else if ([function isEqualToString:@"mgl_numberWithFallbackValues:"] ||
+ [function isEqualToString:@"decimalValue"] ||
+ [function isEqualToString:@"floatValue"] ||
+ [function isEqualToString:@"doubleValue"]) {
+ NSArray *arguments = self.arguments.mgl_jsonExpressionObject;
+ return [@[@"to-number", self.operand.mgl_jsonExpressionObject] arrayByAddingObjectsFromArray:arguments];
+ } else if ([function isEqualToString:@"stringValue"]) {
+ return @[@"to-string", self.operand.mgl_jsonExpressionObject];
+ } else if ([function isEqualToString:@"noindex:"]) {
+ return self.arguments.firstObject.mgl_jsonExpressionObject;
+ } else if ([function isEqualToString:@"mgl_interpolateWithCurveType:parameters:stops:"]) {
+ if (self.arguments.count < 3) {
+ [NSException raise:NSInvalidArgumentException format:
+ @"Too few arguments to ‘mgl_interpolateWithCurveType:parameters:stops:’ function; expected 3 arguments."];
+ } else if (self.arguments.count > 3) {
+ [NSException raise:NSInvalidArgumentException format:
+ @"%lu unexpected arguments to ‘mgl_interpolateWithCurveType:parameters:stops:’ function; expected 3 arguments.",
+ self.arguments.count - 3];
+ }
+ NSString *curveType = self.arguments.firstObject.constantValue;
+ NSMutableArray *interpolationArray = [NSMutableArray arrayWithObject:curveType];
+ if ([curveType isEqualToString:@"exponential"]) {
+ id base = [self.arguments[1] mgl_jsonExpressionObject];
+ [interpolationArray addObject:base];
+ } else if ([curveType isEqualToString:@"cubic-bezier"]) {
+ NSArray *controlPoints = [self.arguments[1].collection mgl_jsonExpressionObject];
+ [interpolationArray addObjectsFromArray:controlPoints];
+ }
+ NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"interpolate", interpolationArray, self.operand.mgl_jsonExpressionObject, nil];
+ NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[2].constantValue;
+ for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
+ [expressionObject addObject:key];
+ [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]];
+ }
+ return expressionObject;
+ } else if ([function isEqualToString:@"mgl_stepWithMinimum:stops:"]) {
+ id minimum = self.arguments.firstObject.mgl_jsonExpressionObject;
+ NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"step", self.operand.mgl_jsonExpressionObject, minimum, nil];
+ NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[1].constantValue;
+ for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
+ [expressionObject addObject:key];
+ [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]];
+ }
+ return expressionObject;
+ } else if ([function isEqualToString:@"mgl_expressionWithContext:"]) {
+ id context = self.arguments.firstObject;
+ if ([context isKindOfClass:[NSExpression class]]) {
+ context = [context constantValue];
+ }
+ NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"let", nil];
+ [context enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, NSExpression * _Nonnull obj, BOOL * _Nonnull stop) {
+ [expressionObject addObject:key];
+ [expressionObject addObject:obj.mgl_jsonExpressionObject];
+ }];
+ [expressionObject addObject:self.operand.mgl_jsonExpressionObject];
+ return expressionObject;
+ } else if ([function isEqualToString:@"median:"] ||
+ [function isEqualToString:@"mode:"] ||
+ [function isEqualToString:@"stddev:"] ||
+ [function isEqualToString:@"random"] ||
+ [function isEqualToString:@"randomn:"] ||
+ [function isEqualToString:@"now"] ||
+ [function isEqualToString:@"bitwiseAnd:with:"] ||
+ [function isEqualToString:@"bitwiseOr:with:"] ||
+ [function isEqualToString:@"bitwiseXor:with:"] ||
+ [function isEqualToString:@"leftshift:by:"] ||
+ [function isEqualToString:@"rightshift:by:"] ||
+ [function isEqualToString:@"onesComplement:"] ||
+ [function isEqualToString:@"distanceToLocation:fromLocation:"]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Expression function %@ not yet implemented.", function];
+ return nil;
+ } else {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Unrecognized expression function %@.", function];
+ return nil;
+ }
+ }
+
+ case NSConditionalExpressionType: {
+ NSMutableArray *arguments = [NSMutableArray arrayWithObjects:self.predicate.mgl_jsonExpressionObject, self.trueExpression.mgl_jsonExpressionObject, nil];
+ if (self.falseExpression.expressionType == NSConditionalExpressionType) {
+ // Fold nested conditionals into a single case expression.
+ NSArray *falseArguments = self.falseExpression.mgl_jsonExpressionObject;
+ falseArguments = [falseArguments subarrayWithRange:NSMakeRange(1, falseArguments.count - 1)];
+ [arguments addObjectsFromArray:falseArguments];
+ } else {
+ [arguments addObject:self.falseExpression.mgl_jsonExpressionObject];
+ }
+
+ [arguments insertObject:@"case" atIndex:0];
+ return arguments;
+ }
+
+ case NSAggregateExpressionType: {
+ NSArray *collection = [self.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ return @[@"literal", collection];
+ }
+
+ case NSEvaluatedObjectExpressionType:
+ case NSUnionSetExpressionType:
+ case NSIntersectSetExpressionType:
+ case NSMinusSetExpressionType:
+ case NSSubqueryExpressionType:
+ case NSAnyKeyExpressionType:
+ case NSBlockExpressionType:
+ [NSException raise:NSInvalidArgumentException
+ format:@"Expression type %lu not yet implemented.", self.expressionType];
+ }
+
+ // NSKeyPathSpecifierExpression
+ if (self.expressionType == 10) {
+ return self.description;
+ }
+ // An assignment expression type is present in the BNF grammar, but the
+ // corresponding NSExpressionType value and property getters are missing.
+ if (self.expressionType == 12) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Assignment expressions not yet implemented."];
+ }
+
+ return nil;
+}
+
+@end