diff --git a/Foundation/CPPredicate/CPPredicate.j b/Foundation/CPPredicate/CPPredicate.j index 44c630821..1e99887ec 100644 --- a/Foundation/CPPredicate/CPPredicate.j +++ b/Foundation/CPPredicate/CPPredicate.j @@ -52,2899 +52,7 @@ - ALL children.age > 12 - ANY children.age > 12 - 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 the 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)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - 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 = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; 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) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var 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(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - 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]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"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]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = 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 = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - 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]; - - 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: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]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate: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 parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - 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], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // 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:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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 - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.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" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - 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 the 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)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - 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 = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; 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) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var 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(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - 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]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"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]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = 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 = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - 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]; - - 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: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]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate: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 parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - 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], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // 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:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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 - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.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" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - 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 the 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)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - 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 = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; 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) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var 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(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - 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]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"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]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = 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 = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - 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]; - - 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: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]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate: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 parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - 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], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // 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:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - 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 - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.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" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - You can create predicates for operations, such as @sum.items.price < 1000. + 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 the evaluateWithObject:substitutionVariables: method.