Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios, macos] Add NSExpression convenience constructors and helper methods. #11278

Merged
merged 9 commits into from
Apr 17, 2018
20 changes: 10 additions & 10 deletions platform/darwin/docs/guides/For Style Authors.md.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,9 @@ In style specification | Method, function, or predicate type | Format string syn
`to-number` | `mgl_numberWithFallbackValues:` | `CAST(zipCode, 'NSNumber')`
`to-string` | `stringValue` | `CAST(ele, 'NSString')`
`typeof` | |
`geometry-type` | |`$geometryType`
`id` | |`$featureIdentifier`
`properties` | |`$featureProperties`
`geometry-type` | `NSExpression.geometryTypeVariableExpression` | `$geometryType`
`id` | `NSExpression.featureIdentifierVariableExpression` | `$featureIdentifier`
`properties` | `NSExpression.featurePropertiesVariableExpression` | `$featureProperties`
`at` | `objectFrom:withIndex:` | `array[n]`
`get` | `+[NSExpression expressionForKeyPath:]` | Key path
`has` | `mgl_does:have:` | `mgl_does:have:(self, 'key')`
Expand All @@ -355,14 +355,14 @@ In style specification | Method, function, or predicate type | Format string syn
`>=` | `NSGreaterThanOrEqualToPredicateOperatorType` | `key >= value`
`all` | `NSAndPredicateType` | `p0 AND … AND pn`
`any` | `NSOrPredicateType` | `p0 OR … OR pn`
`case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` | `TERNARY(1 = 2, YES, NO)` or `MGL_IF(1 = 2, YES, 2 = 2, YES, NO)`
`case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` or `+[NSExpression mgl_expressionForConditional:trueExpression:falseExpresssion:]` | `TERNARY(1 = 2, YES, NO)` or `MGL_IF(1 = 2, YES, 2 = 2, YES, NO)`
`coalesce` | `mgl_coalesce:` | `mgl_coalesce({x, y, z})`
`match` | `MGL_MATCH` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')`
`interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` |
`step` | `mgl_step:withMinimum:stops:` |
`match` | `MGL_MATCH` or `+[NSExpression mgl_expressionForMatchingExpression:inDictionary:defaultExpression:]` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')`
`interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` or `+[NSExpression mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:]` |
`step` | `mgl_step:withMinimum:stops:` or `+[NSExpression mgl_expressionForSteppingExpression:fromExpression:stops:]` |
`let` | `mgl_expressionWithContext:` | `MGL_LET('ios', 11, 'macos', 10.13, $ios + $macos)`
`var` | `+[NSExpression expressionForVariable:]` | `$variable`
`concat` | `mgl_join:` | `mgl_join({'Old', ' ', 'MacDonald'})`
`concat` | `mgl_join:` or `-[NSExpression mgl_expressionByAppendingExpression:]` | `mgl_join({'Old', ' ', 'MacDonald'})`
`downcase` | `lowercase:` | `lowercase('DOWNTOWN')`
`upcase` | `uppercase:` | `uppercase('Elysian Fields')`
<% if (macOS) { -%>
Expand Down Expand Up @@ -398,8 +398,8 @@ In style specification | Method, function, or predicate type | Format string syn
`sin` | |
`sqrt` | `sqrt:` | `sqrt(2)`
`tan` | |
`zoom` | | `$zoom`
`heatmap-density` | | `$heatmapDensity`
`zoom` | `NSExpression.zoomLevelVariableExpression` | `$zoom`
`heatmap-density` | `NSExpression.heatmapDensityVariableExpression` | `$heatmapDensity`

For operators that have no corresponding `NSExpression` symbol, use the
`MGL_FUNCTION()` format string syntax.
Expand Down
124 changes: 124 additions & 0 deletions platform/darwin/src/NSExpression+MGLAdditions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,130 @@

NS_ASSUME_NONNULL_BEGIN

typedef NSString *MGLExpressionInterpolationMode NS_TYPED_ENUM;

/**
An `NSString` identifying the `linear` interpolation type in an `NSExpression`.

This attribute corresponds to the `linear` value in the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-interpolate"><code>interpolate</code></a>
expression reference in the Mapbox Style Specification.
*/
extern MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionInterpolationModeLinear;

/**
An `NSString` identifying the `expotential` interpolation type in an `NSExpression`.

This attribute corresponds to the `exponential` value in the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-interpolate"><code>interpolate</code></a>
expression reference in the Mapbox Style Specification.
*/
extern MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionInterpolationModeExponential;

/**
An `NSString` identifying the `cubic-bezier` interpolation type in an `NSExpression`.

This attribute corresponds to the `cubic-bezier` value in the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-interpolate"><code>interpolate</code></a>
expression reference in the Mapbox Style Specification.
*/
extern MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionInterpolationModeCubicBezier;

@interface NSExpression (MGLVariableAdditions)

/**
`NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-zoom"><code>zoom</code></a>
expression reference in the Mapbox Style Specification.
*/
@property (class, nonatomic, readonly) NSExpression *zoomLevelVariableExpression;

/**
`NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-heatmap-density"><code>heatmap-density</code></a>
expression reference in the Mapbox Style Specification.
*/
@property (class, nonatomic, readonly) NSExpression *heatmapDensityVariableExpression;

/**
`NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#eexpressions-geometry-type"><code>geometry-type</code></a>
expression reference in the Mapbox Style Specification.
*/
@property (class, nonatomic, readonly) NSExpression *geometryTypeVariableExpression;

/**
`NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-id"><code>id</code></a>
expression reference in the Mapbox Style Specification.
*/
@property (class, nonatomic, readonly) NSExpression *featureIdentifierVariableExpression;

/**
`NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-properties"><code>properties</code></a>
expression reference in the Mapbox Style Specification.
*/
@property (class, nonatomic, readonly) NSExpression *featurePropertiesVariableExpression;

@end

@interface NSExpression (MGLInitializerAdditions)

/**
Returns a conditional function expression specifying the string predicate, and
expressions for each condition.

@param conditionPredicate The predicate to get evaluated.
@param trueExpression The expression for conditions equal to true.
@param falseExpression The expression for conditions equal to false.
*/
+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression NS_SWIFT_NAME(init(forMGLConditional:trueExpression:falseExpression:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init(forMGLConditional:true:false:), not init(forMGLConditional:trueExpression:falseExpression:.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stand corrected: in Swift, the initializer is NSExpression(forConditional:trueExpression:falseExpression:), but the properties are just true and false. Go figure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our chat this is not possible in Swift 4.1


/**
Returns a step function expression specifying the stepping, from expression
and stops.

@param steppingExpression The stepping expression.
@param minimumExpression The expression which could be a constant or function expression.
@param stops The stops must be an `NSDictionary` constant `NSExpression`.
*/
+ (instancetype)mgl_expressionForSteppingExpression:(nonnull NSExpression*)steppingExpression fromExpression:(nonnull NSExpression *)minimumExpression stops:(nonnull NSExpression*)stops NS_SWIFT_NAME(init(forMGLStepping:from:stops:));

/**
Returns an interpolated function expression specifying the function operator, curve type,
parameters and steps.

@param inputExpression The interpolating expression input.
@param curveType The curve type could be `MGLExpressionInterpolationModeLinear`,
`MGLExpressionInterpolationModeExponential` and
`MGLExpressionInterpolationModeCubicBezier`.
@param parameters The parameters expression.
@param stops The stops expression.
*/
+ (instancetype)mgl_expressionForInterpolatingExpression:(nonnull NSExpression*)inputExpression withCurveType:(nonnull MGLExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters stops:(nonnull NSExpression*)stops NS_SWIFT_NAME(init(forMGLInterpolating:curveType:parameters:stops:));

/**
Returns a match function expression specifying the input, matching values,
and default value.

@param inputExpression The matching expression.
@param matchedExpressions The matched values expression dictionary must be condition : value.
@param defaultExpression The defaultValue expression to be used in case there is no match.
*/
+ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)inputExpression inDictionary:(nonnull NSDictionary<NSExpression *, NSExpression *> *)matchedExpressions defaultExpression:(nonnull NSExpression *)defaultExpression NS_SWIFT_NAME(init(forMGLMatchingKey:in:default:));
/**
Returns a constant expression appending the passed expression.

@note Both the receiver and the given expression must be an `NSString` constant
expression type; otherwise, an exception is rised.

@param expression The expression to append to the receiver.
*/
- (instancetype)mgl_expressionByAppendingExpression:(nonnull NSExpression *)expression NS_SWIFT_NAME(mgl_appending(_:));

@end

@interface NSExpression (MGLAdditions)

/**
Expand Down
80 changes: 75 additions & 5 deletions platform/darwin/src/NSExpression+MGLAdditions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

#import <mbgl/style/expression/expression.hpp>

const MGLExpressionInterpolationMode MGLExpressionInterpolationModeLinear = @"linear";
const MGLExpressionInterpolationMode MGLExpressionInterpolationModeExponential = @"exponential";
const MGLExpressionInterpolationMode MGLExpressionInterpolationModeCubicBezier = @"cubic-bezier";

@interface MGLAftermarketExpressionInstaller: NSObject
@end

Expand Down Expand Up @@ -557,6 +561,72 @@ - (id)mgl_has:(id)element {
[NSException raise:NSInvalidArgumentException
format:@"Has expressions lack underlying Objective-C implementations."];
return nil;

}

@end
@implementation NSExpression (MGLVariableAdditions)

+ (NSExpression *)zoomLevelVariableExpression {
return [NSExpression expressionForVariable:@"zoomLevel"];
}

+ (NSExpression *)heatmapDensityVariableExpression {
return [NSExpression expressionForVariable:@"heatmapDensity"];
}

+ (NSExpression *)geometryTypeVariableExpression {
return [NSExpression expressionForVariable:@"geometryType"];
}

+ (NSExpression *)featureIdentifierVariableExpression {
return [NSExpression expressionForVariable:@"featureIdentifier"];
}

+ (NSExpression *)featurePropertiesVariableExpression {
return [NSExpression expressionForVariable:@"featureProperties"];
}

@end

@implementation NSExpression (MGLInitializerAdditions)

+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression {
if (@available(iOS 9.0, *)) {
return [NSExpression expressionForConditional:conditionPredicate trueExpression:trueExpression falseExpression:falseExpression];
} else {
return [NSExpression expressionForFunction:@"MGL_IF" arguments:@[[NSExpression expressionWithFormat:@"%@", conditionPredicate], trueExpression, 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_expressionByAppendingExpression:(nonnull NSExpression *)expression {
NSExpression *subexpression = [NSExpression expressionForAggregate:@[self, expression]];
return [NSExpression expressionForFunction:@"mgl_join:" arguments:@[subexpression]];
}

@end
Expand Down Expand Up @@ -767,15 +837,15 @@ + (instancetype)expressionWithMGLJSONObject:(id)object {
return [NSExpression expressionForFunction:@"mgl_step:from:stops:"
arguments:@[inputExpression, minimum, stopExpression]];
} else if ([op isEqualToString:@"zoom"]) {
return [NSExpression expressionForVariable:@"zoomLevel"];
return NSExpression.zoomLevelVariableExpression;
} else if ([op isEqualToString:@"heatmap-density"]) {
return [NSExpression expressionForVariable:@"heatmapDensity"];
return NSExpression.heatmapDensityVariableExpression;
} else if ([op isEqualToString:@"geometry-type"]) {
return [NSExpression expressionForVariable:@"geometryType"];
return NSExpression.geometryTypeVariableExpression;
} else if ([op isEqualToString:@"id"]) {
return [NSExpression expressionForVariable:@"featureIdentifier"];
return NSExpression.featureIdentifierVariableExpression;
} else if ([op isEqualToString:@"properties"]) {
return [NSExpression expressionForVariable:@"featureProperties"];
return NSExpression.featurePropertiesVariableExpression;
} else if ([op isEqualToString:@"var"]) {
return [NSExpression expressionForVariable:argumentObjects.firstObject];
} else if ([op isEqualToString:@"case"]) {
Expand Down
50 changes: 50 additions & 0 deletions platform/darwin/test/MGLExpressionTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1006,4 +1006,54 @@ - (void)testLocalization {
}
}

- (void)testConvenienceInitializers {
{
NSExpression *expression = [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"1 = 2"]
trueExpression:MGLConstantExpression(@YES)
falseExpresssion:MGLConstantExpression(@NO)];

NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO);
}
{
NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)};
NSExpression *expression = [NSExpression mgl_expressionForSteppingExpression:[NSExpression expressionForKeyPath:@"x"]
fromExpression:[NSExpression expressionForConstantValue:@11]
stops:[NSExpression expressionForConstantValue:stops]];
NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
{
NSDictionary *stops = @{@0: MGLConstantExpression(@100), @10: MGLConstantExpression(@200)};
NSExpression *expression = [NSExpression mgl_expressionForInterpolatingExpression:[NSExpression expressionForKeyPath:@"x"]
withCurveType:MGLExpressionInterpolationModeLinear
parameters:nil
stops:[NSExpression expressionForConstantValue:stops]];
NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
{
NSExpression *expression = [[NSExpression expressionForConstantValue:@"Old"] mgl_expressionByAppendingExpression:[NSExpression expressionForConstantValue:@"MacDonald"]];

NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald");
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
{
NSDictionary *values = @{ MGLConstantExpression(@1): MGLConstantExpression(@"one") };
NSExpression *expression = [NSExpression mgl_expressionForMatchingExpression:[NSExpression expressionWithFormat:@"2 * 1"]
inDictionary:values
defaultExpression:[NSExpression expressionForConstantValue:@"default"]];
NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
}


@end
Loading