/* Interface for NSPredicate for GNUStep
   Copyright (C) 2005 Free Software Foundation, Inc.

   Written by:  Dr. H. Nikolaus Schaller
   Created: 2005
   Modifications: Fred Kiefer <FredKiefer@gmx.de>
   Date: May 2007
   Modifications: Richard Frith-Macdoanld <rfm@gnu.org>
   Date: June 2007
   
   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 Lesser 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 Lesser 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.
   */ 

#import "common.h"

#define	EXPOSE_NSComparisonPredicate_IVARS	1
#define	EXPOSE_NSCompoundPredicate_IVARS	1
#define	EXPOSE_NSExpression_IVARS	1

#import "Foundation/NSComparisonPredicate.h"
#import "Foundation/NSCompoundPredicate.h"
#import "Foundation/NSExpression.h"
#import "Foundation/NSPredicate.h"

#import "Foundation/NSArray.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSEnumerator.h"
#import "Foundation/NSException.h"
#import "Foundation/NSKeyValueCoding.h"
#import "Foundation/NSNull.h"
#import "Foundation/NSScanner.h"
#import "Foundation/NSValue.h"

#import "GSPrivate.h"

// For pow()
#include <math.h>

#if     defined(HAVE_UNICODE_UREGEX_H)
#include <unicode/uregex.h>
#endif

/* Object to represent the expression beign evaluated.
 */
static NSExpression	*evaluatedObjectExpression = nil;

extern void     GSPropertyListMake(id,NSDictionary*,BOOL,BOOL,unsigned,id*);

@interface GSPredicateScanner : NSScanner
{
  NSEnumerator	*_args;		// Not retained.
  unsigned	_retrieved;
}

- (id) initWithString: (NSString*)format
		 args: (NSArray*)args;
- (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;
- (NSExpression *) parseSimpleExpression;

@end

@interface GSTruePredicate : NSPredicate
@end

@interface GSFalsePredicate : NSPredicate
@end

@interface GSAndCompoundPredicate : NSCompoundPredicate
@end

@interface GSOrCompoundPredicate : NSCompoundPredicate
@end

@interface GSNotCompoundPredicate : NSCompoundPredicate
@end

@interface NSExpression (Private)
- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables;
@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;
  unsigned int		_argc;
  SEL                   _selector;
  NSString              *_op;        // Not retained;
}
@end

#if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST)
@interface GSBlockPredicate : NSPredicate
{
  GSBlockPredicateBlock _block;
}

- (instancetype) initWithBlock: (GSBlockPredicateBlock)block;
@end


@interface GSBoundBlockPredicate : GSBlockPredicate
{
  GS_GENERIC_CLASS(NSDictionary,NSString*,id)* _bindings;
}
- (instancetype) initWithBlock: (GSBlockPredicateBlock)block
                      bindings: (GS_GENERIC_CLASS(NSDictionary,NSString*,id)*)bindings;
@end
#endif

@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;
  const char            *ptr = [format UTF8String];
  NSMutableArray        *arr = [NSMutableArray arrayWithCapacity: 10];

  while (*ptr != 0)
    {
      char      c = *ptr++;

      if (c == '%')
        {
          c = *ptr;
          switch (c)
            {
              case '%':
                ptr++;
                break;

              case 'K':
              case '@':
                ptr++;
                [arr addObject: va_arg(args, id)];
                break;

              case 'c':
                ptr++;
                [arr addObject: [NSNumber numberWithChar:
                  (char)va_arg(args, NSInteger)]];
                break;

              case 'C':
                ptr++;
                [arr addObject: [NSNumber numberWithShort:
                  (short)va_arg(args, NSInteger)]];
                break;

              case 'd':
              case 'D':
              case 'i':
                ptr++;
                [arr addObject: [NSNumber numberWithInt:
                  va_arg(args, int)]];
                break;

              case 'o':
              case 'O':
              case 'u':
              case 'U':
              case 'x':
              case 'X':
                ptr++;
                [arr addObject: [NSNumber numberWithUnsignedInt:
                  va_arg(args, unsigned)]];
                break;

              case 'e':
              case 'E':
              case 'f':
              case 'g':
              case 'G':
                ptr++;
                [arr addObject: [NSNumber numberWithDouble:
                  va_arg(args, double)]];
                break;

              case 'h':
                ptr++;
                if (*ptr != 0)
                  {
                    c = *ptr;
                    if (c == 'i')
                      {
                        [arr addObject: [NSNumber numberWithShort:
                          (short)va_arg(args, NSInteger)]];
                      }
                    if (c == 'u')
                      {
                        [arr addObject: [NSNumber numberWithUnsignedShort:
                          (unsigned short)va_arg(args, NSInteger)]];
                      }
                  }
                break;

              case 'q':
                ptr++;
                if (*ptr != 0)
                  {
                    c = *ptr;
                    if (c == 'i')
                      {
                        [arr addObject: [NSNumber numberWithLongLong:
                          va_arg(args, long long)]];
                      }
                    if (c == 'u' || c == 'x' || c == 'X')
                      {
                        [arr addObject: [NSNumber numberWithUnsignedLongLong:
                          va_arg(args, unsigned long long)]];
                      }
                  }
                break;
            }
        }
      else if (c == '\'')
        {
          while (*ptr != 0)
            {
              if (*ptr++ == '\'')
                {
                  break;
                }
            }
        }
      else if (c == '"')
        {
          while (*ptr != 0)
            {
              if (*ptr++ == '"')
                {
                  break;
                }
            }
        }
    }
  s = [[GSPredicateScanner alloc] initWithString: format
                                            args: arr];
  p = [s parse];
  RELEASE(s);
  return p;
}

+ (NSPredicate *) predicateWithValue: (BOOL)value
{
  if (value)
    {
      return AUTORELEASE([GSTruePredicate new]);
    }
  else
    {
      return AUTORELEASE([GSFalsePredicate new]);
    }
}

// we don't ever instantiate NSPredicate

- (id) copyWithZone: (NSZone *)z
{
  return NSCopyObject(self, 0, z);
}

- (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 AUTORELEASE([self copy]);  
}

#if OS_API_VERSION(MAC_OS_X_VERSION_10_5, GS_API_LATEST)
- (BOOL) evaluateWithObject: (id)object
      substitutionVariables: (GS_GENERIC_CLASS(NSDictionary, NSString*, id)*)variables
{
  return [[self predicateWithSubstitutionVariables: variables]
                                evaluateWithObject: object];
}
#endif
- (Class) classForCoder
{
  return [NSPredicate class];
}

- (void) encodeWithCoder: (NSCoder *) coder;
{
  // FIXME
  [self subclassResponsibility: _cmd];
}

- (id) initWithCoder: (NSCoder *) coder;
{
  // FIXME
  [self subclassResponsibility: _cmd];
  return self;
}

#if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST)
+ (NSPredicate*)predicateWithBlock: (GSBlockPredicateBlock)block
{
  return [[[GSBlockPredicate alloc] initWithBlock: block] autorelease];
}
#endif
@end

@implementation GSTruePredicate

- (id) copyWithZone: (NSZone *)z
{
  return RETAIN(self);
}

- (BOOL) evaluateWithObject: (id)object
{
  return YES;
}

- (NSString *) predicateFormat
{
  return @"TRUEPREDICATE";
}

@end

@implementation GSFalsePredicate

- (id) copyWithZone: (NSZone *)z
{
  return RETAIN(self);
}

- (BOOL) evaluateWithObject: (id)object
{
  return NO;
}

- (NSString *) predicateFormat
{
  return @"FALSEPREDICATE";
}

@end

@implementation NSCompoundPredicate

+ (NSPredicate *) andPredicateWithSubpredicates: (NSArray *)list
{
  return AUTORELEASE([[GSAndCompoundPredicate alloc]
    initWithType: NSAndPredicateType subpredicates: list]);
}

+ (NSPredicate *) notPredicateWithSubpredicate: (NSPredicate *)predicate
{
  NSArray       *list;

  list = [NSArray arrayWithObject: predicate];
  return AUTORELEASE([[GSNotCompoundPredicate alloc] 
    initWithType: NSNotPredicateType subpredicates: list]);
}

+ (NSPredicate *) orPredicateWithSubpredicates: (NSArray *)list
{
  return AUTORELEASE([[GSOrCompoundPredicate alloc]
    initWithType: NSOrPredicateType subpredicates: list]);
}

- (NSCompoundPredicateType) compoundPredicateType
{
  return _type;
}

- (id) initWithType: (NSCompoundPredicateType)type
      subpredicates: (NSArray *)list
{
  if ((self = [super init]) != nil)
    {
      _type = type;
      ASSIGNCOPY(_subs, list);
    }
  return self;
}

- (void) dealloc
{
  RELEASE(_subs);
  [super dealloc];
}

- (id) copyWithZone: (NSZone *)z
{
  return [[[self class] alloc] initWithType: _type subpredicates: _subs];
}

- (NSArray *) subpredicates
{
  return _subs;
}

- (NSPredicate *) predicateWithSubstitutionVariables: (NSDictionary *)variables
{
  unsigned int count = [_subs count];
  NSMutableArray *esubs = [NSMutableArray arrayWithCapacity: count];
  unsigned int i;
  NSPredicate  *p;

  for (i = 0; i < count; i++)
    {
      [esubs addObject: [[_subs objectAtIndex: i] 
                            predicateWithSubstitutionVariables: variables]];
    }

  p = [[[self class] alloc] initWithType: _type subpredicates: esubs];
  return AUTORELEASE(p);
}

- (Class) classForCoder
{
  return [NSCompoundPredicate class];
}

- (void) encodeWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
}

- (id) initWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
  return self;
}

@end

@implementation GSAndCompoundPredicate

- (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;
}

@end

@implementation GSOrCompoundPredicate

- (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;
}

@end

@implementation GSNotCompoundPredicate

- (BOOL) evaluateWithObject: (id)object
{
  NSPredicate *sub = [_subs objectAtIndex: 0];

  return ![sub evaluateWithObject: object];
}

- (NSString *) predicateFormat
{
  NSPredicate *sub = [_subs objectAtIndex: 0];

  if ([sub isKindOfClass: [NSCompoundPredicate class]]
    && [(NSCompoundPredicate *)sub compoundPredicateType]
      != NSNotPredicateType)
    {
      return [NSString stringWithFormat: @"NOT(%@)", [sub predicateFormat]];
    }
  return [NSString stringWithFormat: @"NOT %@", [sub predicateFormat]];
}

@end

@implementation NSComparisonPredicate

+ (NSPredicate *) predicateWithLeftExpression: (NSExpression *)left
                              rightExpression: (NSExpression *)right
                               customSelector: (SEL) sel
{
  return AUTORELEASE([[self alloc] initWithLeftExpression: left
                                          rightExpression: right 
                                           customSelector: sel]);
}

+ (NSPredicate *) predicateWithLeftExpression: (NSExpression *)left
                              rightExpression: (NSExpression *)right
                                     modifier: (NSComparisonPredicateModifier)modifier
                                         type: (NSPredicateOperatorType)type
                                      options: (NSUInteger)opts
{
  return AUTORELEASE([[self alloc] initWithLeftExpression: left 
                                          rightExpression: right
                                                 modifier: modifier 
                                                     type: type 
                                                  options: opts]);
}

- (NSPredicate *) initWithLeftExpression: (NSExpression *)left
                         rightExpression: (NSExpression *)right
                          customSelector: (SEL)sel
{
  if ((self = [super init]) != nil)
    {
      ASSIGN(_left, left);
      ASSIGN(_right, right);
      _selector = sel;
      _type = NSCustomSelectorPredicateOperatorType;
    }
  return self;
}

- (id) initWithLeftExpression: (NSExpression *)left
              rightExpression: (NSExpression *)right
                     modifier: (NSComparisonPredicateModifier)modifier
                         type: (NSPredicateOperatorType)type
                      options: (NSUInteger)opts
{
  if ((self = [super init]) != nil)
    {
      ASSIGN(_left, left);
      ASSIGN(_right, right);
      _modifier = modifier;
      _type = type;
      _options = opts;
    }
  return self;
}

- (void) dealloc;
{
  RELEASE(_left);
  RELEASE(_right);
  [super dealloc];
}

- (NSComparisonPredicateModifier) comparisonPredicateModifier
{
  return _modifier;
}

- (SEL) customSelector
{
  return _selector;
}

- (NSExpression *) leftExpression
{
  return _left;
}

- (NSUInteger) 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);
        break;
      case NSContainsPredicateOperatorType: 
        comp = @"CONTAINS";
        break;
      case NSBetweenPredicateOperatorType: 
        comp = @"BETWEEN";
        break;
    }
  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
{
  NSExpression *left;
  NSExpression *right;
   
  left = [_left _expressionWithSubstitutionVariables: variables];
  right = [_right _expressionWithSubstitutionVariables: variables];
  if (_type == NSCustomSelectorPredicateOperatorType)
    {
      return [NSComparisonPredicate predicateWithLeftExpression: left 
                                                rightExpression: right 
                                                 customSelector: _selector];
    }
  else
    {
      return [NSComparisonPredicate predicateWithLeftExpression: left 
                                                rightExpression: right 
                                                       modifier: _modifier 
                                                           type: _type 
                                                        options: _options];
    }
}

#if	GS_USE_ICU == 1
static BOOL
GSICUStringMatchesRegex(NSString *string, NSString *regex, NSStringCompareOptions opts)
{
  BOOL result = NO;
  UErrorCode error = 0;
  uint32_t flags = 0;
  NSUInteger stringLength = [string length];
  NSUInteger regexLength = [regex length];
  unichar *stringBuffer;
  unichar *regexBuffer;
  URegularExpression *icuregex = NULL;  

  stringBuffer = malloc(stringLength * sizeof(unichar));
  if (NULL == stringBuffer) { return NO; }
  regexBuffer = malloc(regexLength * sizeof(unichar));
  if (NULL == regexBuffer) { free(stringBuffer); return NO; }

  [string getCharacters: stringBuffer range: NSMakeRange(0, stringLength)];
  [regex getCharacters: regexBuffer range: NSMakeRange(0, regexLength)];

  flags |= UREGEX_DOTALL; // . is supposed to recognize newlines
  if ((opts & NSCaseInsensitiveSearch) != 0) { flags |= UREGEX_CASE_INSENSITIVE; }

  icuregex = uregex_open(regexBuffer, regexLength, flags, NULL, &error);
  if (icuregex != NULL && U_SUCCESS(error))
    {
      uregex_setText(icuregex, stringBuffer, stringLength, &error);
      result = uregex_matches(icuregex, 0, &error);
    }
  uregex_close(icuregex);

  free(stringBuffer);
  free(regexBuffer);

  return result;
}
#endif

- (BOOL) _evaluateLeftValue: (id)leftResult
		 rightValue: (id)rightResult
		     object: (id)object
{
  unsigned compareOptions = 0;
  BOOL leftIsNil;
  BOOL rightIsNil;

  leftIsNil = (leftResult == nil || [leftResult isEqual: [NSNull null]]);
  rightIsNil = (rightResult == nil || [rightResult isEqual: [NSNull null]]);
  if (leftIsNil || rightIsNil)
    {
      if (leftIsNil == rightIsNil)
        {
          /* Both of the values are nil.
           * The result is YES if equality is requested.
           */
          if (NSEqualToPredicateOperatorType == _type
            || NSLessThanOrEqualToPredicateOperatorType == _type
            || NSGreaterThanOrEqualToPredicateOperatorType == _type)
            {
              return YES;
            }
        }
      else if (NSNotEqualToPredicateOperatorType == _type)
        {
          /* One, but not both of the values are nil.
           * The result is YES if inequality is requested.
           */
          return YES;
        }
      return NO;
    }

  // Change predicate options into string options.
  if (!(_options & NSDiacriticInsensitivePredicateOption))
    {
      compareOptions |= NSLiteralSearch;
    }
  if (_options & NSCaseInsensitivePredicateOption)
    {
      compareOptions |= NSCaseInsensitiveSearch;
    }

  /* This is a very optimistic implementation,
   * hoping that the values are of the right type.
   */
  switch (_type)
    {
      case NSLessThanPredicateOperatorType:
        {
          double ld = [leftResult doubleValue];
          double rd = [rightResult doubleValue];
          return (ld < rd) ? YES : NO;
        }
      case NSLessThanOrEqualToPredicateOperatorType:
        {
          double ld = [leftResult doubleValue];
          double rd = [rightResult doubleValue];
          return (ld <= rd) ? YES : NO;
        }
      case NSGreaterThanPredicateOperatorType:
        {
          double ld = [leftResult doubleValue];
          double rd = [rightResult doubleValue];
          return (ld > rd) ? YES : NO;
        }
      case NSGreaterThanOrEqualToPredicateOperatorType:
        {
          double ld = [leftResult doubleValue];
          double rd = [rightResult doubleValue];
          return (ld >= rd) ? YES : NO;
        }
      case NSEqualToPredicateOperatorType:
	return [leftResult isEqual: rightResult];
      case NSNotEqualToPredicateOperatorType:
	return ![leftResult isEqual: rightResult];
      case NSMatchesPredicateOperatorType:
#if	GS_USE_ICU == 1
	return GSICUStringMatchesRegex(leftResult, rightResult, compareOptions);
#else
	return [leftResult compare: rightResult options: compareOptions]
	  == NSOrderedSame ? YES : NO;
#endif
      case NSLikePredicateOperatorType:
#if	GS_USE_ICU == 1
	{
	  NSString *regex;

	  /* The right hand is a pattern with '?' meaning match one character,
	   * and '*' meaning match zero or more characters, so translate that
	   * into a regex.
	   */
	  regex = [rightResult stringByReplacingOccurrencesOfString: @"*"
							 withString: @".*"];
	  regex = [regex stringByReplacingOccurrencesOfString: @"?"
						   withString: @".?"];
	  regex = [NSString stringWithFormat: @"^%@$", regex];
	  return GSICUStringMatchesRegex(leftResult, regex, compareOptions);
	}
#else
	return [leftResult compare: rightResult options: compareOptions]
	  == NSOrderedSame ? YES : NO;
#endif
      case NSBeginsWithPredicateOperatorType:
	{
	  NSRange range = NSMakeRange(0, [rightResult length]);

	  return ([leftResult compare: rightResult
			      options: compareOptions
				range: range] == NSOrderedSame ? YES : NO);
	}
      case NSEndsWithPredicateOperatorType:
	{
	  NSRange range;
          NSUInteger    ll = [leftResult length];
          NSUInteger    rl = [rightResult length];

          if (ll < rl)
            {
              return NO;
            }
	  range = NSMakeRange(ll - rl, rl);
	  return ([leftResult compare: rightResult
			      options: compareOptions
				range: range] == NSOrderedSame ? YES : NO);
	}
      case NSInPredicateOperatorType:
	/* Handle special case where rightResult is a collection
	 * and leftResult an element of it.
	 */
	if (![rightResult isKindOfClass: [NSString class]])
	  {
	    NSEnumerator *e;
	    id value;

	    if (![rightResult respondsToSelector: @selector(objectEnumerator)])
	      {
		[NSException raise: NSInvalidArgumentException 
			    format: @"The right hand side for an IN operator "
		  @"must be a collection"];
	      }

	    e = [rightResult objectEnumerator];
	    while ((value = [e nextObject]))
	      {
		if ([value isEqual: leftResult]) 
		  return YES;		
	      }

	    return NO;
	  }
	return ([rightResult rangeOfString: leftResult
				   options: compareOptions].location
	  != NSNotFound ? YES : NO);
      case NSCustomSelectorPredicateOperatorType:
	{
	  BOOL (*function)(id,SEL,id)
            = (BOOL (*)(id,SEL,id))[leftResult methodForSelector: _selector];
	  return function(leftResult, _selector, rightResult);
	}
      default:
	return NO;
    }
}

- (BOOL) evaluateWithObject: (id)object
{
  id leftValue = [_left expressionValueWithObject: object context: nil];
  id rightValue = [_right expressionValueWithObject: object context: nil];

  if (_modifier == NSDirectPredicateModifier)
    {
      return [self _evaluateLeftValue: leftValue
			   rightValue: rightValue
			       object: object];
    }
  else
    {		
      BOOL result = (_modifier == NSAllPredicateModifier);
      NSEnumerator *e;
      id value;

      if (![leftValue respondsToSelector: @selector(objectEnumerator)])
        {
          [NSException raise: NSInvalidArgumentException 
                      format: @"The left hand side for an ALL or ANY operator must be a collection"];
        }

      e = [leftValue objectEnumerator];
      while ((value = [e nextObject]))
        {
          BOOL eval = [self _evaluateLeftValue: value
				    rightValue: rightValue
					object: object];
          if (eval != result) 
            return eval;		
        }

      return result;
    }
}

- (id) copyWithZone: (NSZone *)z
{
  NSComparisonPredicate *copy;

  copy = (NSComparisonPredicate *)NSCopyObject(self, 0, z);
  copy->_left = [_left copyWithZone: z];
  copy->_right = [_right copyWithZone: z];
  return copy;
}

- (Class) classForCoder
{
  return [NSComparisonPredicate class];
}

- (void) encodeWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
}

- (id) initWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
  return self;
}

@end



@implementation NSExpression

+ (void) initialize
{
  if (self == [NSExpression class] && nil == evaluatedObjectExpression)
    {
      evaluatedObjectExpression = [GSEvaluatedObjectExpression new];
    }
}

+ (NSExpression *) expressionForConstantValue: (id)obj
{
  GSConstantValueExpression *e;

  e = [[GSConstantValueExpression alloc] 
          initWithExpressionType: NSConstantValueExpressionType];
  ASSIGN(e->_obj, obj);
  return AUTORELEASE(e);
}

+ (NSExpression *) expressionForEvaluatedObject
{
  return evaluatedObjectExpression;
}

+ (NSExpression *) expressionForFunction: (NSString *)name
                               arguments: (NSArray *)args
{
  GSFunctionExpression	*e;
  NSString		*s;

  e = [[GSFunctionExpression alloc]
    initWithExpressionType: NSFunctionExpressionType];
  s = [NSString stringWithFormat: @"_eval_%@:", name];
  e->_selector = NSSelectorFromString(s);
  if (![e respondsToSelector: e->_selector])
    {
      [NSException raise: NSInvalidArgumentException
                   format: @"Unknown function implementation: %@", name];
    }
  ASSIGN(e->_function, name);
  e->_argc = [args count];
  ASSIGN(e->_args, args);
  if ([name isEqualToString: @"_add"]) e->_op = @"+";
  else if ([name isEqualToString: @"_sub"]) e->_op = @"-";
  else if ([name isEqualToString: @"_mul"]) e->_op = @"*";
  else if ([name isEqualToString: @"_div"]) e->_op = @"/";
  else if ([name isEqualToString: @"_pow"]) e->_op = @"**";
  return AUTORELEASE(e);
}

+ (NSExpression *) expressionForKeyPath: (NSString *)path
{
  GSKeyPathExpression *e;

  if (![path isKindOfClass: [NSString class]])
    {
      [NSException raise: NSInvalidArgumentException
		  format: @"Keypath is not NSString: %@", path];
    }
  e = [[GSKeyPathExpression alloc] 
          initWithExpressionType: NSKeyPathExpressionType];
  ASSIGN(e->_keyPath, path);
  return AUTORELEASE(e);
}

+ (NSExpression *) expressionForVariable: (NSString *)string
{
  GSVariableExpression *e;

  e = [[GSVariableExpression alloc] 
          initWithExpressionType: NSVariableExpressionType];
  ASSIGN(e->_variable, string);
  return AUTORELEASE(e);
}

- (id) initWithExpressionType: (NSExpressionType)type
{
  if ((self = [super init]) != nil)
    {
      _type = type;
    }
  return self;
}

- (id) copyWithZone: (NSZone *)z
{
  return NSCopyObject(self, 0, z);
}

- (NSArray *) arguments
{
  [self subclassResponsibility: _cmd];
  return nil;
}

- (id) constantValue
{
  [self subclassResponsibility: _cmd];
  return nil;
}

- (NSString *) description
{
  [self subclassResponsibility: _cmd];
  return nil;
}

- (NSExpressionType) expressionType
{
  return _type;
}

- (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;
}

- (Class) classForCoder
{
  return [NSExpression class];
}

- (void) encodeWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
}

- (id) initWithCoder: (NSCoder *)coder
{
  // FIXME
  [self subclassResponsibility: _cmd];
  return nil;
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  [self subclassResponsibility: _cmd];
  return nil;
}

@end

@implementation GSConstantValueExpression

- (id) constantValue
{
  return _obj;
}

- (NSString *) description
{
  if ([_obj isKindOfClass: [NSString class]])
    {
      NSMutableString	*result = nil;

      /* Quote the result string as necessary.
       */
      GSPropertyListMake(_obj, nil, NO, YES, 2, &result);
      return result;
    }
  return [_obj description];
}

- (id) expressionValueWithObject: (id)object
			 context: (NSMutableDictionary *)context
{
  return _obj;
}

- (void) dealloc
{
  RELEASE(_obj);
  [super dealloc];
}

- (id) copyWithZone: (NSZone*)zone
{
  GSConstantValueExpression *copy;

  copy = (GSConstantValueExpression *)[super copyWithZone: zone];
  copy->_obj = [_obj copyWithZone: zone];
  return copy;
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  return self;
}

@end

@implementation GSEvaluatedObjectExpression

- (NSString *) description
{
  return @"SELF";
}

- (id) expressionValueWithObject: (id)object
			 context: (NSMutableDictionary *)context
{
  return object;
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  return self;
}

- (NSString *) keyPath
{
  return @"SELF";
}
@end

@implementation GSVariableExpression

- (NSString *) description
{
  return [NSString stringWithFormat: @"$%@", _variable];
}

- (id) expressionValueWithObject: (id)object
			 context: (NSMutableDictionary *)context
{
  return [context objectForKey: _variable];
}

- (NSString *) variable
{
  return _variable;
}

- (void) dealloc;
{
  RELEASE(_variable);
  [super dealloc];
}

- (id) copyWithZone: (NSZone*)zone
{
  GSVariableExpression *copy;

  copy = (GSVariableExpression *)[super copyWithZone: zone];
  copy->_variable = [_variable copyWithZone: zone];
  return copy;
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  id result = [variables objectForKey: _variable];

  if (result != nil)
    {
      return [NSExpression expressionForConstantValue: result];
    }
  else
    {
      return self;
    }
}

@end

@implementation GSKeyPathExpression

- (NSString *) description
{
  return _keyPath;
}

- (id) expressionValueWithObject: (id)object
			 context: (NSMutableDictionary *)context
{
  return [object valueForKeyPath: _keyPath];
}

- (NSString *) keyPath
{
  return _keyPath;
}

- (void) dealloc;
{
  RELEASE(_keyPath);
  [super dealloc];
}

- (id) copyWithZone: (NSZone*)zone
{
  GSKeyPathExpression *copy;

  copy = (GSKeyPathExpression *)[super copyWithZone: zone];
  copy->_keyPath = [_keyPath copyWithZone: zone];
  return copy;
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  return self;
}

@end

@implementation GSFunctionExpression

- (NSArray *) arguments
{
  return _args;
}

- (NSString *) description
{
  if (nil != _op && 1 == [_args count])
    {
      GSFunctionExpression      *a0 = [_args objectAtIndex: 0];

      if (YES == [a0 isKindOfClass: [self class]] && nil != a0->_op)
        {
          return [NSString stringWithFormat: @"%@(%@)", _op, a0];
        }
      return [NSString stringWithFormat: @"%@%@", _op, a0];
    }

  if (nil != _op)
    {
      GSFunctionExpression      *a0 = [_args objectAtIndex: 0];
      GSFunctionExpression      *a1 = [_args objectAtIndex: 1];

      if (YES == [a0 isKindOfClass: [self class]] && nil != a0->_op)
        {
          if (YES == [a1 isKindOfClass: [self class]] && nil != a1->_op)
            {
              return [NSString stringWithFormat: @"(%@) %@ (%@)", a0, _op, a1];
            }
          return [NSString stringWithFormat: @"(%@) %@ %@", a0, _op, a1];
        }

      if (YES == [a1 isKindOfClass: [self class]] && nil != a1->_op)
        {
          return [NSString stringWithFormat: @"%@ %@ (%@)", a0, _op, a1];
        }

      return [NSString stringWithFormat: @"%@ %@ %@", a0, _op, a1];
    }
  return [NSString stringWithFormat: @"%@(%@)", [self function], _args];
}

- (NSString *) function
{
  return _function;
}

- (NSString *) keyPath
{
  return nil;
}

- (id) expressionValueWithObject: (id)object
			 context: (NSMutableDictionary *)context
{ 
  // temporary space 
  NSMutableArray	*eargs = [NSMutableArray arrayWithCapacity: _argc];
  unsigned int i;

  for (i = 0; i < _argc; i++)
    {
      [eargs addObject: [[_args objectAtIndex: i] 
        expressionValueWithObject: object context: context]];
    }
  // apply method selector
  return [self performSelector: _selector
                    withObject: eargs];
}

- (void) dealloc;
{
  RELEASE(_args);
  RELEASE(_function);
  [super dealloc];
}

- (id) copyWithZone: (NSZone*)zone
{
  GSFunctionExpression *copy;

  copy = (GSFunctionExpression *)[super copyWithZone: zone];
  copy->_function = [_function copyWithZone: zone];
  copy->_args = [_args copyWithZone: zone];
  return copy;
}

- (NSEnumerator*) _enum: (NSArray *)expressions
{
  id    o;

  /* Check to see if this is aggregating over a collection.
   */
  if (1 == _argc && [(o = [expressions lastObject])
    respondsToSelector: @selector(objectEnumerator)])
    {
      return [o objectEnumerator];
    }
  return [expressions objectEnumerator];
}

- (id) _expressionWithSubstitutionVariables: (NSDictionary *)variables
{
  NSMutableArray *args = [NSMutableArray arrayWithCapacity: _argc];
  unsigned int i;
      
  for (i = 0; i < _argc; i++)
    {
      [args addObject: [[_args objectAtIndex: i] 
                           _expressionWithSubstitutionVariables: variables]];
    }

   return [NSExpression expressionForFunction: _function arguments: args];
}

- (id) _eval__chs: (NSArray *)expressions
{
  return [NSNumber numberWithInt: -[[expressions objectAtIndex: 0] intValue]];
}

- (id) _eval__first: (NSArray *)expressions
{
  return [[expressions objectAtIndex: 0] objectAtIndex: 0];
}

- (id) _eval__last: (NSArray *)expressions
{
  return [[expressions objectAtIndex: 0] lastObject];
}

- (id) _eval__index: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  if ([left isKindOfClass: [NSDictionary class]])
    {
      return [left objectForKey: right];
    }
  else
    {
      // raises exception if invalid
      return [left objectAtIndex: [right unsignedIntValue]];
    }
}

- (id) _eval__pow: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  return [NSNumber numberWithDouble:
    pow([left doubleValue], [right doubleValue])];
}

- (id) _eval__mul: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  return [NSNumber numberWithDouble: [left doubleValue] * [right doubleValue]];
}

- (id) _eval__div: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  return [NSNumber numberWithDouble: [left doubleValue] / [right doubleValue]];
}

- (id) _eval__add: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  return [NSNumber numberWithDouble: [left doubleValue] + [right doubleValue]];
}

- (id) _eval__sub: (NSArray *)expressions
{
  id left = [expressions objectAtIndex: 0];
  id right = [expressions objectAtIndex: 1];

  return [NSNumber numberWithDouble: [left doubleValue] - [right doubleValue]];
}

- (id) _eval_count: (NSArray *)expressions
{
  NSAssert(_argc == 1, NSInternalInconsistencyException);
  return [NSNumber numberWithUnsignedInt:
    [[expressions objectAtIndex: 0] count]];
}

- (id) _eval_avg: (NSArray *)expressions 
{
  NSEnumerator  *e = [self _enum: expressions];
  double        sum = 0.0;
  unsigned      count = 0;
  id            o;
    
  while (nil != (o = [e nextObject]))
    {
      sum += [o doubleValue];
      count++;
    }
  return [NSNumber numberWithDouble: sum / count];
}

- (id) _eval_max: (NSArray *)expressions
{
  NSEnumerator  *e = [self _enum: expressions];
  id            o = [e nextObject];
  double        max = (nil == o) ? 0.0 : [o doubleValue];
  double        cur;
  
  while (nil != (o = [e nextObject]))
    {
      cur = [o doubleValue];
      if (max < cur)
        {
          max = cur;
        }
    }
  return [NSNumber numberWithDouble: max];
}

- (id) _eval_min: (NSArray *)expressions
{
  NSEnumerator  *e = [self _enum: expressions];
  id            o = [e nextObject];
  double        min = (nil == o ? 0.0 : [o doubleValue]);
  double        cur;

  while (nil != (o = [e nextObject]))
    {
      cur = [o doubleValue];
      if (min > cur)
        {
          min = cur;
        }
    }
  return [NSNumber numberWithDouble: min];
}

- (id) _eval_sum: (NSArray *)expressions
{
  NSEnumerator  *e = [self _enum: expressions];
  double        sum = 0.0;
  id            o;

  while (nil != (o = [e nextObject]))
    {
      sum += [o doubleValue];
    }
  return [NSNumber numberWithDouble: sum];
}

// add arithmetic functions: average, median, mode, stddev, sqrt, log, ln, exp, floor, ceiling, abs, trunc, random, randomn, now

@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 GS_IMMUTABLE(result);
}

@end

@implementation NSMutableArray (NSPredicate)

- (void) filterUsingPredicate: (NSPredicate *)predicate
{	
  unsigned	count = [self count];

  while (count-- > 0)
    {
      id	object = [self objectAtIndex: count];
	
      if ([predicate evaluateWithObject: object] == NO)
        {
          [self removeObjectAtIndex: count];
        }
    }
}

@end

@implementation NSSet (NSPredicate)

- (NSSet *) filteredSetUsingPredicate: (NSPredicate *)predicate
{
  NSMutableSet	*result;
  NSEnumerator	*e = [self objectEnumerator];
  id		object;

  result = [NSMutableSet setWithCapacity: [self count]];
  while ((object = [e nextObject]) != nil)
    {
      if ([predicate evaluateWithObject: object] == YES)
        {
          [result addObject: object];  // passes filter
        }
    }
  return GS_IMMUTABLE(result);
}

@end

@implementation NSMutableSet (NSPredicate)

- (void) filterUsingPredicate: (NSPredicate *)predicate
{
  NSMutableSet	*rejected;
  NSEnumerator	*e = [self objectEnumerator];
  id		object;

  rejected = [NSMutableSet setWithCapacity: [self count]];
  while ((object = [e nextObject]) != nil)
    {
      if ([predicate evaluateWithObject: object] == NO)
        {
          [rejected addObject: object];
        }
    }
  [self minusSet: rejected];
}

@end



@implementation GSPredicateScanner

- (id) initWithString: (NSString*)format
                 args: (NSArray*)args
{
  self = [super initWithString: format];
  if (self != nil)
    {
      _args = [args objectEnumerator];
    }
  return self;
}

- (id) nextArg
{
  return [_args nextObject];
}

- (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;
    }

  if ([self isAtEnd])
    {
       // ok
      return YES;
    }
  
  // Does the next character still belong to the token?
  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 = nil;

  NS_DURING
    {
      r = [self parsePredicate];
    }
  NS_HANDLER
    {
      NSLog(@"Parsing failed for %@ with %@", [self string], localException);
      [localException raise];
    }
  NS_ENDHANDLER

  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"]
    || [self scanPredicateKeyword: @"&&"])
    {
      NSPredicate	*r = [self parseOr];

      if ([r isKindOfClass: [NSCompoundPredicate class]]
        && [(NSCompoundPredicate *)r compoundPredicateType]
        == NSAndPredicateType)
        {
          NSCompoundPredicate   *right = (NSCompoundPredicate*)r;

          // merge
          if ([l isKindOfClass:[NSCompoundPredicate class]]
            && [(NSCompoundPredicate *)l compoundPredicateType]
            == NSAndPredicateType)
            {
              NSCompoundPredicate       *left;
              NSMutableArray            *subs;

              left = (NSCompoundPredicate*)l;
              subs = [[left subpredicates] mutableCopy];
              [subs addObjectsFromArray: [right subpredicates]];
              l = [NSCompoundPredicate andPredicateWithSubpredicates: subs];
              [subs release];
            }
          else
            {
              NSMutableArray            *subs;

              subs = [[right subpredicates] mutableCopy];
              [subs insertObject: l atIndex: 0];
              l = [NSCompoundPredicate andPredicateWithSubpredicates: subs];
              [subs release];
            }
        }
      else if ([l isKindOfClass: [NSCompoundPredicate class]]
        && [(NSCompoundPredicate *)l compoundPredicateType]
        == NSAndPredicateType)
        {
          NSCompoundPredicate   *left;
          NSMutableArray        *subs;

          left = (NSCompoundPredicate*)l;
          subs = [[left subpredicates] mutableCopy];
          [subs addObject: r];
          l = [NSCompoundPredicate andPredicateWithSubpredicates: subs];
          [subs release];
        }
      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"] || [self scanPredicateKeyword: @"!"])
    {
      // -> 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"]
    || [self scanPredicateKeyword: @"||"])
    {
      NSPredicate	*r = [self parseNot];

      if ([r isKindOfClass: [NSCompoundPredicate class]]
        && [(NSCompoundPredicate *)r compoundPredicateType]
        == NSOrPredicateType)
        {
          NSCompoundPredicate   *right = (NSCompoundPredicate*)r;

          // merge
          if ([l isKindOfClass: [NSCompoundPredicate class]]
            && [(NSCompoundPredicate *)l compoundPredicateType]
            == NSOrPredicateType)
            {
              NSCompoundPredicate       *left = (NSCompoundPredicate*)l;
              NSMutableArray            *subs;

              subs = [[left subpredicates] mutableCopy];
              [subs addObjectsFromArray: [right subpredicates]];
              l = [NSCompoundPredicate orPredicateWithSubpredicates: subs];
              [subs release];
            }
          else
            {
              NSMutableArray            *subs;

              subs = [[right subpredicates] mutableCopy];
              [subs insertObject: l atIndex: 0];
              l = [NSCompoundPredicate orPredicateWithSubpredicates: subs];
              [subs release];
            }
        }
      else if ([l isKindOfClass: [NSCompoundPredicate class]]
        && [(NSCompoundPredicate *)l compoundPredicateType]
        == NSOrPredicateType)
        {
          NSCompoundPredicate   *left = (NSCompoundPredicate*)l;
          NSMutableArray        *subs;

          subs = [[left subpredicates] mutableCopy];
          [subs addObject:r];
          l = [NSCompoundPredicate orPredicateWithSubpredicates: subs];
          [subs release];
        }
      else
        {
          l = [NSCompoundPredicate orPredicateWithSubpredicates: 
            [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;
  NSExpression *right;
  NSPredicate *p;
  BOOL negate = NO;
  BOOL swap = 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 parseExpression];
  if ([self scanString: @"!=" intoString: NULL]
    || [self scanString: @"<>" intoString: NULL])
    {
      type = NSNotEqualToPredicateOperatorType;
    }
  else if ([self scanString: @"<=" intoString: NULL]
    || [self scanString: @"=<" intoString: NULL])
    {
      type = NSLessThanOrEqualToPredicateOperatorType;
    }
  else if ([self scanString: @">=" intoString: NULL]
    || [self scanString: @"=>" intoString: NULL])
    {
      type = NSGreaterThanOrEqualToPredicateOperatorType;
    }
  else if ([self scanString: @"<" intoString: NULL])
    {
      type = NSLessThanPredicateOperatorType;
    }
  else if ([self scanString: @">" intoString: NULL])
    {
      type = NSGreaterThanPredicateOperatorType;
    }
  else if ([self scanString: @"==" intoString: NULL]
    || [self scanString: @"=" intoString: NULL])
    {
      type = NSEqualToPredicateOperatorType;
    }
  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 if ([self scanPredicateKeyword: @"CONTAINS"])
    {
      type = NSInPredicateOperatorType;
      swap = YES;
    }
  else if ([self scanPredicateKeyword: @"BETWEEN"])
    {
      // Requires special handling to transfer into AND of
      // two normal comparison predicates
      NSExpression *exp = [self parseSimpleExpression];
      NSArray *a = (NSArray *)[exp constantValue];
      NSNumber *lower, *upper;
      NSExpression *lexp, *uexp;
      NSPredicate *lp, *up;

      if (![a isKindOfClass: [NSArray class]])
        {
          [NSException raise: NSInvalidArgumentException
                       format: @"BETWEEN operator requires array argument"];
        }

      lower = [a objectAtIndex: 0];
      upper = [a objectAtIndex: 1];
      lexp = [NSExpression expressionForConstantValue: lower];
      uexp = [NSExpression expressionForConstantValue: upper];
      lp = [NSComparisonPredicate predicateWithLeftExpression: left 
                                  rightExpression: lexp
                                  modifier: modifier 
                                  type: NSGreaterThanPredicateOperatorType 
                                  options: opts];
      up = [NSComparisonPredicate predicateWithLeftExpression: left 
                                  rightExpression: uexp
                                  modifier: modifier 
                                  type: NSLessThanPredicateOperatorType 
                                  options: opts];
      return [NSCompoundPredicate andPredicateWithSubpredicates: 
                                       [NSArray arrayWithObjects: lp, up, nil]];
    }
  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;
    }

  right = [self parseExpression];
  if (swap == YES)
    {
      NSExpression      *tmp = left;

      left = right;
      right = tmp;
    }

  p = [NSComparisonPredicate predicateWithLeftExpression: left 
                             rightExpression: right
                             modifier: modifier 
                             type: type 
                             options: opts];

  return negate ? [NSCompoundPredicate notPredicateWithSubpredicate: p] : p;
}

- (NSExpression *) parseExpression
{
//  return [self parseAdditionExpression];
  return [self parseBinaryExpression];
}

- (NSExpression *) parseIdentifierExpression
{
  static NSCharacterSet *_identifier;
  NSString      *ident;

  // skip # as prefix (reserved words)
  [self scanString: @"#" intoString: NULL];
  if (!_identifier)
    {
      ASSIGN(_identifier, [NSCharacterSet characterSetWithCharactersInString: 
	 @"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]);
    }

  if (![self scanCharactersFromSet: _identifier intoString: &ident])
    {
      [NSException raise: NSInvalidArgumentException 
                  format: @"Missing identifier: %@", 
                   [[self string] substringFromIndex: [self scanLocation]]];
    }

  return [NSExpression expressionForKeyPath: ident];
}

- (NSExpression *) parseSimpleExpression
{
  unsigned      location;
  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
          return [NSExpression expressionForConstantValue: a];
        }
      // 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"];
        }
      return [NSExpression expressionForConstantValue: a];
    }

  if ([self scanPredicateKeyword: @"NULL"]
    || [self scanPredicateKeyword: @"NIL"])
    {
      return [NSExpression expressionForConstantValue: [NSNull null]];
    }
  if ([self scanPredicateKeyword: @"TRUE"]
    || [self scanPredicateKeyword: @"YES"])
    {
      return [NSExpression expressionForConstantValue: 
        [NSNumber numberWithBool: YES]];
    }
  if ([self scanPredicateKeyword: @"FALSE"]
    || [self scanPredicateKeyword: @"NO"])
    {
      return [NSExpression expressionForConstantValue: 
        [NSNumber numberWithBool: NO]];
    }
  if ([self scanPredicateKeyword: @"SELF"])
    {
      return [NSExpression expressionForEvaluatedObject];
    }
  if ([self scanString: @"$" intoString: NULL])
    {
      // variable
      NSExpression *var = [self parseIdentifierExpression];

      if (![var keyPath])
        {
          [NSException raise: NSInvalidArgumentException 
                      format: @"Invalid variable identifier: %@", var];
        }
      return [NSExpression expressionForVariable: [var keyPath]];
    }
	
  location = [self scanLocation];

  if ([self scanString: @"%" intoString: NULL])
    {
      if ([self isAtEnd] == NO)
        {
          unichar   c = [[self string] characterAtIndex: [self scanLocation]];

          switch (c)
            {
              case '%':                         // '%%' is treated as '%'
                location = [self scanLocation];
                break;

              case 'K':
                [self setScanLocation: [self scanLocation] + 1];
                return [NSExpression 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 [NSExpression 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 [NSExpression 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 [NSExpression expressionForConstantValue:
                          [self nextArg]];
                      }
                  }
                break;
            }
        }

      [self setScanLocation: location];
    }
	
  if ([self scanString: @"\"" intoString: NULL])
    {
      NSCharacterSet	*skip = [self charactersToBeSkipped];
      NSString *str = nil;

      [self setCharactersToBeSkipped: nil];
      if ([self scanUpToString: @"\"" intoString: &str] == NO)
	{
	  [self setCharactersToBeSkipped: skip];
          [NSException raise: NSInvalidArgumentException 
                      format: @"Invalid double quoted literal at %u", location];
	}
      [self setCharactersToBeSkipped: skip];
      [self scanString: @"\"" intoString: NULL];
      return [NSExpression expressionForConstantValue: str];
    }
	
  if ([self scanString: @"'" intoString: NULL])
    {
      NSCharacterSet	*skip = [self charactersToBeSkipped];
      NSString *str = nil;

      [self setCharactersToBeSkipped: nil];
      if ([self scanUpToString: @"'" intoString: &str] == NO)
	{
	  [self setCharactersToBeSkipped: skip];
          [NSException raise: NSInvalidArgumentException 
                      format: @"Invalid single quoted literal at %u", location];
	}
      [self setCharactersToBeSkipped: skip];
      [self scanString: @"'" intoString: NULL];
      return [NSExpression expressionForConstantValue: str];
    }

  if ([self scanString: @"@" intoString: NULL])
    {
      NSExpression *e = [self parseIdentifierExpression];

      if (![e keyPath])
        {
          [NSException raise: NSInvalidArgumentException 
                      format: @"Invalid keypath identifier: %@", e];
        }

      // prefix with keypath
      return [NSExpression expressionForKeyPath: 
        [NSString stringWithFormat: @"@%@", [e keyPath]]];
    }

  return [self parseIdentifierExpression];
}

- (NSExpression *) parseFunctionalExpression
{
  NSExpression *left = [self parseSimpleExpression];
    
  while (YES)
    {
      if ([self scanString: @"(" intoString: NULL])
        { 
          // function - this parser allows for (max)(a, b, c) to be properly 
          // recognized and even (%K)(a, b, c) if %K evaluates to "max"
          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];
            }

          if (evaluatedObjectExpression != left)
            {
              // concatenate
              left = [NSExpression expressionForKeyPath:
                        [NSString stringWithFormat: @"%@.%@",
                                  [left keyPath], [right keyPath]]];
            }
          else
            {
              left = [NSExpression expressionForKeyPath: [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];
          left = [NSExpression expressionForFunction: @"_pow" 
            arguments: [NSArray arrayWithObjects: left, right, nil]];
        }
      else
        {
          return left;
        }
    }
}

- (NSExpression *) parseMultiplicationExpression
{
  NSExpression *left = [self parsePowerExpression];
	
  while (YES)
    {
      NSExpression *right;
	
      if ([self scanString: @"*" intoString: NULL])
        {
          right = [self parsePowerExpression];
          left = [NSExpression expressionForFunction: @"_mul" 
            arguments: [NSArray arrayWithObjects: left, right, nil]];
        }
      else if ([self scanString: @"/" intoString: NULL])
        {
          right = [self parsePowerExpression];
          left = [NSExpression expressionForFunction: @"_div" 
            arguments: [NSArray arrayWithObjects: left, right, nil]];
        }
      else
        {
          return left;
        }
    }
}

- (NSExpression *) parseAdditionExpression
{
  NSExpression *left = [self parseMultiplicationExpression];
  
  while (YES)
    {
      NSExpression *right;
	
      if ([self scanString: @"+" intoString: NULL])
        {
          right = [self parseMultiplicationExpression];
          left = [NSExpression expressionForFunction: @"_add"
            arguments: [NSArray arrayWithObjects: left, right, nil]];
        }
      else if ([self scanString: @"-" intoString: NULL])
        {
          right = [self parseMultiplicationExpression];
          left = [NSExpression expressionForFunction: @"_sub"
            arguments: [NSArray arrayWithObjects: left, right, nil]];
        }
      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];
          // FIXME
        }
      else
        {
          return left;
        }
    }
}
@end


#if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST)


@implementation GSBlockPredicate

- (instancetype) initWithBlock: (GSBlockPredicateBlock)block
{
  if (nil == (self = [super init]))
    {
      return nil;
    }
  _block = (GSBlockPredicateBlock)[(id)block retain];
  return self;
}

- (instancetype) predicateWithSubstitutionVariables: 
  (GS_GENERIC_CLASS(NSDictionary,NSString*,id)*)variables
{
  return [[[GSBoundBlockPredicate alloc] initWithBlock: _block
                                              bindings: variables] autorelease];
}

- (BOOL) evaluateWithObject: (id)object
      substitutionVariables: (GS_GENERIC_CLASS(NSDictionary,
                                               NSString*,id)*)variables
{
  return CALL_BLOCK(_block, object, variables);
}

- (BOOL) evaluateWithObject: (id)object
{
  return [self evaluateWithObject: object
            substitutionVariables: nil];
}

- (void) dealloc
{
  [(id)_block release];
  _block = NULL;
  [super dealloc];
}

- (NSString*) predicateFormat
{
  return [NSString stringWithFormat: @"BLOCKPREDICATE(%p)", (void*)_block];
}
@end

@implementation GSBoundBlockPredicate

- (instancetype) initWithBlock: (GSBlockPredicateBlock)block
                      bindings: (GS_GENERIC_CLASS(NSDictionary,
                                                   NSString*,id)*)bindings
{
  if (nil == (self = [super initWithBlock: block]))
    {
      return nil;
    }
  ASSIGN(_bindings, bindings);
  return self;
}

- (BOOL) evaluateWithObject: (id)object
{
  return [self evaluateWithObject: object
            substitutionVariables: _bindings];
}

- (void) dealloc
{
  DESTROY(_bindings);
  [super dealloc];
}
@end

#endif
