CPPredicate parsing support:

"($)path.($)variable"
 "SUBQUERY(collection, $x,$x = 2)"
 "sum:(arg1, ...)"
 "FUNCTION(target, selector, arg1, ...)
 "object[expression]"
 "set UNION|INTERSECT|MINUS otherset"
Fixed a bug where CPExpression_set was evaluating to an expression instead of a set.
Replaced parsing exceptions with a generic function indicating where it failed.
This commit is contained in:
cacaodev
2010-11-07 17:56:22 +01:00
parent e3b6cbcee9
commit ca289e081e
13 changed files with 286 additions and 156 deletions
@@ -1,3 +1,4 @@
@import "CPArray.j"
@import "CPNull.j"
@import "CPString.j"
+1 -2
View File
@@ -1,6 +1,5 @@
@import "CPPredicate.j"
@import <Foundation/CPArray.j>
@import <Foundation/CPString.j>
/*!
A predicate to compare directly the left and right hand sides.
+6 -6
View File
@@ -1,8 +1,8 @@
@import <Foundation/CPString.j>
@import <Foundation/CPArray.j>
@import <Foundation/CPKeyValueCoding.j>
@import <Foundation/CPDictionary.j>
@import <Foundation/CPCoder.j>
@import "CPString.j"
@import "CPArray.j"
@import "CPKeyValueCoding.j"
@import "CPDictionary.j"
/*!
An expression that always returns the same value.
@@ -90,7 +90,7 @@ CPMinusSetExpressionType = 9;
*/
+ (CPExpression)expressionForEvaluatedObject
{
return [[CPExpression_self alloc] init];
return [CPExpression_self evaluatedObject];
}
/*!
@@ -1,7 +1,7 @@
@import "CPExpression.j"
@import <Foundation/CPArray.j>
@import <Foundation/CPString.j>
@import "CPArray.j"
@import "CPString.j"
@implementation CPExpression_aggregate : CPExpression
{
@@ -1,6 +1,6 @@
@import "CPExpression.j"
@import <Foundation/CPDictionary.j>
@import "CPDictionary.j"
@implementation CPExpression_constant : CPExpression
{
+29 -12
View File
@@ -1,8 +1,8 @@
@import "CPExpression.j"
@import <Foundation/CPString.j>
@import <Foundation/CPArray.j>
@import <Foundation/CPDictionary.j>
@import "CPString.j"
@import "CPArray.j"
@import "CPDictionary.j"
@implementation CPExpression_function : CPExpression
{
@@ -10,6 +10,7 @@
SEL _selector;
CPArray _arguments;
int _argc;
int _maxargs;
}
- (id)initWithSelector:(SEL)aSelector arguments:(CPArray)parameters
@@ -37,6 +38,7 @@
_operand = operand;
_arguments = parameters;
_argc = [parameters count];
_maxargs = [[CPStringFromSelector(_selector) componentsSeparatedByString:@":"] count] - 1;
return self;
}
@@ -84,30 +86,45 @@
objj_args.push(arg);
}
// If we have too much arguments, concatenate remaining args on the last one.
if (_argc > _maxargs)
{
var r = MAX(_maxargs + 1, 2);
objj_args = objj_args.slice(0, r).concat([objj_args.slice(r)]);
}
return objj_msgSend.apply(this, objj_args);
}
- (CPString)description
{
var result = _operand + [self _function] + "(";
var result = "";
if ([_operand isEqual:[CPExpression expressionForConstantValue:[CPPredicateUtilities class]]])
result += CPStringFromSelector(_selector) + "(";
else
{
result += "FUNCTION(";
result += _operand ? [_operand description] + ", ":"";
result += _selector ? CPStringFromSelector(_selector) + ", ":"";
}
for (var i = 0; i < _argc; i++)
result = result + [_arguments[i] description] + ((i + 1 < _argc) ? ", " : "");
result = result + ")";
result += ")";
return result ;
}
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
{
var array = [CPArray array],
i;
// should we also allow variables for target and selectors ?
for (i = 0; i < _argc; i++)
[array addObject:[[_arguments objectAtIndex:i] _expressionWithSubstitutionVariables:variables]];
var operand = [[self operand] _expressionWithSubstitutionVariables:variables],
args = [CPArray array];
return [CPExpression expressionForFunction:[self operand] selectorName:[self _function] arguments:array];
for (var i = 0; i < _argc; i++)
[args addObject:[_arguments[i] _expressionWithSubstitutionVariables:variables]];
return [CPExpression expressionForFunction:operand selectorName:[self _function] arguments:args];
}
@end
@@ -255,7 +272,7 @@ var CPSelectorNameKey = @"CPSelectorName",
return ROUND(RAND() * num);
}
+ (int)modulus:(int)n by:(int)n
+ (int)modulus:(int)n by:(int)m
{
return n % m;
}
+17 -4
View File
@@ -1,8 +1,8 @@
@import "CPExpression.j"
@import <Foundation/CPString.j>
@import <Foundation/CPKeyValueCoding.j>
@import "CPExpression_function.j"
@import "CPString.j"
@import "CPKeyValueCoding.j"
@implementation CPExpression_keypath : CPExpression_function
{
@@ -37,13 +37,26 @@
- (CPString)keyPath
{
return [[self pathExpression] constantValue];
return [[self pathExpression] keyPath];
}
- (CPString)description
{
return [self keyPath];
var result = "";
if ([_operand expressionType] != CPEvaluatedObjectExpressionType)
result += [_operand description] + ".";
result += [self keyPath];
return result;
}
@end
@implementation CPExpression_constant (KeyPath)
- (CPString)keyPath
{
return [self constantValue];
}
@end
+13 -5
View File
@@ -1,13 +1,21 @@
@import "CPExpression.j"
@import <Foundation/CPString.j>
@import <Foundation/CPDictionary.j>
@import <Foundation/CPCoder.j>
@import "CPString.j"
@import "CPDictionary.j"
var evaluatedObject = nil;
@implementation CPExpression_self : CPExpression
{
}
+ (id)evaluatedObject
{
if (evaluatedObject == nil)
evaluatedObject = [CPExpression_self new];
return evaluatedObject;
}
- (id)init
{
[super initWithExpressionType:CPEvaluatedObjectExpressionType];
@@ -17,7 +25,7 @@
- (id)initWithCoder:(CPCoder)coder
{
return [self init];
return [CPExpression_self evaluatedObject];
}
- (void)encodeWithCoder:(CPCoder)coder
@@ -26,7 +34,7 @@
- (BOOL)isEqual:(id)object
{
return (_type == CPEvaluatedObjectExpressionType);
return (object === self);
}
- (id)expressionValueWithObject:(id)object context:(CPDictionary)context
+2 -2
View File
@@ -30,7 +30,7 @@
- (id)expressionValueWithObject:object context:(CPDictionary)context
{
var right = [_right expressionValueWithObject:object context:context];
if ([right isKindOfClass:[CPArray class]]) // Or we could do [[right objectEnumerator] allObjects]
if ([right isKindOfClass:[CPArray class]])
right = [CPSet setWithArray:right];
else if ([right isKindOfClass:[CPDictionary class]])
right = [CPSet setWithArray:[right allValues]];
@@ -53,7 +53,7 @@
default:
}
return [CPExpression expressionForConstantValue:result];
return result;
}
- (CPExpression )_expressionWithSubstitutionVariables:(CPDictionary )variables
+32 -20
View File
@@ -1,3 +1,4 @@
@import "CPExpression.j"
@implementation CPExpression_subquery : CPExpression
@@ -8,13 +9,19 @@
}
- (id)initWithExpression:(CPExpression)collection usingIteratorVariable:(CPString)variable predicate:(CPPredicate)subpredicate
{
var variableExpression = [CPExpression expressionForVariable:variable];
return [self initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
}
- (id)initWithExpression:(CPExpression)collection usingIteratorExpression:(CPExpression)variableExpression predicate:(CPPredicate)subpredicate
{
[super initWithExpressionType:CPSubqueryExpressionType];
_subpredicate = subpredicate;
_collection = collection;
_variableExpression = [CPExpression expressionForVariable:variable];
_variableExpression = variableExpression;
return self;
}
@@ -23,16 +30,16 @@
var collection = [_collection expressionValueWithObject:object context:context],
count = [collection count],
result = [CPArray array],
bindings = [CPDictionary dictionaryWithObject:[CPExpression expressionForEvaluatedObject] forKey:[self variable]];
for (var i = 0; i < count; i++)
{
var item = [collection objectAtIndex:i];
if ([_subpredicate evaluateWithObject:item substitutionVariables:bindings])
[result addObject:item];
}
return result;
}
@@ -40,14 +47,14 @@
{
if (self === object)
return YES;
if (![_collection isEqual:[object collection]] || ![_subpredicate isEqual:[object predicate]])
return NO;
return YES;
}
- (id)collection
- (CPExpression)collection
{
return _collection;
}
@@ -57,22 +64,27 @@
return [[CPExpression_subquery alloc] initWithExpression:[_collection copy] usingIteratorExpression:[_variableExpression copy] predicate:[_subpredicate copy]];
}
- (id)predicate
- (CPPredicate)predicate
{
return _subpredicate;
}
- (id)predicateFormat
- (CPString)description
{
return [self predicateFormat];
}
- (CPString)predicateFormat
{
return @"SUBQUERY(" + [_collection description] + ", " + [_variableExpression description] + ", " + [_subpredicate predicateFormat] + ")";
}
- (id)variable
- (CPString)variable
{
return [_variableExpression variable];
}
- (id)variableExpression
- (CPExpression)variableExpression
{
return _variableExpression;
}
@@ -81,23 +93,23 @@
var CPExpressionKey = @"CPExpression",
CPSubpredicateKey = @"CPSubpredicate",
CPVariableKey = @"CPVariable";
@implementation CPExpression_subquery (CPCoding)
- (id)initWithCoder:(CPCoder)coder
{
var collection = [coder decodeObjectForKey:CPExpressionKey],
var collection = [coder decodeObjectForKey:CPExpressionKey],
subpredicate = [coder decodeObjectForKey:CPSubpredicateKey],
variable = [coder decodeObjectForKey:CPVariableKey];
return [self initWithExpression:collection usingIteratorVariable:variable predicate:subpredicate];
variableExpression = [coder decodeObjectForKey:CPVariableKey];
return [self initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
}
- (void)encodeWithCoder:(CPCoder)coder
{
[coder encodeObject:_collection forKey:CPExpressionKey];
[coder encodeObject:_subpredicate forKey:CPSubpredicateKey];
[coder encodeObject:[variableExpression variable] forKey:CPVariableKey];
[coder encodeObject:_variableExpression forKey:CPVariableKey];
}
@end
@end
@@ -1,7 +1,7 @@
@import "CPExpression.j"
@import <Foundation/CPString.j>
@import <Foundation/CPDictionary.j>
@import "CPString.j"
@import "CPDictionary.j"
@implementation CPExpression_variable : CPExpression
{
@@ -35,8 +35,8 @@
- (id)expressionValueWithObject:object context:(CPDictionary)context
{
var expression = [self _expressionWithSubstitutionVariables:context];
return [expression expressionValueWithObject:object context:context];
return [expression expressionValueWithObject:object context:context];
}
- (CPString)description
@@ -49,10 +49,10 @@
var value = [variables objectForKey:_variable];
if (value == nil)
[CPException raise:CPInvalidArgumentException reason:@"Can't get value for '" + _variable + "' in bindings" + variables];
if ([value isKindOfClass:[CPExpression class]])
return value;
return [CPExpression expressionForConstantValue:value];
}
+109 -69
View File
@@ -1,9 +1,9 @@
@import <Foundation/CPValue.j>
@import <Foundation/CPArray.j>
@import <Foundation/CPSet.j>
@import <Foundation/CPNull.j>
@import <Foundation/CPScanner.j>
@import "CPValue.j"
@import "CPArray.j"
@import "CPSet.j"
@import "CPNull.j"
@import "CPScanner.j"
/*!
@ingroup foundation
@@ -285,12 +285,16 @@ function(newValue)\
}
catch(error)
{
CPLogConsole(@"Parsing failed for "+[self string]+" with " + error);
CPLogConsole(@"Unable to parse predicate '"+[self string]+"' with " + error);
}
finally
{
if (![self isAtEnd])
CPLogConsole(@"Format string contains extra characters: \""+[self string]+"\"");
{
var pstr = [self string],
loc = [self scanLocation];
CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'");
}
}
return r;
@@ -340,7 +344,7 @@ function(newValue)\
var r = [self parsePredicate];
if (![self scanString:@")" intoString:NULL])
[CPException raise:CPInvalidArgumentException reason:@"Missing ) in compound predicate"];
CPRaiseParseError(self, @"predicate");
return r;
}
@@ -475,7 +479,7 @@ function(newValue)\
type = CPBetweenPredicateOperatorType;
}
else
[CPException raise:CPInvalidArgumentException reason:@"Invalid comparison predicate: "+ [[self string] substringFromIndex: [self scanLocation]]];
CPRaiseParseError(self, @"comparison predicate");
if ([self scanString:@"[cd]" intoString:NULL])
{
@@ -525,7 +529,7 @@ function(newValue)\
var arg = [self parseExpression];
if (![self scanString:@")" intoString:NULL])
[CPException raise:CPInvalidArgumentException reason:@"Missing ) in expression"];
CPRaiseParseError(self, @"expression");
return arg;
}
@@ -542,7 +546,7 @@ function(newValue)\
[a addObject:[self parseExpression]];
if (![self scanString:@"}" intoString:NULL])
[CPException raise:CPInvalidArgumentException reason:@"Missing } in aggregate"];
CPRaiseParseError(self, @"expression");
return [CPExpression expressionForAggregate:a];
}
@@ -566,12 +570,12 @@ function(newValue)\
if ([self scanString:@"$" intoString:NULL])
{
var variable = [self parseExpression];
var variable = [self parseSimpleExpression];
if (![variable keyPath])
[CPException raise:CPInvalidArgumentException reason:@"Invalid variable identifier: " + variable];
CPRaiseParseError(self, @"expression");
return [CPExpression expressionForVariable:[variable keyPath]];
return [CPExpression expressionForVariable:variable];
}
location = [self scanLocation];
@@ -648,10 +652,7 @@ function(newValue)\
[self scanUpToString:@"\"" intoString:REFERENCE(str)];
if ([self scanString:@"\"" intoString:NULL] == NO)
{
[self setCharactersToBeSkipped:skip];
[CPException raise:CPInvalidArgumentException reason:@"Invalid double quoted literal at "+location];
}
CPRaiseParseError(self, @"expression");
[self setCharactersToBeSkipped:skip];
@@ -667,10 +668,7 @@ function(newValue)\
[self scanUpToString:@"'" intoString:REFERENCE(str)];
if ([self scanString:@"'" intoString:NULL] == NO)
{
[self setCharactersToBeSkipped:skip];
[CPException raise:CPInvalidArgumentException reason:@"Invalid double quoted literal at " + location];
}
CPRaiseParseError(self, @"expression");
[self setCharactersToBeSkipped:skip];
@@ -682,17 +680,55 @@ function(newValue)\
var e = [self parseExpression];
if (![e keyPath])
[CPException raise:CPInvalidArgumentException reason:@"Invalid keypath identifier: " + e];
CPRaiseParseError(self, @"expression");
return [CPExpression expressionForKeyPath:[e keyPath] + "@"];
}
if ([self scanString:@"SUBQUERY" intoString:NULL])
{
if (![self scanString:@"(" intoString:NULL])
CPRaiseParseError(self, @"expression");
var collection = [self parseExpression],
variableExpression,
subpredicate;
if (![self scanString:@"," intoString:NULL])
CPRaiseParseError(self, @"expression");
variableExpression = [self parseExpression];
if (![self scanString:@"," intoString:NULL])
CPRaiseParseError(self, @"expression");
subpredicate = [self parsePredicate];
if (![self scanString:@")" intoString:NULL])
CPRaiseParseError(self, @"expression");
return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
}
if ([self scanString:@"FUNCTION" intoString:NULL])
{
if (![self scanString:@"(" intoString:NULL])
CPRaiseParseError(self, @"expression");
var args = [CPArray arrayWithObject:[self parseExpression]];
while ([self scanString:@"," intoString:NULL])
[args addObject:[self parseExpression]];
if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType)
CPRaiseParseError(self, @"expression");
return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)];
}
[self scanString:@"#" intoString:NULL];
if (!identifier)
identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"];
if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)])
[CPException raise:CPInvalidArgumentException reason:@"Missing identifier: "+[[self string] substringFromIndex:[self scanLocation]]];
CPRaiseParseError(self, @"expression");
return [CPExpression expressionForKeyPath:ident];
}
@@ -703,48 +739,17 @@ function(newValue)\
while (YES)
{
if ([self scanString:@"(" intoString:NULL])
if ([self scanString:@"." intoString:NULL])
{
// function - this parser allows for (max)(a, b, c) to be properly
// recognized and even (%K)(a, b, c) if %K evaluates to "max"
var args = [CPMutableArray arrayWithCapacity:5];
if (![left keyPath])
[CPException raise:CPInvalidArgumentException reason:@"Invalid function identifier: " + left];
if (![self scanString:@")" intoString:NULL])
{
// any arguments
// first argument
[args addObject:[self parseExpression]];
while ([self scanString:@"," intoString:NULL])
{
// more arguments
[args addObject:[self parseExpression]];
}
if (![self scanString:@")" intoString:NULL])
[CPException raise:CPInvalidArgumentException reason:@"Missing ) in function arguments"];
}
left = [CPExpression expressionForFunction:[left keyPath] arguments:args];
}
else if ([self scanString:@"." intoString:NULL])
{
// keypath - this parser allows for (a).(b.c)
// to be properly recognized
// and even %K.((%K)) if the first %K evaluates to "a" and the
// second %K to "b.c"
if (![left keyPath])
[CPException raise:CPInvalidArgumentException reason:@"Invalid left keypath:" + left];
var right = [self parseSimpleExpression];
if (![right keyPath])
[CPException raise:CPInvalidArgumentException reason:@"Invalid right keypath:" + right];
// concatenate
left = [CPExpression expressionForKeyPath:[left keyPath]+ "." + [right keyPath]];
var right = [self parseSimpleExpression],
expressionType = [right expressionType];
if (expressionType == CPKeyPathExpressionType)
left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]];
else if (expressionType == CPVariableExpressionType)
left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]];
else
CPRaiseParseError(self, @"expression");
}
else if ([self scanString:@"[" intoString:NULL])
{
@@ -763,13 +768,44 @@ function(newValue)\
}
else
{
var integer;
if ([self scanInt:REFERENCE(integer)])
left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, [CPExpression expressionForConstantValue:integer]]];
var index = [self parseExpression];
left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]];
}
if (![self scanString:@"]" intoString:NULL])
[CPException raise:CPInvalidArgumentException reason:@"Missing ] in index argument"];
CPRaiseParseError(self, @"expression");
}
else if ([self scanString:@":(" intoString:NULL])
{
// function - this parser allows for (max)(a, b, c) to be properly
// recognized and even (%K)(a, b, c) if %K evaluates to "max"
var args = [CPMutableArray arrayWithCapacity:5];
if (![left keyPath])
CPRaiseParseError(self, @"expression");
if (![self scanString:@")" intoString:NULL])
{
[args addObject:[self parseExpression]];
while ([self scanString:@"," intoString:NULL])
[args addObject:[self parseExpression]];
if (![self scanString:@")" intoString:NULL])
CPRaiseParseError(self, @"expression");
}
left = [CPExpression expressionForFunction:([left keyPath] + ":") arguments:args];
}
else if ([self scanString:@"UNION" intoString:NULL])
{
left = [CPExpression expressionForUnionSet:left with:[self parseExpression]];
}
else if ([self scanString:@"INTERSECT" intoString:NULL])
{
left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]];
}
else if ([self scanString:@"MINUS" intoString:NULL])
{
left = [CPExpression expressionForMinusSet:left with:[self parseExpression]];
}
else
{
@@ -872,7 +908,11 @@ function(newValue)\
@end
var CPRaiseParseError = function CPRaiseParseError(aScanner, target)
{
[CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]];
}
@import "CPCompoundPredicate.j"
@import "CPComparisonPredicate.j"
@import "CPExpression.j"
+67 -27
View File
@@ -36,7 +36,7 @@
var expression_keypath = [CPExpression expressionForKeyPath:@"name"];
[self assertNotNull:expression_keypath message:"KeyPath Expression should not be nil"];
[self assertTrue:[expression_keypath keyPath] == @"name" message:"-keyPath should not be \"name\""];
var expression_str = [CPExpression expressionForConstantValue:@"j[a-z]an"];
[self assertNotNull:expression_str message:"ConstantValue Expression should not be nil"];
@@ -83,26 +83,28 @@
var right = [CPExpression expressionForConstantValue:[CPArray arrayWithObjects:@"a",@"b",@"d"]];
var expression = [CPExpression expressionForIntersectSet:left with:right];
var eval = [[expression expressionValueWithObject:nil context:nil] constantValue];
var eval = [expression expressionValueWithObject:nil context:nil];
[self assertTrue:[eval isEqualToSet:[CPSet setWithObjects:@"a",@"b"]] message:"Result should be {(a, b)}, is " + eval];
expression = [CPExpression expressionForUnionSet:left with:right];
eval = [[expression expressionValueWithObject:nil context:nil] constantValue];
eval = [expression expressionValueWithObject:nil context:nil];
[self assertTrue:[eval isEqualToSet:[CPSet setWithObjects:@"a",@"b",@"c",@"d"]] message:"Result should be {(a, b, c, d)}, is " + eval];
expression = [CPExpression expressionForMinusSet:left with:right];
eval = [[expression expressionValueWithObject:nil context:nil] constantValue];
eval = [expression expressionValueWithObject:nil context:nil];
[self assertTrue:[eval isEqualToSet:[CPSet setWithObjects:@"c"]] message:"Result should be {(c)}, is " + eval];
}
- (void)testFunctionExpressionEvaluation
{
var expression = [CPExpression expressionForConstantValue:[1,2,3]];
var function_exp = [CPExpression expressionForFunction:"sum:" arguments:[expression]];
// Built-in function
var args = [[CPExpression expressionForConstantValue:1], [CPExpression expressionForConstantValue:2], [CPExpression expressionForConstantValue:3]];
var function_exp = [CPExpression expressionForFunction:"sum:" arguments:args];
var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:function_exp rightExpression:[CPExpression expressionForConstantValue:6] modifier:CPDirectPredicateModifier type:CPEqualToPredicateOperatorType options:0];
[self assertTrue:[pred evaluateWithObject:nil] message:[pred description] + " should be true"];
// Custom function
var operand = [CPExpression expressionForConstantValue:@"text"];
var arg = [CPExpression expressionForConstantValue:2];
function_exp = [CPExpression expressionForFunction:operand selectorName:@"substringFromIndex:" arguments:[arg]];
@@ -115,7 +117,7 @@
// Replace with constant
var expression = [CPExpression expressionForVariable:@"variable"],
bindings = [CPDictionary dictionaryWithObject:20 forKey:@"variable"],
eval = [expression expressionValueWithObject:@"variable" context:bindings];
eval = [expression expressionValueWithObject:@"variable" context:bindings];
[self assertTrue:(eval == 20) message:"'"+ eval + "' should be 20"];
// Replace with constant expression
@@ -132,11 +134,11 @@
- (void)testSubqueryExpressionEvaluation
{
var collection = [CPExpression expressionForKeyPath:@"Record1.Children"],
iteratorVariable = @"x",
iteratorVariable = @"x",
predicate = [CPPredicate predicateWithFormat:@"$x BEGINSWITH 'Kid'"];
var expression = [CPExpression expressionForSubquery:collection usingIteratorVariable:iteratorVariable predicate:predicate];
var eval = [expression expressionValueWithObject:dict context:nil];
var expected = [CPArray arrayWithObjects:"Kid1", "Kid2"];
[self assertTrue:([eval isEqual:expected]) message:"'"+ [expression predicateFormat] + "' result is "+ eval + "but should be " + expected];
@@ -197,9 +199,9 @@
pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:nil] rightExpression:[CPExpression expressionForConstantValue:nil] customSelector:@selector(yes:)];
[self assertFalse:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be false"];
pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Name"] rightExpression:[CPExpression expressionForConstantValue:nil] modifier:CPDirectPredicateModifier type:CPBeginsWithPredicateOperatorType options:0];
[self assertFalse:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be false"];
// Predicates with operators
@@ -210,9 +212,9 @@
pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:nil] rightExpression:[CPExpression expressionForConstantValue:nil] modifier:CPDirectPredicateModifier type:CPGreaterThanOrEqualToPredicateOperatorType options:0];
[self assertTrue:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be true"];
pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:nil] rightExpression:[CPExpression expressionForConstantValue:nil] modifier:CPDirectPredicateModifier type:CPBeginsWithPredicateOperatorType options:0];
[self assertFalse:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be false"];
}
@@ -266,16 +268,19 @@
[self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"];
// Test Aggregate
predicate = [CPPredicate predicateWithFormat:@"{Record1 .Name, Record1.Age} = {'John',34}"];
predicate = [CPPredicate predicateWithFormat:@"{Record1.Name, Record1.Age} = {'John',34}"];
[self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"];
// Test Symbolic token
predicate = [CPPredicate predicateWithFormat:@"Record1.Children[FIRST] = 'Kid1'"];
[self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"];
predicate = [CPPredicate predicateWithFormat:@"Record1.Children[1] = 'Kid2'"];
[self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"];
predicate = [CPPredicate predicateWithFormat:@"Record1.Children[count:(Record1.Children) - 1] = 'Kid2'"];
[self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"];
// Test arithm
var n = 2;
predicate = [CPPredicate predicateWithFormat:@"SELF +1 = 3"];
@@ -296,26 +301,61 @@
predicate = [CPPredicate predicateWithFormat:@"SELF** 3 = 8"];
[self assertTrue:[predicate evaluateWithObject:n] message:[predicate description] + " should be true"];
// TEST Operator type
// TEST Operator type
predicate = [CPPredicate predicateWithFormat: @"a CONTAINS[c] \"b\""];
[self assertTrue:([predicate predicateOperatorType] == CPContainsPredicateOperatorType) message:[predicate description] + " operator should be a CPContainsPredicateOperatorType"];
predicate = [CPPredicate predicateWithFormat: @"a BETWEEN {%f,%f}", 20, 40];
[self assertTrue:([predicate predicateOperatorType] == CPBetweenPredicateOperatorType) message:[predicate description] + " operator should be a CPBetweenPredicateOperatorType"];
// TEST Empty string
predicate = [CPPredicate predicateWithFormat: @"a CONTAINS \"\""];
[self assertNotNull:predicate message:[predicate description] + " should not be nil"];
// TEST variable
predicate = [CPPredicate predicateWithFormat: @"$x CONTAINS \"\""];
[self assertTrue:[[predicate leftExpression] expressionType] == CPVariableExpressionType message:"Left Expression should be a CPVariableExpressionType"];
/*
Variable in multiple paths will fail. Left exp should be FUNCTION(keyPathExp"keypath", valueForKey:, variableExp"variable")
predicate = [CPPredicate predicateWithFormat: @"keypath.$variable CONTAINS \"\""];
var type = [[predicate leftExpression] expressionType];
[self assertTrue: type == CPFunctionExpressionType message:"Left Expression should be a CPFunctionExpressionType is " + type];
*/
// TEST variable inside keypath
predicate = [CPPredicate predicateWithFormat: @"Record1.$age = 34"];
var bindings = [CPDictionary dictionaryWithObject:@"Age" forKey:@"age"];
[self assertTrue:[predicate evaluateWithObject:dict substitutionVariables:bindings] message:"Predicate " + predicate + " should evaluate to TRUE"];
predicate = [CPPredicate predicateWithFormat: @"$record.Age = 34"];
[bindings setObject:[CPExpression expressionForKeyPath:@"Record1"] forKey:@"record"];
[self assertTrue:[predicate evaluateWithObject:dict substitutionVariables:bindings] message:"Predicate " + predicate + " should evaluate to TRUE"];
predicate = [CPPredicate predicateWithFormat: @"$record.$age = 34"];
[self assertTrue:[predicate evaluateWithObject:dict substitutionVariables:bindings] message:"Predicate " + predicate + " should evaluate to TRUE"];
// TEST built-in functions
predicate = [CPPredicate predicateWithFormat: @"sum:(1,1) = 2"];
[self assertTrue:[predicate evaluateWithObject:nil] message:"Predicate " + predicate + " should evaluate to TRUE"];
// predicate = [CPPredicate predicateWithFormat: @"multiply:by:(5,3) = 15"];
// [self assertTrue:[predicate evaluateWithObject:nil] message:"Predicate " + predicate + " should evaluate to TRUE"];
// TEST custom functions
predicate = [CPPredicate predicateWithFormat:@"FUNCTION('a/path', 'lastPathComponent') = 'path'"];
[self assertTrue:[predicate evaluateWithObject:nil] message:"Predicate " + predicate + " should evaluate to TRUE"];
predicate = [CPPredicate predicateWithFormat:@"FUNCTION('a/path', 'substringFromIndex:', 2) = 'path'"];
[self assertTrue:[predicate evaluateWithObject:nil] message:"Predicate " + predicate + " should evaluate to TRUE"];
// TEST Subquery -- This means: search people who have 2 boys.
predicate = [CPPredicate predicateWithFormat: @"SUBQUERY(Record1.Children, $x, $x BEGINSWITH 'Kid')[SIZE] = 2"];
[self assertTrue:[predicate evaluateWithObject:dict] message:"Predicate " + predicate + " should evaluate to TRUE"];
// Test Set expressions
// Parsing is ok but the evaluation of this predicate will return NO for 2 reasons:
// 1- CPSet -isEqual: is unimplemented.
// 2- lhs will evaluate to a CPSet and rhs to a CPArray (aggregate exp). Comparing sets against arrays will always fail in CPComparisonPredicate. This is also cocoa behavior but i guess it's for historical reasons (set expressions are 10.5+) and should be changed in capp in my opinion.
var object = [CPDictionary dictionaryWithObject:[CPSet setWithObjects:@"a"] forKey:"a"],
result = [CPSet setWithObjects:@"a",@"b"];
predicate = [CPPredicate predicateWithFormat:@"a UNION {'b'} = {'a','b'}"];
var left = [[predicate leftExpression] expressionValueWithObject:object context:nil];
[self assertTrue:[left isEqualToSet:result] message:"Expression eval " + left + " should be " + result];
}
@end