diff options
Diffstat (limited to 'platform/darwin/src')
-rw-r--r-- | platform/darwin/src/MGLCircleStyleLayer.h | 2 | ||||
-rw-r--r-- | platform/darwin/src/MGLHeatmapStyleLayer.h | 4 | ||||
-rw-r--r-- | platform/darwin/src/MGLLineStyleLayer.h | 2 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleValue.h | 14 | ||||
-rw-r--r-- | platform/darwin/src/NSExpression+MGLAdditions.mm | 467 | ||||
-rw-r--r-- | platform/darwin/src/NSExpression+MGLPrivateAdditions.h | 2 | ||||
-rw-r--r-- | platform/darwin/src/NSPredicate+MGLAdditions.h | 2 | ||||
-rw-r--r-- | platform/darwin/src/NSPredicate+MGLAdditions.mm | 30 |
8 files changed, 380 insertions, 143 deletions
diff --git a/platform/darwin/src/MGLCircleStyleLayer.h b/platform/darwin/src/MGLCircleStyleLayer.h index 2523637179..aa2d07db26 100644 --- a/platform/darwin/src/MGLCircleStyleLayer.h +++ b/platform/darwin/src/MGLCircleStyleLayer.h @@ -83,7 +83,7 @@ typedef NS_ENUM(NSUInteger, MGLCircleTranslationAnchor) { let layer = MGLCircleStyleLayer(identifier: "circles", source: population) layer.sourceLayerIdentifier = "population" layer.circleColor = NSExpression(forConstantValue: UIColor.green) - layer.circleRadius = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.75, %@)", + layer.circleRadius = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.75, %@)", [12: 2, 22: 180]) layer.circleOpacity = NSExpression(forConstantValue: 0.7) diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h index ff2b92f226..1c68e2cae8 100644 --- a/platform/darwin/src/MGLHeatmapStyleLayer.h +++ b/platform/darwin/src/MGLHeatmapStyleLayer.h @@ -35,10 +35,10 @@ NS_ASSUME_NONNULL_BEGIN ```swift let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) - layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + layer.heatmapWeight = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(magnitude, 'linear', nil, %@)", [0: 0, 6: 1]) - layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + layer.heatmapIntensity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [0: 1, 9: 3]) mapView.style?.addLayer(layer) diff --git a/platform/darwin/src/MGLLineStyleLayer.h b/platform/darwin/src/MGLLineStyleLayer.h index bdb5464ebc..d8e109d559 100644 --- a/platform/darwin/src/MGLLineStyleLayer.h +++ b/platform/darwin/src/MGLLineStyleLayer.h @@ -93,7 +93,7 @@ typedef NS_ENUM(NSUInteger, MGLLineTranslationAnchor) { ```swift let layer = MGLLineStyleLayer(identifier: "trails-path", source: trails) layer.sourceLayerIdentifier = "trails" - layer.lineWidth = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.5, %@)", + layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.5, %@)", [14: 2, 18: 20]) layer.lineColor = NSExpression(forConstantValue: UIColor.brown) diff --git a/platform/darwin/src/MGLStyleValue.h b/platform/darwin/src/MGLStyleValue.h index f6e9c51729..65f89c5909 100644 --- a/platform/darwin/src/MGLStyleValue.h +++ b/platform/darwin/src/MGLStyleValue.h @@ -8,13 +8,13 @@ NS_ASSUME_NONNULL_BEGIN typedef NSString *MGLStyleFunctionOption NS_STRING_ENUM NS_UNAVAILABLE; -extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionInterpolationBase __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolateWithCurveType:parameters:stops: function with a curve type of “exponential” and a non-nil parameter."))); +extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionInterpolationBase __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolate:withCurveType:parameters:stops: function with a curve type of “exponential” and a non-nil parameter."))); extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue __attribute__((unavailable("Use +[NSExpression expressionForConditional:trueExpression:falseExpression:] instead."))); typedef NS_ENUM(NSUInteger, MGLInterpolationMode) { - MGLInterpolationModeExponential __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolateWithCurveType:parameters:stops: function with a curve type of “exponential”."))) = 0, - MGLInterpolationModeInterval __attribute__((unavailable("Use NSExpression instead, calling the mgl_stepWithMinimum:stops: function."))), + MGLInterpolationModeExponential __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolate:withCurveType:parameters:stops: function with a curve type of “exponential”."))) = 0, + MGLInterpolationModeInterval __attribute__((unavailable("Use NSExpression instead, calling the mgl_step:from:stops: function."))), MGLInterpolationModeCategorical __attribute__((unavailable("Use NSExpression instead."))), MGLInterpolationModeIdentity __attribute__((unavailable("Use +[NSExpression expressionForKeyPath:] instead."))) } __attribute__((unavailable("Use NSExpression instead."))); @@ -29,19 +29,19 @@ MGL_EXPORT __attribute__((unavailable("Use +[NSExpression expressionForConstantV @compatibility_alias MGLStyleConstantValue MGLConstantStyleValue; -MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, calling the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function."))) +MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, calling the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function."))) @interface MGLStyleFunction<T> : MGLStyleValue<T> @end -MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function to the $zoomLevel variable."))) +MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function to the $zoomLevel variable."))) @interface MGLCameraStyleFunction<T> : MGLStyleFunction<T> @end -MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function to a key path expression."))) +MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function to a key path expression."))) @interface MGLSourceStyleFunction<T> : MGLStyleFunction<T> @end -MGL_EXPORT __attribute__((unavailable("Use a NSExpression instead with nested mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function calls."))) +MGL_EXPORT __attribute__((unavailable("Use a NSExpression instead with nested mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function calls."))) @interface MGLCompositeStyleFunction<T> : MGLStyleFunction<T> @end diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index a193b85caa..7fe8ce3c91 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -11,8 +11,168 @@ #import "NSPredicate+MGLAdditions.h" #import "NSValue+MGLStyleAttributeAdditions.h" +#import <objc/runtime.h> + #import <mbgl/style/expression/expression.hpp> +@interface MGLAftermarketExpressionInstaller: NSObject +@end + +@implementation MGLAftermarketExpressionInstaller + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self installFunctions]; + }); +} + +/** + Adds to NSExpression’s built-in repertoire of functions. + */ ++ (void)installFunctions { + Class MGLAftermarketExpressionInstaller = [self class]; + + // NSExpression’s built-in functions are backed by class methods on a + // private class, so use a function expression to get at the class. + // http://funwithobjc.tumblr.com/post/2922267976/using-custom-functions-with-nsexpression + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"sum({})"]; + NSString *className = NSStringFromClass([functionExpression.operand.constantValue class]); + + // Effectively categorize the class with some extra class methods. + Class NSPredicateUtilities = objc_getMetaClass(className.UTF8String); +#pragma clang push +#pragma clang diagnostic ignored "-Wundeclared-selector" + #define INSTALL_METHOD(sel) \ + { \ + Method method = class_getInstanceMethod(MGLAftermarketExpressionInstaller, @selector(sel)); \ + class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), method_getTypeEncoding(method)); \ + } + #define INSTALL_CONTROL_STRUCTURE(sel) \ + { \ + Method method = class_getInstanceMethod(MGLAftermarketExpressionInstaller, @selector(sel:)); \ + class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), method_getTypeEncoding(method)); \ + class_addMethod(NSPredicateUtilities, @selector(sel:), method_getImplementation(method), method_getTypeEncoding(method)); \ + } + + // Install method-like functions, taking the number of arguments implied by + // the selector name. + INSTALL_METHOD(mgl_join:); + INSTALL_METHOD(mgl_interpolate:withCurveType:parameters:stops:); + INSTALL_METHOD(mgl_step:from:stops:); + INSTALL_METHOD(mgl_coalesce:); + INSTALL_METHOD(mgl_does:have:); + + // Install functions that resemble control structures, taking arbitrary + // numbers of arguments. Vararg aftermarket functions need to be declared + // with an explicit and implicit first argument. + INSTALL_CONTROL_STRUCTURE(MGL_LET); + INSTALL_CONTROL_STRUCTURE(MGL_MATCH); + INSTALL_CONTROL_STRUCTURE(MGL_IF); + INSTALL_CONTROL_STRUCTURE(MGL_FUNCTION); + + #undef INSTALL_AFTERMARKET_FN +#pragma clang pop +} + +/** + Joins the given components into a single string by concatenating each component + in order. + */ +- (NSString *)mgl_join:(NSArray<NSString *> *)components { + return [components componentsJoinedByString:@""]; +} + +/** + A placeholder for a method that evaluates an interpolation expression. + */ +- (id)mgl_interpolate:(id)inputExpression withCurveType:(NSString *)curveType parameters:(NSDictionary *)params stops:(NSDictionary *)stops { + [NSException raise:NSInvalidArgumentException + format:@"Interpolation expressions lack underlying Objective-C implementations."]; + return nil; +} + +/** + A placeholder for a method that evaluates a step expression. + */ +- (id)mgl_step:(id)inputExpression from:(id)minimumExpression stops:(NSDictionary *)stops { + [NSException raise:NSInvalidArgumentException + format:@"Step expressions lack underlying Objective-C implementations."]; + return nil; +} + +/** + A placeholder for a method that evaluates a coalesce expression. + */ +- (id)mgl_coalesce:(NSArray<NSExpression *> *)elements { + [NSException raise:NSInvalidArgumentException + format:@"Coalesce expressions lack underlying Objective-C implementations."]; + return nil; +} + +/** + Returns a Boolean value indicating whether the object has a value for the given + key. + */ +- (BOOL)mgl_does:(id)object have:(NSString *)key { + return [object valueForKey:key] != nil; +} + +/** + A placeholder for a method that evaluates an expression based on an arbitrary + number of variable names and assigned expressions. + */ +- (id)MGL_LET:(NSString *)firstVariableName, ... { + [NSException raise:NSInvalidArgumentException + format:@"Assignment expressions lack underlying Objective-C implementations."]; + return nil; +} + +/** + A placeholder for a method that evaluates an expression and returns the matching element. + */ +- (id)MGL_MATCH:(id)firstCondition, ... { + [NSException raise:NSInvalidArgumentException + format:@"Assignment expressions lack underlying Objective-C implementations."]; + return nil; +} + +/** + A placeholder for a method that evaluates an expression and returns the matching element. + */ +- (id)MGL_IF:(id)firstCondition, ... { + va_list argumentList; + va_start(argumentList, firstCondition); + + for (id eachExpression = firstCondition; eachExpression; eachExpression = va_arg(argumentList, id)) { + if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { + id valueExpression = va_arg(argumentList, id); + if ([eachExpression evaluateWithObject:nil]) { + return valueExpression; + } + } else { + return eachExpression; + } + } + va_end(argumentList); + + return nil; +} + + +/** + A placeholder for a catch-all method that evaluates an arbitrary number of + arguments as an expression according to the Mapbox Style Specification’s + expression language. + */ +- (id)MGL_FUNCTION:(id)firstArgument, ... { + [NSException raise:NSInvalidArgumentException + format:@"Mapbox GL function expressions lack underlying Objective-C implementations."]; + return nil; +} + +@end + @implementation NSExpression (MGLPrivateAdditions) - (std::vector<mbgl::Value>)mgl_aggregateMBGLValue { @@ -260,7 +420,7 @@ - (id)mgl_coalesce { [NSException raise:NSInvalidArgumentException - format:@"Coalesce expressions lack underlying Objective-C implementations."]; + format:@"Coalesce expressions lack underlying Objective-C implementations."]; return nil; } @@ -330,6 +490,7 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { @"^": @"raise:toPower:", @"upcase": @"uppercase:", @"downcase": @"lowercase:", + @"let": @"MGL_LET", }; }); @@ -382,12 +543,15 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return [NSExpression expressionForFunction:operand selectorName:@"boolValue" arguments:@[]]; } else if ([op isEqualToString:@"to-number"]) { NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject]; + if (argumentObjects.count == 1) { + return [NSExpression expressionWithFormat:@"CAST(%@, 'NSNumber')", operand]; + } 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:@[]]; + return [NSExpression expressionWithFormat:@"CAST(%@, 'NSString')", operand]; } else if ([op isEqualToString:@"get"]) { if (argumentObjects.count == 2) { NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.lastObject]; @@ -400,7 +564,12 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return [NSExpression expressionForKeyPath:argumentObjects.firstObject]; } else if ([op isEqualToString:@"length"]) { NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression expressionForFunction:@"count:" arguments:@[subexpressions.firstObject]]; + NSString *function = @"count:"; + if ([subexpressions.firstObject expressionType] == NSConstantValueExpressionType + && [[subexpressions.firstObject constantValue] isKindOfClass:[NSString class]]) { + function = @"length:"; + } + return [NSExpression expressionForFunction:function arguments:@[subexpressions.firstObject]]; } else if ([op isEqualToString:@"min"]) { NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; @@ -415,9 +584,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { 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]; + NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; + return [NSExpression expressionForFunction:@"mgl_join:" arguments:@[subexpression]]; } else if ([op isEqualToString:@"at"]) { NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); NSExpression *index = subexpressions.firstObject; @@ -425,9 +593,9 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return [NSExpression expressionForFunction:@"objectFrom:withIndex:" arguments:@[operand, index]]; } else if ([op isEqualToString:@"has"]) { NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *operand = argumentObjects.count > 1 ? subexpressions[1] : [NSExpression expressionWithFormat:@"self"]; - NSExpression *property = subexpressions.firstObject; - return [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_has:', %@)", operand, property]; + NSExpression *operand = argumentObjects.count > 1 ? subexpressions[1] : [NSExpression expressionForEvaluatedObject]; + NSExpression *key = subexpressions.firstObject; + return [NSExpression expressionForFunction:@"mgl_does:have:" arguments:@[operand, key]]; } else if ([op isEqualToString:@"interpolate"]) { NSArray *interpolationOptions = argumentObjects.firstObject; NSString *curveType = interpolationOptions.firstObject; @@ -440,7 +608,7 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { } NSExpression *curveParameterExpression = [NSExpression mgl_expressionWithJSONObject:curveParameters]; argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects.firstObject]; + NSExpression *inputExpression = [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; @@ -449,11 +617,10 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { stops[key] = [NSExpression mgl_expressionWithJSONObject:valueExpression]; } NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression expressionForFunction:operand - selectorName:@"mgl_interpolateWithCurveType:parameters:stops:" - arguments:@[curveTypeExpression, curveParameterExpression, stopExpression]]; + return [NSExpression expressionForFunction:@"mgl_interpolate:withCurveType:parameters:stops:" + arguments:@[inputExpression, curveTypeExpression, curveParameterExpression, stopExpression]]; } else if ([op isEqualToString:@"step"]) { - NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects[0]]; + NSExpression *inputExpression = [NSExpression mgl_expressionWithJSONObject:argumentObjects[0]]; NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; NSExpression *minimum; if (stopExpressions.count % 2) { @@ -471,9 +638,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { } } NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression expressionForFunction:operand - selectorName:@"mgl_stepWithMinimum:stops:" - arguments:@[minimum, stopExpression]]; + return [NSExpression expressionForFunction:@"mgl_step:from:stops:" + arguments:@[inputExpression, minimum, stopExpression]]; } else if ([op isEqualToString:@"zoom"]) { return [NSExpression expressionForVariable:@"zoomLevel"]; } else if ([op isEqualToString:@"heatmap-density"]) { @@ -484,48 +650,37 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return [NSExpression expressionForVariable:@"mgl_featureIdentifier"]; } else if ([op isEqualToString:@"properties"]) { return [NSExpression expressionForVariable:@"mgl_featureProperties"]; - } 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"]) { - NSArray *caseExpressions = argumentObjects; - NSExpression *firstConditional = [NSExpression expressionWithFormat:@"%@", [NSPredicate mgl_predicateWithJSONObject:caseExpressions[0]]]; NSMutableArray *arguments = [NSMutableArray array]; - for (NSUInteger index = 1; index < caseExpressions.count; index++) { - if ([caseExpressions[index] isKindOfClass:[NSArray class]]) { - NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:caseExpressions[index]]; + for (NSUInteger index = 0; index < argumentObjects.count; index++) { + if ([argumentObjects[index] isKindOfClass:[NSArray class]]) { + NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:argumentObjects[index]]; NSExpression *argument = [NSExpression expressionWithFormat:@"%@", conditional]; [arguments addObject:argument]; } else { - [arguments addObject:[NSExpression mgl_expressionWithJSONObject:caseExpressions[index]]]; + [arguments addObject:[NSExpression mgl_expressionWithJSONObject:argumentObjects[index]]]; } } - return [NSExpression expressionForFunction:firstConditional selectorName:@"mgl_case:" arguments:arguments]; + if (@available(iOS 9.0, *)) { + if (arguments.count == 3) { + NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:argumentObjects.firstObject]; + return [NSExpression expressionForConditional:conditional trueExpression:arguments[1] falseExpression:arguments[2]]; + } + } + return [NSExpression expressionForFunction:@"MGL_IF" arguments:arguments]; } else if ([op isEqualToString:@"match"]) { - NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects[0]]; - NSArray *matchOptions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSMutableArray *optionsArray = [NSMutableArray array]; - NSEnumerator *optionsEnumerator = matchOptions.objectEnumerator; + NSEnumerator *optionsEnumerator = argumentObjects.objectEnumerator; while (id object = optionsEnumerator.nextObject) { NSExpression *option = [NSExpression mgl_expressionWithJSONObject:object]; [optionsArray addObject:option]; } - return [NSExpression expressionForFunction:operand - selectorName:@"mgl_match:" + return [NSExpression expressionForFunction:@"MGL_MATCH" arguments:optionsArray]; } else if ([op isEqualToString:@"coalesce"]) { NSMutableArray *expressions = [NSMutableArray array]; @@ -533,10 +688,10 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { [expressions addObject:[NSExpression mgl_expressionWithJSONObject:operand]]; } - return [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", expressions]; - }else { - [NSException raise:NSInvalidArgumentException - format:@"Expression operator %@ not yet implemented.", op]; + return [NSExpression expressionWithFormat:@"mgl_coalesce(%@)", expressions]; + } else { + NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array); + return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions]; } } @@ -561,6 +716,10 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { @"raise:toPower:": @"^", @"uppercase:": @"upcase", @"lowercase:": @"downcase", + @"length:": @"length", + // Vararg aftermarket expressions need to be declared with an explicit and implicit first argument. + @"MGL_LET": @"let", + @"MGL_LET:": @"let", }; }); @@ -673,6 +832,9 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { } 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:@"mgl_join:"]) { + NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + return [@[@"concat"] arrayByAddingObjectsFromArray:arguments]; } else if ([function isEqualToString:@"stringByAppendingString:"]) { NSArray *arguments = self.arguments.mgl_jsonExpressionObject; return [@[@"concat", self.operand.mgl_jsonExpressionObject] arrayByAddingObjectsFromArray:arguments]; @@ -690,46 +852,15 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return @[@"to-string", self.operand.mgl_jsonExpressionObject]; } else if ([function isEqualToString:@"noindex:"]) { return self.arguments.firstObject.mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"mgl_has:"]) { - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"has", self.arguments[0].mgl_jsonExpressionObject, nil]; - if (self.operand.expressionType != NSEvaluatedObjectExpressionType) { - [expressionObject addObject:self.operand.mgl_jsonExpressionObject]; - } - return expressionObject; - } 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_does:have:"] || + [function isEqualToString:@"mgl_has:"]) { + return self.mgl_jsonHasExpressionObject; + } else if ([function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"] + || [function isEqualToString:@"mgl_interpolateWithCurveType:parameters:stops:"]) { + return self.mgl_jsonInterpolationExpressionObject; + } else if ([function isEqualToString:@"mgl_step:from:stops:"] + || [function isEqualToString:@"mgl_stepWithMinimum:stops:"]) { + return self.mgl_jsonStepExpressionObject; } else if ([function isEqualToString:@"mgl_expressionWithContext:"]) { id context = self.arguments.firstObject; if ([context isKindOfClass:[NSExpression class]]) { @@ -742,37 +873,28 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { }]; [expressionObject addObject:self.operand.mgl_jsonExpressionObject]; return expressionObject; - } else if ([function isEqualToString:@"mgl_case:"]) { - NSPredicate *firstConditional = (NSPredicate *)self.operand.constantValue; - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"case", firstConditional.mgl_jsonExpressionObject, nil]; - - for (NSExpression *option in self.arguments) { - if ([option respondsToSelector:@selector(constantValue)] && [option.constantValue isKindOfClass:[NSComparisonPredicate class]]) { - NSPredicate *predicate = (NSPredicate *)option.constantValue; - [expressionObject addObject:predicate.mgl_jsonExpressionObject]; - } else { - [expressionObject addObject:option.mgl_jsonExpressionObject]; - } - } - - return expressionObject; - } else if ([function isEqualToString:@"mgl_match:"]) { - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"match", self.operand.mgl_jsonExpressionObject, nil]; + } else if ([function isEqualToString:@"MGL_IF"] || + [function isEqualToString:@"mgl_if:"]) { + return self.mgl_jsonIfExpressionObject; + } else if ([function isEqualToString:@"MGL_MATCH"] || + [function isEqualToString:@"mgl_match:"]) { + return self.mgl_jsonMatchExpressionObject; + } else if ([function isEqualToString:@"mgl_coalesce:"] || + [function isEqualToString:@"mgl_coalesce"]) { - - for (NSExpression *option in self.arguments) { - [expressionObject addObject:option.mgl_jsonExpressionObject]; - } - - return expressionObject; - } else if ([function isEqualToString:@"mgl_coalesce"]) { - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"coalesce", nil]; - - for (NSExpression *expression in self.operand.constantValue) { - [expressionObject addObject:[expression mgl_jsonExpressionObject]]; + return self.mgl_jsonCoalesceExpressionObject; + } else if ([function isEqualToString:@"castObject:toType:"]) { + id object = self.arguments.firstObject.mgl_jsonExpressionObject; + NSString *type = self.arguments[1].mgl_jsonExpressionObject; + if ([type isEqualToString:@"NSString"]) { + return @[@"to-string", object]; + } else if ([type isEqualToString:@"NSNumber"]) { + return @[@"to-number", object]; } - - return expressionObject; + [NSException raise:NSInvalidArgumentException + format:@"Casting expression to %@ not yet implemented.", type]; + } else if ([function isEqualToString:@"MGL_FUNCTION"]) { + return self.arguments.mgl_jsonExpressionObject; } else if ([function isEqualToString:@"median:"] || [function isEqualToString:@"mode:"] || [function isEqualToString:@"stddev:"] || @@ -851,4 +973,121 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return nil; } +- (id)mgl_jsonInterpolationExpressionObject { + NSUInteger expectedArgumentCount = [self.function componentsSeparatedByString:@":"].count - 1; + if (self.arguments.count < expectedArgumentCount) { + [NSException raise:NSInvalidArgumentException format: + @"Too few arguments to ‘%@’ function; expected %lu arguments.", + self.function, expectedArgumentCount]; + } else if (self.arguments.count > expectedArgumentCount) { + [NSException raise:NSInvalidArgumentException format: + @"%lu unexpected arguments to ‘%@’ function; expected %lu arguments.", + self.arguments.count - expectedArgumentCount, self.function, expectedArgumentCount]; + } + + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"]; + NSUInteger curveTypeIndex = isAftermarketFunction ? 1 : 0; + NSString *curveType = self.arguments[curveTypeIndex].constantValue; + NSMutableArray *interpolationArray = [NSMutableArray arrayWithObject:curveType]; + if ([curveType isEqualToString:@"exponential"]) { + id base = [self.arguments[curveTypeIndex + 1] mgl_jsonExpressionObject]; + [interpolationArray addObject:base]; + } else if ([curveType isEqualToString:@"cubic-bezier"]) { + NSArray *controlPoints = [self.arguments[curveTypeIndex + 1].collection mgl_jsonExpressionObject]; + [interpolationArray addObjectsFromArray:controlPoints]; + } + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"interpolate", interpolationArray, nil]; + [expressionObject addObject:(isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject]; + NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[curveTypeIndex + 2].constantValue; + for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + [expressionObject addObject:key]; + [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; + } + return expressionObject; +} + +- (id)mgl_jsonStepExpressionObject { + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_step:from:stops:"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + id minimum = self.arguments[minimumIndex].mgl_jsonExpressionObject; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"step", (isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject, minimum, nil]; + NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[minimumIndex + 1].constantValue; + for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + [expressionObject addObject:key]; + [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; + } + return expressionObject; +} + +- (id)mgl_jsonMatchExpressionObject { + BOOL isAftermarketFunction = [self.function isEqualToString:@"MGL_MATCH"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"match", (isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject, nil]; + NSArray<NSExpression *> *arguments = isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; + + for (NSUInteger index = minimumIndex; index < arguments.count; index++) { + [expressionObject addObject:arguments[index].mgl_jsonExpressionObject]; + } + + return expressionObject; +} + +- (id)mgl_jsonIfExpressionObject { + BOOL isAftermarketFunction = [self.function isEqualToString:@"MGL_IF"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + NSExpression *firstCondition; + id condition; + + if (isAftermarketFunction) { + firstCondition = self.arguments.firstObject; + } else { + firstCondition = self.operand; + } + + if ([firstCondition respondsToSelector:@selector(constantValue)] && [firstCondition.constantValue isKindOfClass:[NSComparisonPredicate class]]) { + NSPredicate *predicate = (NSPredicate *)firstCondition.constantValue; + condition = predicate.mgl_jsonExpressionObject; + } else { + condition = firstCondition.mgl_jsonExpressionObject; + } + + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"case", condition, nil]; + NSArray<NSExpression *> *arguments = isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; + + for (NSUInteger index = minimumIndex; index < arguments.count; index++) { + if ([arguments[index] respondsToSelector:@selector(constantValue)] && [arguments[index].constantValue isKindOfClass:[NSComparisonPredicate class]]) { + NSPredicate *predicate = (NSPredicate *)arguments[index].constantValue; + [expressionObject addObject:predicate.mgl_jsonExpressionObject]; + } else { + [expressionObject addObject:arguments[index].mgl_jsonExpressionObject]; + } + } + + return expressionObject; +} + +- (id)mgl_jsonCoalesceExpressionObject { + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_coalesce:"]; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"coalesce", nil]; + + for (NSExpression *expression in (isAftermarketFunction ? self.arguments.firstObject : self.operand).constantValue) { + [expressionObject addObject:[expression mgl_jsonExpressionObject]]; + } + + return expressionObject; +} + +- (id)mgl_jsonHasExpressionObject { + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_does:have:"]; + NSExpression *operand = isAftermarketFunction ? self.arguments[0] : self.operand; + NSExpression *key = self.arguments[isAftermarketFunction ? 1 : 0]; + + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"has", key.mgl_jsonExpressionObject, nil]; + if (operand.expressionType != NSEvaluatedObjectExpressionType) { + [expressionObject addObject:operand.mgl_jsonExpressionObject]; + } + return expressionObject; +} + @end diff --git a/platform/darwin/src/NSExpression+MGLPrivateAdditions.h b/platform/darwin/src/NSExpression+MGLPrivateAdditions.h index f726dd3e71..d255cfdb0f 100644 --- a/platform/darwin/src/NSExpression+MGLPrivateAdditions.h +++ b/platform/darwin/src/NSExpression+MGLPrivateAdditions.h @@ -54,8 +54,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) id mgl_jsonExpressionObject; -@property (nonatomic, readonly) id mgl_coalesce; - @end @interface NSDictionary (MGLExpressionAdditions) diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.h b/platform/darwin/src/NSPredicate+MGLAdditions.h index a67c6ca005..a73b1a61ba 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.h +++ b/platform/darwin/src/NSPredicate+MGLAdditions.h @@ -16,7 +16,7 @@ @property (nonatomic, readonly) id mgl_jsonExpressionObject; -- (id)mgl_case:(id)firstValue, ...; +- (id)mgl_if:(id)firstValue, ...; - (id)mgl_match:(NSExpression *)firstCase, ...; diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 1a7bb30a92..8fe5735bf9 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -324,34 +324,34 @@ NSArray *MGLSubpredicatesWithJSONObjects(NSArray *objects) { return nil; } -- (id)mgl_case:(id)firstValue, ... { +- (id)mgl_if:(id)firstValue, ... { if ([self evaluateWithObject:nil]) { - return firstValue; - } - + return firstValue; + } + id eachExpression; va_list argumentList; va_start(argumentList, firstValue); - + while ((eachExpression = va_arg(argumentList, id))) { - if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { - id valueExpression = va_arg(argumentList, id); - if ([eachExpression evaluateWithObject:nil]) { - return valueExpression; - } - } else { - return eachExpression; + if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { + id valueExpression = va_arg(argumentList, id); + if ([eachExpression evaluateWithObject:nil]) { + return valueExpression; + } + } else { + return eachExpression; + } } - } va_end(argumentList); - + return nil; } - (id)mgl_match:(NSExpression *)firstCase, ... { [NSException raise:NSInvalidArgumentException - format:@"Match expressions lack underlying Objective-C implementations."]; + format:@"Match expressions lack underlying Objective-C implementations."]; return nil; } |