diff options
Diffstat (limited to 'platform/darwin/src/NSExpression+MGLAdditions.mm')
-rw-r--r-- | platform/darwin/src/NSExpression+MGLAdditions.mm | 1605 |
1 files changed, 0 insertions, 1605 deletions
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm deleted file mode 100644 index 80008aa69b..0000000000 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ /dev/null @@ -1,1605 +0,0 @@ -#import "MGLFoundation_Private.h" -#import "MGLGeometry_Private.h" -#import "NSExpression+MGLPrivateAdditions.h" - -#import "MGLTypes.h" -#if TARGET_OS_IPHONE - #import "UIColor+MGLAdditions.h" -#else - #import "NSColor+MGLAdditions.h" -#endif -#import "NSPredicate+MGLAdditions.h" -#import "NSValue+MGLStyleAttributeAdditions.h" -#import "MGLVectorTileSource_Private.h" -#import "MGLAttributedExpression.h" - -#import <objc/runtime.h> - -#import <mbgl/style/expression/expression.hpp> - -const MGLExpressionInterpolationMode MGLExpressionInterpolationModeLinear = @"linear"; -const MGLExpressionInterpolationMode MGLExpressionInterpolationModeExponential = @"exponential"; -const MGLExpressionInterpolationMode MGLExpressionInterpolationModeCubicBezier = @"cubic-bezier"; - -@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 diagnostic 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_round:); - INSTALL_METHOD(mgl_interpolate:withCurveType:parameters:stops:); - INSTALL_METHOD(mgl_step:from:stops:); - INSTALL_METHOD(mgl_coalesce:); - INSTALL_METHOD(mgl_does:have:); - INSTALL_METHOD(mgl_acos:); - INSTALL_METHOD(mgl_cos:); - INSTALL_METHOD(mgl_asin:); - INSTALL_METHOD(mgl_sin:); - INSTALL_METHOD(mgl_atan:); - INSTALL_METHOD(mgl_tan:); - INSTALL_METHOD(mgl_log2:); - INSTALL_METHOD(mgl_attributed:); - - // 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 diagnostic pop -} - -/** - Joins the given components into a single string by concatenating each component - in order. - */ -- (NSString *)mgl_join:(NSArray<NSString *> *)components { - return [components componentsJoinedByString:@""]; -} - -- (NSString *)mgl_attributed:(NSArray<MGLAttributedExpression *> *)attributedExpressions { - [NSException raise:NSInvalidArgumentException - format:@"Text format expressions lack underlying Objective-C implementations."]; - return nil; -} - -/** - Rounds the given number to the nearest integer. If the number is halfway - between two integers, this method rounds it away from zero. - */ -- (NSNumber *)mgl_round:(NSNumber *)number { - return @(round(number.doubleValue)); -} - -/** - Computes the principal value of the inverse cosine. - */ -- (NSNumber *)mgl_acos:(NSNumber *)number { - return @(acos(number.doubleValue)); -} - -/** - Computes the principal value of the cosine. - */ -- (NSNumber *)mgl_cos:(NSNumber *)number { - return @(cos(number.doubleValue)); -} - -/** - Computes the principal value of the inverse sine. - */ -- (NSNumber *)mgl_asin:(NSNumber *)number { - return @(asin(number.doubleValue)); -} - -/** - Computes the principal value of the sine. - */ -- (NSNumber *)mgl_sin:(NSNumber *)number { - return @(sin(number.doubleValue)); -} - -/** - Computes the principal value of the inverse tangent. - */ -- (NSNumber *)mgl_atan:(NSNumber *)number { - return @(atan(number.doubleValue)); -} - -/** - Computes the principal value of the tangent. - */ -- (NSNumber *)mgl_tan:(NSNumber *)number { - return @(tan(number.doubleValue)); -} - -/** - Computes the logarithm base two of the value. - */ -- (NSNumber *)mgl_log2:(NSNumber *)number { - return @(log2(number.doubleValue)); -} - -/** - 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 { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector<mbgl::Value> convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_constantMBGLValue); - } - return convertedValues; - } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; -} - -- (mbgl::Value)mgl_constantMBGLValue { - id value = self.constantValue; - if ([value isKindOfClass:NSString.class]) { - return { std::string([(NSString *)value UTF8String]) }; - } else if ([value isKindOfClass:NSNumber.class]) { - NSNumber *number = (NSNumber *)value; - if ((strcmp([number objCType], @encode(char)) == 0) || - (strcmp([number objCType], @encode(BOOL)) == 0)) { - // char: 32-bit boolean - // BOOL: 64-bit boolean - return { (bool)number.boolValue }; - } else if (strcmp([number objCType], @encode(double)) == 0) { - // Double values on all platforms are interpreted precisely. - return { (double)number.doubleValue }; - } else if (strcmp([number objCType], @encode(float)) == 0) { - // Float values when taken as double introduce precision problems, - // so warn the user to avoid them. This would require them to - // explicitly use -[NSNumber numberWithFloat:] arguments anyway. - // We still do this conversion in order to provide a valid value. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"Float value in expression will be converted to a double; some imprecision may result. " - @"Use double values explicitly when specifying constant expression values and " - @"when specifying arguments to predicate and expression format strings. " - @"This will be logged only once."); - }); - return { (double)number.doubleValue }; - } else if ([number compare:@(0)] == NSOrderedDescending || - [number compare:@(0)] == NSOrderedSame) { - // Positive integer or zero; use uint64_t per mbgl::Value definition. - // We use unsigned long long here to avoid any truncation. - return { (uint64_t)number.unsignedLongLongValue }; - } else if ([number compare:@(0)] == NSOrderedAscending) { - // Negative integer; use int64_t per mbgl::Value definition. - // We use long long here to avoid any truncation. - return { (int64_t)number.longLongValue }; - } - } else if ([value isKindOfClass:[MGLColor class]]) { - auto hexString = [(MGLColor *)value mgl_color].stringify(); - return { hexString }; - } else if (value && value != [NSNull null]) { - [NSException raise:NSInvalidArgumentException - format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value]; - } - return {}; -} - -- (std::vector<mbgl::FeatureType>)mgl_aggregateFeatureType { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector<mbgl::FeatureType> convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_featureType); - } - return convertedValues; - } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; -} - -- (mbgl::FeatureType)mgl_featureType { - id value = self.constantValue; - if ([value isKindOfClass:NSString.class]) { - if ([value isEqualToString:@"Point"]) { - return mbgl::FeatureType::Point; - } - if ([value isEqualToString:@"LineString"]) { - return mbgl::FeatureType::LineString; - } - if ([value isEqualToString:@"Polygon"]) { - return mbgl::FeatureType::Polygon; - } - } else if ([value isKindOfClass:NSNumber.class]) { - switch ([value integerValue]) { - case 1: - return mbgl::FeatureType::Point; - case 2: - return mbgl::FeatureType::LineString; - case 3: - return mbgl::FeatureType::Polygon; - default: - break; - } - } - return mbgl::FeatureType::Unknown; -} - -- (std::vector<mbgl::FeatureIdentifier>)mgl_aggregateFeatureIdentifier { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector<mbgl::FeatureIdentifier> convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_featureIdentifier); - } - return convertedValues; - } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; -} - -- (mbgl::FeatureIdentifier)mgl_featureIdentifier { - mbgl::Value mbglValue = self.mgl_constantMBGLValue; - - if (mbglValue.is<std::string>()) { - return mbglValue.get<std::string>(); - } - if (mbglValue.is<double>()) { - return mbglValue.get<double>(); - } - if (mbglValue.is<uint64_t>()) { - return mbglValue.get<uint64_t>(); - } - if (mbglValue.is<int64_t>()) { - return mbglValue.get<int64_t>(); - } - - return {}; -} - -@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"]; -} - -- (id)mgl_coalesce { - [NSException raise:NSInvalidArgumentException - format:@"Coalesce expressions lack underlying Objective-C implementations."]; - return nil; -} - -@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; -} - -- (id)mgl_has:(id)element { - [NSException raise:NSInvalidArgumentException - format:@"Has expressions lack underlying Objective-C implementations."]; - return nil; - -} - -@end - -@implementation NSExpression (MGLExpressionAdditions) - -- (NSExpression *)mgl_expressionWithContext:(NSDictionary<NSString *, NSExpression *> *)context { - [NSException raise:NSInternalInconsistencyException - format:@"Assignment expressions lack underlying Objective-C implementations."]; - return self; -} - -- (id)mgl_has:(id)element { - [NSException raise:NSInvalidArgumentException - format:@"Has expressions lack underlying Objective-C implementations."]; - return nil; -} - -@end - -@implementation NSExpression (MGLAdditions) - -+ (NSExpression *)zoomLevelVariableExpression { - return [NSExpression expressionForVariable:@"zoomLevel"]; -} - -+ (NSExpression *)heatmapDensityVariableExpression { - return [NSExpression expressionForVariable:@"heatmapDensity"]; -} - -+ (NSExpression *)lineProgressVariableExpression { - return [NSExpression expressionForVariable:@"lineProgress"]; -} - -+ (NSExpression *)featureAccumulatedVariableExpression { - return [NSExpression expressionForVariable:@"featureAccumulated"]; -} - -+ (NSExpression *)geometryTypeVariableExpression { - return [NSExpression expressionForVariable:@"geometryType"]; -} - -+ (NSExpression *)featureIdentifierVariableExpression { - return [NSExpression expressionForVariable:@"featureIdentifier"]; -} - -+ (NSExpression *)featureAttributesVariableExpression { - return [NSExpression expressionForVariable:@"featureAttributes"]; -} - -+ (NSExpression *)featurePropertiesVariableExpression { - return [self featureAttributesVariableExpression]; -} - -+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression { - return [NSExpression expressionForConditional:conditionPredicate trueExpression:trueExpression falseExpression:falseExpression]; -} - -+ (instancetype)mgl_expressionForSteppingExpression:(nonnull NSExpression *)steppingExpression fromExpression:(nonnull NSExpression *)minimumExpression stops:(nonnull NSExpression *)stops { - return [NSExpression expressionForFunction:@"mgl_step:from:stops:" - arguments:@[steppingExpression, minimumExpression, stops]]; -} - -+ (instancetype)mgl_expressionForInterpolatingExpression:(nonnull NSExpression *)inputExpression withCurveType:(nonnull MGLExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters stops:(nonnull NSExpression *)stops { - NSExpression *sanitizeParams = parameters ? parameters : [NSExpression expressionForConstantValue:nil]; - return [NSExpression expressionForFunction:@"mgl_interpolate:withCurveType:parameters:stops:" - arguments:@[inputExpression, [NSExpression expressionForConstantValue:curveType], sanitizeParams, stops]]; -} - -+ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)inputExpression inDictionary:(nonnull NSDictionary<NSExpression *, NSExpression *> *)matchedExpressions defaultExpression:(nonnull NSExpression *)defaultExpression { - NSMutableArray *optionsArray = [NSMutableArray arrayWithObjects:inputExpression, nil]; - - NSEnumerator *matchEnumerator = matchedExpressions.keyEnumerator; - while (NSExpression *key = matchEnumerator.nextObject) { - [optionsArray addObject:key]; - [optionsArray addObject:[matchedExpressions objectForKey:key]]; - } - - [optionsArray addObject:defaultExpression]; - return [NSExpression expressionForFunction:@"MGL_MATCH" - arguments:optionsArray]; -} - -+ (instancetype)mgl_expressionForAttributedExpressions:(nonnull NSArray<NSExpression *> *)attributedExpressions { - return [NSExpression expressionForFunction:@"mgl_attributed:" arguments:attributedExpressions]; -} - -- (instancetype)mgl_expressionByAppendingExpression:(nonnull NSExpression *)expression { - NSExpression *subexpression = [NSExpression expressionForAggregate:@[self, expression]]; - return [NSExpression expressionForFunction:@"mgl_join:" arguments:@[subexpression]]; -} - -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 expressionWithMGLJSONObject:object]; - [subexpressions addObject:expression]; - } - return subexpressions; -} - -+ (instancetype)expressionWithMGLJSONObject:(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:", - @"abs": @"abs:", - @"round": @"mgl_round:", - @"acos" : @"mgl_acos:", - @"cos" : @"mgl_cos:", - @"asin" : @"mgl_asin:", - @"sin" : @"mgl_sin:", - @"atan" : @"mgl_atan:", - @"tan" : @"mgl_tan:", - @"log2" : @"mgl_log2:", - @"floor": @"floor:", - @"ceil": @"ceiling:", - @"^": @"raise:toPower:", - @"upcase": @"uppercase:", - @"downcase": @"lowercase:", - @"let": @"MGL_LET", - }; - }); - 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 expressionWithMGLJSONObject:obj]; - }]; - return [NSExpression expressionForConstantValue:dictionary]; - } - if ([object isKindOfClass:[NSArray class]]) { - NSArray *array = (NSArray *)object; - NSString *op = array.firstObject; - - if (![op isKindOfClass:[NSString class]]) { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions]; - } - - 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:@"collator"]) { - // Avoid wrapping collator options object in literal expression. - return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:array]; - } else if ([op isEqualToString:@"literal"]) { - if ([argumentObjects.firstObject isKindOfClass:[NSArray class]]) { - return [NSExpression expressionForAggregate:MGLSubexpressionsWithJSONObjects(argumentObjects.firstObject)]; - } - return [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"to-boolean"]) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionForFunction:operand selectorName:@"boolValue" arguments:@[]]; - } else if ([op isEqualToString:@"to-number"] || [op isEqualToString:@"number"]) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject: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"] || [op isEqualToString:@"string"]) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"CAST(%@, 'NSString')", operand]; - } else if ([op isEqualToString:@"to-color"]) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - - if (argumentObjects.count == 1) { -#if TARGET_OS_IPHONE - return [NSExpression expressionWithFormat:@"CAST(%@, 'UIColor')", operand]; -#else - return [NSExpression expressionWithFormat:@"CAST(%@, 'NSColor')", operand]; -#endif - } - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions]; - - } else if ([op isEqualToString:@"to-rgba"]) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"CAST(noindex(%@), 'NSArray')", operand]; - } else if ([op isEqualToString:@"get"]) { - if (argumentObjects.count == 2) { - NSExpression *operand = [NSExpression expressionWithMGLJSONObject:argumentObjects.lastObject]; - if ([argumentObjects.firstObject isKindOfClass:[NSString class]]) { - return [NSExpression expressionWithFormat:@"%@.%K", operand, argumentObjects.firstObject]; - } - NSExpression *key = [NSExpression expressionWithMGLJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"%@.%@", operand, key]; - } - return [NSExpression expressionForKeyPath:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"length"]) { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - 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:@"rgb"]) { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression mgl_expressionForRGBComponents:subexpressions]; - } else if ([op isEqualToString:@"rgba"]) { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression mgl_expressionForRGBAComponents:subexpressions]; - } 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 *subexpression = [NSExpression expressionForAggregate:subexpressions]; - return [NSExpression expressionForFunction:@"mgl_join:" arguments:@[subexpression]]; - } else if ([op isEqualToString:@"at"]) { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *index = subexpressions.firstObject; - NSExpression *operand = subexpressions[1]; - 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 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; - NSExpression *curveTypeExpression = [NSExpression expressionWithMGLJSONObject:curveType]; - id curveParameters; - if ([curveType isEqual:@"exponential"]) { - curveParameters = interpolationOptions[1]; - } else if ([curveType isEqualToString:@"cubic-bezier"]) { - curveParameters = @[@"literal", [interpolationOptions subarrayWithRange:NSMakeRange(1, 4)]]; - } - else { - curveParameters = [NSNull null]; - } - NSExpression *curveParameterExpression = [NSExpression expressionWithMGLJSONObject:curveParameters]; - argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSExpression *inputExpression = [NSExpression expressionWithMGLJSONObject: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 expressionWithMGLJSONObject:valueExpression]; - } - NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression expressionForFunction:@"mgl_interpolate:withCurveType:parameters:stops:" - arguments:@[inputExpression, curveTypeExpression, curveParameterExpression, stopExpression]]; - } else if ([op isEqualToString:@"step"]) { - NSExpression *inputExpression = [NSExpression expressionWithMGLJSONObject:argumentObjects[0]]; - NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSExpression *minimum; - if (stopExpressions.count % 2) { - minimum = [NSExpression expressionWithMGLJSONObject: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 expressionWithMGLJSONObject:valueExpression]; - } else { - minimum = [NSExpression expressionWithMGLJSONObject:valueExpression]; - } - } - - NSAssert(minimum, @"minimum should be non-nil"); - if (minimum) { - NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression expressionForFunction:@"mgl_step:from:stops:" - arguments:@[inputExpression, minimum, stopExpression]]; - } - - } else if ([op isEqualToString:@"zoom"]) { - return NSExpression.zoomLevelVariableExpression; - } else if ([op isEqualToString:@"heatmap-density"]) { - return NSExpression.heatmapDensityVariableExpression; - } else if ([op isEqualToString:@"line-progress"]) { - return NSExpression.lineProgressVariableExpression; - } else if ([op isEqualToString:@"accumulated"]) { - return NSExpression.featureAccumulatedVariableExpression; - } else if ([op isEqualToString:@"geometry-type"]) { - return NSExpression.geometryTypeVariableExpression; - } else if ([op isEqualToString:@"id"]) { - return NSExpression.featureIdentifierVariableExpression; - } else if ([op isEqualToString:@"properties"]) { - return NSExpression.featureAttributesVariableExpression; - } else if ([op isEqualToString:@"var"]) { - return [NSExpression expressionForVariable:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"case"]) { - NSMutableArray *arguments = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index++) { - if (index % 2 == 0 && index != argumentObjects.count - 1) { - NSPredicate *predicate = [NSPredicate predicateWithMGLJSONObject:argumentObjects[index]]; - NSExpression *argument = [NSExpression expressionForConstantValue:predicate]; - [arguments addObject:argument]; - } else { - [arguments addObject:[NSExpression expressionWithMGLJSONObject:argumentObjects[index]]]; - } - } - - if (arguments.count == 3) { - NSPredicate *conditional = [arguments.firstObject constantValue]; - return [NSExpression expressionForConditional:conditional trueExpression:arguments[1] falseExpression:arguments[2]]; - } - return [NSExpression expressionForFunction:@"MGL_IF" arguments:arguments]; - } else if ([op isEqualToString:@"match"]) { - NSMutableArray *optionsArray = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index++) { - NSExpression *option = [NSExpression expressionWithMGLJSONObject:argumentObjects[index]]; - // match operators with arrays as matching values should not parse arrays as generic functions. - if (index > 0 && index < argumentObjects.count - 1 && !(index % 2 == 0) && [argumentObjects[index] isKindOfClass:[NSArray class]]) { - option = [NSExpression expressionForAggregate:MGLSubexpressionsWithJSONObjects(argumentObjects[index])]; - } - [optionsArray addObject:option]; - } - - return [NSExpression expressionForFunction:@"MGL_MATCH" - arguments:optionsArray]; - } else if ([op isEqualToString:@"format"]) { - NSMutableArray *attributedExpressions = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index+=2) { - NSExpression *expression = [NSExpression expressionWithMGLJSONObject:argumentObjects[index]]; - NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; - if ((index + 1) < argumentObjects.count) { - attrs = [NSMutableDictionary dictionaryWithDictionary:argumentObjects[index + 1]]; - } - - for (NSString *key in attrs.allKeys) { - attrs[key] = [NSExpression expressionWithMGLJSONObject:attrs[key]]; - } - MGLAttributedExpression *attributedExpression = [[MGLAttributedExpression alloc] initWithExpression:expression attributes:attrs]; - - [attributedExpressions addObject:[NSExpression expressionForConstantValue:attributedExpression]]; - } - return [NSExpression expressionForFunction:@"mgl_attributed:" arguments:attributedExpressions]; - - } else if ([op isEqualToString:@"coalesce"]) { - NSMutableArray *expressions = [NSMutableArray array]; - for (id operand in argumentObjects) { - [expressions addObject:[NSExpression expressionWithMGLJSONObject:operand]]; - } - - return [NSExpression expressionWithFormat:@"mgl_coalesce(%@)", expressions]; - } else { - NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions]; - } - } - - [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:": @"^", - @"ceiling:": @"ceil", - @"abs:": @"abs", - @"floor:": @"floor", - @"uppercase:": @"upcase", - @"lowercase:": @"downcase", - @"length:": @"length", - @"mgl_round:": @"round", - @"mgl_acos:" : @"acos", - @"mgl_cos:" : @"cos", - @"mgl_asin:" : @"asin", - @"mgl_sin:" : @"sin", - @"mgl_atan:" : @"atan", - @"mgl_tan:" : @"tan", - @"mgl_log2:" : @"log2", - // Vararg aftermarket expressions need to be declared with an explicit and implicit first argument. - @"MGL_LET": @"let", - @"MGL_LET:": @"let", - }; - }); - - switch (self.expressionType) { - case NSVariableExpressionType: { - if ([self.variable isEqualToString:@"heatmapDensity"]) { - return @[@"heatmap-density"]; - } - if ([self.variable isEqualToString:@"lineProgress"]) { - return @[@"line-progress"]; - } - if ([self.variable isEqualToString:@"zoomLevel"]) { - return @[@"zoom"]; - } - if ([self.variable isEqualToString:@"featureAccumulated"]) { - return @[@"accumulated"]; - } - if ([self.variable isEqualToString:@"geometryType"]) { - return @[@"geometry-type"]; - } - if ([self.variable isEqualToString:@"featureIdentifier"]) { - return @[@"id"]; - } - if ([self.variable isEqualToString:@"featureAttributes"]) { - return @[@"properties"]; - } - 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])]]; - } - } - if ([constantValue isKindOfClass:[MGLAttributedExpression class]]) { - MGLAttributedExpression *attributedExpression = (MGLAttributedExpression *)constantValue; - id jsonObject = attributedExpression.expression.mgl_jsonExpressionObject; - NSMutableDictionary<MGLAttributedExpressionKey, NSExpression *> *attributedDictionary = [NSMutableDictionary dictionary]; - - if (attributedExpression.attributes) { - attributedDictionary = [NSMutableDictionary dictionaryWithDictionary:attributedExpression.attributes]; - - for (NSString *key in attributedExpression.attributes.allKeys) { - attributedDictionary[key] = attributedExpression.attributes[key].mgl_jsonExpressionObject; - } - - } - return @[jsonObject, attributedDictionary]; - } - return self.constantValue; - } - - case NSKeyPathExpressionType: { - NSArray *expressionObject; - NSArray *keyPath = [self.keyPath componentsSeparatedByString:@"."]; - for (NSString *pathComponent in keyPath) { - if (expressionObject) { - expressionObject = @[@"get", pathComponent, expressionObject]; - } else { - expressionObject = @[@"get", pathComponent]; - } - } - - NSAssert(expressionObject.count > 0, @"expressionObject should be non-empty"); - - // Return a non-null value to quieten static analysis - return expressionObject ?: @[]; - } - - case NSFunctionExpressionType: { - NSString *function = self.function; - - BOOL hasCollectionProperty = !( ! [self.arguments.firstObject isKindOfClass: [NSExpression class]] || self.arguments.firstObject.expressionType != NSAggregateExpressionType || self.arguments.firstObject.expressionType == NSSubqueryExpressionType); - NSString *op = MGLExpressionOperatorsByFunctionNames[function]; - if (op) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[op] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"valueForKey:"] || [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; - if (hasCollectionProperty) { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments 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; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } - return [@[@"min"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"max:"]) { - NSArray *arguments; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - 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:@"trunc:"]) { - return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)", - self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"mgl_join:"]) { - NSArray *arguments; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - 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]; - } else if ([function isEqualToString:@"objectFrom:withIndex:"]) { - id index = self.arguments[1].mgl_jsonExpressionObject; - - if ([self.arguments[1] expressionType] == NSConstantValueExpressionType - && [[self.arguments[1] constantValue] isKindOfClass:[NSString class]]) { - id value = self.arguments[1].constantValue; - - if ([value isEqualToString:@"FIRST"]) { - index = [NSExpression expressionForConstantValue:@0].mgl_jsonExpressionObject; - } else if ([value isEqualToString:@"LAST"]) { - index = [NSExpression expressionWithFormat:@"count(%@) - 1", self.arguments[0]].mgl_jsonExpressionObject; - } else if ([value isEqualToString:@"SIZE"]) { - return [NSExpression expressionWithFormat:@"count(%@)", self.arguments[0]].mgl_jsonExpressionObject; - } - } - - return @[@"at", index, self.arguments[0].mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"boolValue"]) { - return @[@"to-boolean", self.operand.mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"mgl_number"] || - [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_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]]) { - 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:@"MGL_IF"] || - [function isEqualToString:@"MGL_IF:"] || - [function isEqualToString:@"mgl_if:"]) { - return self.mgl_jsonIfExpressionObject; - } else if ([function isEqualToString:@"MGL_MATCH"] || - [function isEqualToString:@"MGL_MATCH:"] || - [function isEqualToString:@"mgl_match:"]) { - return self.mgl_jsonMatchExpressionObject; - } else if ([function isEqualToString:@"mgl_coalesce:"] || - [function isEqualToString:@"mgl_coalesce"]) { - - 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]; - } -#if TARGET_OS_IPHONE - else if ([type isEqualToString:@"UIColor"] || [type isEqualToString:@"MGLColor"]) { - return @[@"to-color", object]; - } -#else - else if ([type isEqualToString:@"NSColor"] || [type isEqualToString:@"MGLColor"]) { - return @[@"to-color", object]; - } -#endif - else if ([type isEqualToString:@"NSArray"]) { - NSExpression *operand = self.arguments.firstObject; - if ([operand expressionType] == NSFunctionExpressionType ) { - operand = self.arguments.firstObject.arguments.firstObject; - } - if (([operand expressionType] != NSConstantValueExpressionType) || - ([operand expressionType] == NSConstantValueExpressionType && - [[operand constantValue] isKindOfClass:[MGLColor class]])) { - return @[@"to-rgba", object]; - } - } - [NSException raise:NSInvalidArgumentException - format:@"Casting expression to %@ not yet implemented.", type]; - } else if ([function isEqualToString:@"mgl_attributed:"]) { - return [self mgl_jsonFormatExpressionObject]; - - } else if ([function isEqualToString:@"MGL_FUNCTION"] || - [function isEqualToString:@"MGL_FUNCTION:"]) { - NSExpression *firstOp = self.arguments.firstObject; - if (firstOp.expressionType == NSConstantValueExpressionType - && [firstOp.constantValue isEqualToString:@"collator"]) { - // Avoid wrapping collator options object in literal expression. - return @[@"collator", self.arguments[1].constantValue]; - } - if (firstOp.expressionType == NSConstantValueExpressionType - && [firstOp.constantValue isEqualToString:@"format"]) { - // Avoid wrapping format options object in literal expression. - NSMutableArray *expressionObject = [NSMutableArray array]; - [expressionObject addObject:@"format"]; - - for (NSUInteger index = 1; index < self.arguments.count; index++) { - if (index % 2 == 1) { - [expressionObject addObject:self.arguments[index].mgl_jsonExpressionObject]; - } else { - [expressionObject addObject:self.arguments[index].constantValue]; - } - - } - - return expressionObject; - } - - return self.arguments.mgl_jsonExpressionObject; - } else if (op == [MGLColor class] && [function isEqualToString:@"colorWithRed:green:blue:alpha:"]) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[@"rgba"] arrayByAddingObjectsFromArray:arguments]; - } 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:@"case", self.predicate.mgl_jsonExpressionObject, nil]; - [arguments addObject:self.trueExpression.mgl_jsonExpressionObject]; - [arguments addObject:self.falseExpression.mgl_jsonExpressionObject]; - - 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.", (unsigned long)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; -} - -- (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, (unsigned long)expectedArgumentCount]; - } else if (self.arguments.count > expectedArgumentCount) { - [NSException raise:NSInvalidArgumentException format: - @"%lu unexpected arguments to ‘%@’ function; expected %lu arguments.", - self.arguments.count - (unsigned long)expectedArgumentCount, self.function, (unsigned long)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]; - } - - NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[curveTypeIndex + 2].constantValue; - - if (stops.count == 0) { - [NSException raise:NSInvalidArgumentException format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; - } - - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"interpolate", interpolationArray, nil]; - [expressionObject addObject:(isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject]; - 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; - NSDictionary<NSNumber *, NSExpression *> *stops = self.arguments[minimumIndex + 1].constantValue; - - if (stops.count == 0) { - [NSException raise:NSInvalidArgumentException format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; - } - - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"step", (isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject, minimum, nil]; - - 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 hasPrefix:@"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++) { - NSArray *argumentObject = arguments[index].mgl_jsonExpressionObject; - // match operators with arrays as matching values should not parse arrays using the literal operator. - if (index > 0 && index < arguments.count - 1 && !(index % 2 == 0)) { - NSExpression *expression = arguments[index]; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - if (expression.expressionType == NSAggregateExpressionType || - (expression.expressionType == NSConstantValueExpressionType && [expression.constantValue isKindOfClass:[NSArray class]])) { - argumentObject = argumentObject.count == 2 ? argumentObject[1] : argumentObject; - } - } - [expressionObject addObject:argumentObject]; - } - - return expressionObject; -} - -- (id)mgl_jsonIfExpressionObject { - BOOL isAftermarketFunction = [self.function hasPrefix:@"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; -} - -- (id)mgl_jsonFormatExpressionObject { - NSArray<NSExpression *> *attributedExpressions; - NSExpression *formatArray = self.arguments.firstObject; - - if ([formatArray respondsToSelector:@selector(constantValue)] && [formatArray.constantValue isKindOfClass:[NSArray class]]) { - attributedExpressions = (NSArray *)formatArray.constantValue; - } else { - attributedExpressions = self.arguments; - } - - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"format", nil]; - - for (NSUInteger index = 0; index < attributedExpressions.count; index++) { - [expressionObject addObjectsFromArray:attributedExpressions[index].mgl_jsonExpressionObject]; - } - - return expressionObject; -} - -#pragma mark Localization - -/** - Returns a localized copy of the given collection. - - If no localization takes place, this method returns the original collection. - */ -NSArray<NSExpression *> *MGLLocalizedCollection(NSArray<NSExpression *> *collection, NSLocale * _Nullable locale) { - __block NSMutableArray *localizedCollection; - [collection enumerateObjectsUsingBlock:^(NSExpression * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) { - NSExpression *localizedItem = [item mgl_expressionLocalizedIntoLocale:locale]; - if (localizedItem != item) { - if (!localizedCollection) { - localizedCollection = [collection mutableCopy]; - } - localizedCollection[idx] = localizedItem; - } - }]; - return localizedCollection ?: collection; -}; - -/** - Returns a localized copy of the given stop dictionary. - - If no localization takes place, this method returns the original stop - dictionary. - */ -NSDictionary<NSNumber *, NSExpression *> *MGLLocalizedStopDictionary(NSDictionary<NSNumber *, NSExpression *> *stops, NSLocale * _Nullable locale) { - __block NSMutableDictionary *localizedStops; - [stops enumerateKeysAndObjectsUsingBlock:^(id _Nonnull zoomLevel, NSExpression * _Nonnull value, BOOL * _Nonnull stop) { - if (![value isKindOfClass:[NSExpression class]]) { - value = [NSExpression expressionForConstantValue:value]; - } - NSExpression *localizedValue = [value mgl_expressionLocalizedIntoLocale:locale]; - if (localizedValue != value) { - if (!localizedStops) { - localizedStops = [stops mutableCopy]; - } - localizedStops[zoomLevel] = localizedValue; - } - }]; - return localizedStops ?: stops; -}; - -- (NSExpression *)mgl_expressionLocalizedIntoLocale:(nullable NSLocale *)locale { - switch (self.expressionType) { - case NSConstantValueExpressionType: { - if ([self.constantValue isKindOfClass:[NSDictionary class]]) { - NSDictionary *localizedStops = MGLLocalizedStopDictionary(self.constantValue, locale); - if (localizedStops != self.constantValue) { - return [NSExpression expressionForConstantValue:localizedStops]; - } - } else if ([self.constantValue isKindOfClass:[NSArray class]]) { - NSArray *localizedValues = MGLLocalizedCollection(self.constantValue, locale); - if (localizedValues != self.constantValue) { - return [NSExpression expressionForConstantValue:localizedValues]; - } - } else if ([self.constantValue isKindOfClass:[MGLAttributedExpression class]]) { - MGLAttributedExpression *attributedExpression = (MGLAttributedExpression *)self.constantValue; - NSExpression *localizedExpression = [attributedExpression.expression mgl_expressionLocalizedIntoLocale:locale]; - MGLAttributedExpression *localizedAttributedExpression = [MGLAttributedExpression attributedExpression:localizedExpression attributes:attributedExpression.attributes]; - - return [NSExpression expressionForConstantValue:localizedAttributedExpression]; - } - return self; - } - - case NSKeyPathExpressionType: { - if ([self.keyPath isEqualToString:@"name"] || [self.keyPath hasPrefix:@"name_"]) { - NSString *localizedKeyPath = @"name"; - if (![locale.localeIdentifier isEqualToString:@"mul"]) { - NSArray *preferences = locale ? @[locale.localeIdentifier] : [NSLocale preferredLanguages]; - NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]; - if (preferredLanguage) { - localizedKeyPath = [NSString stringWithFormat:@"name_%@", preferredLanguage]; - } - } - // If the keypath is `name`, no need to fallback - if ([localizedKeyPath isEqualToString:@"name"]) { - return [NSExpression expressionForKeyPath:localizedKeyPath]; - } - // If the keypath is `name_zh-Hans`, fallback to `name_zh` to `name`. - // CN tiles might using `name_zh-CN` for Simplified Chinese. - if ([localizedKeyPath isEqualToString:@"name_zh-Hans"]) { - return [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K, %K, %K})", - localizedKeyPath, @"name_zh-CN", @"name_zh", @"name"]; - } - // Mapbox Streets v8 has `name_zh-Hant`, we should fallback to Simplified Chinese if the field has no value. - if ([localizedKeyPath isEqualToString:@"name_zh-Hant"]) { - return [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K, %K, %K, %K})", - localizedKeyPath, @"name_zh-Hans", @"name_zh-CN", @"name_zh", @"name"]; - } - - // Other keypath fallback to `name` - return [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", localizedKeyPath, @"name"]; - } - return self; - } - - case NSFunctionExpressionType: { - NSExpression *operand = self.operand; - NSExpression *localizedOperand = [operand mgl_expressionLocalizedIntoLocale:locale]; - - NSArray *arguments = self.arguments; - NSArray *localizedArguments = MGLLocalizedCollection(arguments, locale); - if (localizedArguments != arguments) { - return [NSExpression expressionForFunction:localizedOperand - selectorName:self.function - arguments:localizedArguments]; - } - if (localizedOperand != operand) { - return [NSExpression expressionForFunction:localizedOperand - selectorName:self.function - arguments:self.arguments]; - } - return self; - } - - case NSConditionalExpressionType: { - NSExpression *trueExpression = self.trueExpression; - NSExpression *localizedTrueExpression = [trueExpression mgl_expressionLocalizedIntoLocale:locale]; - NSExpression *falseExpression = self.falseExpression; - NSExpression *localizedFalseExpression = [falseExpression mgl_expressionLocalizedIntoLocale:locale]; - if (localizedTrueExpression != trueExpression || localizedFalseExpression != falseExpression) { - return [NSExpression expressionForConditional:self.predicate - trueExpression:localizedTrueExpression - falseExpression:localizedFalseExpression]; - } - return self; - } - - case NSAggregateExpressionType: { - NSArray *collection = self.collection; - if ([collection isKindOfClass:[NSArray class]]) { - NSArray *localizedCollection = MGLLocalizedCollection(collection, locale); - if (localizedCollection != collection) { - return [NSExpression expressionForAggregate:localizedCollection]; - } - } - return self; - } - - default: - return self; - } -} - -@end |