summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2018-03-26 14:27:14 -0700
committerFabian Guerra <fabian.guerra@mapbox.com>2018-03-28 19:38:25 -0400
commite467df8b24cebaab02d462347609a6402f7ba116 (patch)
tree55e7558bb10e006a38fd25f29065bb0dbbd0e0df
parent1dcc93d9213a7aee2f049ec3cb52b04d7a244373 (diff)
downloadqtlocation-mapboxgl-e467df8b24cebaab02d462347609a6402f7ba116.tar.gz
[ios, macos] Refactored aftermarket expression functions
Refactored the installation of aftermarket expression functions to use macros. It is no longer necessary to handwrite the type encoding of a function. Also added aftermarket functions for interpolating and stepping.
-rw-r--r--platform/darwin/docs/guides/Predicates and Expressions.md38
-rw-r--r--platform/darwin/src/NSExpression+MGLAdditions.mm194
-rw-r--r--platform/darwin/test/MGLExpressionTests.mm12
3 files changed, 168 insertions, 76 deletions
diff --git a/platform/darwin/docs/guides/Predicates and Expressions.md b/platform/darwin/docs/guides/Predicates and Expressions.md
index 5e6af06dc4..fd0f82c0eb 100644
--- a/platform/darwin/docs/guides/Predicates and Expressions.md
+++ b/platform/darwin/docs/guides/Predicates and Expressions.md
@@ -190,10 +190,12 @@ functions just like the predefined functions above, using either the
`+[NSExpression expressionForFunction:arguments:]` method or a convenient format
string syntax:
-Initializer parameter | Format string syntax | Arguments | Returns
-----------------------|----------------------|-----------|--------
-`mgl_join:` | `mgl_join({'Old', 'MacDonald'})` | An aggregate expression or `NSArray` constant value expression containing one or more `NSExpression`s, each evaluating to a string. | An `NSString` object (the result of concatenating together all the elements of an array in order).
-`MGL_LET` | `MGL_LET('age', uppercase('old'), 'name', uppercase('MacDonald'), mgl_join({$age, $name}))` | Any number of variable names interspersed with their assigned `NSExpression` values, followed by an `NSExpression` that may contain references to those variables.
+Initializer parameter | Format string syntax | Description
+----------------------|----------------------|------------
+`mgl_interpolate:withCurveType:parameters:stops:` | `mgl_interpolate:withCurveType:parameters:stops:($zoom, 'linear', x, nil, %@)` | Produces continuous, smooth results by interpolating between pairs of input and output values (“stops”). Compared to the `mgl_interpolateWithCurveType:parameters:stops:` custom function, the input expression (that function’s target) is instead passed in as the first argument to this function.
+`mgl_step:from:stops:` | `mgl_step:from:stops:(x, 11, %@)` |Produces discrete, stepped results by evaluating a piecewise-constant function defined by pairs of input and output values ("stops"). Compared to the `mgl_stepWithMinimum:stops:` custom function, the input expression (that function’s target) is instead passed in as the first argument to this function.
+`mgl_join:` | `mgl_join({'Old', 'MacDonald'})` | Returns the result of concatenating together all the elements of an array in order. Compared to the `stringByAppendingString:` custom function, this function takes only one argument, which is an aggregate expression containing the strings to concatenate.
+`MGL_LET` | `MGL_LET('age', uppercase('old'), 'name', uppercase('MacDonald'), mgl_join({$age, $name}))` | Any number of variable names interspersed with their assigned `NSExpression` values, followed by an `NSExpression` that may contain references to those variables. Compared to the `mgl_expressionWithContext:` custom function, this function takes the variable names and values inline before the expression that contains references to those variables.
The following custom functions are also available with the
`+[NSExpression expressionForFunction:selectorName:arguments:]` method or the
@@ -216,6 +218,22 @@ The following custom functions are also available with the
</td>
</tr>
<tr>
+ <td><code>mgl_expressionWithContext:</code></td>
+ <td>
+ An `NSExpression` that may contain references to the variables defined in
+ the context dictionary.
+ </td>
+ <td>
+ An `NSDictionary` with `NSString`s as keys and `NSExpression`s as values.
+ Each key is a variable name and each value is the variable’s value within
+ the target expression.
+ </td>
+ <td>
+ The target expression with variable subexpressions replaced with the
+ values defined in the context dictionary.
+ </td>
+</tr>
+<tr>
<td><code>mgl_interpolateWithCurveType:parameters:stops:</code></td>
<td>
An `NSExpression` that evaluates to a number and contains a variable or
@@ -305,6 +323,18 @@ The following custom functions are also available with the
</td>
</tr>
<tr>
+ <td><code>stringByAppendingString:</code></td>
+ <td>
+ An `NSExpression` that evaluates to a string.
+ </td>
+ <td>
+ One or more `NSExpression`s, each evaluating to a string.
+ </td>
+ <td>
+ The target string with each of the argument strings appended in order.
+ </td>
+</tr>
+<tr>
<td><code>stringValue</code></td>
<td>
An `NSExpression` that evaluates to a Boolean value, number, or string.
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm
index 16c1656fb8..4ee0371e9d 100644
--- a/platform/darwin/src/NSExpression+MGLAdditions.mm
+++ b/platform/darwin/src/NSExpression+MGLAdditions.mm
@@ -15,28 +15,24 @@
#import <mbgl/style/expression/expression.hpp>
-/**
- Joins the given components into a single string by concatenating each component
- in order.
- */
-NSString *MGLJoinComponents(Class self, SEL _cmd, NSArray<NSString *> *components) {
- return [components componentsJoinedByString:@""];
-}
+@interface MGLAftermarketExpressionInstaller: NSObject
+@end
-/**
- A placeholder for a method that evaluates an expression based on an arbitrary
- number of variable names and assigned expressions.
- */
-id MGLEvaluateWithContext(Class self, SEL _cmd, NSString *firstVariableName, ...) {
- [NSException raise:NSInvalidArgumentException
- format:@"Assignment expressions lack underlying Objective-C implementations."];
- return nil;
-};
+@implementation MGLAftermarketExpressionInstaller
+
++ (void)load {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ [self installFunctions];
+ });
+}
/**
Adds to NSExpression’s built-in repertoire of functions.
*/
-void MGLInstallAftermarketExpressionFunctions() {
++ (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
@@ -47,21 +43,67 @@ void MGLInstallAftermarketExpressionFunctions() {
Class NSPredicateUtilities = objc_getMetaClass(className.UTF8String);
#pragma clang push
#pragma clang diagnostic ignored "-Wundeclared-selector"
- class_addMethod(NSPredicateUtilities, @selector(mgl_join:), (IMP)MGLJoinComponents, "@@:@");
+ #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)); \
+ }
- // Vararg aftermarket expressions need to be declared with an explicit and implicit first argument.
- class_addMethod(NSPredicateUtilities, @selector(MGL_LET), (IMP)MGLEvaluateWithContext, "@@:@");
- class_addMethod(NSPredicateUtilities, @selector(MGL_LET:), (IMP)MGLEvaluateWithContext, "@@:@");
+ // 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 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);
+
+ #undef INSTALL_AFTERMARKET_FN
#pragma clang pop
}
-@interface MGLAftermarketExpressionInstaller: NSObject
-@end
+/**
+ Joins the given components into a single string by concatenating each component
+ in order.
+ */
+- (NSString *)mgl_join:(NSArray<NSString *> *)components {
+ return [components componentsJoinedByString:@""];
+}
-@implementation MGLAftermarketExpressionInstaller
+/**
+ 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;
+}
-+ (void)load {
- MGLInstallAftermarketExpressionFunctions();
+/**
+ 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 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;
}
@end
@@ -501,7 +543,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;
@@ -510,11 +552,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) {
@@ -532,9 +573,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"]) {
@@ -753,40 +793,12 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
[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_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]]) {
@@ -918,4 +930,50 @@ 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;
+}
+
@end
diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm
index 94c9410892..b37c880783 100644
--- a/platform/darwin/test/MGLExpressionTests.mm
+++ b/platform/darwin/test/MGLExpressionTests.mm
@@ -594,30 +594,34 @@ using namespace std::string_literals;
- (void)testInterpolationExpressionObject {
{
NSDictionary *stops = @{@0: MGLConstantExpression(@100), @10: MGLConstantExpression(@200)};
- NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", stops];
+ NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'linear', nil, %@)", stops];
+ NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", stops];
NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
}
{
NSDictionary *stops = @{@1: MGLConstantExpression(@2), @3: MGLConstantExpression(@6)};
- NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 2, %@)", stops];
+ NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'exponential', 2, %@)", stops];
NSArray *jsonExpression = @[@"interpolate", @[@"exponential", @2], @[@"get", @"x"], @1, @2, @3, @6];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
}
{
NSDictionary *stops = @{@0: MGLConstantExpression(@0), @100: MGLConstantExpression(@100)};
- NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops];
+ NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops];
NSArray *jsonExpression = @[@"interpolate", @[@"cubic-bezier", @0.42, @0, @0.58, @1], @[@"get", @"x"], @0, @0, @100, @100];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
}
{
NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)};
- NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops];
+ NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(x, 11, %@)", stops];
+ NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops];
NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
}
}