/* Interface for NSPredicate for GNUStep Copyright (C) 2005 Free Software Foundation, Inc. Written by: Dr. H. Nikolaus Schaller Created: 2005 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NIMP [NSException raise: NSGenericException \ format: @"%s(%s) has not implemented %s",\ GSClassNameFromObject(self), GSObjCIsInstance(self) ? "instance" : "class",\ GSNameFromSelector(_cmd)] @interface GSPredicateScanner : NSScanner { NSEnumerator *_args; // Not retained. va_list _vargs; unsigned _retrieved; } - (id) initWithString: (NSString*)format args: (NSArray*)args; - (id) initWithString: (NSString*)format vargs: (va_list)vargs; - (id) nextArg; - (BOOL) scanPredicateKeyword: (NSString *) key; - (NSPredicate *) parse; - (NSPredicate *) parsePredicate; - (NSPredicate *) parseAnd; - (NSPredicate *) parseNot; - (NSPredicate *) parseOr; - (NSPredicate *) parseComparison; - (NSExpression *) parseExpression; - (NSExpression *) parseFunctionalExpression; - (NSExpression *) parsePowerExpression; - (NSExpression *) parseMultiplicationExpression; - (NSExpression *) parseAdditionExpression; - (NSExpression *) parseBinaryExpression; @end @interface GSTruePredicate : NSPredicate @end @interface GSFalsePredicate : NSPredicate @end @interface GSAndCompoundPredicate : NSCompoundPredicate { @public NSArray *_subs; } - (id) _initWithSubpredicates: (NSArray *)list; @end @interface GSOrCompoundPredicate : NSCompoundPredicate { @public NSArray *_subs; } - (id) _initWithSubpredicates: (NSArray *)list; @end @interface GSNotCompoundPredicate : NSCompoundPredicate { @public NSPredicate *_sub; } - (id) _initWithSubpredicate: (id)predicateOrList; @end @interface GSConstantValueExpression : NSExpression { @public id _obj; } @end @interface GSEvaluatedObjectExpression : NSExpression @end @interface GSVariableExpression : NSExpression { @public NSString *_variable; } @end @interface GSKeyPathExpression : NSExpression { @public NSString *_keyPath; } @end @interface GSFunctionExpression : NSExpression { @public NSString *_function; NSArray *_args; NSMutableArray *_eargs; // temporary space unsigned int _argc; SEL _selector; } @end @implementation NSPredicate + (NSPredicate *) predicateWithFormat: (NSString *) format, ... { NSPredicate *p; va_list va; va_start (va, format); p = [self predicateWithFormat: format arguments: va]; va_end (va); return p; } + (NSPredicate *) predicateWithFormat: (NSString *)format argumentArray: (NSArray *)args { GSPredicateScanner *s; NSPredicate *p; s = [[GSPredicateScanner alloc] initWithString: format args: args]; p = [s parse]; RELEASE(s); return p; } + (NSPredicate *) predicateWithFormat: (NSString *)format arguments: (va_list)args { GSPredicateScanner *s; NSPredicate *p; s = [[GSPredicateScanner alloc] initWithString: format vargs: args]; p = [s parse]; RELEASE(s); return p; } + (NSPredicate *) predicateWithValue: (BOOL)value { if (value) { return (NSPredicate *)[GSTruePredicate new]; } return (NSPredicate *)[GSFalsePredicate new]; } // we don't ever instantiate NSPredicate - (id) copyWithZone: (NSZone *)z { [self subclassResponsibility: _cmd]; return RETAIN(self); } - (BOOL) evaluateWithObject: (id)object { [self subclassResponsibility: _cmd]; return NO; } - (NSString *) description { return [self predicateFormat]; } - (NSString *) predicateFormat { [self subclassResponsibility: _cmd]; return nil; } - (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables { return [[self copy] autorelease]; } - (void) encodeWithCoder: (NSCoder *) coder; { [self subclassResponsibility: _cmd]; } - (id) initWithCoder: (NSCoder *) coder; { [self subclassResponsibility: _cmd]; return self; } @end @implementation GSTruePredicate - (id) copyWithZone: (NSZone *) z { return [self retain]; } - (BOOL) evaluateWithObject: (id)object { return YES; } - (NSString *) predicateFormat { return @"TRUEPREDICATE"; } @end @implementation GSFalsePredicate - (id) copyWithZone: (NSZone *)z { return [self retain]; } - (BOOL) evaluateWithObject: (id)object { return NO; } - (NSString *) predicateFormat { return @"FALSEPREDICATE"; } @end @implementation NSCompoundPredicate + (NSPredicate *) andPredicateWithSubpredicates: (NSArray *)list { return [[[GSAndCompoundPredicate alloc] _initWithSubpredicates: list] autorelease]; } + (NSPredicate *) notPredicateWithSubpredicate: (NSPredicate *)predicate { return [[[GSNotCompoundPredicate alloc] _initWithSubpredicate: predicate] autorelease]; } + (NSPredicate *) orPredicateWithSubpredicates: (NSArray *)list { return [[[GSOrCompoundPredicate alloc] _initWithSubpredicates: list] autorelease]; } - (NSCompoundPredicateType) compoundPredicateType { [self subclassResponsibility: _cmd]; return 0; } - (id) initWithType: (NSCompoundPredicateType)type subpredicates: (NSArray *)list { [self release]; switch (type) { case NSAndPredicateType: return [[GSAndCompoundPredicate alloc] _initWithSubpredicates: list]; case NSOrPredicateType: return [[GSOrCompoundPredicate alloc] _initWithSubpredicates: list]; case NSNotPredicateType: return [[GSNotCompoundPredicate alloc] _initWithSubpredicate: list]; default: return nil; } } - (id) copyWithZone: (NSZone *)z { [self subclassResponsibility: _cmd]; return [self retain]; } - (NSArray *) subpredicates { [self subclassResponsibility: _cmd]; return nil; } - (void) encodeWithCoder: (NSCoder *)coder { [self subclassResponsibility: _cmd]; } - (id) initWithCoder: (NSCoder *)coder { return self; } @end @implementation GSAndCompoundPredicate - (id) _initWithSubpredicates: (NSArray *)list { NSAssert ([list count] > 1, NSInvalidArgumentException); if ((self = [super init]) != nil) { _subs = [list retain]; } return self; } - (void) dealloc { [_subs release]; [super dealloc]; } - (NSCompoundPredicateType) compoundPredicateType { return NSAndPredicateType; } - (BOOL) evaluateWithObject: (id) object { NSEnumerator *e = [_subs objectEnumerator]; NSPredicate *p; while ((p = [e nextObject]) != nil) { if ([p evaluateWithObject: object] == NO) { return NO; // any NO returns NO } } return YES; // all are true } - (NSString *) predicateFormat { NSString *fmt = @""; NSEnumerator *e = [_subs objectEnumerator]; NSPredicate *sub; unsigned cnt = 0; while ((sub = [e nextObject]) != nil) { // when to add ()? -> if sub is compound and of type "or" if (cnt == 0) { fmt = [sub predicateFormat]; // first } else { if (cnt == 1 && [[_subs objectAtIndex: 0] isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)[_subs objectAtIndex: 0] compoundPredicateType] == NSOrPredicateType) { // we need () around first OR on left side fmt = [NSString stringWithFormat: @"(%@)", fmt]; } if ([sub isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *) sub compoundPredicateType] == NSOrPredicateType) { // we need () around right OR fmt = [NSString stringWithFormat: @"%@ AND (%@)", fmt, [sub predicateFormat]]; } else { fmt = [NSString stringWithFormat: @"%@ AND %@", fmt, [sub predicateFormat]]; } } cnt++; } return fmt; } - (NSArray *) subpredicates { return _subs; } - (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables { GSAndCompoundPredicate *copy = [self copy]; unsigned int count = [copy->_subs count]; unsigned int i; for (i = 0; i < count; i++) { NSPredicate *rep; rep = [_subs objectAtIndex: i]; rep = [rep predicateWithSubstitutionVariables: variables]; [(NSMutableArray *)(copy->_subs) replaceObjectAtIndex: i withObject: rep]; } return [copy autorelease]; } @end @implementation GSOrCompoundPredicate - (id) _initWithSubpredicates: (NSArray *)list { NSAssert ([list count] > 1, NSInvalidArgumentException); if ((self = [super init]) != nil) { _subs = [list retain]; } return self; } - (void) dealloc { [_subs release]; [super dealloc]; } - (NSCompoundPredicateType) compoundPredicateType { return NSOrPredicateType; } - (BOOL) evaluateWithObject: (id)object { NSEnumerator *e = [_subs objectEnumerator]; NSPredicate *p; while ((p = [e nextObject]) != nil) { if ([p evaluateWithObject: object] == YES) { return YES; // any YES returns YES } } return NO; // none is true } - (NSString *) predicateFormat { NSString *fmt = @""; NSEnumerator *e = [_subs objectEnumerator]; NSPredicate *sub; while ((sub = [e nextObject]) != nil) { if ([fmt length] > 0) { fmt = [NSString stringWithFormat: @"%@ OR %@", fmt, [sub predicateFormat]]; } else { fmt = [sub predicateFormat]; // first } } return fmt; } - (NSArray *) subpredicates { return _subs; } - (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables { GSOrCompoundPredicate *copy = [self copy]; unsigned int count = [copy->_subs count]; unsigned int i; for (i = 0; i < count; i++) { NSPredicate *rep; rep = [_subs objectAtIndex: i]; rep = [rep predicateWithSubstitutionVariables: variables]; [(NSMutableArray *)(copy->_subs) replaceObjectAtIndex: i withObject: rep]; } return [copy autorelease]; } @end @implementation GSNotCompoundPredicate - (id) _initWithSubpredicate: (id)listOrPredicate { if ((self = [super init]) != nil) { if ([listOrPredicate isKindOfClass: [NSArray class]]) { _sub = [[listOrPredicate objectAtIndex: 0] retain]; } else { _sub = [listOrPredicate retain]; } } return self; } - (void) dealloc { [_sub release]; [super dealloc]; } - (NSCompoundPredicateType) compoundPredicateType { return NSNotPredicateType; } - (BOOL) evaluateWithObject: (id)object { return ![_sub evaluateWithObject: object]; } - (NSString *) predicateFormat { if ([_sub isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)_sub compoundPredicateType] != NSNotPredicateType) { return [NSString stringWithFormat: @"NOT(%@)", [_sub predicateFormat]]; } return [NSString stringWithFormat: @"NOT %@", [_sub predicateFormat]]; } - (NSArray *) subpredicates { return [NSArray arrayWithObject: _sub]; } - (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables { GSNotCompoundPredicate *copy = [self copy]; copy->_sub = [_sub predicateWithSubstitutionVariables: variables]; return [copy autorelease]; } @end @implementation NSComparisonPredicate + (NSPredicate *) predicateWithLeftExpression: (NSExpression *)left rightExpression: (NSExpression *)right customSelector: (SEL) sel { return [[[self alloc] initWithLeftExpression: left rightExpression: right customSelector: sel] autorelease]; } + (NSPredicate *) predicateWithLeftExpression: (NSExpression *)left rightExpression: (NSExpression *)right modifier: (NSComparisonPredicateModifier)modifier type: (NSPredicateOperatorType)type options: (unsigned)opts { return [[[self alloc] initWithLeftExpression: left rightExpression: right modifier: modifier type: type options: opts] autorelease]; } - (NSComparisonPredicateModifier) comparisonPredicateModifier { return _modifier; } - (SEL) customSelector { return _selector; } - (NSPredicate *) initWithLeftExpression: (NSExpression *)left rightExpression: (NSExpression *)right customSelector: (SEL)sel { if ((self = [super init]) != nil) { _left = [left retain]; _right = [right retain]; _selector = sel; _type = NSCustomSelectorPredicateOperatorType; } return self; } - (id) initWithLeftExpression: (NSExpression *)left rightExpression: (NSExpression *)right modifier: (NSComparisonPredicateModifier)modifier type: (NSPredicateOperatorType)type options: (unsigned)opts { if ((self = [super init]) != nil) { _left = [left retain]; _right = [right retain]; _modifier = modifier; _type = type; _options = opts; } return self; } - (void) dealloc; { [_left release]; [_right release]; [super dealloc]; } - (NSExpression *) leftExpression { return _left; } - (unsigned) options { return _options; } - (NSPredicateOperatorType) predicateOperatorType { return _type; } - (NSExpression *) rightExpression { return _right; } - (NSString *) predicateFormat { NSString *modi = @""; NSString *comp = @"?comparison?"; NSString *opt = @""; switch (_modifier) { case NSDirectPredicateModifier: break; case NSAnyPredicateModifier: modi = @"ANY "; break; case NSAllPredicateModifier: modi = @"ALL"; break; default: modi = @"?modifier?"; break; } switch (_type) { case NSLessThanPredicateOperatorType: comp = @"<"; break; case NSLessThanOrEqualToPredicateOperatorType: comp = @"<="; break; case NSGreaterThanPredicateOperatorType: comp = @">="; break; case NSGreaterThanOrEqualToPredicateOperatorType: comp = @">"; break; case NSEqualToPredicateOperatorType: comp = @"="; break; case NSNotEqualToPredicateOperatorType: comp = @"!="; break; case NSMatchesPredicateOperatorType: comp = @"MATCHES"; break; case NSLikePredicateOperatorType: comp = @"LIKE"; break; case NSBeginsWithPredicateOperatorType: comp = @"BEGINSWITH"; break; case NSEndsWithPredicateOperatorType: comp = @"ENDSWITH"; break; case NSInPredicateOperatorType: comp = @"IN"; break; case NSCustomSelectorPredicateOperatorType: { comp = NSStringFromSelector (_selector); } } switch (_options) { case NSCaseInsensitivePredicateOption: opt = @"[c]"; break; case NSDiacriticInsensitivePredicateOption: opt = @"[d]"; break; case NSCaseInsensitivePredicateOption | NSDiacriticInsensitivePredicateOption: opt = @"[cd]"; break; default: opt = @"[?options?]"; break; } return [NSString stringWithFormat: @"%@%@ %@%@ %@", modi, _left, comp, opt, _right]; } - (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables { NSComparisonPredicate *copy = [self copy]; // FIXME ... perform substitution in the left and right expressions return [copy autorelease]; } @end @implementation NSExpression + (NSExpression *) expressionForConstantValue: (id)obj { GSConstantValueExpression *e; e = [[[GSConstantValueExpression alloc] init] autorelease]; e->_obj = [obj retain]; return e; } + (NSExpression *) expressionForEvaluatedObject { return [[[GSEvaluatedObjectExpression alloc] init] autorelease]; } + (NSExpression *) expressionForFunction: (NSString *)name arguments: (NSArray *)args { GSFunctionExpression *e; NSString *s; e = [[[GSFunctionExpression alloc] init] autorelease]; s = [NSString stringWithFormat: @"_eval_%@: context: ", name]; e->_selector = NSSelectorFromString(s); if (![e respondsToSelector: e->_selector]) { [NSException raise: NSInvalidArgumentException format: @"Unknown function implementation: %@", name]; } e->_function = [name retain]; e->_argc = [args count]; e->_args = [args retain]; e->_eargs = [args copy]; // space for evaluated arguments return e; } + (NSExpression *) expressionForKeyPath: (NSString *)path { GSKeyPathExpression *e; if (![path isKindOfClass: [NSString class]]) { [NSException raise: NSInvalidArgumentException format: @"Keypath is not NSString: %@", path]; } e = [[[GSKeyPathExpression alloc] init] autorelease]; e->_keyPath = [path retain]; return e; } + (NSExpression *) expressionForVariable: (NSString *)string { GSVariableExpression *e; e = [[[GSVariableExpression alloc] init] autorelease]; e->_variable = [string retain]; return e; } - (NSArray *) arguments { [self subclassResponsibility: _cmd]; return nil; } - (id) constantValue { [self subclassResponsibility: _cmd]; return nil; } - (NSString *) description { [self subclassResponsibility: _cmd]; return nil; } - (NSExpressionType) expressionType { [self subclassResponsibility: _cmd]; return 0; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { [self subclassResponsibility: _cmd]; return nil; } - (NSString *) function { [self subclassResponsibility: _cmd]; return nil; } - (NSString *) keyPath { [self subclassResponsibility: _cmd]; return nil; } - (NSExpression *) operand { [self subclassResponsibility: _cmd]; return nil; } - (NSString *) variable { [self subclassResponsibility: _cmd]; return nil; } - (id) initWithExpressionType: (NSExpressionType)type { [self release]; switch (type) { case NSConstantValueExpressionType: return [[GSConstantValueExpression alloc] init]; case NSEvaluatedObjectExpressionType: return [[GSEvaluatedObjectExpression alloc] init]; case NSVariableExpressionType: return [[GSVariableExpression alloc] init]; case NSKeyPathExpressionType: return [[GSKeyPathExpression alloc] init]; case NSFunctionExpressionType: return [[GSFunctionExpression alloc] init]; default: return nil; } } - (id) copyWithZone: (NSZone *)z { [self subclassResponsibility: _cmd]; return nil; } - (void) encodeWithCoder: (NSCoder *)coder { [self subclassResponsibility: _cmd]; } - (id) initWithCoder: (NSCoder *)coder { [self subclassResponsibility: _cmd]; return nil; } @end @implementation GSConstantValueExpression - (NSArray *) arguments { return nil; } - (id) constantValue { return _obj; } - (NSString *) description { return _obj; } - (NSExpressionType) expressionType { return NSConstantValueExpressionType; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { return _obj; } - (NSString *) function { return nil; } - (NSString *) keyPath { return nil; } - (NSExpression *) operand { return nil; } - (NSString *) variable { return nil; } - (void) dealloc { [_obj release]; [super dealloc]; } @end @implementation GSEvaluatedObjectExpression - (NSArray *) arguments { return nil; } - (id) constantValue { return nil; } - (NSString *) description { return @"SELF"; } - (NSExpressionType) expressionType { return NSEvaluatedObjectExpressionType; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { return self; } - (NSString *) function { return nil; } - (NSString *) keyPath { return nil; } - (NSExpression *) operand { return nil; } - (NSString *) variable { return nil; } @end @implementation GSVariableExpression - (NSArray *) arguments { return nil; } - (id) constantValue { return nil; } - (NSString *) description { return [NSString stringWithFormat: @"$%@", _variable]; } - (NSExpressionType) expressionType { return NSVariableExpressionType; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { return [context objectForKey: _variable]; } - (NSString *) function { return nil; } - (NSString *) keyPath { return nil; } - (NSExpression *) operand { return nil; } - (NSString *) variable { return _variable; } - (void) dealloc; { [_variable release]; [super dealloc]; } @end @implementation GSKeyPathExpression - (NSArray *) arguments { return nil; } - (id) constantValue { return nil; } - (NSString *) description { return _keyPath; } - (NSExpressionType) expressionType { return NSKeyPathExpressionType; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { return [object valueForKeyPath: _keyPath]; } - (NSString *) function { return nil; } - (NSString *) keyPath { return _keyPath; } - (NSExpression *) operand { return nil; } - (NSString *) variable { return nil; } - (void) dealloc; { [_keyPath release]; [super dealloc]; } @end @implementation GSFunctionExpression - (NSArray *) arguments { return _args; } - (id) constantValue { return nil; } - (NSString *) description { // here we should recognize binary and unary operators // and convert back to standard format // and add parentheses if required return [NSString stringWithFormat: @"%@(%@)", [NSStringFromSelector (_selector) substringFromIndex: 6], _args]; } - (NSExpressionType) expressionType { return NSFunctionExpressionType; } - (id) expressionValueWithObject: (id)object context: (NSMutableDictionary *)context { // apply method selector unsigned int i; for (i = 0; i < _argc; i++) { id o; o = [_args objectAtIndex: i]; o = [o expressionValueWithObject: object context: context]; [_eargs replaceObjectAtIndex: i withObject: o]; } return [self performSelector: _selector withObject: object withObject: context]; } - (id) _eval__chs: (id)object context: (NSMutableDictionary *)context { return [NSNumber numberWithInt: -[[_eargs objectAtIndex: 0] intValue]]; } - (id) _eval__first: (id)object context: (NSMutableDictionary *)context { return [[_eargs objectAtIndex: 0] objectAtIndex: 0]; } - (id) _eval__last: (id)object context: (NSMutableDictionary *)context { return [[_eargs objectAtIndex: 0] lastObject]; } - (id) _eval__index: (id)object context: (NSMutableDictionary *)context { if ([[_eargs objectAtIndex: 0] isKindOfClass: [NSDictionary class]]) return [[_eargs objectAtIndex: 0] objectForKey: [_eargs objectAtIndex: 1]]; return [[_eargs objectAtIndex: 0] objectAtIndex: [[_eargs objectAtIndex: 1] unsignedIntValue]]; // raises exception if invalid } - (id) _eval_count: (id)object context: (NSMutableDictionary *)context { if (_argc != 1) ; // error return [NSNumber numberWithUnsignedInt: [[_eargs objectAtIndex: 0] count]]; } - (id) _eval_avg: (NSArray *)expressions context: (NSMutableDictionary *)context { NIMP; return [NSNumber numberWithDouble: 0.0]; } - (id) _eval_sum: (NSArray *)expressions context: (NSMutableDictionary *)context { NIMP; return [NSNumber numberWithDouble: 0.0]; } - (id) _eval_min: (NSArray *)expressions context: (NSMutableDictionary *)context { NIMP; return [NSNumber numberWithDouble: 0.0]; } - (id) _eval_max: (NSArray *)expressions context: (NSMutableDictionary *)context { NIMP; return [NSNumber numberWithDouble: 0.0]; } // add arithmetic functions: average, median, mode, stddev, sqrt, log, ln, exp, floor, ceiling, abs, trunc, random, randomn, now - (NSString *) function { return _function; } - (NSString *) keyPath { return nil; } - (NSExpression *) operand { return nil; } - (NSString *) variable { return nil; } - (void) dealloc; { [_args release]; [_eargs release]; [_function release]; [super dealloc]; } @end @implementation NSArray (NSPredicate) - (NSArray *) filteredArrayUsingPredicate: (NSPredicate *)predicate { NSMutableArray *result; NSEnumerator *e = [self objectEnumerator]; id object; result = [NSMutableArray arrayWithCapacity: [self count]]; while ((object = [e nextObject]) != nil) { if ([predicate evaluateWithObject: object] == YES) { [result addObject: object]; // passes filter } } return result; // we could/should convert to a non-mutable copy } @end @implementation GSPredicateScanner - (id) initWithString: (NSString*)format args: (NSArray*)args { self = [super initWithString: format]; if (self != nil) { _args = [args objectEnumerator]; } return self; } - (id) initWithString: (NSString*)format vargs: (va_list)vargs { self = [super initWithString: format]; if (self != nil) { #ifdef __va_copy __va_copy(_vargs, vargs); #else _vargs = vargs; #endif } return self; } - (id) nextArg { id o; if (_args != nil) { o = [_args nextObject]; } else { unsigned i; va_list ap; #ifdef __va_copy __va_copy(ap, _vargs); #else ap = _vargs; #endif for (i = 0; i < _retrieved; i++) { o = va_arg(ap, id); } _retrieved++; o = va_arg(ap, id); } return o; } - (BOOL) scanPredicateKeyword: (NSString *)key { // save to back up unsigned loc = [self scanLocation]; unichar c; [self setCaseSensitive: NO]; if (![self scanString: key intoString: NULL]) { // no match return NO; } c = [[self string] characterAtIndex: [self scanLocation]]; if (![[NSCharacterSet alphanumericCharacterSet] characterIsMember: c]) { // ok return YES; } // back up [self setScanLocation: loc]; // no match return NO; } - (NSPredicate *) parse { NSPredicate *r; r = [self parsePredicate]; if (![self isAtEnd]) { [NSException raise: NSInvalidArgumentException format: @"Format string contains extra characters: \"%@\"", [self string]]; } return r; } - (NSPredicate *) parsePredicate { return [self parseAnd]; } - (NSPredicate *) parseAnd { NSPredicate *l = [self parseOr]; while ([self scanPredicateKeyword: @"AND"]) { NSPredicate *r = [self parseOr]; if ([r isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)r compoundPredicateType] == NSAndPredicateType) { // merge if ([l isKindOfClass:[NSCompoundPredicate class]] && [(NSCompoundPredicate *)l compoundPredicateType] == NSAndPredicateType) { [(NSMutableArray *)[(NSCompoundPredicate *)l subpredicates] addObjectsFromArray: [(NSCompoundPredicate *)r subpredicates]]; } else { [(NSMutableArray *)[(NSCompoundPredicate *)r subpredicates] insertObject: l atIndex: 0]; l = r; } } else if ([l isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)l compoundPredicateType] == NSAndPredicateType) { // add to l [(NSMutableArray *)[(NSCompoundPredicate *)l subpredicates] addObject: r]; } else { l = [NSCompoundPredicate andPredicateWithSubpredicates: [NSArray arrayWithObjects:l, r, nil]]; } } return l; } - (NSPredicate *) parseNot { if ([self scanString: @"(" intoString: NULL]) { NSPredicate *r = [self parsePredicate]; if (![self scanString: @")" intoString: NULL]) { [NSException raise: NSInvalidArgumentException format: @"Missing ) in compound predicate"]; } return r; } if ([self scanPredicateKeyword: @"NOT"]) { // -> NOT NOT x or NOT (y) return [NSCompoundPredicate notPredicateWithSubpredicate: [self parseNot]]; } if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) { return [NSPredicate predicateWithValue: YES]; } if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) { return [NSPredicate predicateWithValue: NO]; } return [self parseComparison]; } - (NSPredicate *) parseOr { NSPredicate *l = [self parseNot]; while ([self scanPredicateKeyword:@"OR"]) { NSPredicate *r = [self parseNot]; if ([r isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)r compoundPredicateType] == NSOrPredicateType) { // merge if ([l isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)l compoundPredicateType] == NSOrPredicateType) { [(NSMutableArray *)[(NSCompoundPredicate *)l subpredicates] addObjectsFromArray: [(NSCompoundPredicate *)r subpredicates]]; } else { [(NSMutableArray *)[(NSCompoundPredicate *)r subpredicates] insertObject: l atIndex: 0]; l = r; } } else if ([l isKindOfClass: [NSCompoundPredicate class]] && [(NSCompoundPredicate *)l compoundPredicateType] == NSOrPredicateType) { [(NSMutableArray *) [(NSCompoundPredicate *) l subpredicates] addObject:r]; } else { l = [NSCompoundPredicate andPredicateWithSubpredicates: [NSArray arrayWithObjects: l, r, nil]]; } } return l; } - (NSPredicate *) parseComparison { // there must always be a comparison NSComparisonPredicateModifier modifier = NSDirectPredicateModifier; NSPredicateOperatorType type = 0; unsigned opts = 0; NSExpression *left; NSPredicate *p; BOOL negate = NO; if ([self scanPredicateKeyword: @"ANY"]) { modifier = NSAnyPredicateModifier; } else if ([self scanPredicateKeyword: @"ALL"]) { modifier = NSAllPredicateModifier; } else if ([self scanPredicateKeyword: @"NONE"]) { modifier = NSAnyPredicateModifier; negate = YES; } else if ([self scanPredicateKeyword: @"SOME"]) { modifier = NSAllPredicateModifier; negate = YES; } left = [self parseBinaryExpression]; if ([self scanString: @"<" intoString: NULL]) { type = NSLessThanPredicateOperatorType; } else if ([self scanString: @"<=" intoString: NULL]) { type = NSLessThanOrEqualToPredicateOperatorType; } else if ([self scanString: @">" intoString: NULL]) { type = NSGreaterThanPredicateOperatorType; } else if ([self scanString: @">=" intoString: NULL]) { type = NSGreaterThanOrEqualToPredicateOperatorType; } else if ([self scanString: @"=" intoString: NULL]) { type = NSEqualToPredicateOperatorType; } else if ([self scanString: @"!=" intoString: NULL]) { type = NSNotEqualToPredicateOperatorType; } else if ([self scanPredicateKeyword: @"MATCHES"]) { type = NSMatchesPredicateOperatorType; } else if ([self scanPredicateKeyword: @"LIKE"]) { type = NSLikePredicateOperatorType; } else if ([self scanPredicateKeyword: @"BEGINSWITH"]) { type = NSBeginsWithPredicateOperatorType; } else if ([self scanPredicateKeyword: @"ENDSWITH"]) { type = NSEndsWithPredicateOperatorType; } else if ([self scanPredicateKeyword: @"IN"]) { type = NSInPredicateOperatorType; } else { [NSException raise: NSInvalidArgumentException format: @"Invalid comparison predicate: %@", [[self string] substringFromIndex: [self scanLocation]]]; } if ([self scanString: @"[cd]" intoString: NULL]) { opts = NSCaseInsensitivePredicateOption | NSDiacriticInsensitivePredicateOption; } else if ([self scanString: @"[c]" intoString: NULL]) { opts = NSCaseInsensitivePredicateOption; } else if ([self scanString: @"[d]" intoString: NULL]) { opts = NSDiacriticInsensitivePredicateOption; } p = [NSComparisonPredicate predicateWithLeftExpression: left rightExpression: [self parseBinaryExpression] modifier: modifier type: type options: opts]; return negate ? [NSCompoundPredicate notPredicateWithSubpredicate: p] : p; } - (NSExpression *) parseExpression { static NSCharacterSet *_identifier; NSString *ident; double dbl; if ([self scanDouble: &dbl]) { return [NSExpression expressionForConstantValue: [NSNumber numberWithDouble: dbl]]; } // FIXME: handle integer, hex constants, 0x 0o 0b if ([self scanString: @"-" intoString: NULL]) { return [NSExpression expressionForFunction: @"_chs" arguments: [NSArray arrayWithObject: [self parseExpression]]]; } if ([self scanString: @"(" intoString: NULL]) { NSExpression *arg = [self parseExpression]; if (![self scanString: @")" intoString: NULL]) { [NSException raise: NSInvalidArgumentException format: @"Missing ) in expression"]; } return arg; } if ([self scanString: @"{" intoString: NULL]) { NSMutableArray *a = [NSMutableArray arrayWithCapacity: 10]; if ([self scanString: @"}" intoString: NULL]) { // empty // FIXME return nil; } // first element [a addObject: [self parseExpression]]; while ([self scanString: @"," intoString: NULL]) { // more elements [a addObject: [self parseExpression]]; } if (![self scanString: @"}" intoString: NULL]) { [NSException raise: NSInvalidArgumentException format: @"Missing } in aggregate"]; } // FIXME return nil; } if ([self scanPredicateKeyword: @"NULL"]) { return [NSExpression expressionForConstantValue: [NSNull null]]; } if ([self scanPredicateKeyword: @"TRUE"]) { return [NSExpression expressionForConstantValue: [NSNumber numberWithBool: YES]]; } if ([self scanPredicateKeyword: @"FALSE"]) { return [NSExpression expressionForConstantValue: [NSNumber numberWithBool: NO]]; } if ([self scanPredicateKeyword: @"SELF"]) { return [NSExpression expressionForEvaluatedObject]; } if ([self scanString: @"$" intoString: NULL]) { // variable NSExpression *var = [self parseExpression]; if (![var keyPath]) { [NSException raise: NSInvalidArgumentException format: @"Invalid variable identifier: %@", var]; } return [NSExpression expressionForVariable:[var keyPath]]; } if ([self scanPredicateKeyword: @"%K"]) { return [NSExpression expressionForKeyPath: [self nextArg]]; } if ([self scanPredicateKeyword: @"%@"]) { return [NSExpression expressionForConstantValue: [self nextArg]]; } // FIXME: other formats if ([self scanString: @"\"" intoString: NULL]) { NSString *str = @"string constant"; return [NSExpression expressionForConstantValue: str]; } if ([self scanString: @"'" intoString: NULL]) { NSString *str = @"string constant"; return [NSExpression expressionForConstantValue: str]; } if ([self scanString: @"@" intoString: NULL]) { NSExpression *e = [self parseExpression]; if (![e keyPath]) { [NSException raise: NSInvalidArgumentException format: @"Invalid keypath identifier: %@", e]; } // prefix with keypath return [NSExpression expressionForKeyPath: [NSString stringWithFormat: @"@%@", [e keyPath]]]; } // skip # as prefix (reserved words) [self scanString: @"#" intoString: NULL]; if (!_identifier) { _identifier = [NSCharacterSet characterSetWithCharactersInString: @"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; RETAIN(_identifier); } if (![self scanCharactersFromSet: _identifier intoString: &ident]) { [NSException raise: NSInvalidArgumentException format: @"Missing identifier: %@", [[self string] substringFromIndex: [self scanLocation]]]; } return [NSExpression expressionForKeyPath: ident]; } - (NSExpression *) parseFunctionalExpression { NSExpression *left = [self parseExpression]; 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" NSMutableArray *args = [NSMutableArray arrayWithCapacity: 5]; if (![left keyPath]) { [NSException raise: NSInvalidArgumentException format: @"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]) { [NSException raise: NSInvalidArgumentException format: @"Missing ) in function arguments"]; } } left = [NSExpression expressionForFunction: [left keyPath] arguments: args]; } else if ([self scanString: @"[" intoString: NULL]) { // index expression if ([self scanPredicateKeyword: @"FIRST"]) { left = [NSExpression expressionForFunction: @"_first" arguments: [NSArray arrayWithObject: [self parseExpression]]]; } else if ([self scanPredicateKeyword: @"LAST"]) { left = [NSExpression expressionForFunction: @"_last" arguments: [NSArray arrayWithObject: [self parseExpression]]]; } else if ([self scanPredicateKeyword: @"SIZE"]) { left = [NSExpression expressionForFunction: @"count" arguments: [NSArray arrayWithObject: [self parseExpression]]]; } else { left = [NSExpression expressionForFunction: @"_index" arguments: [NSArray arrayWithObjects: left, [self parseExpression], nil]]; } if (![self scanString: @"]" intoString: NULL]) { [NSException raise: NSInvalidArgumentException format: @"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" NSExpression *right; if (![left keyPath]) { [NSException raise: NSInvalidArgumentException format: @"Invalid left keypath: %@", left]; } right = [self parseExpression]; if (![right keyPath]) { [NSException raise: NSInvalidArgumentException format: @"Invalid right keypath: %@", left]; } // concatenate left = [NSExpression expressionForKeyPath: [NSString stringWithFormat: @"%@.%@", [left keyPath], [right keyPath]]]; } else { // done with suffixes return left; } } } - (NSExpression *) parsePowerExpression { NSExpression *left = [self parseFunctionalExpression]; while (YES) { NSExpression *right; if ([self scanString: @"**" intoString: NULL]) { right = [self parseFunctionalExpression]; // FIXME } else { return left; } } } - (NSExpression *) parseMultiplicationExpression { NSExpression *left = [self parsePowerExpression]; while (YES) { NSExpression *right; if ([self scanString: @"*" intoString: NULL]) { right = [self parsePowerExpression]; // FIXME } else if ([self scanString: @"/" intoString: NULL]) { right = [self parsePowerExpression]; // FIXME } else { return left; } } } - (NSExpression *) parseAdditionExpression { NSExpression *left = [self parseMultiplicationExpression]; while (YES) { NSExpression *right; if ([self scanString: @"+" intoString: NULL]) { right = [self parseMultiplicationExpression]; // FIXME } else if ([self scanString: @"-" intoString: NULL]) { right = [self parseMultiplicationExpression]; // FIXME } else { return left; } } } - (NSExpression *) parseBinaryExpression { NSExpression *left = [self parseAdditionExpression]; while (YES) { NSExpression *right; if ([self scanString: @":=" intoString: NULL]) // assignment { // check left to be a variable? right = [self parseAdditionExpression]; } else { return left; } } } @end