From 6ecc557abb06a21f044778bc3a114d1f2ad8c1c4 Mon Sep 17 00:00:00 2001 From: cacaodev Date: Sat, 14 Feb 2009 15:51:08 +0100 Subject: [PATCH] CPPredicate + CPCompoundPredicate + CPComparisonPredicate + CPExpression + CPPredicateTest.j --- Foundation/CPPredicate/CPComparisonPredicate.j | 479 ++++++++++++++++++++ Foundation/CPPredicate/CPCompoundPredicate.j | 134 ++++++ Foundation/CPPredicate/CPExpression.j | 163 +++++++ Foundation/CPPredicate/CPExpression_aggregate.j | 96 ++++ Foundation/CPPredicate/CPExpression_assignment.j | 43 ++ Foundation/CPPredicate/CPExpression_constant.j | 52 +++ Foundation/CPPredicate/CPExpression_function.j | 358 +++++++++++++++ Foundation/CPPredicate/CPExpression_intersectset.j | 48 ++ Foundation/CPPredicate/CPExpression_keypath.j | 49 ++ Foundation/CPPredicate/CPExpression_minusset.j | 44 ++ Foundation/CPPredicate/CPExpression_operator.j | 115 +++++ Foundation/CPPredicate/CPExpression_self.j | 39 ++ Foundation/CPPredicate/CPExpression_unionset.j | 44 ++ Foundation/CPPredicate/CPExpression_variable.j | 55 +++ Foundation/CPPredicate/CPPredicate.j | 119 +++++ Tests/Foundation/CPPredicateTest.j | 184 ++++++++ 16 files changed, 2022 insertions(+), 0 deletions(-) create mode 100644 Foundation/CPPredicate/CPComparisonPredicate.j create mode 100644 Foundation/CPPredicate/CPCompoundPredicate.j create mode 100644 Foundation/CPPredicate/CPExpression.j create mode 100644 Foundation/CPPredicate/CPExpression_aggregate.j create mode 100644 Foundation/CPPredicate/CPExpression_assignment.j create mode 100644 Foundation/CPPredicate/CPExpression_constant.j create mode 100644 Foundation/CPPredicate/CPExpression_function.j create mode 100644 Foundation/CPPredicate/CPExpression_intersectset.j create mode 100644 Foundation/CPPredicate/CPExpression_keypath.j create mode 100644 Foundation/CPPredicate/CPExpression_minusset.j create mode 100644 Foundation/CPPredicate/CPExpression_operator.j create mode 100644 Foundation/CPPredicate/CPExpression_self.j create mode 100644 Foundation/CPPredicate/CPExpression_unionset.j create mode 100644 Foundation/CPPredicate/CPExpression_variable.j create mode 100644 Foundation/CPPredicate/CPPredicate.j create mode 100644 Tests/Foundation/CPPredicateTest.j diff --git a/Foundation/CPPredicate/CPComparisonPredicate.j b/Foundation/CPPredicate/CPComparisonPredicate.j new file mode 100644 index 0000000..00a70f6 --- /dev/null +++ b/Foundation/CPPredicate/CPComparisonPredicate.j @@ -0,0 +1,479 @@ + +@import +@import +@import +@import +@import "CPPredicate.j" +@import "CPExpression.j" +@import "CPExpression_operator.j" + +CPDirectPredicateModifier = 0; +CPAllPredicateModifier = 1; +CPAnyPredicateModifier = 2; + +CPLessThanPredicateOperatorType = 0; +CPLessThanOrEqualToPredicateOperatorType = 1; +CPGreaterThanPredicateOperatorType = 2; +CPGreaterThanOrEqualToPredicateOperatorType = 3; +CPEqualToPredicateOperatorType = 4; +CPNotEqualToPredicateOperatorType = 5; +CPMatchesPredicateOperatorType = 6; +CPLikePredicateOperatorType = 7; +CPBeginsWithPredicateOperatorType = 8; +CPEndsWithPredicateOperatorType = 9; +CPInPredicateOperatorType = 10; +CPCustomSelectorPredicateOperatorType = 11; +CPContainsPredicateOperatorType = 99; +CPBetweenPredicateOperatorType = 100; + +CPCaseInsensitivePredicateOption = 1; +CPDiacriticInsensitivePredicateOption = 2; + +CPDiacriticInsensitiveSearch = 128; + +var CPComparisonPredicateModifier; +var CPPredicateOperatorType; + +@implementation CPString (Compare) + +- (CPComparisonResult)compare:(CPString)aString options:(int)aMask range:(CPRange)range +{ + if (range.location + range.length > [self length]) + [CPException raise:CPRangeException reason:"Range exceeds the length of the receiver"]; // should raise in cocoa but doesn't + + var lhs = [self substringWithRange:range], + rhs = aString; + + return [lhs.stripDiacritics() compare:rhs.stripDiacritics() options:aMask]; +} + +@end + +@implementation CPComparisonPredicate : CPPredicate +{ + CPExpression _left; + CPExpression _right; + + CPComparisonPredicateModifier _modifier; + CPPredicateOperatorType _type; + unsigned int _options; + SEL _customSelector; +} + +- (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(CPPredicateOperatorType)type options:(unsigned)options +{ + _left = left; + _right = right; + _modifier = modifier; + _type = type; + _options = options; + + if (type != CPMatchesPredicateOperatorType && + type != CPLikePredicateOperatorType && + type != CPBeginsWithPredicateOperatorType && + type != CPEndsWithPredicateOperatorType && + type != CPInPredicateOperatorType && + type != CPContainsPredicateOperatorType) + _options = 0; + + _customSelector = 0; + + return self; +} + +- (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector +{ + _left = left; + _right = right; + _modifier = CPDirectPredicateModifier; + _type = CPCustomSelectorPredicateOperatorType; + _options = 0; + _customSelector = selector; + + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + // UNIMPLEMENTED + return self; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + // UNIMPLEMENTED +} + ++ (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(int)type options:(unsigned)options +{ + return [[self alloc] initWithLeftExpression:left rightExpression:right modifier:modifier type:type options:options]; +} + ++ (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector +{ + return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector]; +} + +- (CPString)predicateFormat +{ + var modifier; + + switch (_modifier) + { + case CPDirectPredicateModifier: + modifier = ""; + break; + + case CPAllPredicateModifier: + modifier = "ALL "; + break; + + case CPAnyPredicateModifier: + modifier = "ANY "; + break; + + default: + modifier = ""; + break; + } + + var options; + + switch (_options) + { + case CPCaseInsensitivePredicateOption: + options = "[c]"; + break; + + case CPDiacriticInsensitivePredicateOption: + options = "[d]"; + break; + + case CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption: + options = "[cd]"; + break; + + default: + options = ""; + break; + } + + var operator; + + switch (_type) + { + case CPLessThanPredicateOperatorType: + operator = "<"; + break; + + case CPLessThanOrEqualToPredicateOperatorType: + operator = "<="; + break; + + case CPGreaterThanPredicateOperatorType: + operator = ">"; + break; + + case CPGreaterThanOrEqualToPredicateOperatorType: + operator = ">="; + break; + + case CPEqualToPredicateOperatorType: + operator = "=="; + break; + + case CPNotEqualToPredicateOperatorType: + operator = "!="; + break; + + case CPMatchesPredicateOperatorType: + operator = "MATCHES"; + break; + + case CPLikePredicateOperatorType: + operator = "LIKE"; + break; + + case CPBeginsWithPredicateOperatorType: + operator = "BEGINSWITH"; + break; + + case CPEndsWithPredicateOperatorType: + operator = "ENDSWITH"; + break; + + case CPInPredicateOperatorType: + operator = "IN"; + break; + case CPContainsPredicateOperatorType: + operator = "CONTAINS"; + break; + + // FIX, not right + case CPCustomSelectorPredicateOperatorType: + operator = CPStringFromSelector(_customSelector); + break; + } + + return [CPString stringWithFormat:@"%s%s %s%s %s",modifier,[_left description],operator,options,[_right description]]; + +} + +- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables +{ + var left = [_left _expressionWithSubstitutionVariables:variables]; + var right = [_right _expressionWithSubstitutionVariables:variables]; + + if (_type != CPCustomSelectorPredicateOperatorType) + return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:_modifier type:_type options:_options]; + else + return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right customSelector:_customSelector]; +} + +- (CPExpression)leftExpression +{ + return _left; +} + +- (CPExpression)rightExpression +{ + return _right; +} + +- (CPPredicateOperatorType)predicateOperatorType +{ + return _type; +} + +- (CPComparisonPredicateModifier)comparisonPredicateModifier +{ + return _modifier; +} + +- (unsigned)options +{ + return _options; +} + +- (SEL)customSelector +{ + return _customSelector; +} + +- (BOOL)_evaluateValue:lhs rightValue:rhs +{ + + var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]); + var rightIsNil = (rhs == nil || [rhs isEqual:[CPNull null]]); + + if (leftIsNil || rightIsNil) + return ((leftIsNil == rightIsNil) && + (_type == CPEqualToPredicateOperatorType || + _type == CPLessThanOrEqualToPredicateOperatorType || + _type == CPGreaterThanOrEqualToPredicateOperatorType)); + + var string_compare_options = 0; + + // left and right should be casted first [CAST()] following 10.5 rules. + switch (_type) + { + case CPLessThanPredicateOperatorType: + return ([lhs compare:rhs] == CPOrderedAscending); + + case CPLessThanOrEqualToPredicateOperatorType: + return ([lhs compare:rhs] != CPOrderedDescending); + + case CPGreaterThanPredicateOperatorType: + return ([lhs compare:rhs] == CPOrderedDescending); + + case CPGreaterThanOrEqualToPredicateOperatorType: + return ([lhs compare:rhs] != CPOrderedAscending); + + case CPEqualToPredicateOperatorType: + return [lhs isEqual:rhs]; + + case CPNotEqualToPredicateOperatorType: + return (![lhs isEqual:rhs]); + + case CPMatchesPredicateOperatorType: + if (_options & CPDiacriticInsensitivePredicateOption) + { + lhs = lhs.stripDiacritics(); + rhs = rhs.stripDiacritics(); + } + + var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g"; + var reg = new RegExp(rhs,commut); + return reg.test(lhs); + + case CPLikePredicateOperatorType: + if (_options & CPDiacriticInsensitivePredicateOption) + { + lhs = lhs.stripDiacritics(); + rhs = rhs.stripDiacritics(); + } + var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g"; + var reg = new RegExp(rhs.escapeForRegExp(),commut); + return reg.test(lhs); + + case CPBeginsWithPredicateOperatorType: + var range = CPMakeRange(0,[rhs length]); + if (_options & CPCaseInsensitivePredicateOption) string_compare_options |= CPCaseInsensitiveSearch; + if (_options & CPDiacriticInsensitivePredicateOption) string_compare_options |= CPDiacriticInsensitiveSearch; + + return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame); + + case CPEndsWithPredicateOperatorType: + var range = CPMakeRange([lhs length] - [rhs length],[rhs length]); + if (_options & CPCaseInsensitivePredicateOption) string_compare_options |= CPCaseInsensitiveSearch; + if (_options & CPDiacriticInsensitivePredicateOption) string_compare_options |= CPDiacriticInsensitiveSearch; + + return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame); + + case CPInPredicateOperatorType: + // Handle special case where rhs is a collection and lhs an element of it. + if (![rhs isKindOfClass: [CPString class]]) + { + if (![rhs respondsToSelector: @selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The right hand side for an IN operator must be a collection"]; + + var e = [rhs objectEnumerator], + value; + while (value = [e nextObject]) + if ([value isEqual:lhs]) + return YES; + + return NO; + } + + if (_options & CPCaseInsensitivePredicateOption) string_compare_options |= CPCaseInsensitiveSearch; + if (_options & CPDiacriticInsensitivePredicateOption) string_compare_options |= CPDiacriticInsensitiveSearch; + + return ([rhs rangeOfString:lhs options:string_compare_options].location != CPNotFound); + + case CPCustomSelectorPredicateOperatorType: + return [lhs performSelector:_customSelector withObject:rhs]; + + case CPContainsPredicateOperatorType: + + if (![lhs isKindOfClass: [CPString class]]) + { + if (![lhs respondsToSelector: @selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The left hand side for a CONTAINS operator must be a collection"]; + + var e = [lhs objectEnumerator], + value; + while (value = [e nextObject]) + if ([value isEqual:rhs]) + return YES; + + return NO; + } + + if (_options & CPCaseInsensitivePredicateOption) string_compare_options |= CPCaseInsensitiveSearch; + if (_options & CPDiacriticInsensitivePredicateOption) string_compare_options |= CPDiacriticInsensitiveSearch; + + return ([lhs rangeOfString:rhs options:string_compare_options].location != CPNotFound); + + case CPBetweenPredicateOperatorType: + if ([lhs count] <2) + [CPException raise:CPInvalidArgumentException reason:@"The right hand side for a BETWEEN operator must contain 2 objects"]; + + var lower = [rhs objectAtIndex:0]; + var upper = [rhs objectAtIndex:1]; + + return ([lhs compare:lower] == CPOrderedDescending && [lhs compare:upper] == CPOrderedAscending); + default: + return NO; + } +} + +- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables +{ + var p = [self predicateWithSubstitutionVariables:variables]; + return [p evaluateWithObject:object]; +} + +- (BOOL)evaluateWithObject:(id)object +{ + var leftValue = [_left expressionValueWithObject:object context:nil]; + var rightValue = [_right expressionValueWithObject:object context:nil]; + + if (_modifier == CPDirectPredicateModifier) + return [self _evaluateValue:leftValue rightValue:rightValue]; + else + { + if (![leftValue respondsToSelector:@selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The left hand side for an ALL or ANY operator must be either a CPArray or a CPSet"]; + + var e = [leftValue objectEnumerator], + result = (_modifier == CPAllPredicateModifier), + value; + + while (value = [e nextObject]) + { + var eval = [self _evaluateValue:value rightValue:rightValue]; + if (eval != result) + return eval; + } + + return result; + } +} + +@end + +var diacritics = [[192,198],[224,230],[231,231],[232,235],[236,239],[242,246],[249,252]]; +var normalized = [65,97,99,101,105,111,117]; + +String.prototype.stripDiacritics = function () +{ + var output = ""; + for (var indexSource = 0; indexSource < this.length; indexSource++) + { + var code = this.charCodeAt(indexSource); + + for (var i = 0; i < diacritics.length; i++) + { + var drange = diacritics[i]; + + if (code >= drange[0] && code <= drange[1]) + { + code = normalized[i]; + break; + } + } + + output += String.fromCharCode(code); + } + + return output; +} + +var source = ['(',')','{','}','.','*','+','?','|']; +var c = ['\\(','\\)','\\{','\\}','\\.','.*','\\+','.?','\\|']; + +String.prototype.escapeForRegExp = function() +{ + var foundChar = false; + for (var i = 0; i < source.length; ++i) { + if (this.indexOf(source[i]) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + var sourceIndex; + for (var i = 0; i < this.length; ++i) { + var sourceIndex = source.indexOf(this.charAt(i)); + if (sourceIndex !== -1) + result += dest[sourceIndex]; + else + result += this.charAt(i); + } + return result; +} diff --git a/Foundation/CPPredicate/CPCompoundPredicate.j b/Foundation/CPPredicate/CPCompoundPredicate.j new file mode 100644 index 0000000..e221392 --- /dev/null +++ b/Foundation/CPPredicate/CPCompoundPredicate.j @@ -0,0 +1,134 @@ +@import "CPPredicate.j" +@import +@import + +var CPCompoundPredicateType; +CPNotPredicateType = 0; +CPAndPredicateType = 1; +CPOrPredicateType = 2; + +@implementation CPCompoundPredicate : CPPredicate +{ + CPCompoundPredicateType _type; + CPArray _predicates; + +} + +- (id)initWithType:(CPCompoundPredicateType)type subpredicates:(CPArray)predicates +{ + _type = type; + _predicates = predicates; + return self; +} + ++ (CPPredicate)notPredicateWithSubpredicate:(CPPredicate)predicate +{ + return [[self alloc] initWithType:CPNotPredicateType subpredicates:[CPArray arrayWithObject:predicate]]; +} + ++ (CPPredicate)andPredicateWithSubpredicates:(CPArray)predicates +{ + return [[self alloc] initWithType:CPAndPredicateType subpredicates:predicates]; +} + ++ (CPPredicate)orPredicateWithSubpredicates:(CPArray)predicates +{ + return [[self alloc] initWithType:CPOrPredicateType subpredicates:predicates]; +} + +- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables +{ + var subp = [CPArray array], + i; + + for (i =0; i < [subp count]; i++) + { + var p = [subp objectAtIndex:i]; + var sp = [p predicateWithSubstitutionVariables:variables]; + [subp addObject:sp]; + } + return [[CPCompoundPredicate alloc] initWithType:_type subpredicates:subp]; +} + +- (CPString)predicateFormat +{ + var result = "", + args = [CPArray array], + i; + + for (i = 0;i < [_predicates count];i++) + { + var subpredicate = [_predicates objectAtIndex:i]; + var precedence = [subpredicate predicateFormat]; + + if ([subpredicate isKindOfClass:[CPCompoundPredicate class]] && [[subpredicate subpredicates] count]> 1 && [subpredicate compoundPredicateType] != _type) + precedence = [CPString stringWithFormat:@"(%s)",precedence]; + + if(precedence != nil) [args addObject:precedence]; + } + + switch (_type) + { + case CPNotPredicateType: + result += "NOT %s" + [args objectAtIndex:0]; + break; + + case CPAndPredicateType: + result += [args objectAtIndex:0]; + for(var j=1;j<[args count];j++) result += " AND " + [args objectAtIndex:j]; + break; + + case CPOrPredicateType: + result += [args objectAtIndex:0]; + + for(var j=1;j<[args count];j++) result += " OR " + [args objectAtIndex:j]; + break; + } + + + return result; +} + +- (CPCompoundPredicateType)compoundPredicateType +{ + return _type; +} + +- (CPArray)subpredicates +{ + return _predicates; +} + +- (BOOL)evaluateWithObject:(id)object +{ + var result = NO, + count = [_predicates count], + i; + + for (i = 0;i < count;i++) + { + var predicate = [_predicates objectAtIndex:i]; + + switch (_type) + { + case CPNotPredicateType: + return ![predicate evaluateWithObject:object]; + + case CPAndPredicateType: + if (i == 0) + result = [predicate evaluateWithObject:object]; + else + result = result && [predicate evaluateWithObject:object]; + break; + + case CPOrPredicateType: + if ([predicate evaluateWithObject:object]) + return YES; + break; + } + } + + return result; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression.j b/Foundation/CPPredicate/CPExpression.j new file mode 100644 index 0000000..39e49fe --- /dev/null +++ b/Foundation/CPPredicate/CPExpression.j @@ -0,0 +1,163 @@ + +@import +@import +@import +@import +@import + +CPConstantValueExpressionType = 0; +CPEvaluatedObjectExpressionType = 1; +CPVariableExpressionType = 2; +CPKeyPathExpressionType = 3; +CPFunctionExpressionType = 4; +CPAggregateExpressionType = 5; +CPSubqueryExpressionType = 6; +CPUnionSetExpressionType = 7; +CPIntersectSetExpressionType = 8; +CPMinusSetExpressionType = 9; + +@implementation CPExpression : CPObject +{ + int _type; +} + +- (id)initWithExpressionType:(int)type +{ + _type = type; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ +// IMPLEMENTED BY CONCRETE SUBCLASSES +} + +- (void)encodeWithCoder:(CPCoder)coder +{ +// IMPLEMENTED BY CONCRETE SUBCLASSES +} + ++ (CPExpression)expressionForConstantValue:(id)value +{ + return [[CPExpression_constant alloc] initWithValue:value]; +} + ++ (CPExpression)expressionForEvaluatedObject +{ + return [[CPExpression_self alloc] init]; +} + ++ (CPExpression)expressionForVariable:(CPString)string +{ + return [[CPExpression_variable alloc] initWithVariable:string]; +} + ++ (CPExpression)expressionForKeyPath:(CPString)keyPath +{ + return [[CPExpression_keypath alloc] initWithKeyPath:keyPath]; +} + ++ (CPExpression)expressionForFunction:(CPString)function_name arguments:(CPArray)arguments +{ + + var expression_function = [[CPExpression_function alloc] initWithName:function_name arguments:arguments]; + + var selector = CPSelectorFromString(function_name); + + if (![expression_function respondsToSelector:selector]) + [CPException raise: CPInvalidArgumentException reason:@"Unknown function implementation: " + function_name]; + + [expression_function setSelector:selector]; + + return expression_function; +} + ++ (CPExpression)expressionForAggregate:(CPArray)collection +{ + return [[CPExpression_aggregate alloc] initWithAggregate:collection]; +} + ++ (CPExpression)expressionForSubquery:(CPExpression)expression usingIteratorVariable:(CPString)variable predicate:(id)predicate +{ + return nil; // UNIMPLEMENTED +} + ++ (CPExpression)expressionForUnionSet:(CPExpression)left with:(CPExpression)right +{ + return [[CPExpression_unionset alloc] initWithLeft:left right:right]; +} + ++ (CPExpression)expressionForIntersectSetSet:(CPExpression)left with:(CPExpression)right +{ + return [[CPExpression_intersectset alloc] initWithLeft:left right:right]; +} + ++ (CPExpression)expressionForMinusSet:(CPExpression)left with:(CPExpression)right +{ + return [[CPExpression_minusset alloc] initWithLeft:left right:right]; +} + +- (int)expressionType +{ + return _type; +} + +- (id)constantValue +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPConstantValueExpressionType"]; + return nil; +} + +- (CPString)variable +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPVariableExpressionType"]; + return nil; +} + +- (CPString)keyPath +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPKeyPathExpressionType"]; + return nil; +} + +- (CPString)function +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"]; + return nil; +} + +- (CPArray)arguments +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"]; + return nil; +} + +- (id)collection +{ + [CPException raise:CPInvalidArgumentException reason:@"self is not of CPAggregateExpressionType"]; + return nil; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ +// IMPLEMENTED BY SUBCLASSES + return nil; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ +// IMPLEMENTED BY SUBCLASSES + return self; +} + +@end + +@import "CPExpression_constant.j" +@import "CPExpression_self.j" +@import "CPExpression_variable.j" +@import "CPExpression_keypath.j" +@import "CPExpression_function.j" +@import "CPExpression_aggregate.j" +@import "CPExpression_unionset.j" +@import "CPExpression_intersectset.j" +@import "CPExpression_minusset.j" diff --git a/Foundation/CPPredicate/CPExpression_aggregate.j b/Foundation/CPPredicate/CPExpression_aggregate.j new file mode 100644 index 0000000..d46d3ce --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_aggregate.j @@ -0,0 +1,96 @@ + +@import "CPExpression.j" +@import +@import + +@implementation CPExpression_aggregate : CPExpression +{ + CPArray _aggregate; +} + +- (id)initWithAggregate:(CPArray)collection +{ + [super initWithExpressionType:CPAggregateExpressionType]; + _aggregate = collection; + return self; +} + ++ (CPExpression)expressionForAggregate:(CPArray)collection +{ + return [[self alloc] initWithAggregate:collection]; +} + +- (id)initWithCoder:(CPCoder)coder +{ + var aggregate = [coder decodeObjectForKey:@"CPExpressionAggregate"]; + return [self initWithAggregate:aggregate]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + [coder encodeObject:_aggregate forKey:@"CPExpressionAggregate"]; // subexpressions must be CPCoding compliant. +} + +- (id)collection +{ + return _aggregate; +} + +- (CPExpression)rightExpression +{ + if ([_aggregate count] > 0) + return [_aggregate lastObject]; + + return nil; +} + +- (CPExpression)leftExpression +{ + if ([_aggregate count] > 0) + return [_aggregate objectAtIndex:0]; + + return nil; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var eval_array = [CPArray array], + collection = [_aggregate objectEnumerator], + exp; + + while (exp = [collection nextObject]) + { + var eval = [exp expressionValueWithObject:object context:context]; + if (eval != nil)[eval_array addObject:eval]; + } + + return eval_array; +} + +- (CPString)description +{ + var i, + count = [_aggregate count], + result = "{"; + + for (i = 0;i < count;i++) + result = result + [CPString stringWithFormat:@"%s%s", [[_aggregate objectAtIndex:i] description], (i + 1 < count) ? @", " : @""]; + + result = result + "}"; + + return result; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + var subst_array = [CPArray array], + count = [_aggregate count], + i; + + for (i = 0; i < count; i++) + [subst_array addObject:[[_aggregate objectAtIndex:i] _expressionWithSubstitutionVariables:variables]]; + + return [CPExpression expressionForAggregate:subst_array]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_assignment.j b/Foundation/CPPredicate/CPExpression_assignment.j new file mode 100644 index 0000000..c9da3a7 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_assignment.j @@ -0,0 +1,43 @@ + +@import "CPExpression.j" +@import "CPExpression_operator.j" +@import + + +@implementation CPExpression_assignment: CPExpression +{ + CPExpression _variable; + CPExpression _expression; +} + +- (id)initWithVariable:(CPExpression)variable expression:(CPExpression)expression +{ + _variable = variable; + _expression = expression; + + return self; +} + ++ (CPExpression)expressionWithVariable:(CPExpression)variable expression:(CPExpression)expression +{ + return [[self alloc] initWithVariable:variable expression:expression]; +} + +- (CPString)description +{ + var pretty = [_expression description]; + + if ([_expression isKindOfClass:[CPExpression_operator class]]) + pretty = [CPString stringWithFormat:@"(%@)", pretty]; + + return [CPString stringWithFormat:@"%@ := %@", _variable, pretty]; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ +// FIX? + return self; +} + +@end + diff --git a/Foundation/CPPredicate/CPExpression_constant.j b/Foundation/CPPredicate/CPExpression_constant.j new file mode 100644 index 0000000..bd2daa8 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_constant.j @@ -0,0 +1,52 @@ + +@import "CPExpression.j" +@import + +@implementation CPExpression_constant : CPExpression +{ + id _value; +} + +- (id)initWithValue:(id)value +{ + [super initWithExpressionType:CPConstantValueExpressionType]; + _value = value; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + var value = [coder decodeObjectForKey:@"CPExpressionConstantValue"]; + return [self initWithValue:value]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + if ([_value respondsToSelector:@selector(encodeOject:forKey:)]) + [coder encodeObject:_value forKey:@"CPExpressionConstantValue"]; +} + +- (id)constantValue +{ + return _value; +} + +- (id)expressionValueWithObject:object context:(CPDictionary)context +{ + return _value; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + return self; +} + +- (CPString)description +{ + if ([_value isKindOfClass:[CPString class]]) + return [CPString stringWithFormat:@"\"%s\"",_value]; + else + return [_value description]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_function.j b/Foundation/CPPredicate/CPExpression_function.j new file mode 100644 index 0000000..6bb9af1 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_function.j @@ -0,0 +1,358 @@ +@import "CPExpression.j" +@import +@import +@import + +@implementation CPExpression_function : CPExpression +{ + CPString _name; + SEL _selector; + CPArray _arguments; + int _argc; + CPInvocation _invocation; +} + +- (void)setSelector:(SEL)selector +{ + _selector = selector; + /* + var signature = [CPExpression_function methodSignatureForSelector:selector]; + _invocation = [CPInvocation invocationWithMethodSignature:signature]; + [_invocation setSelector:_selector]; + [_invocation setTarget:self]; + */ +} + +- (id)initWithName:(CPString)name arguments:(CPArray)arguments +{ + [super initWithExpressionType:CPFunctionExpressionType]; + + _name = name; + _arguments = arguments; + _argc = [_arguments count]; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + var name = [coder decodeObjectForKey:@"CPExpressionFunctionName"], + arguments = [coder decodeObjectForKey:@"CPExpressionFunctionArguments"]; + + return [self initWithName:name arguments:arguments]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + [coder encodeObject:_name forKey:@"CPExpressionFunctionName"]; + [coder encodeObject:_arguments forKey:@"CPExpressionArguments"]; +} + +- (CPString)function +{ + return _name; +} + +- (CPArray)arguments +{ + return _arguments; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var eval_args = [CPArray array], + i; + + for (i = 0; i < _argc; i++) + { + var arg = [[_arguments objectAtIndex:i] expressionValueWithObject:object context:context]; + if(arg != nil) [eval_args addObject:arg]; + } + + return [self performSelector:_selector withObject:eval_args]; +} + +- (CPString)description +{ + var result = [CPString stringWithFormat:@"%s(", _name], + i; + + for (i = 0; i < _argc; i++){ + result = result + [CPString stringWithFormat:@"%s%s", [_arguments objectAtIndex:i] , (i+1<_argc) ? ", " : ""]; + } + + result = result + ")"; + + return result ; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + var array = [CPArray array], + i; + + for (i = 0; i < _argc; i++) + [array addObject:[[_arguments objectAtIndex:i] _expressionWithSubstitutionVariables:variables]]; + + return [CPExpression expressionForFunction:_name arguments:array]; +} + +/* +10.5 documentation for expressionForFunction:arguments: + + This method throws an exception immediately if the selector is unknown; it throws at runtime if the parameters are incorrect. + Parameters + name + The name of the function to invoke. + + parameters + An array containing NSExpression objects that will be used as parameters during the invocation of selector. + + For a selector taking no parameters, the array should be empty. For a selector taking one or more parameters, the array should contain one NSExpression object which will evaluate to an instance of the appropriate type for each parameter. + + If there is a mismatch between the number of parameters expected and the number you provide during evaluation, an exception may be raised or missing parameters may simply be replaced by nil (which occurs depends on how many parameters are provided, and whether you have over- or underflow). + + Return Value + A new expression that invokes the function name using the parameters in parameters. +*/ + +// 10.4 Functions are sum, count, min, max, average + + +- (CPNumber)average:(CPArray)parameters +{ + if(_argc < 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var i, + sum = 0.0; + + for (i = 0; i < _argc; i++) + { + sum += [[parameters objectAtIndex: i] doubleValue]; + } + return [CPNumber numberWithDouble: sum / _argc]; +} + +- (CPNumber)sum:(CPArray)parameters +{ + if(_argc < 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var i, + sum = 0.0; + + for (i = 0; i < _argc; i++) + { + sum += [[parameters objectAtIndex: i] doubleValue]; + } + return [CPNumber numberWithDouble: sum]; +} + +- (CPNumber)count:(CPArray)parameters +{ + if(_argc < 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + return [CPNumber numberWithUnsignedInt: [[parameters objectAtIndex: 0] count]]; +} + +- (CPNumber)min:(CPArray)parameters +{ + if(_argc < 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + return MIN([parameters objectAtIndex: 0],[parameters objectAtIndex: 1]); +} + +- (CPNumber)max:(CPArray)parameters +{ + if(_argc < 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + return MAX([parameters objectAtIndex: 0],[parameters objectAtIndex: 1]); +} + +- (CPNumber)add:to:(CPArray)parameters +{ + if(_argc != 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var left = [parameters objectAtIndex: 0], + right = [parameters objectAtIndex: 1]; + + return [CPNumber numberWithDouble: [left doubleValue] + [right doubleValue]]; +} + +- (CPNumber)from:subtract:(CPArray)parameters +{ + if(_argc != 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var left = [parameters objectAtIndex: 0], + right = [parameters objectAtIndex: 1]; + + return [CPNumber numberWithDouble: [left doubleValue] - [right doubleValue]]; +} + +- (CPNumber)multiply:by:(CPArray)parameters +{ + if(_argc != 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var left = [parameters objectAtIndex: 0], + right = [parameters objectAtIndex: 1]; + + return [CPNumber numberWithDouble: [left doubleValue] * [right doubleValue]]; +} + +- (CPNumber)divide:by:(CPArray)parameters +{ + if(_argc != 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var left = [parameters objectAtIndex: 0], + right = [parameters objectAtIndex: 1]; + + return [CPNumber numberWithDouble: [left doubleValue] / [right doubleValue]]; +} + +- (CPNumber)sqrt:(CPArray)parameters +{ + if(_argc != 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + + return [CPNumber numberWithDouble: SQRT(num)]; +} + +- (CPNumber)raise:to:(CPArray)parameters +{ + if(_argc < 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + var power = [[parameters objectAtIndex: 1] doubleValue]; + + return [CPNumber numberWithDouble: POW(num,power)]; + +} + +- (CPNumber)abs:(CPArray)parameters +{ + if(_argc != 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + + return [CPNumber numberWithDouble:ABS(num)]; +} + +- (CPDate)now +{ + return [CPDate date]; +} + +- (CPNumber)ln:(CPArray)parameters +{ + if(_argc != 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + + return [CPNumber numberWithDouble:Math.log(num)]; +} + +- (CPNumber)exp:(CPArray)parameters +{ + if(_argc != 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + + return [CPNumber numberWithDouble:EXP(num)]; +} + +- (CPNumber)ceiling:(CPArray)parameters +{ + if(_argc != 1) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var num = [[parameters objectAtIndex: 0] doubleValue]; + + return [CPNumber numberWithDouble:CEIL(num)]; +} + +- (CPNumber)random +{ + return [CPNumber numberWithDouble:RAND()]; +} + +- (CPNumber)modulus:by:(CPArray)parameters +{ + if(_argc != 2) + [CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"]; + + var left = [parameters objectAtIndex: 0], + right = [parameters objectAtIndex: 1]; + + return [CPNumber numberWithInt:([left intValue] % [right intValue])]; +} + + +/* +- (CPNumber)median:(CPArray)parameters +{ +} +- (CPNumber)mode:(CPArray)parameters +{ +} +- (CPNumber)stddev:(CPArray)parameters +{ +} +- (CPNumber)log:(CPArray)parameters +{ +} +- (CPNumber)raise:to:(CPArray)parameters +{ +} + +- (CPNumber)trunc:(CPArray)parameters +{ +} + +// These functions are generated when parsing + +- (id) first: (CPArray)parameters +{ + return [[parameters objectAtIndex: 0] objectAtIndex: 0]; +} + +- (id) last: (CPArray)parameters +{ + return [[parameters objectAtIndex: 0] lastObject]; +} + +- (CPNumber)chs:(CPArray)parameters +{ + return [CPNumber numberWithInt: -[[parameters objectAtIndex: 0] intValue]]; +} + +- (id)index:(CPArray)parameters +{ + var left = [parameters objectAtIndex: 0]; + var right = [parameters objectAtIndex: 1]; + + if ([left isKindOfClass: [CPDictionary class]]) + { + return [left objectForKey: right]; + } + else + { + // raises exception if invalid + return [left objectAtIndex: [right unsignedIntValue]]; + } +} +*/ + +@end diff --git a/Foundation/CPPredicate/CPExpression_intersectset.j b/Foundation/CPPredicate/CPExpression_intersectset.j new file mode 100644 index 0000000..1885e5a --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_intersectset.j @@ -0,0 +1,48 @@ +@import "CPExpression.j" + +@implementation CPExpression_intersectset : CPExpression +{ + CPExpression _left; + CPExpression _right; +} + +- (id)initWithLeft:(CPExpression)left right:(CPExpression)right +{ + [super initWithExpressionType:CPIntersectSetExpressionType]; + _left = left ; + _right = right; + + return self; +} + +- (id)expressionValueWithObject:object context:(CPDictionary)context +{ + var right = [_right expressionValueWithObject:object context:context]; + if (![right respondsToSelector: @selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The right expression for a CPIntersectSetExpressionType expression must be either a CPArray, CPDictionary or CPSet"]; + + var left = [_left expressionValueWithObject:object context:context]; + if (![left isKindOfClass:[CPSet set]]) + [CPException raise:CPInvalidArgumentException reason:@"The left expression for a CPIntersectSetExpressionType expression must a CPSet"]; + + var set = [CPSet setWithSet:left], + e = [right objectEnumerator], + item; + + while (item = [e nextObject]) + if([left containsObject:item])[set addObject:item]; + + return [CPExpression expressionForConstantValue:set]; +} + +- (CPExpression )_expressionWithSubstitutionVariables:(CPDictionary )variables +{ + return self; +} + +- (CPString )description +{ + return [_left description] +" INTERSECT "+ [_right description]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_keypath.j b/Foundation/CPPredicate/CPExpression_keypath.j new file mode 100644 index 0000000..fe69f53 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_keypath.j @@ -0,0 +1,49 @@ + +@import "CPExpression.j" +@import +@import + +@implementation CPExpression_keypath : CPExpression +{ + CPString _keyPath; +} + +- (id)initWithKeyPath:(CPString)keyPath +{ + [super initWithExpressionType:CPKeyPathExpressionType]; + _keyPath = keyPath ; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + var keyPath = [coder decodeObjectForKey:@"CPExpressionKeyPath"]; + return [self initWithKeyPath:keyPath]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + [coder encodeObject:_keyPath forKey:@"CPExpressionKeyPath"]; +} + +- (CPString)keyPath +{ + return _keyPath; +} + +- (id)expressionValueWithObject:object context:(CPDictionary)context +{ + return [object valueForKeyPath:_keyPath]; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + return self; +} + +- (CPString)description +{ + return _keyPath; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_minusset.j b/Foundation/CPPredicate/CPExpression_minusset.j new file mode 100644 index 0000000..b835413 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_minusset.j @@ -0,0 +1,44 @@ +@import "CPExpression.j" + +@implementation CPExpression_minusset : CPExpression + +- (id)initWithLeft:(CPExpression)left right:(CPExpression)right +{ + [super initWithExpressionType:CPMinusSetExpressionType]; + _left = left ; + _right = right; + + return self; +} + +-(id)expressionValueWithObject:object context:(CPDictionary)context +{ + var right = [_right expressionValueWithObject:object context:context]; + if (![right respondsToSelector: @selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The right expression for a CPIntersectSetExpressionType expression must be either a CPArray, CPDictionary or CPSet"]; + + var left = [_left expressionValueWithObject:object context:context]; + if (![left isKindOfClass:[CPSet set]]) + [CPException raise:CPInvalidArgumentException reason:@"The left expression for a CPIntersectSetExpressionType expression must a CPSet"]; + + var set = [CPSet setWithSet:left], + e = [right objectEnumerator], + item; + + while (item = [e nextObject]) + [set removeObject:item]; + + return [CPExpression expressionForConstantValue:set]; +} + +- (CPExpression )_expressionWithSubstitutionVariables:(CPDictionary )variables +{ + return self; +} + +- (CPString )description +{ + return [_left description] +" MINUS "+ [_right description]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_operator.j b/Foundation/CPPredicate/CPExpression_operator.j new file mode 100644 index 0000000..6f019e0 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_operator.j @@ -0,0 +1,115 @@ + +@import "CPExpression.j" +@import +@import +@import + +var CPExpressionOperatorNegate = "CPExpressionOperatorNegate"; +var CPExpressionOperatorAdd = "CPExpressionOperatorAdd"; +var CPExpressionOperatorSubtract = "CPExpressionOperatorSubtract"; +var CPExpressionOperatorMultiply = "CPExpressionOperatorMultiply"; +var CPExpressionOperatorDivide = "CPExpressionOperatorDivide"; +var CPExpressionOperatorExp = "CPExpressionOperatorExp"; +var CPExpressionOperatorAssign = "CPExpressionOperatorAssign"; +var CPExpressionOperatorKeypath = "CPExpressionOperatorKeypath"; +var CPExpressionOperatorIndex = "CPExpressionOperatorIndex"; +var CPExpressionOperatorIndexFirst = "CPExpressionOperatorIndexFirst"; +var CPExpressionOperatorIndexLast = "CPExpressionOperatorIndexLast"; +var CPExpressionOperatorIndexSize = "CPExpressionOperatorIndexSize"; + +@implementation CPExpression_operator : CPExpression +{ + int _operator; + CPArray _arguments; +} + +- (id)initWithOperator:(int)operator arguments:(CPArray)arguments +{ + _operator = operator; + _arguments = arguments; + return self; +} + ++ (CPExpression)expressionForOperator:(CPExpressionOperator)operator arguments:(CPArray)arguments +{ + return [self initWithOperator:operator arguments:arguments]; +} + +- (CPArray)arguments +{ + return _arguments; +} + +- (CPString)description +{ + var result = [CPString string], + args = [CPArray array], + count = [_arguments count], + i; + + for (i = 0; i < count; i++) + { + var check = [_arguments objectAtIndex:i]; + var precedence = [check description]; + + if ([check isKindOfClass:[CPExpression_operator class]]) + precedence = [CPString stringWithFormat:@"(%@)", precedence]; + + [args addObject:precedence]; + } + + switch (_operator) + { + case CPExpressionOperatorNegate : result = result + [CPString stringWithFormat:@"-%@", [args objectAtIndex:0]]; + break; + + case CPExpressionOperatorAdd : result = result + [CPString stringWithFormat:@"%@ + %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorSubtract : result = result + [CPString stringWithFormat:@"%@ - %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorMultiply : result = result + [CPString stringWithFormat:@"%@ * %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorDivide : result = result + [CPString stringWithFormat:@"%@ / %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorExp : result = result + [CPString stringWithFormat:@"%@ ** %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorAssign : result = result + [CPString stringWithFormat:@"%@ := %@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorKeypath : result = result + [CPString stringWithFormat:@"%@.%@", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorIndex : result = result + [CPString stringWithFormat:@"%@[%@]", [args objectAtIndex:0], [args objectAtIndex:1]]; + break; + + case CPExpressionOperatorIndexFirst : result = result + [CPString stringWithFormat:@"%@[FIRST]", [args objectAtIndex:0]]; + break; + + case CPExpressionOperatorIndexLast : result = result + [CPString stringWithFormat:@"%@[LAST]", [args objectAtIndex:0]]; + break; + + case CPExpressionOperatorIndexSize : result = result + [CPString stringWithFormat:@"%@[SIZE]", [args objectAtIndex:0]]; + break; + } + + return result; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + var array = [CPArray array], + count = [_arguments count], + i; + + for (i = 0; i < count; i++) + [array addObject:[[_arguments objectAtIndex:i] _expressionWithSubstitutionVariables:variables]]; + + return [CPExpression_operator expressionForOperator:_operator arguments:array]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_self.j b/Foundation/CPPredicate/CPExpression_self.j new file mode 100644 index 0000000..8dd94b7 --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_self.j @@ -0,0 +1,39 @@ + +@import "CPExpression.j" +@import +@import +@import + +@implementation CPExpression_self : CPExpression{} + +- (id)init +{ + [super initWithExpressionType:CPEvaluatedObjectExpressionType]; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + return [self init]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ +} + +- (id)expressionValueWithObject:object context:(CPDictionary)context +{ + return object; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + return self; +} + +- (CPString)description +{ + return @"SELF"; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_unionset.j b/Foundation/CPPredicate/CPExpression_unionset.j new file mode 100644 index 0000000..e18af6d --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_unionset.j @@ -0,0 +1,44 @@ +@import "CPExpression.j" + +@implementation CPExpression_unionset + +- (id)initWithLeft:(CPExpression)left right:(CPExpression)right +{ + [super initWithExpressionType:CPUnionSetExpressionType]; + _left = left; + _right = right; + + return self; +} + +-(id)expressionValueWithObject:object context:(CPDictionary )context +{ + var right = [_right expressionValueWithObject:object context:context]; + if (![right respondsToSelector: @selector(objectEnumerator)]) + [CPException raise:CPInvalidArgumentException reason:@"The right expression for a CPIntersectSetExpressionType expression must be either a CPArray, CPDictionary or CPSet"]; + + var left = [_left expressionValueWithObject:object context:context]; + if (![left isKindOfClass:[CPSet set]]) + [CPException raise:CPInvalidArgumentException reason:@"The left expression for a CPIntersectSetExpressionType expression must a CPSet"]; + + var unionset = [CPSet setWithSet:left], + e = [right objectEnumerator], + item; + + while (item = [e nextObject]) + [unionset addObject:item]; + + return [CPExpression expressionForConstantValue:unionset]; +} + +- (CPExpression )_expressionWithSubstitutionVariables:(CPDictionary )variables +{ + return self; +} + +- (CPString )description +{ + return [_left description] +" UNION "+ [_right description]; +} + +@end diff --git a/Foundation/CPPredicate/CPExpression_variable.j b/Foundation/CPPredicate/CPExpression_variable.j new file mode 100644 index 0000000..b8cf7cc --- /dev/null +++ b/Foundation/CPPredicate/CPExpression_variable.j @@ -0,0 +1,55 @@ + +@import "CPExpression.j" +@import +@import + +@implementation CPExpression_variable : CPExpression +{ + CPString _variable; +} + +- (id)initWithVariable:(CPString)variable +{ + [super initWithExpressionType:CPVariableExpressionType]; + _variable = [variable copy]; + return self; +} + +- (id)initWithCoder:(CPCoder)coder +{ + var variable = [coder decodeObjectForKey:@"CPExpressionVariable"]; + return [self initWithVariable:variable]; +} + +- (void)encodeWithCoder:(CPCoder)coder +{ + [coder encodeObject:_variable forKey:@"CPExpressionVariable"]; +} + +- (CPString)variable +{ + return _variable; +} + +- (id)expressionValueWithObject:object context:(CPDictionary)context +{ + return [context objectForKey:_variable]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"$%s", _variable]; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables +{ + var aconstant = [variables objectForKey:_variable]; + + if (aconstant != nil) + return [CPExpression expressionForConstantValue:aconstant]; + + return self; +} + + +@end diff --git a/Foundation/CPPredicate/CPPredicate.j b/Foundation/CPPredicate/CPPredicate.j new file mode 100644 index 0000000..14e1997 --- /dev/null +++ b/Foundation/CPPredicate/CPPredicate.j @@ -0,0 +1,119 @@ + +@import +@import +@import + +@implementation CPPredicate : CPObject +{ +} + +- (CPString)description +{ + return [self predicateFormat]; +} + ++ (CPPredicate)predicateWithValue:(BOOL)value +{ + return [[CPPredicate_BOOL alloc] initWithBool:value]; +} + +- (BOOL) evaluateWithObject:(id)object +{ + // IMPLEMENTED BY SUBCLASSES +} + +- (CPString) predicateFormat +{ + // IMPLEMENTED BY SUBCLASSES +} + +- (CPPredicate) predicateWithSubstitutionVariables: (CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + ++ (CPPredicate) predicateWithFormat: (CPString) format, ... +{ + // UNIMPLEMENTED + return nil; +} + ++ (CPPredicate) predicateWithFormat: (CPString)format argumentArray: (CPArray)args +{ + // UNIMPLEMENTED + return nil; +} + ++ (CPPredicate) predicateWithFormat: (CPString)format arguments: (va_list)args +{ + // UNIMPLEMENTED + return nil; +} + +@end + +@implementation CPPredicate_BOOL : CPPredicate +{ + BOOL _value; +} + +- (id)initWithBool:(BOOL)value +{ + _value = value; + return self; +} + +- (BOOL)evaluateObject:(id)object +{ + return _value; +} + +- (CPString)predicateFormat +{ + return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; +} + +@end + +@implementation CPArray (CPPredicate) + +- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPArray array], + i; + + for (i = 0;i < count;i++) + { + var object = [self objectAtIndex:i]; + + if ([predicate evaluateWithObject:object]) + [result addObject:object]; + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (--count >= 0) + { + var check = [self objectAtIndex:count]; + + if (![predicate evaluateWithObject:check]) + [self removeObjectAtIndex:count]; + } +} + +@end + + +@import "CPCompoundPredicate.j" +@import "CPComparisonPredicate.j" +@import "CPExpression.j" + +@import "CPExpression_operator.j" +@import "CPExpression_aggregate.j" +@import "CPExpression_assignment.j" diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j new file mode 100644 index 0000000..9d9bafd --- /dev/null +++ b/Tests/Foundation/CPPredicateTest.j @@ -0,0 +1,184 @@ +@import + +@implementation CPPredicateTest : OJTestCase +{ + CPDictionary dict; +} + +- (id)init +{ + self = [super init]; + if (self != nil) + { + var d, + objects; + + dict = [[CPDictionary alloc] init]; + [dict setObject: @"A Title" forKey:@"title"]; + + var keys = [CPArray arrayWithObjects:@"Name",@"Age",@"Children",nil]; + objects = [CPArray arrayWithObjects:@"John",[CPNumber numberWithInt:34],[CPArray arrayWithObjects:@"Kid1", @"Kid2", nil],nil]; + + d = [CPDictionary dictionaryWithObjects:objects forKeys:keys]; + [dict setObject:d forKey:@"Record1"]; + + objects = [CPArray arrayWithObjects:@"Mary",[CPNumber numberWithInt:30],[CPArray arrayWithObjects:@"Kid1", @"Girl1", nil],nil]; + + d = [CPDictionary dictionaryWithObjects:objects forKeys:keys]; + [dict setObject:d forKey:@"Record2"]; + + } + return self; +} + +- (void)testExpressionsInit +{ + var expression_keypath = [CPExpression expressionForKeyPath:@"name"]; + [self assertNotNull:expression_keypath message:"Expression should not be nil"]; + + var expression_str = [CPExpression expressionForConstantValue:@"j[a-z]an"]; + [self assertNotNull:expression_str message:"Expression should not be nil"]; + + var expression_num = [CPExpression expressionForConstantValue:[CPNumber numberWithInt:1]]; + [self assertNotNull:expression_num message:"Expression should not be nil"]; + + var expression_collection = [CPExpression expressionForConstantValue:[CPArray arrayWithObjects:@"a",@"b",@"d",nil]]; + [self assertNotNull:expression_collection message:"Expression should not be nil"]; + + var expression_var = [CPExpression expressionForVariable:@"variable"]; + [self assertNotNull:expression_var message:"Expression should not be nil"]; + + var expression_function = [CPExpression expressionForFunction:@"sum:" arguments:[CPArray arrayWithObjects:expression_num,expression_num,nil]]; + [self assertNotNull:expression_function message:"Expression should not be nil"]; + + var expression_self = [CPExpression expressionForEvaluatedObject]; + [self assertNotNull:expression_self message:"Expression should not be nil"]; + + var expression_aggregate = [CPExpression expressionForAggregate:[CPArray arrayWithObjects:expression_str,expression_num,expression_function,nil]]; + [self assertNotNull:expression_aggregate message:"Expression should not be nil"]; + + var expression_subquery = [CPExpression expressionForSubquery:expression_collection usingIteratorVariable:@"self" predicate:[CPPredicate predicateWithValue:YES]]; + [self assertNotNull:expression_subquery message:"Expression should not be nil"]; + + var set = [CPSet setWithObjects:@"a",@"b",@"d",nil]; + var array = [CPArray arrayWithObjects:@"a",@"b",@"d",nil]; + + var expression_intersect = [CPExpression expressionForIntersectSet:set with:array]; + [self assertNotNull:expression_intersect message:"Expression should not be nil"]; + + var expression_unionset = [CPExpression expressionForUnionSet:set with:array]; + [self assertNotNull:expression_unionset message:"Expression should not be nil"]; + + var expression_minusset = [CPExpression expressionForMinusSet:set with:array]; + [self assertNotNull:expression_minusset message:"Expression should not be nil"]; +} + +-(void)testFunctionExpression +{ + var function_exp = [CPExpression expressionForFunction:"sum:" arguments:[CPArray arrayWithObjects:[CPExpression expressionForConstantValue:1],[CPExpression expressionForConstantValue:2],[CPExpression expressionForConstantValue:3],nil]]; + + var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:function_exp rightExpression:[CPExpression expressionForConstantValue:3] modifier:CPDirectPredicateModifier type:CPGreaterThanPredicateOperatorType options:0]; + + [self assertTrue:[pred evaluateWithObject:nil] message:[pred description] + " should be true"]; +} + +-(void)testVariableExpression +{ + var variable_exp = [CPExpression expressionForVariable:@"variable"]; + var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Age"] rightExpression:variable_exp modifier:CPDirectPredicateModifier type:CPGreaterThanPredicateOperatorType options:0]; + + var variables = [CPDictionary dictionaryWithObject:20 forKey:@"variable"]; + + [self assertTrue:[pred evaluateWithObject:dict substitutionVariables:variables] message:"'"+ [pred description] + "' should be true"]; +} + +- (void)testOptions +{ + var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"àa"] rightExpression:[CPExpression expressionForConstantValue:@"aà"] modifier:CPDirectPredicateModifier type:CPLikePredicateOperatorType options:3]; + [self assertTrue:[pred evaluateWithObject:nil] message:"/"+ [pred description] + "/ should be true"]; + + pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"aB"] rightExpression:[CPExpression expressionForConstantValue:@"Ab"] modifier:CPDirectPredicateModifier type:CPLikePredicateOperatorType options:1]; + [self assertTrue:[pred evaluateWithObject:nil] message:"/"+ [pred description] + "/ should be true"]; +} + +-(void)testModifier +{ + var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record2.Children"] rightExpression:[CPExpression expressionForConstantValue:@"Gi"] modifier:CPAnyPredicateModifier type:CPBeginsWithPredicateOperatorType options:0]; + [self assertTrue:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be true"]; + + pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Children"] rightExpression:[CPExpression expressionForConstantValue:@"Kid"] modifier:CPAllPredicateModifier type:CPBeginsWithPredicateOperatorType options:0]; + [self assertTrue:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be true"]; +} + +-(void)testOperators +{ + var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:"Farenight 451"] rightExpression:[CPExpression expressionForConstantValue:"(F|g)\\w+\\s\\d{3}"] modifier:CPDirectPredicateModifier type:CPMatchesPredicateOperatorType options:2]; + [self assertTrue:[pred evaluateWithObject:nil] message:"'"+ [pred description] + "' should be true"]; + + pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"b"] rightExpression:[CPExpression expressionForConstantValue:@"[a-c]"] modifier:CPDirectPredicateModifier type:CPLikePredicateOperatorType options:1]; + [self assertTrue:[pred evaluateWithObject:nil] message:"'"+ [pred description] + "' should be true"]; + + pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"Aa"] rightExpression:[CPExpression expressionForConstantValue:@"ab"] modifier:CPDirectPredicateModifier type:CPLessThanPredicateOperatorType options:0]; + [self assertTrue:[pred evaluateWithObject:nil] message:"'"+ [pred description] + "' should be true"]; + + pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"Ac"] rightExpression:[CPExpression expressionForConstantValue:@"ab"] modifier:CPDirectPredicateModifier type:CPLessThanPredicateOperatorType options:2]; + [self assertTrue:[pred evaluateWithObject:nil] message:"'"+ [pred description] + "' should be true"]; + +} + +-(void)testCompoundPredicate +{ + var predOne = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Name"] rightExpression:[CPExpression expressionForConstantValue:@"J"] modifier:CPDirectPredicateModifier type:CPBeginsWithPredicateOperatorType options:0]; + + var predTwo = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Age"] rightExpression:[CPExpression expressionForConstantValue:[CPNumber numberWithInt:40]] modifier:CPDirectPredicateModifier type:CPLessThanPredicateOperatorType options:0]; + + var pred = [[CPCompoundPredicate alloc] initWithType:CPAndPredicateType subpredicates:[CPArray arrayWithObjects:predOne,predTwo,nil]]; + + [self assertTrue:[pred evaluateWithObject:dict] message:"'"+ [pred description] + "' should be true"]; +} + +-(void)testPredicateParsing +{ + var predicate; +// TEST STRING + predicate = [CPPredicate predicateWithFormat: @"%K == %@", @"Record1.Name", @"John"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"%K MATCHES[c] %@", @"Record1.Name", @"john"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"%K BEGINSWITH %@", @"Record1.Name", @"Jo"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"(%K == %@) AND (%K == %@)", @"Record1.Name", @"John", @"Record2.Name", @"Mary"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + +// TEST integer + predicate = [CPPredicate predicateWithFormat: @"%K == %d", @"Record1.Age", 34]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"%K < %d", @"Record1.Age", 40]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"%K BETWEEN %@", @"Record1.Age", [CPArray arrayWithObjects:[CPNumber numberWithInt: 20], [CPNumber numberWithInt:40], nil]]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"(%K == %d) OR (%K == %d)", @"Record1.Age", 34, @"Record2.Age", 34]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + +// TEST float + predicate = [CPPredicate predicateWithFormat: @"%K < %f", @"Record1.Age", 40.5]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"%f > %K", 40.5, @"Record1.Age"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + +// TEST Aggregate + predicate = [CPPredicate predicateWithFormat: @"%@ IN %K", @"Kid1", @"Record1.Children"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; + + predicate = [CPPredicate predicateWithFormat: @"Any %K == %@", @"Record2.Children", @"Girl1"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:[predicate description] + " should be true"]; +} + +@end \ No newline at end of file -- 1.6.0.2