mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-06 10:53:42 +00:00
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:
@@ -1,3 +1,4 @@
|
||||
|
||||
@import "CPArray.j"
|
||||
@import "CPNull.j"
|
||||
@import "CPString.j"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user