mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-15 04:54:29 +00:00
CPPredicate &al implementation. OJUnit test.
This commit is contained in:
1 parent
fce726a031
commit
974cf64e1d
17 files changed
+3437
No files matched your search
@@ -0,0 +1,605 @@
|
||||
@import "CPArray.j"
|
||||
@import "CPNull.j"
|
||||
@import "CPString.j"
|
||||
@import "CPEnumerator.j"
|
||||
@import "CPPredicate.j"
|
||||
@import "CPExpression.j"
|
||||
@import "CPExpression_operator.j"
|
||||
|
||||
/*!
|
||||
A predicate to compare directly the left and right hand sides.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPDirectPredicateModifier = 0;
|
||||
/*!
|
||||
A predicate to compare all entries in the destination of a to-many relationship.
|
||||
|
||||
The left hand side must be a collection. The corresponding predicate compares each value in the left hand side with the right hand side, and returns NO when it finds the first mismatch—or YES if all match.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPAllPredicateModifier = 1;
|
||||
/*!
|
||||
A predicate to match with any entry in the destination of a to-many relationship.
|
||||
|
||||
The left hand side must be a collection. The corresponding predicate compares each value in the left hand side against the right hand side and returns YES when it finds the first match—or NO if no match is found.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPAnyPredicateModifier = 2;
|
||||
|
||||
/*!
|
||||
A case-insensitive predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPCaseInsensitivePredicateOption = 1;
|
||||
/*!
|
||||
A diacritic-insensitive predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPDiacriticInsensitivePredicateOption = 2;
|
||||
CPDiacriticInsensitiveSearch = 128;
|
||||
|
||||
/*!
|
||||
A less-than predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPLessThanPredicateOperatorType = 0;
|
||||
/*!
|
||||
A less-than-or-equal-to predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPLessThanOrEqualToPredicateOperatorType = 1;
|
||||
/*!
|
||||
A greater-than predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPGreaterThanPredicateOperatorType = 2;
|
||||
/*!
|
||||
A greater-than-or-equal-to predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPGreaterThanOrEqualToPredicateOperatorType = 3;
|
||||
/*!
|
||||
An equal-to predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPEqualToPredicateOperatorType = 4;
|
||||
/*!
|
||||
A not-equal-to predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPNotEqualToPredicateOperatorType = 5;
|
||||
/*!
|
||||
A full regular expression matching predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPMatchesPredicateOperatorType = 6;
|
||||
/*!
|
||||
A simple subset of the matches predicate, similar in behavior to SQL LIKE.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPLikePredicateOperatorType = 7;
|
||||
/*!
|
||||
A begins-with predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPBeginsWithPredicateOperatorType = 8;
|
||||
/*!
|
||||
An ends-with predicate.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPEndsWithPredicateOperatorType = 9;
|
||||
/*!
|
||||
A predicate to determine if the left hand side is in the right hand side.
|
||||
|
||||
For strings, returns YES if the left hand side is a substring of the right hand side . For collections, returns YES if the left hand side is in the right hand side.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPInPredicateOperatorType = 10;
|
||||
/*!
|
||||
Predicate that uses a custom selector that takes a single argument and returns a BOOL value.
|
||||
|
||||
The selector is invoked on the left hand side with the right hand side.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPCustomSelectorPredicateOperatorType = 11;
|
||||
/*!
|
||||
A predicate to determine if the left hand side contains the right hand side.
|
||||
|
||||
Returns YES if [lhs contains rhs]; the left hand side must be a CPExpression object that evaluates to a collection
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPContainsPredicateOperatorType = 99;
|
||||
/*!
|
||||
A predicate to determine if the right hand side lies between bounds specified by the left hand side.
|
||||
|
||||
Returns YES if [lhs between rhs]; the right hand side must be an array in which the first element sets the lower bound and the second element the upper, inclusive. Comparison is performed using compare: or the class-appropriate equivalent.
|
||||
@global
|
||||
@class CPComparisonPredicate
|
||||
*/
|
||||
CPBetweenPredicateOperatorType = 100;
|
||||
|
||||
var CPComparisonPredicateModifier;
|
||||
var CPPredicateOperatorType;
|
||||
|
||||
/*!
|
||||
@ingroup foundation
|
||||
@class CPComparisonPredicate
|
||||
@brief CPComparisonPredicate is a subclass of CPPredicate used to compare expressions.
|
||||
|
||||
Comparison predicates are predicates used to compare the results of two expressions. Comparison predicates take an operator, a left expression, and a right expression, and return as a BOOL the result of invoking the operator with the results of evaluating the expressions. Expressions are represented by instances of the CPExpression class.
|
||||
*/
|
||||
@implementation CPComparisonPredicate : CPPredicate
|
||||
{
|
||||
CPExpression _left;
|
||||
CPExpression _right;
|
||||
|
||||
CPComparisonPredicateModifier _modifier;
|
||||
CPPredicateOperatorType _type;
|
||||
unsigned int _options;
|
||||
SEL _customSelector;
|
||||
}
|
||||
|
||||
// Constructors
|
||||
/*!
|
||||
Returns a new predicate formed by combining the left and right expressions using a given selector.
|
||||
@param left The left hand side expression.
|
||||
@param right The right hand side expression.
|
||||
@param selector The selector to use for comparison. The method defined by the selector must take a single argument and return a BOOL value.
|
||||
@return A new predicate formed by combining the left and right expressions using selector.
|
||||
*/
|
||||
+ (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
|
||||
{
|
||||
return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a predicate of a given type formed by combining given left and right expressions using a given modifier and options.
|
||||
@param left The left hand expression.
|
||||
@param right The right hand expression.
|
||||
@param modifier The modifier to apply.
|
||||
@param type The predicate operator type.
|
||||
@param options The options to apply (see CPComparisonPredicate Options).
|
||||
@return A new predicate of type type formed by combining the given left and right expressions using the modifier and options.
|
||||
*/
|
||||
+ (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];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a predicate formed by combining given left and right expressions using a given selector.
|
||||
@param left The left hand side expression.
|
||||
@param right The right hand side expression.
|
||||
@param selector The selector to use for comparison. The method defined by the selector must take a single argument and return a BOOL value.
|
||||
@return The receiver, initialized by combining the left and right expressions using selector.
|
||||
*/
|
||||
- (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
|
||||
{
|
||||
_left = left;
|
||||
_right = right;
|
||||
_modifier = CPDirectPredicateModifier;
|
||||
_type = CPCustomSelectorPredicateOperatorType;
|
||||
_options = 0;
|
||||
_customSelector = selector;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a predicate to a given type formed by combining given left and right expressions using a given modifier and options.
|
||||
@param left The left hand expression.
|
||||
@param right The right hand expression.
|
||||
@param modifier The modifier to apply.
|
||||
@param type The predicate operator type.
|
||||
@param options The options to apply (see CPComparisonPredicate Options).
|
||||
@return The receiver, initialized to a predicate of type type formed by combining the left and right expressions using the modifier and options.
|
||||
*/
|
||||
- (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 = (type != CPMatchesPredicateOperatorType &&
|
||||
type != CPLikePredicateOperatorType &&
|
||||
type != CPBeginsWithPredicateOperatorType &&
|
||||
type != CPEndsWithPredicateOperatorType &&
|
||||
type != CPInPredicateOperatorType &&
|
||||
type != CPContainsPredicateOperatorType) ? 0 : options;
|
||||
|
||||
_customSelector = NULL;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Getting Information About a Comparison Predicate
|
||||
/*!
|
||||
Returns the comparison predicate modifier for the receiver.
|
||||
@return The comparison predicate modifier for the receiver.
|
||||
*/
|
||||
- (CPComparisonPredicateModifier)comparisonPredicateModifier
|
||||
{
|
||||
return _modifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selector for the receiver.
|
||||
@return The selector for the receiver, or NULL if there is none.
|
||||
*/
|
||||
- (SEL)customSelector
|
||||
{
|
||||
return _customSelector;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the left expression for the receiver.
|
||||
@return The left expression for the receiver, or nil if there is none.
|
||||
*/
|
||||
- (CPExpression)leftExpression
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the options that are set for the receiver.
|
||||
@return The options that are set for the receiver.
|
||||
*/
|
||||
- (unsigned)options
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the predicate type for the receiver.
|
||||
@return Returns the predicate type for the receiver.
|
||||
*/
|
||||
- (CPPredicateOperatorType)predicateOperatorType
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the right expression for the receiver.
|
||||
@return The right expression for the receiver, or nil if there is none.
|
||||
*/
|
||||
- (CPExpression)rightExpression
|
||||
{
|
||||
return _right;
|
||||
}
|
||||
|
||||
|
||||
- (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;
|
||||
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],
|
||||
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];
|
||||
}
|
||||
|
||||
- (BOOL)_evaluateValue:lhs rightValue:rhs
|
||||
{
|
||||
var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]),
|
||||
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:
|
||||
var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g";
|
||||
if (_options & CPDiacriticInsensitivePredicateOption)
|
||||
{
|
||||
lhs = lhs.stripDiacritics();
|
||||
rhs = rhs.stripDiacritics();
|
||||
}
|
||||
|
||||
return (new RegExp(rhs,commut)).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 or a string"];
|
||||
|
||||
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],
|
||||
upper = [rhs objectAtIndex:1];
|
||||
|
||||
return ([lhs compare:lower] == CPOrderedDescending && [lhs compare:upper] == CPOrderedAscending);
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)evaluateWithObject:(id)object
|
||||
{
|
||||
return [self evaluateWithObject:object substitutionVariables:nil];
|
||||
}
|
||||
|
||||
- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
var left = _left,
|
||||
right = _right;
|
||||
|
||||
if(variables != nil)
|
||||
{
|
||||
left = [left _expressionWithSubstitutionVariables:variables];
|
||||
right = [right _expressionWithSubstitutionVariables:variables];
|
||||
}
|
||||
|
||||
var leftValue = [left expressionValueWithObject:object context:nil],
|
||||
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
|
||||
|
||||
@implementation CPComparisonPredicate (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_left = [coder decodeObjectForKey:@"CPComparisonPredicateLeftExpression"];
|
||||
_right = [coder decodeObjectForKey:@"CPComparisonPredicateRightExpression"];
|
||||
_modifier = [coder decodeIntForKey:@"CPComparisonPredicateModifier"];
|
||||
_type = [coder decodeIntForKey:@"CPComparisonPredicateType"];
|
||||
_options = [coder decodeIntForKey:@"CPComparisonPredicateOptions"];
|
||||
_customSelector = [coder decodeObjectForKey:@"CPComparisonPredicateCustomSelector"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_left forKey:@"CPComparisonPredicateLeftExpression"];
|
||||
[coder encodeObject:_right forKey:@"CPComparisonPredicateRightExpression"];
|
||||
[coder encodeInt:_modifier forKey:@"CPComparisonPredicateModifier"];
|
||||
[coder encodeInt:_type forKey:@"CPComparisonPredicateType"];
|
||||
[coder encodeInt:_options forKey:@"CPComparisonPredicateOptions"];
|
||||
[coder encodeObject:_customSelector forKey:@"CPComparisonPredicateCustomSelector"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var source = ['*','?','(',')','{','}','.','+','|','/','$','^'];
|
||||
var dest = ['.*','.?','\\(','\\)','\\{','\\}','\\.','\\+','\\|','\\/','\\$','\\^'];
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
@import "CPPredicate.j"
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
/*!
|
||||
A predicate to compare directly the left and right hand sides.
|
||||
@global
|
||||
@class CPCompoundPredicate
|
||||
*/
|
||||
CPNotPredicateType = 0;
|
||||
/*!
|
||||
A predicate to compare directly the left and right hand sides.
|
||||
@global
|
||||
@class CPCompoundPredicate
|
||||
*/
|
||||
CPAndPredicateType = 1;
|
||||
/*!
|
||||
A predicate to compare directly the left and right hand sides.
|
||||
@global
|
||||
@class CPCompoundPredicate
|
||||
*/
|
||||
CPOrPredicateType = 2;
|
||||
|
||||
var CPCompoundPredicateType;
|
||||
|
||||
/*!
|
||||
@class CPCompoundPredicate
|
||||
@ingroup foundation
|
||||
@brief CPCompoundPredicate is a subclass of CPPredicate used to represent logical “gate” operations (AND/OR/NOT) and comparison operations.
|
||||
|
||||
Comparison operations are based on two expressions, as represented by instances of the CPExpression class. Expressions are created for constant values, key paths, and so on.
|
||||
|
||||
A compound predicate with 0 elements evaluates to TRUE, and a compound predicate with a single sub-predicate evaluates to the truth of its sole subpredicate.
|
||||
*/
|
||||
@implementation CPCompoundPredicate : CPPredicate
|
||||
{
|
||||
CPCompoundPredicateType _type;
|
||||
CPArray _predicates;
|
||||
}
|
||||
|
||||
// Constructors
|
||||
/*!
|
||||
Returns the receiver initialized to a given type using predicates from a given array.
|
||||
@param type The type of the new predicate.
|
||||
@return The receiver initialized with its type set to type and subpredicates array to subpredicates.
|
||||
*/
|
||||
- (id)initWithType:(CPCompoundPredicateType)type subpredicates:(CPArray)predicates
|
||||
{
|
||||
_type = type;
|
||||
_predicates = predicates;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new predicate formed by NOT-ing the predicates in a given array.
|
||||
@param subpredicates An array of CPPredicate objects.
|
||||
@return A new predicate formed by NOT-ing the predicates specified by subpredicates.
|
||||
*/
|
||||
+ (CPPredicate)notPredicateWithSubpredicate:(CPPredicate)predicate
|
||||
{
|
||||
return [[self alloc] initWithType:CPNotPredicateType subpredicates:[CPArray arrayWithObject:predicate]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new predicate formed by AND-ing the predicates in a given array.
|
||||
@param subpredicates An array of CPPredicate objects.
|
||||
@return A new predicate formed by AND-ing the predicates specified by subpredicates.
|
||||
*/
|
||||
+ (CPPredicate)andPredicateWithSubpredicates:(CPArray)subpredicates
|
||||
{
|
||||
return [[self alloc] initWithType:CPAndPredicateType subpredicates:subpredicates];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new predicate formed by OR-ing the predicates in a given array.
|
||||
@param subpredicates An array of CPPredicate objects.
|
||||
@return A new predicate formed by OR-ing the predicates specified by subpredicates.
|
||||
*/
|
||||
+ (CPPredicate)orPredicateWithSubpredicates:(CPArray)predicates
|
||||
{
|
||||
return [[self alloc] initWithType:CPOrPredicateType subpredicates:predicates];
|
||||
}
|
||||
|
||||
// Getting Information About a Compound Predicate
|
||||
/*!
|
||||
Returns the predicate type for the receiver.
|
||||
@return The predicate type for the receiver.
|
||||
*/
|
||||
- (CPCompoundPredicateType)compoundPredicateType
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the array of the receiver’s subpredicates.
|
||||
@return The array of the receiver’s subpredicates.
|
||||
*/
|
||||
- (CPArray)subpredicates
|
||||
{
|
||||
return _predicates;
|
||||
}
|
||||
|
||||
- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
var subp = [CPArray array],
|
||||
i;
|
||||
|
||||
for (i = 0; i < [subp count]; i++)
|
||||
{
|
||||
var p = [subp objectAtIndex:i],
|
||||
sp = [p predicateWithSubstitutionVariables:variables];
|
||||
|
||||
[subp addObject:sp];
|
||||
}
|
||||
|
||||
return [[CPCompoundPredicate alloc] initWithType:_type subpredicates:subp];
|
||||
}
|
||||
|
||||
- (CPString)predicateFormat
|
||||
{
|
||||
var result = "",
|
||||
args = [CPArray array],
|
||||
count = [_predicates count],
|
||||
i;
|
||||
|
||||
if (count == 0)
|
||||
return @"TRUPREDICATE";
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
var subpredicate = [_predicates objectAtIndex:i],
|
||||
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;
|
||||
}
|
||||
|
||||
- (BOOL)evaluateWithObject:(id)object
|
||||
{
|
||||
return [self evaluateWithObject:object substitutionVariables:nil];
|
||||
}
|
||||
|
||||
- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
var result = NO,
|
||||
count = [_predicates count],
|
||||
i;
|
||||
|
||||
if (count == 0)
|
||||
return YES;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
var predicate = [_predicates objectAtIndex:i];
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case CPNotPredicateType:
|
||||
return ![predicate evaluateWithObject:object substitutionVariables:variables];
|
||||
case CPAndPredicateType:
|
||||
if (i == 0)
|
||||
result = [predicate evaluateWithObject:object substitutionVariables:variables];
|
||||
else
|
||||
result = result && [predicate evaluateWithObject:object substitutionVariables:variables];
|
||||
break;
|
||||
case CPOrPredicateType:
|
||||
if ([predicate evaluateWithObject:object substitutionVariables:variables])
|
||||
return YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPCompoundPredicate (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_predicates = [coder decodeObjectForKey:@"CPCompoundPredicateSubpredicates"];
|
||||
_type = [coder decodeIntForKey:@"CPCompoundPredicateType"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_predicates forKey:@"CPCompoundPredicateSubpredicates"];
|
||||
[coder encodeInt:_type forKey:@"CPCompoundPredicateType"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,324 @@
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPKeyValueCoding.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPCoder.j>
|
||||
|
||||
/*!
|
||||
An expression that always returns the same value.
|
||||
*/
|
||||
CPConstantValueExpressionType = 0;
|
||||
/*!
|
||||
An expression that always returns the parameter object itself.
|
||||
*/
|
||||
CPEvaluatedObjectExpressionType = 1;
|
||||
/*!
|
||||
An expression that always returns whatever value is associated with the key specified by ‘variable’ in the bindings dictionary.
|
||||
*/
|
||||
CPVariableExpressionType = 2;
|
||||
/*!
|
||||
An expression that returns something that can be used as a key path.
|
||||
*/
|
||||
CPKeyPathExpressionType = 3;
|
||||
/*!
|
||||
An expression that returns the result of evaluating a function.
|
||||
*/
|
||||
CPFunctionExpressionType = 4;
|
||||
/*!
|
||||
An expression that defines an aggregate of NSExpression objects.
|
||||
*/
|
||||
CPAggregateExpressionType = 5;
|
||||
/*!
|
||||
An expression that filters a collection using a subpredicate.
|
||||
*/
|
||||
CPSubqueryExpressionType = 6;
|
||||
/*!
|
||||
An expression that creates a union of the results of two nested expressions.
|
||||
*/
|
||||
CPUnionSetExpressionType = 7;
|
||||
/*!
|
||||
An expression that creates an intersection of the results of two nested expressions.
|
||||
*/
|
||||
CPIntersectSetExpressionType = 8;
|
||||
/*!
|
||||
An expression that combines two nested expression results by set subtraction.
|
||||
*/
|
||||
CPMinusSetExpressionType = 9;
|
||||
|
||||
/*!
|
||||
@ingroup foundation
|
||||
@class CPExpression
|
||||
@brief CPExpression is used to represent expressions in a predicate.
|
||||
|
||||
Comparison operations in an CPPredicate are based on two expressions, as represented by instances of the CPExpression class. Expressions are created for constant values, key paths, and so on.
|
||||
|
||||
Generally, anywhere in the CPExpression class hierarchy where there is composite API and subtypes that may only reasonably respond to a subset of that API, invoking a method that does not make sense for that subtype will cause an exception to be thrown.
|
||||
*/
|
||||
|
||||
@implementation CPExpression : CPObject
|
||||
{
|
||||
int _type;
|
||||
}
|
||||
|
||||
// Initializing an Expression
|
||||
/*!
|
||||
Initializes the receiver with the specified expression type.
|
||||
@param type The type of the new expression, as defined by CPExpressionType.
|
||||
@return An initialized CPExpression object of the type type.
|
||||
*/
|
||||
- (id)initWithExpressionType:(int)type
|
||||
{
|
||||
_type = type;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
//Creating an Expression for a Value
|
||||
/*!
|
||||
Returns a new expression that represents a given constant value.
|
||||
@param value The constant value the new expression is to represent.
|
||||
@return A new expression that represents the constant value.
|
||||
*/
|
||||
+ (CPExpression)expressionForConstantValue:(id)value
|
||||
{
|
||||
return [[CPExpression_constant alloc] initWithValue:value];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new expression that represents the object being evaluated.
|
||||
@return A new expression that represents the object being evaluated.
|
||||
*/
|
||||
+ (CPExpression)expressionForEvaluatedObject
|
||||
{
|
||||
return [[CPExpression_self alloc] init];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new expression that extracts a value from the variable bindings dictionary for a given key.
|
||||
@param string The key for the variable to extract from the variable bindings dictionary.
|
||||
@return A new expression that extracts from the variable bindings dictionary the value for the key string.
|
||||
*/
|
||||
+ (CPExpression)expressionForVariable:(CPString)string
|
||||
{
|
||||
return [[CPExpression_variable alloc] initWithVariable:string];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new expression that invokes valueForKeyPath: with a given key path.
|
||||
@param keyPath The key path that the new expression should evaluate.
|
||||
@return A new expression that invokes valueForKeyPath: with keyPath.
|
||||
*/
|
||||
+ (CPExpression)expressionForKeyPath:(CPString)keyPath
|
||||
{
|
||||
return [[CPExpression_keypath alloc] initWithKeyPath:keyPath];
|
||||
}
|
||||
|
||||
//Creating a Collection Expression
|
||||
/*!
|
||||
Returns a new aggregate expression for a given collection.
|
||||
@param collection A collection object (an instance of CPArray, CPSet, or CPDictionary) that contains further expressions.
|
||||
@return A new expression that contains the expressions in collection.
|
||||
*/
|
||||
+ (CPExpression)expressionForAggregate:(CPArray)collection
|
||||
{
|
||||
return [[CPExpression_aggregate alloc] initWithAggregate:collection];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new CPExpression object that represent the union of a given set and collection.
|
||||
@param left An expression that evaluates to an CPSet object.
|
||||
@param right An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
|
||||
@return A new CPExpression object that represents the union of left and right.
|
||||
*/
|
||||
+ (CPExpression)expressionForUnionSet:(CPExpression)left with:(CPExpression)right
|
||||
{
|
||||
return [[CPExpression_unionset alloc] initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new CPExpression object that represent the intersection of a given set and collection.
|
||||
@param left An expression that evaluates to an CPSet object.
|
||||
@param right An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
|
||||
@return A new CPExpression object that represents the intersection of left and right.
|
||||
*/
|
||||
+ (CPExpression)expressionForIntersectSetSet:(CPExpression)left with:(CPExpression)right
|
||||
{
|
||||
return [[CPExpression_intersectset alloc] initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new CPExpression object that represent the subtraction of a given collection from a given set.
|
||||
@param left An expression that evaluates to an CPSet object.
|
||||
@param left An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
|
||||
@return A new CPExpression object that represents the subtraction of right from left.
|
||||
*/
|
||||
+ (CPExpression)expressionForMinusSet:(CPExpression)left with:(CPExpression)right
|
||||
{
|
||||
return [[CPExpression_minusset alloc] initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
// Creating an Expression for a Function
|
||||
/*!
|
||||
Returns a new expression that will invoke one of the predefined functions.
|
||||
@param function_name The name of the function to invoke.
|
||||
@param 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 A new expression that invokes the function name using the parameters in parameters.
|
||||
|
||||
The name parameter can be one of the following predefined functions:
|
||||
@verbatim
|
||||
name parameter array contents returns
|
||||
-------------------------------------------------------------------------------------------------------------------------------------
|
||||
sum: CPExpression instances representing numbers CPNumber
|
||||
count: CPExpression instances representing numbers CPNumber
|
||||
min: CPExpression instances representing numbers CPNumber
|
||||
max: CPExpression instances representing numbers CPNumber
|
||||
average: CPExpression instances representing numbers CPNumber
|
||||
median: CPExpression instances representing numbers CPNumber
|
||||
mode: CPExpression instances representing numbers CPArray (returned array will contain all occurrences of the mode)
|
||||
stddev: CPExpression instances representing numbers CPNumber
|
||||
add:to: CPExpression instances representing numbers CPNumber
|
||||
from:subtract: two CPExpression instances representing numbers CPNumber
|
||||
multiply:by: two CPExpression instances representing numbers CPNumber
|
||||
divide:by: two CPExpression instances representing numbers CPNumber
|
||||
modulus:by: two CPExpression instances representing numbers CPNumber
|
||||
sqrt: one CPExpression instance representing numbers CPNumber
|
||||
log: one CPExpression instance representing a number CPNumber
|
||||
ln: one CPExpression instance representing a number CPNumber
|
||||
raise:toPower: one CPExpression instance representing a number CPNumber
|
||||
exp: one CPExpression instance representing a number CPNumber
|
||||
floor: one CPExpression instance representing a number CPNumber
|
||||
ceiling: one CPExpression instance representing a number CPNumber
|
||||
abs: one CPExpression instance representing a number CPNumber
|
||||
trunc: one CPExpression instance representing a number CPNumber
|
||||
uppercase: one CPExpression instance representing a string CPString
|
||||
lowercase: one CPExpression instance representing a string CPString
|
||||
random none CPNumber (integer)
|
||||
random: one CPExpression instance representing a number CPNumber (integer) such that 0 <= rand < param
|
||||
now none [CPDate now]
|
||||
bitwiseAnd:with: two CPExpression instances representing numbers CPNumber (numbers will be treated as CPInteger)
|
||||
bitwiseOr:with: two CPExpression instances representing numbers CPNumber (numbers will be treated as CPInteger)
|
||||
bitwiseXor:with: two CPExpression instances representing numbers CPNumber (numbers will be treated as CPInteger)
|
||||
leftshift:by: two CPExpression instances representing numbers CPNumber (numbers will be treated as CPInteger)
|
||||
rightshift:by: two CPExpression instances representing numbers CPNumber (numbers will be treated as CPInteger)
|
||||
onesComplement: one CPExpression instance representing a numbers CPNumber (numbers will be treated as CPInteger)
|
||||
@endverbatim
|
||||
|
||||
This method raises an exception immediately if the selector is invalid; it raises an exception at runtime if the parameters are incorrect.
|
||||
*/
|
||||
+ (CPExpression)expressionForFunction:(CPString)function_name arguments:(CPArray)parameters
|
||||
{
|
||||
return [[CPExpression_function alloc] initWithSelector:CPSelectorFromString(function_name) arguments:parameters];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an expression which will return the result of invoking on a given target a selector with a given name using given arguments.
|
||||
@param target A CPExpression object which will evaluate an object on which the selector identified by name may be invoked.
|
||||
@param function_name The name of the method to be invoked.
|
||||
@param parameters An array containing CPExpression objects which can be evaluated to provide parameters for the method specified by name.
|
||||
@return An expression which will return the result of invoking the selector named name on the result of evaluating the target expression with the parameters specified by evaluating the elements of parameters.
|
||||
See the description of expressionForFunction:arguments: for examples of how to construct the parameter array.
|
||||
*/
|
||||
+ (CPExpression)expressionForFunction:(CPExpression)target selectorName:(CPString)function_name arguments:(CPArray)parameters
|
||||
{
|
||||
return [[CPExpression_function alloc] initWithTarget:target selector:CPSelectorFromString(function_name) arguments:parameters];
|
||||
}
|
||||
|
||||
|
||||
+ (CPExpression)expressionForSubquery:(CPExpression)expression usingIteratorVariable:(CPString)variable predicate:(id)predicate
|
||||
{
|
||||
return nil; // UNIMPLEMENTED
|
||||
}
|
||||
|
||||
|
||||
// Getting Information About an Expression
|
||||
/*!
|
||||
Returns the expression type for the receiver.
|
||||
@return The expression type for the receiver.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (int)expressionType
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the constant value of the receiver.
|
||||
@return The constant value of the receiver.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (id)constantValue
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPConstantValueExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the variable for the receiver.
|
||||
@return The variable for the receiver.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (CPString)variable
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPVariableExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the key path for the receiver.
|
||||
@return The key path for the receiver.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (CPString)keyPath
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPKeyPathExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the function for the receiver.
|
||||
@return The function for the receiver.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (CPString)function
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the arguments for the receiver.
|
||||
@return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (CPArray)arguments
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the collection of expressions in an aggregate expression, or the collection element of a subquery expression.
|
||||
@return Returns the collection of expressions in an aggregate expression, or the collection element of a subquery expression.
|
||||
This method raises an exception if it is not applicable to the receiver.
|
||||
*/
|
||||
- (id)collection
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPAggregateExpressionType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@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"
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@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.
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object collection] isEqual:[self collection]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import "CPExpression_variable.j"
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
@implementation CPExpression_assignment: CPExpression
|
||||
{
|
||||
CPExpression_variable _assignmentVariable;
|
||||
CPExpression _subexpression;
|
||||
}
|
||||
|
||||
- (id)initWithAssignmentVariable:(CPString)variable expression:(CPExpression)expression
|
||||
{
|
||||
_assignmentVariable = [CPExpression expressionForVariable:variable];
|
||||
_subexpression = expression;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithAssignmentExpression:(CPExpression)variableExpression expression:(CPExpression)expression
|
||||
{
|
||||
_assignmentVariable = variableExpression;
|
||||
_subexpression = expression;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
var variable = [coder decodeObjectForKey:@"CPExpressionAssignmentVariable"];
|
||||
var expression = [coder decodeObjectForKey:@"CPExpressionAssignmentExpression"];
|
||||
|
||||
return [self initWithAssignmentVariable:variable expression:expression];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_assignmentVariable forKey:@"CPExpressionAssignmentVariable"];
|
||||
[coder encodeObject:_subexpression forKey:@"CPExpressionAssignmentExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object subexpression] isEqual:[self subexpression]] || ![[object variable] isEqualToString:[self variable]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPExpression)assignmentVariable
|
||||
{
|
||||
return _assignmentVariable;
|
||||
}
|
||||
|
||||
- (CPExpression)subexpression
|
||||
{
|
||||
return _subexpression;
|
||||
}
|
||||
|
||||
- (CPString)variable
|
||||
{
|
||||
return [_assignmentVariable variable];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var pretty = [_expression description];
|
||||
|
||||
if ([_subexpression isKindOfClass:[CPExpression_operator class]])
|
||||
pretty = [CPString stringWithFormat:@"(%@)", pretty];
|
||||
|
||||
return [CPString stringWithFormat:@"%@ := %@", [self variable], pretty];
|
||||
}
|
||||
|
||||
- (id)expressionValueWithObject:(id)object context:(id)context
|
||||
{
|
||||
// UNIMPLEMENTED
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
// UNIMPLEMENTED
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPDictionary.j>
|
||||
|
||||
@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
|
||||
{
|
||||
[coder encodeObject:_value forKey:@"CPExpressionConstantValue"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object constantValue] isEqual:[self constantValue]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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 @"\"" + _value + @"\"";
|
||||
|
||||
return [_value description];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
|
||||
@implementation CPExpression_function : CPExpression
|
||||
{
|
||||
CPExpression _operand;
|
||||
SEL _selector;
|
||||
CPArray _arguments;
|
||||
int _argc;
|
||||
}
|
||||
|
||||
- (id)initWithSelector:(SEL)aselector arguments:(CPArray)parameters
|
||||
{
|
||||
[super initWithExpressionType:CPFunctionExpressionType];
|
||||
|
||||
if (![self respondsToSelector:aselector])
|
||||
[CPException raise: CPInvalidArgumentException reason:@"Unknown function implementation: " + aselector];
|
||||
|
||||
_selector = aselector;
|
||||
_operand = nil;
|
||||
_arguments = parameters;
|
||||
_argc = [parameters count];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithTarget:(CPExpression)targetExpression selector:(SEL)aselector arguments:(CPArray)parameters
|
||||
{
|
||||
[super initWithExpressionType:CPFunctionExpressionType];
|
||||
|
||||
var target = [targetExpression expressionValueWithObject:object context:context];
|
||||
if (![target respondsToSelector:aselector])
|
||||
[CPException raise: CPInvalidArgumentException reason:@"Unknown function implementation: " + aselector];
|
||||
|
||||
_selector = aselector;
|
||||
_operand = targetExpression;
|
||||
_arguments = parameters;
|
||||
_argc = [parameters count];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
var selector = CPSelectorFromString([coder decodeObjectForKey:@"CPExpressionFunctionName"]);
|
||||
var arguments = [coder decodeObjectForKey:@"CPExpressionFunctionArguments"];
|
||||
|
||||
return [self initWithSelector:selector arguments:arguments];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:[self _function] forKey:@"CPExpressionFunctionName"];
|
||||
[coder encodeObject:_arguments forKey:@"CPExpressionArguments"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object _function] isEqualToString:[self _function]] || ![[object operand] isEqual:[self operand]] || ![[object arguments] isEqualToArray:[self arguments]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPString)_function // OBJJ preprocessor does not like function as a method
|
||||
{
|
||||
return CPStringFromSelector(_selector);
|
||||
}
|
||||
|
||||
- (CPString)function
|
||||
{
|
||||
return [self _function];
|
||||
}
|
||||
|
||||
- (CPArray)arguments
|
||||
{
|
||||
return _arguments;
|
||||
}
|
||||
|
||||
- (CPExpression)operand
|
||||
{
|
||||
return _operand;
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
var target = (_operand == nil) ? self : [_operand expressionValueWithObject:object context:context];
|
||||
return [target performSelector:_selector withObject:eval_args];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var result = [CPString stringWithFormat:@"%@ %s(", [_operand description], [self _function]],
|
||||
i;
|
||||
|
||||
for (i = 0; i < _argc; i++)
|
||||
result = result + [_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:[self operand] selectorName:[self _function] arguments:array];
|
||||
}
|
||||
|
||||
- (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)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)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],
|
||||
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])];
|
||||
}
|
||||
|
||||
|
||||
- (id)first:(CPArray)parameters
|
||||
{
|
||||
if (_argc == 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"];
|
||||
|
||||
return [[parameters objectAtIndex:0] objectAtIndex:0];
|
||||
}
|
||||
|
||||
- (id)last:(CPArray)parameters
|
||||
{
|
||||
if (_argc == 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"];
|
||||
|
||||
return [[parameters objectAtIndex:0] lastObject];
|
||||
}
|
||||
|
||||
- (CPNumber)chs:(CPArray)parameters
|
||||
{
|
||||
if (_argc == 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"];
|
||||
|
||||
return [CPNumber numberWithInt: - [[parameters objectAtIndex:0] intValue]];
|
||||
}
|
||||
|
||||
- (id)index:(CPArray)parameters
|
||||
{
|
||||
if (_argc < 2)
|
||||
[CPException raise:CPInvalidArgumentException reason:"Invalid number of parameters"];
|
||||
|
||||
var left = [parameters objectAtIndex:0],
|
||||
right = [parameters objectAtIndex:1];
|
||||
|
||||
if ([left isKindOfClass: [CPDictionary class]])
|
||||
return [left objectForKey:right];
|
||||
else
|
||||
return [left objectAtIndex: [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 used when parsing
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
@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)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
var left = [coder decodeObjectForKey:@"CPExpressionUnionSetLeftExpression"];
|
||||
var right = [coder decodeObjectForKey:@"CPExpressionUnionSetRightExpression"];
|
||||
|
||||
return [self initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_left forKey:@"CPExpressionUnionSetLeftExpression"];
|
||||
[coder encodeObject:_right forKey:@"CPExpressionUnionSetRightExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object leftExpression] isEqual:[self leftExpression]] || ![[object rightExpression] isEqual:[self rightExpression]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (CPExpression)leftExpression
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
|
||||
- (CPExpression)rightExpression
|
||||
{
|
||||
return _right;
|
||||
}
|
||||
|
||||
- (CPString )description
|
||||
{
|
||||
return [_left description] +" INTERSECT "+ [_right description];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPKeyValueCoding.j>
|
||||
|
||||
@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"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object keyPath] isEqualToString:[self keyPath]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
|
||||
@implementation CPExpression_minusset : CPExpression
|
||||
|
||||
- (id)initWithLeft:(CPExpression)left right:(CPExpression)right
|
||||
{
|
||||
[super initWithExpressionType:CPMinusSetExpressionType];
|
||||
_left = left ;
|
||||
_right = right;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
var left = [coder decodeObjectForKey:@"CPExpressionMinusSetLeftExpression"];
|
||||
var right = [coder decodeObjectForKey:@"CPExpressionMinusSetRightExpression"];
|
||||
|
||||
return [self initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_left forKey:@"CPExpressionMinusSetLeftExpression"];
|
||||
[coder encodeObject:_right forKey:@"CPExpressionMinusSetRightExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object leftExpression] isEqual:[self leftExpression]] || ![[object rightExpression] isEqual:[self rightExpression]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (CPExpression)leftExpression
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
|
||||
- (CPExpression)rightExpression
|
||||
{
|
||||
return _right;
|
||||
}
|
||||
|
||||
- (CPString )description
|
||||
{
|
||||
return [_left description] +" MINUS "+ [_right description];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
|
||||
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],
|
||||
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
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPCoder.j>
|
||||
|
||||
@implementation CPExpression_self : CPExpression{}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
[super initWithExpressionType:CPEvaluatedObjectExpressionType];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
return [self init];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
return (object == self);
|
||||
}
|
||||
|
||||
- (id)expressionValueWithObject:object context:(CPDictionary)context
|
||||
{
|
||||
return object;
|
||||
}
|
||||
|
||||
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return @"SELF";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
|
||||
@implementation CPExpression_unionset
|
||||
|
||||
- (id)initWithLeft:(CPExpression)left right:(CPExpression)right
|
||||
{
|
||||
[super initWithExpressionType:CPUnionSetExpressionType];
|
||||
_left = left;
|
||||
_right = right;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
var left = [coder decodeObjectForKey:@"CPExpressionUnionSetLeftExpression"];
|
||||
var right = [coder decodeObjectForKey:@"CPExpressionUnionSetRightExpression"];
|
||||
|
||||
return [self initWithLeft:left right:right];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_left forKey:@"CPExpressionUnionSetLeftExpression"];
|
||||
[coder encodeObject:_right forKey:@"CPExpressionUnionSetRightExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa
|
||||
|| [object expressionType] != [self expressionType]
|
||||
|| ![[object leftExpression] isEqual:[self leftExpression]]
|
||||
|| ![[object rightExpression] isEqual:[self rightExpression]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (CPExpression)leftExpression
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
|
||||
- (CPExpression)rightExpression
|
||||
{
|
||||
return _right;
|
||||
}
|
||||
|
||||
- (CPString )description
|
||||
{
|
||||
return [_left description] +" UNION "+ [_right description];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
|
||||
@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"];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object)
|
||||
return YES;
|
||||
|
||||
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object variable] isEqualToString:[self variable]])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
@@ -0,0 +1,913 @@
|
||||
|
||||
@import <Foundation/CPValue.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPSet.j>
|
||||
@import <Foundation/CPNull.j>
|
||||
@import <Foundation/CPScanner.j>
|
||||
|
||||
/*!
|
||||
@ingroup foundation
|
||||
@class CPPredicate
|
||||
@brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering.
|
||||
|
||||
You use predicates to represent logical conditions, used for describing objects in persistent stores and in-memory filtering of objects. Although it is common to create predicates directly from instances of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include:
|
||||
|
||||
Simple comparisons, such as grade == "7" or firstName like "Shaffiq"\n
|
||||
Case/diacritic insensitive lookups, such as name contains[cd] "itroen"\n
|
||||
Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley")\n
|
||||
“Between” predicates such as date between {$YESTERDAY, $TOMORROW}.\n
|
||||
|
||||
You can create predicates for relationships, such as:
|
||||
|
||||
group.name like "work*"\n
|
||||
ALL children.age > 12\n
|
||||
ANY children.age > 12\n
|
||||
You can create predicates for operations, such as @sum.items.price < 1000.
|
||||
|
||||
You can also create predicates that include variables, so that the predicate can be pre-defined before substituting concrete values at runtime with evaluateWithObject:substitutionVariables: method.
|
||||
*/
|
||||
|
||||
@implementation CPPredicate : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a new predicate formed by creating a new string with a given format and parsing the result.
|
||||
@param format The format string for the new predicate.
|
||||
@param … A comma-separated list of arguments to substitute into format.
|
||||
@return A new predicate formed by creating a new string with format and parsing the result.
|
||||
*/
|
||||
|
||||
+ (CPPredicate)predicateWithFormat:(CPString) format, ...
|
||||
{
|
||||
if (!format)
|
||||
[CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
|
||||
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
return [self predicateWithFormat:arguments[2] argumentArray:args];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result.
|
||||
@param format The format string for the new predicate.
|
||||
@param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array.
|
||||
@return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result.
|
||||
*/
|
||||
+ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)arguments
|
||||
{
|
||||
if (!format)
|
||||
[CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
|
||||
|
||||
var s = [[CPPredicateScanner alloc] initWithString:format args:arguments];
|
||||
var p = [s parse];
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result.
|
||||
@param format The format string for the new predicate.
|
||||
@param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list.
|
||||
@return A new predicate by substituting the values in argList into predicateFormat and parsing the result.
|
||||
*/
|
||||
+ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList
|
||||
{
|
||||
// UNIMPLEMENTED
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary.
|
||||
@param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
|
||||
@return A copy of the receiver with the receiver’s variables substituted by values specified in variables.
|
||||
*/
|
||||
- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
// IMPLEMENTED BY SUBCLASSES
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a predicate that always evaluates to a given value.
|
||||
@param value The value to which the new predicate should evaluate.
|
||||
@return A predicate that always evaluates to value.
|
||||
*/
|
||||
+ (CPPredicate)predicateWithValue:(BOOL)value
|
||||
{
|
||||
return [[CPPredicate_BOOL alloc] initWithBool:value];
|
||||
}
|
||||
|
||||
// Evaluating a Predicate
|
||||
/*!
|
||||
Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver.
|
||||
@param object The object against which to evaluate the receiver.
|
||||
@return YES if object matches the conditions specified by the receiver, otherwise NO.
|
||||
*/
|
||||
- (BOOL)evaluateWithObject:(id)object
|
||||
{
|
||||
// IMPLEMENTED BY SUBCLASSES
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary.
|
||||
@param object The object against which to evaluate the receiver.
|
||||
@param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
|
||||
@return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO.
|
||||
*/
|
||||
- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
|
||||
{
|
||||
// IMPLEMENTED BY SUBCLASSES
|
||||
}
|
||||
|
||||
// Getting Format Information
|
||||
/*!
|
||||
Returns the receiver’s format string.
|
||||
@return The receiver’s format string.
|
||||
*/
|
||||
- (CPString)predicateFormat
|
||||
{
|
||||
// IMPLEMENTED BY SUBCLASSES
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [self predicateFormat];
|
||||
}
|
||||
|
||||
@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 object = [self objectAtIndex:count];
|
||||
|
||||
if (![predicate evaluateWithObject:object])
|
||||
[self removeObjectAtIndex:count];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPSet (CPPredicate)
|
||||
|
||||
- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate
|
||||
{
|
||||
var count = [self count],
|
||||
result = [CPSet set],
|
||||
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 object = [self objectAtIndex:count];
|
||||
|
||||
if (![predicate evaluateWithObject:object])
|
||||
[self removeObjectAtIndex:count];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#define REFERENCE(variable) \
|
||||
function(newValue)\
|
||||
{\
|
||||
var oldValue = variable;\
|
||||
if (typeof newValue != 'undefined')\
|
||||
variable = newValue;\
|
||||
return oldValue;\
|
||||
}
|
||||
|
||||
@implementation CPPredicateScanner : CPScanner
|
||||
{
|
||||
CPEnumerator _args;
|
||||
unsigned _retrieved;
|
||||
}
|
||||
|
||||
- (id) initWithString:(CPString)format args:(CPArray)args
|
||||
{
|
||||
self = [super initWithString:format];
|
||||
if (self != nil)
|
||||
{
|
||||
_args = [args objectEnumerator];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) nextArg
|
||||
{
|
||||
return [_args nextObject];
|
||||
}
|
||||
|
||||
- (BOOL)scanPredicateKeyword:(CPString)key
|
||||
{
|
||||
var loc = [self scanLocation];
|
||||
var c;
|
||||
|
||||
[self setCaseSensitive:NO];
|
||||
if (![self scanString:key intoString:NULL])
|
||||
return NO;
|
||||
|
||||
if ([self isAtEnd])
|
||||
return YES;
|
||||
|
||||
c = [[self string] characterAtIndex:[self scanLocation]];
|
||||
if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c])
|
||||
return YES;
|
||||
|
||||
[self setScanLocation:loc];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPPredicate) parse
|
||||
{
|
||||
var r = nil;
|
||||
|
||||
try
|
||||
{
|
||||
[self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]];
|
||||
r = [self parsePredicate];
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
CPLogConsole(@"Parsing failed for "+[self string]+" with " + error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (![self isAtEnd])
|
||||
CPLogConsole(@"Format string contains extra characters: \""+[self string]+"\"");
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
- (CPPredicate) parsePredicate
|
||||
{
|
||||
return [self parseAnd];
|
||||
}
|
||||
|
||||
- (CPPredicate) parseAnd
|
||||
{
|
||||
var l = [self parseOr];
|
||||
|
||||
while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"])
|
||||
{
|
||||
var r = [self parseOr];
|
||||
|
||||
if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType)
|
||||
{
|
||||
if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
|
||||
{
|
||||
[[l subpredicates] addObjectsFromArray:[r subpredicates]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[r subpredicates] insertObject:l atIndex:0];
|
||||
l = r;
|
||||
}
|
||||
}
|
||||
else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
|
||||
{
|
||||
[[l subpredicates] addObject:r];
|
||||
}
|
||||
else
|
||||
{
|
||||
l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r, nil]];
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
- (CPPredicate) parseNot
|
||||
{
|
||||
if ([self scanString:@"(" intoString:NULL])
|
||||
{
|
||||
var r = [self parsePredicate];
|
||||
|
||||
if (![self scanString:@")" intoString:NULL])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Missing ) in compound predicate"];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"])
|
||||
{
|
||||
return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]];
|
||||
}
|
||||
if ([self scanPredicateKeyword:@"TRUEPREDICATE"])
|
||||
{
|
||||
return [CPPredicate predicateWithValue:YES];
|
||||
}
|
||||
if ([self scanPredicateKeyword:@"FALSEPREDICATE"])
|
||||
{
|
||||
return [CPPredicate predicateWithValue:NO];
|
||||
}
|
||||
|
||||
return [self parseComparison];
|
||||
}
|
||||
|
||||
- (CPPredicate) parseOr
|
||||
{
|
||||
var l = [self parseNot];
|
||||
while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"])
|
||||
{
|
||||
var r = [self parseNot];
|
||||
|
||||
if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType)
|
||||
{
|
||||
if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
|
||||
{
|
||||
[[l subpredicates] addObjectsFromArray:[r subpredicates]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[r subpredicates] insertObject:l atIndex:0];
|
||||
l = r;
|
||||
}
|
||||
}
|
||||
else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
|
||||
{
|
||||
[[l subpredicates] addObject:r];
|
||||
}
|
||||
else
|
||||
{
|
||||
l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r, nil]];
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
- (CPPredicate) parseComparison
|
||||
{
|
||||
var modifier = CPDirectPredicateModifier,
|
||||
type = 0,
|
||||
opts = 0,
|
||||
left,
|
||||
right,
|
||||
p,
|
||||
negate = NO,
|
||||
swap = NO;
|
||||
|
||||
if ([self scanPredicateKeyword:@"ANY"])
|
||||
{
|
||||
modifier = CPAnyPredicateModifier;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"ALL"])
|
||||
{
|
||||
modifier = CPAllPredicateModifier;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"NONE"])
|
||||
{
|
||||
modifier = CPAnyPredicateModifier;
|
||||
negate = YES;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"SOME"])
|
||||
{
|
||||
modifier = CPAllPredicateModifier;
|
||||
negate = YES;
|
||||
}
|
||||
|
||||
left = [self parseExpression];
|
||||
if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL])
|
||||
{
|
||||
type = CPNotEqualToPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL])
|
||||
{
|
||||
type = CPLessThanOrEqualToPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL])
|
||||
{
|
||||
type = CPGreaterThanOrEqualToPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanString:@"<" intoString:NULL])
|
||||
{
|
||||
type = CPLessThanPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanString:@">" intoString:NULL])
|
||||
{
|
||||
type = CPGreaterThanPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL])
|
||||
{
|
||||
type = CPEqualToPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"MATCHES"])
|
||||
{
|
||||
type = CPMatchesPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"LIKE"])
|
||||
{
|
||||
type = CPLikePredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"BEGINSWITH"])
|
||||
{
|
||||
type = CPBeginsWithPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"ENDSWITH"])
|
||||
{
|
||||
type = CPEndsWithPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"IN"])
|
||||
{
|
||||
type = CPInPredicateOperatorType;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"CONTAINS"])
|
||||
{
|
||||
type = CPInPredicateOperatorType;
|
||||
swap = YES;
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"BETWEEN"])
|
||||
{
|
||||
var exp = [self parseSimpleExpression];
|
||||
var a = [exp constantValue];
|
||||
var lower, upper;
|
||||
var lexp, uexp;
|
||||
var lp, up;
|
||||
|
||||
if (![a isKindOfClass:[CPArray class]])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"BETWEEN operator requires array argument"];
|
||||
|
||||
lower = [a objectAtIndex:0];
|
||||
upper = [a objectAtIndex:1];
|
||||
lexp = [CPExpression expressionForConstantValue:lower];
|
||||
uexp = [CPExpression expressionForConstantValue:upper];
|
||||
lp = [CPComparisonPredicate predicateWithLeftExpression:left
|
||||
rightExpression:lexp
|
||||
modifier:modifier
|
||||
type:CPGreaterThanPredicateOperatorType
|
||||
options:opts];
|
||||
up = [CPComparisonPredicate predicateWithLeftExpression:left
|
||||
rightExpression:uexp
|
||||
modifier:modifier
|
||||
type:CPLessThanPredicateOperatorType
|
||||
options:opts];
|
||||
return [CPCompoundPredicate andPredicateWithSubpredicates:
|
||||
[CPArray arrayWithObjects:lp, up, nil]];
|
||||
}
|
||||
else
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid comparison predicate: "+ [[self string] substringFromIndex: [self scanLocation]]];
|
||||
|
||||
if ([self scanString:@"[cd]" intoString:NULL])
|
||||
{
|
||||
opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption;
|
||||
}
|
||||
else if ([self scanString:@"[c]" intoString:NULL])
|
||||
{
|
||||
opts = CPCaseInsensitivePredicateOption;
|
||||
}
|
||||
else if ([self scanString:@"[d]" intoString:NULL])
|
||||
{
|
||||
opts = CPDiacriticInsensitivePredicateOption;
|
||||
}
|
||||
|
||||
right = [self parseExpression];
|
||||
|
||||
if (swap == YES)
|
||||
{
|
||||
var tmp = left;
|
||||
|
||||
left = right;
|
||||
right = tmp;
|
||||
}
|
||||
|
||||
p = [CPComparisonPredicate predicateWithLeftExpression:left
|
||||
rightExpression:right
|
||||
modifier:modifier
|
||||
type:type
|
||||
options:opts];
|
||||
|
||||
return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p;
|
||||
}
|
||||
|
||||
- (CPExpression) parseExpression
|
||||
{
|
||||
return [self parseBinaryExpression];
|
||||
}
|
||||
|
||||
- (CPExpression) parseSimpleExpression
|
||||
{
|
||||
var identifier,
|
||||
location,
|
||||
ident,
|
||||
dbl;
|
||||
|
||||
if ([self scanDouble:REFERENCE(dbl)])
|
||||
return [CPExpression expressionForConstantValue:[CPNumber numberWithDouble:dbl]];
|
||||
|
||||
// FIXME: handle integer, hex constants, 0x 0o 0b
|
||||
if ([self scanString:@"-" intoString:NULL])
|
||||
return [CPExpression expressionForFunction:@"chs" arguments:[CPArray arrayWithObject:[self parseExpression]]];
|
||||
|
||||
if ([self scanString:@"(" intoString:NULL])
|
||||
{
|
||||
var arg = [self parseExpression];
|
||||
|
||||
if (![self scanString:@")" intoString:NULL])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Missing ) in expression"];
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
if ([self scanString:@"{" intoString:NULL])
|
||||
{
|
||||
var a = [CPMutableArray arrayWithCapacity:10];
|
||||
|
||||
if ([self scanString:@"}" intoString:NULL])
|
||||
return [CPExpression expressionForConstantValue:a];
|
||||
|
||||
[a addObject:[self parseExpression]];
|
||||
while ([self scanString:@"," intoString:NULL])
|
||||
[a addObject:[self parseExpression]];
|
||||
|
||||
if (![self scanString:@"}" intoString:NULL])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Missing } in aggregate"];
|
||||
|
||||
return [CPExpression expressionForConstantValue:a];
|
||||
}
|
||||
|
||||
if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"])
|
||||
{
|
||||
return [CPExpression expressionForConstantValue:[CPNull null]];
|
||||
}
|
||||
if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"])
|
||||
{
|
||||
return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]];
|
||||
}
|
||||
if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"])
|
||||
{
|
||||
return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]];
|
||||
}
|
||||
if ([self scanPredicateKeyword:@"SELF"])
|
||||
{
|
||||
return [CPExpression expressionForEvaluatedObject];
|
||||
}
|
||||
|
||||
if ([self scanString:@"$" intoString:NULL])
|
||||
{
|
||||
var variable = [self parseExpression];
|
||||
|
||||
if (![variable keyPath])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid variable identifier: " + variable];
|
||||
|
||||
return [CPExpression expressionForVariable:[variable keyPath]];
|
||||
}
|
||||
|
||||
location = [self scanLocation];
|
||||
|
||||
if ([self scanString:@"%" intoString:NULL])
|
||||
{
|
||||
if ([self isAtEnd] == NO)
|
||||
{
|
||||
var c = [[self string] characterAtIndex:[self scanLocation]];
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '%':// '%%' is treated as '%'
|
||||
location = [self scanLocation];
|
||||
break;
|
||||
case 'K':
|
||||
[self setScanLocation:[self scanLocation] + 1];
|
||||
return [CPExpression expressionForKeyPath:[self nextArg]];
|
||||
case '@':
|
||||
case 'c':
|
||||
case 'C':
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'O':
|
||||
case 'u':
|
||||
case 'U':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'g':
|
||||
case 'G':
|
||||
[self setScanLocation:[self scanLocation] + 1];
|
||||
return [CPExpression expressionForConstantValue:[self nextArg]];
|
||||
case 'h':
|
||||
[self scanString:@"h" intoString:NULL];
|
||||
if ([self isAtEnd] == NO)
|
||||
{
|
||||
c = [[self string] characterAtIndex:[self scanLocation]];
|
||||
if (c == 'i' || c == 'u')
|
||||
{
|
||||
[self setScanLocation:[self scanLocation] + 1];
|
||||
return [CPExpression expressionForConstantValue:[self nextArg]];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
[self scanString:@"q" intoString:NULL];
|
||||
if ([self isAtEnd] == NO)
|
||||
{
|
||||
c = [[self string] characterAtIndex:[self scanLocation]];
|
||||
if (c == 'i' || c == 'u' || c == 'x' || c == 'X')
|
||||
{
|
||||
[self setScanLocation:[self scanLocation] + 1];
|
||||
return [CPExpression expressionForConstantValue:[self nextArg]];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self setScanLocation:location];
|
||||
}
|
||||
|
||||
if ([self scanString:@"\"" intoString:NULL])
|
||||
{
|
||||
var skip = [self charactersToBeSkipped];
|
||||
var str;
|
||||
|
||||
[self setCharactersToBeSkipped:nil];
|
||||
if ([self scanUpToString:@"\"" intoString:REFERENCE(str)] == NO)
|
||||
{
|
||||
[self setCharactersToBeSkipped:skip];
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid double quoted literal at "+location];
|
||||
}
|
||||
|
||||
[self scanString:@"\"" intoString:NULL];
|
||||
[self setCharactersToBeSkipped:skip];
|
||||
|
||||
return [CPExpression expressionForConstantValue:str];
|
||||
}
|
||||
if ([self scanString:@"'" intoString:NULL])
|
||||
{
|
||||
var skip = [self charactersToBeSkipped];
|
||||
var str;
|
||||
|
||||
[self setCharactersToBeSkipped:nil];
|
||||
if ([self scanUpToString:@"'" intoString:REFERENCE(str)] == NO)
|
||||
{
|
||||
[self setCharactersToBeSkipped:skip];
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid single quoted literal at "+location];
|
||||
}
|
||||
|
||||
[self scanString:@"'" intoString:NULL];
|
||||
[self setCharactersToBeSkipped:skip];
|
||||
|
||||
return [CPExpression expressionForConstantValue:str];
|
||||
}
|
||||
|
||||
if ([self scanString:@"@" intoString:NULL])
|
||||
{
|
||||
var e = [self parseExpression];
|
||||
|
||||
if (![e keyPath])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid keypath identifier: "+e];
|
||||
|
||||
return [CPExpression expressionForKeyPath:[e keyPath]+"@"];
|
||||
}
|
||||
|
||||
[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]]];
|
||||
|
||||
return [CPExpression expressionForKeyPath:ident];
|
||||
}
|
||||
|
||||
- (CPExpression)parseFunctionalExpression
|
||||
{
|
||||
var left = [self parseSimpleExpression];
|
||||
|
||||
while (YES)
|
||||
{
|
||||
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])
|
||||
{
|
||||
// index expression
|
||||
if ([self scanPredicateKeyword:@"FIRST"])
|
||||
{
|
||||
left = [CPExpression expressionForFunction:@"first" arguments:[CPArray arrayWithObject:[self parseExpression]]];
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"LAST"])
|
||||
{
|
||||
left = [CPExpression expressionForFunction:@"last" arguments:[CPArray arrayWithObject:[self parseExpression]]];
|
||||
}
|
||||
else if ([self scanPredicateKeyword:@"SIZE"])
|
||||
{
|
||||
left = [CPExpression expressionForFunction:@"count" arguments:[CPArray arrayWithObject:[self parseExpression]]];
|
||||
}
|
||||
else
|
||||
{
|
||||
left = [CPExpression expressionForFunction:@"index" arguments:[CPArray arrayWithObjects:left, [self parseExpression], nil]];
|
||||
}
|
||||
if (![self scanString:@"]" intoString:NULL])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Missing ] in index argument"];
|
||||
}
|
||||
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"
|
||||
var right;
|
||||
|
||||
if (![left keyPath])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid left keypath:" + left];
|
||||
|
||||
right = [self parseExpression];
|
||||
if (![right keyPath])
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Invalid right keypath:" + left];
|
||||
|
||||
// concatenate
|
||||
left = [CPExpression expressionForKeyPath:[left keyPath]+ "." + [right keyPath]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// done with suffixes
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CPExpression)parsePowerExpression
|
||||
{
|
||||
var left = [self parseFunctionalExpression];
|
||||
|
||||
while (YES)
|
||||
{
|
||||
var right;
|
||||
|
||||
if ([self scanString:@"**" intoString:NULL])
|
||||
{
|
||||
right = [self parseFunctionalExpression];
|
||||
left = [CPExpression expressionForFunction:@"pow" arguments:[CPArray arrayWithObjects:left, right, nil]];
|
||||
}
|
||||
else
|
||||
{
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CPExpression)parseMultiplicationExpression
|
||||
{
|
||||
var left = [self parsePowerExpression];
|
||||
|
||||
while (YES)
|
||||
{
|
||||
var right;
|
||||
|
||||
if ([self scanString:@"*" intoString:NULL])
|
||||
{
|
||||
right = [self parsePowerExpression];
|
||||
left = [CPExpression expressionForFunction:@"_mul" arguments:[CPArray arrayWithObjects:left, right, nil]];
|
||||
}
|
||||
else if ([self scanString:@"/" intoString:NULL])
|
||||
{
|
||||
right = [self parsePowerExpression];
|
||||
left = [CPExpression expressionForFunction:@"_div" arguments:[CPArray arrayWithObjects:left, right, nil]];
|
||||
}
|
||||
else
|
||||
{
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CPExpression)parseAdditionExpression
|
||||
{
|
||||
var left = [self parseMultiplicationExpression];
|
||||
|
||||
while (YES)
|
||||
{
|
||||
var right;
|
||||
|
||||
if ([self scanString:@"+" intoString:NULL])
|
||||
{
|
||||
right = [self parseMultiplicationExpression];
|
||||
left = [CPExpression expressionForFunction:@"_add" arguments:[CPArray arrayWithObjects:left, right, nil]];
|
||||
}
|
||||
else if ([self scanString:@"-" intoString:NULL])
|
||||
{
|
||||
right = [self parseMultiplicationExpression];
|
||||
left = [CPExpression expressionForFunction:@"_sub" arguments:[CPArray arrayWithObjects:left, right, nil]];
|
||||
}
|
||||
else
|
||||
{
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CPExpression)parseBinaryExpression
|
||||
{
|
||||
var left = [self parseAdditionExpression];
|
||||
|
||||
while (YES)
|
||||
{
|
||||
var right;
|
||||
|
||||
if ([self scanString:@":=" intoString:NULL]) // assignment
|
||||
{
|
||||
// check left to be a variable?
|
||||
right = [self parseAdditionExpression];
|
||||
// FIXME
|
||||
}
|
||||
else
|
||||
{
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "CPCompoundPredicate.j"
|
||||
@import "CPComparisonPredicate.j"
|
||||
|
||||
@import "CPExpression.j"
|
||||
@import "CPExpression_operator.j"
|
||||
@import "CPExpression_aggregate.j"
|
||||
@import "CPExpression_assignment.j"
|
||||
@@ -24,12 +24,15 @@
|
||||
@import "CPBundle.j"
|
||||
@import "CPCharacterSet.j"
|
||||
@import "CPCoder.j"
|
||||
@import "CPComparisonPredicate.j"
|
||||
@import "CPCompoundPredicate.j"
|
||||
@import "CPData.j"
|
||||
@import "CPDate.j"
|
||||
@import "CPDictionary.j"
|
||||
@import "CPEnumerator.j"
|
||||
@import "CPException.j"
|
||||
@import "CPFormatter.j"
|
||||
@import "CPExpression.j"
|
||||
@import "CPIndexSet.j"
|
||||
@import "CPInvocation.j"
|
||||
@import "CPJSONPConnection.j"
|
||||
@@ -45,6 +48,7 @@
|
||||
@import "CPObjJRuntime.j"
|
||||
@import "CPOperation.j"
|
||||
@import "CPOperationQueue.j"
|
||||
@import "CPPredicate.j"
|
||||
@import "CPPropertyListSerialization.j"
|
||||
@import "CPRange.j"
|
||||
@import "CPRunLoop.j"
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@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:"KeyPath Expression should not be nil"];
|
||||
|
||||
var expression_str = [CPExpression expressionForConstantValue:@"j[a-z]an"];
|
||||
[self assertNotNull:expression_str message:"ConstantValue Expression should not be nil"];
|
||||
|
||||
var expression_num = [CPExpression expressionForConstantValue:[CPNumber numberWithInt:1]];
|
||||
[self assertNotNull:expression_num message:"ConstantValue Expression should not be nil"];
|
||||
|
||||
var expression_collection = [CPExpression expressionForConstantValue:[CPArray arrayWithObjects:@"a",@"b",@"d",nil]];
|
||||
[self assertNotNull:expression_collection message:"ConstantValue Expression should not be nil"];
|
||||
|
||||
var expression_var = [CPExpression expressionForVariable:@"variable"];
|
||||
[self assertNotNull:expression_var message:"Variable Expression should not be nil"];
|
||||
|
||||
var expression_function = [CPExpression expressionForFunction:@"sum:" arguments:[CPArray arrayWithObjects:expression_num,expression_num,nil]];
|
||||
[self assertNotNull:expression_function message:"Function Expression should not be nil"];
|
||||
|
||||
var expression_self = [CPExpression expressionForEvaluatedObject];
|
||||
[self assertNotNull:expression_self message:"Function Expression should not be nil"];
|
||||
|
||||
var expression_aggregate = [CPExpression expressionForAggregate:[CPArray arrayWithObjects:expression_str,expression_num,expression_function,nil]];
|
||||
[self assertNotNull:expression_aggregate message:"Aggregate Expression should not be nil"];
|
||||
|
||||
var expression_subquery = [CPExpression expressionForSubquery:expression_collection usingIteratorVariable:@"self" predicate:[CPPredicate predicateWithValue:YES]];
|
||||
[self assertNotNull:expression_subquery message:"Subquery 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:"IntersectSet Expression should not be nil"];
|
||||
|
||||
var expression_unionset = [CPExpression expressionForUnionSet:set with:array];
|
||||
[self assertNotNull:expression_unionset message:"UnionSet Expression should not be nil"];
|
||||
|
||||
var expression_minusset = [CPExpression expressionForMinusSet:set with:array];
|
||||
[self assertNotNull:expression_minusset message:"MinusSet 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: @"Record1.Age BETWEEN {%f,%f}", 20, 40];
|
||||
[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
|
||||
Reference in new issue
Block a user