From 800e32edad58f4a5b20a76e5e85d0fa7530c188c Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Tue, 17 May 2011 21:11:31 -0700 Subject: [PATCH 1/7] Missed a text substitution, darkened up the method header a bit --- Tools/Documentation/doxygen.css | 2 +- Tools/Documentation/postprocess/002.transform_text.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/Documentation/doxygen.css b/Tools/Documentation/doxygen.css index ed1289ceb..0f21f5d0b 100644 --- a/Tools/Documentation/doxygen.css +++ b/Tools/Documentation/doxygen.css @@ -478,7 +478,7 @@ table.memberdecls { border-top: 1px solid #ccc; padding: 6px 0px 6px 0px; color: #253554; - background-color: #fbfbfb; + background-color: #f7f7f7; font-family: Consolas, "Lucida Console"; font-weight: bold; } diff --git a/Tools/Documentation/postprocess/002.transform_text.sh b/Tools/Documentation/postprocess/002.transform_text.sh index 850114903..3b116a3d8 100755 --- a/Tools/Documentation/postprocess/002.transform_text.sh +++ b/Tools/Documentation/postprocess/002.transform_text.sh @@ -23,5 +23,6 @@ sed -i '' -E \ -e 's/ \[implementation\]<\/code>/\ /g' \ -e 's/Static Public Member Functions/Class Methods/g' \ -e 's/Public Member Functions/Instance Methods/g' \ +-e 's/Member Function Documentation/Method Documentation/g' \ -e 's/(AppKit|Foundation)\.doc/\1/g' \ "$2"/*.html From 6d962bc51a414cc6d94791e67eec15a66ebeb0d5 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 18 May 2011 07:49:52 -0700 Subject: [PATCH 2/7] More documentation enhancements... - Changed to centered, fixed-width layout, it results in shorter line lengths which reads more easily. - Moved support files to separate support directory. - Removed blank lines at end of multi-parameter method prototypes. --- Tools/Documentation/doxygen.css | 77 +++++++++++++------ .../postprocess/001.cleanup_headers.sh | 2 +- .../postprocess/002.transform_text.sh | 15 +--- .../preprocess/001.markdown_readme.sh | 2 +- .../preprocess/002.make_headers.sh | 2 +- Tools/Documentation/support/massage_text.py | 40 ++++++++++ .../{ => support}/processor_setup.sh | 0 7 files changed, 99 insertions(+), 39 deletions(-) create mode 100755 Tools/Documentation/support/massage_text.py rename Tools/Documentation/{ => support}/processor_setup.sh (100%) diff --git a/Tools/Documentation/doxygen.css b/Tools/Documentation/doxygen.css index 0f21f5d0b..4c7c19801 100644 --- a/Tools/Documentation/doxygen.css +++ b/Tools/Documentation/doxygen.css @@ -6,7 +6,7 @@ body, table, td, div, p, dl, dd, dt, em { } body, table, div, p { - font: 13px normal "Lucida Grande", Helvetica, Arial, Geneva, sans-serif; + font: normal 13px "Lucida Grande", Helvetica, Arial, Geneva, sans-serif; } table { @@ -18,7 +18,7 @@ pre, code { } p { - margin: 1em 0; + margin: 0 0 1em 0; } dl { @@ -47,7 +47,7 @@ em { h1, h2, h3, h4 { font-family: "Lucida Grande", Helvetica, Arial, Geneva, sans-serif; - margin: 2em 0 1em 0; + margin: 1.5em 0 .5em; } h1 { @@ -109,17 +109,15 @@ div.qindex, div.navtab { background-color: #ECEFF6; border: 1px solid #A4B5D6; text-align: center; - margin: 2px; - padding: 2px; + padding: .25em; } div.qindex, div.navpath { - width: 100%; line-height: 140%; } -div.qindex+table { - margin: 1.5em; +div.contents div.qindex+table { + margin: 1.5em 15px; } div.qindex+table td { @@ -238,12 +236,32 @@ body { } div.contents { - margin: 1.5em; + width: 1024px; + margin: auto; + border: 1px solid #eee; + border-width: 0 1px; + padding: 15px; } /* If the first element of the contents is a table, give it some space */ div.contents > table { - margin-top: 1.5em; + margin: 1.5em 0 0; +} + +div.contents > p, +div.contents > h1, +div.contents > h2, +div.contents > dl, +.dynheader, +.dynsummary, +.dyncontent { + padding-left: 0; + padding-right: 0; +} + +div.contents hr { + margin-left: -15px; + margin-right: -15px; } td.indexkey { @@ -262,10 +280,6 @@ td.indexvalue { padding: .25em 0 .25em .5em; } -table tr.memlist:first-child td { - padding-top: 1.5em; -} - table tr.memlist > td { padding-left: .5em; } @@ -405,6 +419,7 @@ hr { hr.footer { height: 1px; + margin-top: 0; } /* @group Member Descriptions */ @@ -413,6 +428,7 @@ table.memberdecls { font-family: Consolas, "Lucida Console"; border-spacing: 0px; padding: 0px; + margin: 0 !important; } .mdescLeft, .mdescRight, @@ -470,13 +486,13 @@ table.memberdecls { .memname { white-space: nowrap; font: bold 105% Consolas, "Lucida Console"; - margin: .25em 0 0 .25em; + margin: .25em 0 .1em .25em; } .memproto { - margin-top: 2em; + margin: 2em -15px 0; border-top: 1px solid #ccc; - padding: 6px 0px 6px 0px; + padding: 6px 15px; color: #253554; background-color: #f7f7f7; font-family: Consolas, "Lucida Console"; @@ -484,7 +500,8 @@ table.memberdecls { } .memdoc { - padding: .5em .5em 0; + margin: 0 -15px; + padding: .5em 15px 0; } .memdoc > p { @@ -493,6 +510,11 @@ table.memberdecls { .memdoc dl dd td { padding-right: 1em; + padding-top: .5em; +} + +.memdoc dl dd table tr:first-child td { + padding-top: 0; } .memdoc dl dd table tr td:first-child { @@ -621,7 +643,15 @@ table.memberdecls { /* @end */ div.dynheader { - margin-top: 8px; + margin-top: 8px; +} + +div.dynheader.closed { + margin-bottom: 1em; +} + +div.dyncontent { + margin-bottom: 1em; } address { @@ -700,7 +730,6 @@ div.summary { float: right; font-size: 90%; - padding-right: 5px; width: 50%; text-align: right; } @@ -715,9 +744,12 @@ div.header background-image:url('nav_h.png'); background-repeat:repeat-x; background-color: #F9FAFC; - margin: 0; - padding: 1.5em; + width: 1024px; + margin: 0 auto; + padding: 1.5em 15px; border-bottom: 1px solid #C5CFE5; + border-left: 1px solid #eee; + border-right: 1px solid #eee; } div.headertitle h1 { @@ -728,7 +760,6 @@ div.headertitle h1 { .tabs, .tabs2, .tabs3 { background-image: url('tab_b.png'); - width: 100%; z-index: 101; font-size: 100%; } diff --git a/Tools/Documentation/postprocess/001.cleanup_headers.sh b/Tools/Documentation/postprocess/001.cleanup_headers.sh index bbb23414c..bd271367b 100755 --- a/Tools/Documentation/postprocess/001.cleanup_headers.sh +++ b/Tools/Documentation/postprocess/001.cleanup_headers.sh @@ -6,7 +6,7 @@ # $2 Generated documentation directory # Do this if you want to use the utility functions -source "$1"/processor_setup.sh +source "$1"/support/processor_setup.sh # Cleanup the files we generated to feed to doxygen processor_msg "Cleaning up generated header files..." diff --git a/Tools/Documentation/postprocess/002.transform_text.sh b/Tools/Documentation/postprocess/002.transform_text.sh index 3b116a3d8..3cd354752 100755 --- a/Tools/Documentation/postprocess/002.transform_text.sh +++ b/Tools/Documentation/postprocess/002.transform_text.sh @@ -6,12 +6,7 @@ # $2 Generated documentation directory # Do this if you want to use the utility functions -source "$1"/processor_setup.sh - -# The following transforms are performed: -# - Strip useless "[implementation]" littering the docs -# - Change "Static Public Member Functions" to "Class Methods" -# - Change "Public Member Functions" to "Instance Methods" +source "$1"/support/processor_setup.sh if [ ! -d "$2" ]; then exit 0 @@ -19,10 +14,4 @@ fi processor_msg 'Massaging text...' -sed -i '' -E \ --e 's/ \[implementation\]<\/code>/\ /g' \ --e 's/Static Public Member Functions/Class Methods/g' \ --e 's/Public Member Functions/Instance Methods/g' \ --e 's/Member Function Documentation/Method Documentation/g' \ --e 's/(AppKit|Foundation)\.doc/\1/g' \ -"$2"/*.html +exec "$1"/support/massage_text.py "$2" diff --git a/Tools/Documentation/preprocess/001.markdown_readme.sh b/Tools/Documentation/preprocess/001.markdown_readme.sh index 72a040eff..8f23e4ef4 100755 --- a/Tools/Documentation/preprocess/001.markdown_readme.sh +++ b/Tools/Documentation/preprocess/001.markdown_readme.sh @@ -5,7 +5,7 @@ # $1 Cappuccino documentation directory # Do this if you want to use the utility functions -source "$1"/processor_setup.sh +source "$1"/support/processor_setup.sh markdown=`which markdown` diff --git a/Tools/Documentation/preprocess/002.make_headers.sh b/Tools/Documentation/preprocess/002.make_headers.sh index f4e43fef3..72c1dfba0 100755 --- a/Tools/Documentation/preprocess/002.make_headers.sh +++ b/Tools/Documentation/preprocess/002.make_headers.sh @@ -5,7 +5,7 @@ # $1 Cappuccino documentation directory # Do this if you want to use the utility functions -source "$1"/processor_setup.sh +source "$1"/support/processor_setup.sh if [ -d AppKit.doc ]; then rm -rf AppKit.doc diff --git a/Tools/Documentation/support/massage_text.py b/Tools/Documentation/support/massage_text.py new file mode 100755 index 000000000..aa82fb8c0 --- /dev/null +++ b/Tools/Documentation/support/massage_text.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# +# $1 Generated documentation directory + +# The following transforms are performed: +# - Strip useless "[implementation]" littering the docs +# - Change "Static Public Member Functions" to "Class Methods" +# - Change "Public Member Functions" to "Instance Methods" +# - Change "Member Function Documentation" to "Method Documentation" +# - Remove empty line left at the end of multi-parameter method prototypes + +import glob +import os.path +import re +import sys + +transforms = [ + re.compile(r" \[implementation\]"), " ", + re.compile(r"Static Public Member Functions"), "Class Methods", + re.compile(r"Public Member Functions"), "Instance Methods", + re.compile(r"Member Function Documentation"), "Method Documentation", + re.compile(r"(AppKit|Foundation)\.doc"), r"\1", + re.compile(r"\s*\n(\s*\n){2}\s*(){2} \n\s*"), "" +] + +html = glob.glob(os.path.join(sys.argv[1], "*.html")) + +for count, filename in enumerate(html): + f = open(filename, "r+") + text = f.read() + i = 0 + + while i < len(transforms): + text = transforms[i].sub(transforms[i + 1], text) + i += 2 + + f.seek(0) + f.truncate() + f.write(text) + f.close() diff --git a/Tools/Documentation/processor_setup.sh b/Tools/Documentation/support/processor_setup.sh similarity index 100% rename from Tools/Documentation/processor_setup.sh rename to Tools/Documentation/support/processor_setup.sh From 896f91215e6c4cd81e5c642786746a4693facf9e Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 18 May 2011 16:53:51 -0700 Subject: [PATCH 3/7] Whitespace cleanup --- Tools/Documentation/support/massage_text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/Documentation/support/massage_text.py b/Tools/Documentation/support/massage_text.py index aa82fb8c0..d3edead64 100755 --- a/Tools/Documentation/support/massage_text.py +++ b/Tools/Documentation/support/massage_text.py @@ -29,11 +29,11 @@ for count, filename in enumerate(html): f = open(filename, "r+") text = f.read() i = 0 - + while i < len(transforms): text = transforms[i].sub(transforms[i + 1], text) i += 2 - + f.seek(0) f.truncate() f.write(text) From f132ec5a5029f80dac1840caf2c8bed1a4ae5ec3 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 18 May 2011 16:55:15 -0700 Subject: [PATCH 4/7] Fixed formatting of lists and code in class description. The code did not change, I don't know why git says it did. --- Foundation/CPPredicate/CPPredicate.j | 2917 +++++++++++++++++++++++++- 1 file changed, 2907 insertions(+), 10 deletions(-) diff --git a/Foundation/CPPredicate/CPPredicate.j b/Foundation/CPPredicate/CPPredicate.j index 9d778a28b..44c630821 100644 --- a/Foundation/CPPredicate/CPPredicate.j +++ b/Foundation/CPPredicate/CPPredicate.j @@ -36,21 +36,2918 @@ @class CPPredicate @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. - You use predicates to represent logical conditions, used for describing objects in persistent stores and in-memory filtering of objects. Although it is common to create predicates directly from instances of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: + You use predicates to represent logical conditions, used for describing objects in persistent stores + and in-memory filtering of objects. Although it is common to create predicates directly from instances + of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a + format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: - Simple comparisons, such as grade == "7" or firstName like "Shaffiq"\n - Case/diacritic insensitive lookups, such as name contains[cd] "itroen"\n - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley")\n - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}.\n + - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" + - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" + - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") + - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. You can create predicates for relationships, such as: - group.name like "work*"\n - ALL children.age > 12\n - ANY children.age > 12\n - You can create predicates for operations, such as @sum.items.price < 1000. + - group.name like "work*" + - ALL children.age > 12 + - ANY children.age > 12 - You can also create predicates that include variables, so that the predicate can be pre-defined before substituting concrete values at runtime with evaluateWithObject:substitutionVariables: method. + You can create predicates for operations, such as @sum.items.price < 1000. + + You can also create predicates that include variables, so that the predicate can be pre-defined before + substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. +*/ + +@implementation CPPredicate : CPObject +{ +} + +/*! + Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. + @param format The format string for the new predicate. + @param … A comma-separated list of arguments to substitute into format. + @return A new predicate formed by creating a new string with format and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format, ... +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var args = Array.prototype.slice.call(arguments, 3); + return [self predicateWithFormat:arguments[2] argumentArray:args]; +} + +/*! + Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. + @param format The format string for the new predicate. + @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. + @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var s = [[CPPredicateScanner alloc] initWithString:format args:args], + p = [s parse]; + + return p; +} + +/*! + Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. + @param format The format string for the new predicate. + @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. + @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList +{ + // UNIMPLEMENTED + return nil; +} + +/*! + Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. +*/ +- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Creates and returns a predicate that always evaluates to a given value. + @param value The value to which the new predicate should evaluate. + @return A predicate that always evaluates to value. +*/ ++ (CPPredicate)predicateWithValue:(BOOL)value +{ + return [[CPPredicate_BOOL alloc] initWithBool:value]; +} + +// Evaluating a Predicate +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. + @param object The object against which to evaluate the receiver. + @return YES if object matches the conditions specified by the receiver, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. + @param object The object against which to evaluate the receiver. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +// Getting Format Information +/*! + Returns the receiver’s format string. + @return The receiver’s format string. +*/ +- (CPString)predicateFormat +{ + // IMPLEMENTED BY SUBCLASSES +} + +- (CPString)description +{ + return [self predicateFormat]; +} + +@end + +@implementation CPPredicate_BOOL : CPPredicate +{ + BOOL _value; +} + +- (id)initWithBool:(BOOL)value +{ + _value = value; + return self; +} + +- (BOOL)evaluateObject:(id)object +{ + return _value; +} + +- (CPString)predicateFormat +{ + return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; +} + +@end + + +@implementation CPArray (CPPredicate) + +- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPArray array], + i = 0; + + for (; i < count; i++) + { + var object = self[i]; + if ([predicate evaluateWithObject:object]) + result.push(object); + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (count--) + { + if (![predicate evaluateWithObject:self[count]]) + splice(count, 1); + } +} + +@end + +@implementation CPSet (CPPredicate) + +- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPSet set], + i = 0; + + for (; i < count; i++) + { + var object = [self objectAtIndex:i]; + + if ([predicate evaluateWithObject:object]) + [result addObject:object]; + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (--count >= 0) + { + var object = [self objectAtIndex:count]; + + if (![predicate evaluateWithObject:object]) + [self removeObjectAtIndex:count]; + } +} + +@end + +#define REFERENCE(variable) \ +function(newValue)\ +{\ + var oldValue = variable;\ + if (typeof newValue != 'undefined')\ + variable = newValue;\ + return oldValue;\ +} + +@implementation CPPredicateScanner : CPScanner +{ + CPEnumerator _args; + unsigned _retrieved; +} + +- (id)initWithString:(CPString)format args:(CPArray)args +{ + self = [super initWithString:format] + + if (self) + { + _args = [args objectEnumerator]; + } + return self; +} + +- (id)nextArg +{ + return [_args nextObject]; +} + +- (BOOL)scanPredicateKeyword:(CPString)key +{ + var loc = [self scanLocation]; + + [self setCaseSensitive:NO]; + if (![self scanString:key intoString:NULL]) + return NO; + + if ([self isAtEnd]) + return YES; + + var c = [[self string] characterAtIndex:[self scanLocation]]; + if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) + return YES; + + [self setScanLocation:loc]; + + return NO; +} + +- (CPPredicate)parse +{ + var r = nil; + + try + { + [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; + r = [self parsePredicate]; + } + catch(error) + { + CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); + } + finally + { + if (![self isAtEnd]) + { + var pstr = [self string], + loc = [self scanLocation]; + CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); + } + } + + return r; +} + +- (CPPredicate)parsePredicate +{ + return [self parseAnd]; +} + +- (CPPredicate)parseAnd +{ + var l = [self parseOr]; + + while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) + { + var r = [self parseOr]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseNot +{ + if ([self scanString:@"(" intoString:NULL]) + { + var r = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + return r; + } + + if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) + { + return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; + } + if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) + { + return [CPPredicate predicateWithValue:YES]; + } + if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) + { + return [CPPredicate predicateWithValue:NO]; + } + + return [self parseComparison]; +} + +- (CPPredicate)parseOr +{ + var l = [self parseNot]; + while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) + { + var r = [self parseNot]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseComparison +{ + var modifier = CPDirectPredicateModifier, + type = 0, + opts = 0, + left, + right, + p, + negate = NO; + + if ([self scanPredicateKeyword:@"ANY"]) + { + modifier = CPAnyPredicateModifier; + } + else if ([self scanPredicateKeyword:@"ALL"]) + { + modifier = CPAllPredicateModifier; + } + else if ([self scanPredicateKeyword:@"NONE"]) + { + modifier = CPAnyPredicateModifier; + negate = YES; + } + else if ([self scanPredicateKeyword:@"SOME"]) + { + modifier = CPAllPredicateModifier; + negate = YES; + } + + left = [self parseExpression]; + if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) + { + type = CPNotEqualToPredicateOperatorType; + } + else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) + { + type = CPLessThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) + { + type = CPGreaterThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@"<" intoString:NULL]) + { + type = CPLessThanPredicateOperatorType; + } + else if ([self scanString:@">" intoString:NULL]) + { + type = CPGreaterThanPredicateOperatorType; + } + else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) + { + type = CPEqualToPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"MATCHES"]) + { + type = CPMatchesPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"LIKE"]) + { + type = CPLikePredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BEGINSWITH"]) + { + type = CPBeginsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"ENDSWITH"]) + { + type = CPEndsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"IN"]) + { + type = CPInPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"CONTAINS"]) + { + type = CPContainsPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BETWEEN"]) + { + type = CPBetweenPredicateOperatorType; + } + else + CPRaiseParseError(self, @"comparison predicate"); + + if ([self scanString:@"[cd]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; + } + else if ([self scanString:@"[c]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption; + } + else if ([self scanString:@"[d]" intoString:NULL]) + { + opts = CPDiacriticInsensitivePredicateOption; + } + + right = [self parseExpression]; + + p = [CPComparisonPredicate predicateWithLeftExpression:left + rightExpression:right + modifier:modifier + type:type + options:opts]; + + return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; +} + +- (CPExpression)parseExpression +{ + return [self parseBinaryExpression]; +} + +- (CPExpression)parseSimpleExpression +{ + var identifier, + location, + ident, + dbl; + + if ([self scanDouble:REFERENCE(dbl)]) + return [CPExpression expressionForConstantValue:dbl]; + + // FIXME: handle integer, hex constants, 0x 0o 0b + if ([self scanString:@"-" intoString:NULL]) + return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; + + if ([self scanString:@"(" intoString:NULL]) + { + var arg = [self parseExpression]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return arg; + } + + if ([self scanString:@"{" intoString:NULL]) + { + if ([self scanString:@"}" intoString:NULL]) + return [CPExpression expressionForConstantValue:a]; + + var a = []; + + [a addObject:[self parseExpression]]; + + while ([self scanString:@"," intoString:NULL]) + [a addObject:[self parseExpression]]; + + if (![self scanString:@"}" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForAggregate:a]; + } + + if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) + { + return [CPExpression expressionForConstantValue:[CPNull null]]; + } + if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; + } + if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; + } + if ([self scanPredicateKeyword:@"SELF"]) + { + return [CPExpression expressionForEvaluatedObject]; + } + + if ([self scanString:@"$" intoString:NULL]) + { + var variable = [self parseSimpleExpression]; + + if (![variable keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForVariable:variable]; + } + + location = [self scanLocation]; + + if ([self scanString:@"%" intoString:NULL]) + { + if ([self isAtEnd] == NO) + { + var c = [[self string] characterAtIndex:[self scanLocation]]; + + switch (c) + { + case '%':// '%%' is treated as '%' + location = [self scanLocation]; + break; + case 'K': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForKeyPath:[self nextArg]]; + case '@': + case 'c': + case 'C': + case 'd': + case 'D': + case 'i': + case 'o': + case 'O': + case 'u': + case 'U': + case 'x': + case 'X': + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + case 'h': + [self scanString:@"h" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + case 'q': + [self scanString:@"q" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u' || c == 'x' || c == 'X') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + } + } + + [self setScanLocation:location]; + } + + if ([self scanString:@"\"" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"\"" intoString:REFERENCE(str)]; + + if ([self scanString:@"\"" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"'" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"'" intoString:REFERENCE(str)]; + + if ([self scanString:@"'" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"@" intoString:NULL]) + { + var e = [self parseExpression]; + + if (![e keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; + } + + if ([self scanString:@"SUBQUERY" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var collection = [self parseExpression], + variableExpression, + subpredicate; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + variableExpression = [self parseExpression]; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + subpredicate = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + } + + if ([self scanString:@"FUNCTION" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var args = [CPArray arrayWithObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; + } + + [self scanString:@"#" intoString:NULL]; + if (!identifier) + identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; + + if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:ident]; +} + +- (CPExpression)parseFunctionalExpression +{ + var left = [self parseSimpleExpression]; + + while (YES) + { + if ([self scanString:@"." intoString:NULL]) + { + var right = [self parseSimpleExpression], + expressionType = [right expressionType]; + + if (expressionType == CPKeyPathExpressionType) + left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; + else if (expressionType == CPVariableExpressionType) + left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; + else + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@"[" intoString:NULL]) + { + // index expression + if ([self scanPredicateKeyword:@"FIRST"]) + { + left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"LAST"]) + { + left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"SIZE"]) + { + left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; + } + else + { + var index = [self parseExpression]; + left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; + } + + if (![self scanString:@"]" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@":" intoString:NULL]) + { + // function - this parser allows for (max)(a, b, c) to be properly + // recognized and even (%K)(a, b, c) if %K evaluates to "max" + + if (![left keyPath]) + CPRaiseParseError(self, @"expression"); + + var selector = [left keyPath] + @":", + args = []; + + if (![self scanString:@"(" intoString:NULL]) + { + var str; + [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; + + if (![self scanString:@":(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + selector += str + @":"; + } + + if (![self scanString:@")" intoString:NULL]) + { + [args addObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + + left = [CPExpression expressionForFunction:selector arguments:args]; + } + else if ([self scanString:@"UNION" intoString:NULL]) + { + left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"INTERSECT" intoString:NULL]) + { + left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"MINUS" intoString:NULL]) + { + left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; + } + else + { + // done with suffixes + return left; + } + } +} + +- (CPExpression)parsePowerExpression +{ + var left = [self parseFunctionalExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"**" intoString:NULL]) + { + right = [self parseFunctionalExpression]; + left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseMultiplicationExpression +{ + var left = [self parsePowerExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"*" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"/" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseAdditionExpression +{ + var left = [self parseMultiplicationExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"+" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"-" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseBinaryExpression +{ + var left = [self parseAdditionExpression]; + + while (YES) + { + var right; + + if ([self scanString:@":=" intoString:NULL]) // assignment + { + // check left to be a variable? + right = [self parseAdditionExpression]; + // FIXME + } + else + { + return left; + } + } +} + +@end + +var CPRaiseParseError = function CPRaiseParseError(aScanner, target) +{ + [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; +} + +@import "CPCompoundPredicate.j" +@import "CPComparisonPredicate.j" +@import "CPExpression.j" +/* + * CPPredicate.j + * + * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) + * Copyright (c) 2005 Free Software Foundation. + * + * Created by cacaodev. + * Copyright 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPArray.j" +@import "CPException.j" +@import "CPNull.j" +@import "CPObject.j" +@import "CPScanner.j" +@import "CPSet.j" +@import "CPValue.j" +@import "CPCharacterSet.j" + +/*! + @ingroup foundation + @class CPPredicate + @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. + + You use predicates to represent logical conditions, used for describing objects in persistent stores + and in-memory filtering of objects. Although it is common to create predicates directly from instances + of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a + format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: + + - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" + - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" + - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") + - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. + + You can create predicates for relationships, such as: + + - group.name like "work*" + - ALL children.age > 12 + - ANY children.age > 12 + + You can create predicates for operations, such as @sum.items.price < 1000. + + You can also create predicates that include variables, so that the predicate can be pre-defined before + substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. +*/ + +@implementation CPPredicate : CPObject +{ +} + +/*! + Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. + @param format The format string for the new predicate. + @param … A comma-separated list of arguments to substitute into format. + @return A new predicate formed by creating a new string with format and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format, ... +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var args = Array.prototype.slice.call(arguments, 3); + return [self predicateWithFormat:arguments[2] argumentArray:args]; +} + +/*! + Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. + @param format The format string for the new predicate. + @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. + @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var s = [[CPPredicateScanner alloc] initWithString:format args:args], + p = [s parse]; + + return p; +} + +/*! + Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. + @param format The format string for the new predicate. + @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. + @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList +{ + // UNIMPLEMENTED + return nil; +} + +/*! + Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. +*/ +- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Creates and returns a predicate that always evaluates to a given value. + @param value The value to which the new predicate should evaluate. + @return A predicate that always evaluates to value. +*/ ++ (CPPredicate)predicateWithValue:(BOOL)value +{ + return [[CPPredicate_BOOL alloc] initWithBool:value]; +} + +// Evaluating a Predicate +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. + @param object The object against which to evaluate the receiver. + @return YES if object matches the conditions specified by the receiver, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. + @param object The object against which to evaluate the receiver. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +// Getting Format Information +/*! + Returns the receiver’s format string. + @return The receiver’s format string. +*/ +- (CPString)predicateFormat +{ + // IMPLEMENTED BY SUBCLASSES +} + +- (CPString)description +{ + return [self predicateFormat]; +} + +@end + +@implementation CPPredicate_BOOL : CPPredicate +{ + BOOL _value; +} + +- (id)initWithBool:(BOOL)value +{ + _value = value; + return self; +} + +- (BOOL)evaluateObject:(id)object +{ + return _value; +} + +- (CPString)predicateFormat +{ + return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; +} + +@end + + +@implementation CPArray (CPPredicate) + +- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPArray array], + i = 0; + + for (; i < count; i++) + { + var object = self[i]; + if ([predicate evaluateWithObject:object]) + result.push(object); + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (count--) + { + if (![predicate evaluateWithObject:self[count]]) + splice(count, 1); + } +} + +@end + +@implementation CPSet (CPPredicate) + +- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPSet set], + i = 0; + + for (; i < count; i++) + { + var object = [self objectAtIndex:i]; + + if ([predicate evaluateWithObject:object]) + [result addObject:object]; + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (--count >= 0) + { + var object = [self objectAtIndex:count]; + + if (![predicate evaluateWithObject:object]) + [self removeObjectAtIndex:count]; + } +} + +@end + +#define REFERENCE(variable) \ +function(newValue)\ +{\ + var oldValue = variable;\ + if (typeof newValue != 'undefined')\ + variable = newValue;\ + return oldValue;\ +} + +@implementation CPPredicateScanner : CPScanner +{ + CPEnumerator _args; + unsigned _retrieved; +} + +- (id)initWithString:(CPString)format args:(CPArray)args +{ + self = [super initWithString:format] + + if (self) + { + _args = [args objectEnumerator]; + } + return self; +} + +- (id)nextArg +{ + return [_args nextObject]; +} + +- (BOOL)scanPredicateKeyword:(CPString)key +{ + var loc = [self scanLocation]; + + [self setCaseSensitive:NO]; + if (![self scanString:key intoString:NULL]) + return NO; + + if ([self isAtEnd]) + return YES; + + var c = [[self string] characterAtIndex:[self scanLocation]]; + if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) + return YES; + + [self setScanLocation:loc]; + + return NO; +} + +- (CPPredicate)parse +{ + var r = nil; + + try + { + [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; + r = [self parsePredicate]; + } + catch(error) + { + CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); + } + finally + { + if (![self isAtEnd]) + { + var pstr = [self string], + loc = [self scanLocation]; + CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); + } + } + + return r; +} + +- (CPPredicate)parsePredicate +{ + return [self parseAnd]; +} + +- (CPPredicate)parseAnd +{ + var l = [self parseOr]; + + while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) + { + var r = [self parseOr]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseNot +{ + if ([self scanString:@"(" intoString:NULL]) + { + var r = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + return r; + } + + if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) + { + return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; + } + if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) + { + return [CPPredicate predicateWithValue:YES]; + } + if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) + { + return [CPPredicate predicateWithValue:NO]; + } + + return [self parseComparison]; +} + +- (CPPredicate)parseOr +{ + var l = [self parseNot]; + while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) + { + var r = [self parseNot]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseComparison +{ + var modifier = CPDirectPredicateModifier, + type = 0, + opts = 0, + left, + right, + p, + negate = NO; + + if ([self scanPredicateKeyword:@"ANY"]) + { + modifier = CPAnyPredicateModifier; + } + else if ([self scanPredicateKeyword:@"ALL"]) + { + modifier = CPAllPredicateModifier; + } + else if ([self scanPredicateKeyword:@"NONE"]) + { + modifier = CPAnyPredicateModifier; + negate = YES; + } + else if ([self scanPredicateKeyword:@"SOME"]) + { + modifier = CPAllPredicateModifier; + negate = YES; + } + + left = [self parseExpression]; + if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) + { + type = CPNotEqualToPredicateOperatorType; + } + else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) + { + type = CPLessThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) + { + type = CPGreaterThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@"<" intoString:NULL]) + { + type = CPLessThanPredicateOperatorType; + } + else if ([self scanString:@">" intoString:NULL]) + { + type = CPGreaterThanPredicateOperatorType; + } + else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) + { + type = CPEqualToPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"MATCHES"]) + { + type = CPMatchesPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"LIKE"]) + { + type = CPLikePredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BEGINSWITH"]) + { + type = CPBeginsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"ENDSWITH"]) + { + type = CPEndsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"IN"]) + { + type = CPInPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"CONTAINS"]) + { + type = CPContainsPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BETWEEN"]) + { + type = CPBetweenPredicateOperatorType; + } + else + CPRaiseParseError(self, @"comparison predicate"); + + if ([self scanString:@"[cd]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; + } + else if ([self scanString:@"[c]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption; + } + else if ([self scanString:@"[d]" intoString:NULL]) + { + opts = CPDiacriticInsensitivePredicateOption; + } + + right = [self parseExpression]; + + p = [CPComparisonPredicate predicateWithLeftExpression:left + rightExpression:right + modifier:modifier + type:type + options:opts]; + + return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; +} + +- (CPExpression)parseExpression +{ + return [self parseBinaryExpression]; +} + +- (CPExpression)parseSimpleExpression +{ + var identifier, + location, + ident, + dbl; + + if ([self scanDouble:REFERENCE(dbl)]) + return [CPExpression expressionForConstantValue:dbl]; + + // FIXME: handle integer, hex constants, 0x 0o 0b + if ([self scanString:@"-" intoString:NULL]) + return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; + + if ([self scanString:@"(" intoString:NULL]) + { + var arg = [self parseExpression]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return arg; + } + + if ([self scanString:@"{" intoString:NULL]) + { + if ([self scanString:@"}" intoString:NULL]) + return [CPExpression expressionForConstantValue:a]; + + var a = []; + + [a addObject:[self parseExpression]]; + + while ([self scanString:@"," intoString:NULL]) + [a addObject:[self parseExpression]]; + + if (![self scanString:@"}" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForAggregate:a]; + } + + if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) + { + return [CPExpression expressionForConstantValue:[CPNull null]]; + } + if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; + } + if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; + } + if ([self scanPredicateKeyword:@"SELF"]) + { + return [CPExpression expressionForEvaluatedObject]; + } + + if ([self scanString:@"$" intoString:NULL]) + { + var variable = [self parseSimpleExpression]; + + if (![variable keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForVariable:variable]; + } + + location = [self scanLocation]; + + if ([self scanString:@"%" intoString:NULL]) + { + if ([self isAtEnd] == NO) + { + var c = [[self string] characterAtIndex:[self scanLocation]]; + + switch (c) + { + case '%':// '%%' is treated as '%' + location = [self scanLocation]; + break; + case 'K': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForKeyPath:[self nextArg]]; + case '@': + case 'c': + case 'C': + case 'd': + case 'D': + case 'i': + case 'o': + case 'O': + case 'u': + case 'U': + case 'x': + case 'X': + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + case 'h': + [self scanString:@"h" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + case 'q': + [self scanString:@"q" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u' || c == 'x' || c == 'X') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + } + } + + [self setScanLocation:location]; + } + + if ([self scanString:@"\"" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"\"" intoString:REFERENCE(str)]; + + if ([self scanString:@"\"" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"'" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"'" intoString:REFERENCE(str)]; + + if ([self scanString:@"'" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"@" intoString:NULL]) + { + var e = [self parseExpression]; + + if (![e keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; + } + + if ([self scanString:@"SUBQUERY" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var collection = [self parseExpression], + variableExpression, + subpredicate; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + variableExpression = [self parseExpression]; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + subpredicate = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + } + + if ([self scanString:@"FUNCTION" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var args = [CPArray arrayWithObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; + } + + [self scanString:@"#" intoString:NULL]; + if (!identifier) + identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; + + if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:ident]; +} + +- (CPExpression)parseFunctionalExpression +{ + var left = [self parseSimpleExpression]; + + while (YES) + { + if ([self scanString:@"." intoString:NULL]) + { + var right = [self parseSimpleExpression], + expressionType = [right expressionType]; + + if (expressionType == CPKeyPathExpressionType) + left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; + else if (expressionType == CPVariableExpressionType) + left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; + else + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@"[" intoString:NULL]) + { + // index expression + if ([self scanPredicateKeyword:@"FIRST"]) + { + left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"LAST"]) + { + left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"SIZE"]) + { + left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; + } + else + { + var index = [self parseExpression]; + left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; + } + + if (![self scanString:@"]" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@":" intoString:NULL]) + { + // function - this parser allows for (max)(a, b, c) to be properly + // recognized and even (%K)(a, b, c) if %K evaluates to "max" + + if (![left keyPath]) + CPRaiseParseError(self, @"expression"); + + var selector = [left keyPath] + @":", + args = []; + + if (![self scanString:@"(" intoString:NULL]) + { + var str; + [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; + + if (![self scanString:@":(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + selector += str + @":"; + } + + if (![self scanString:@")" intoString:NULL]) + { + [args addObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + + left = [CPExpression expressionForFunction:selector arguments:args]; + } + else if ([self scanString:@"UNION" intoString:NULL]) + { + left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"INTERSECT" intoString:NULL]) + { + left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"MINUS" intoString:NULL]) + { + left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; + } + else + { + // done with suffixes + return left; + } + } +} + +- (CPExpression)parsePowerExpression +{ + var left = [self parseFunctionalExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"**" intoString:NULL]) + { + right = [self parseFunctionalExpression]; + left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseMultiplicationExpression +{ + var left = [self parsePowerExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"*" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"/" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseAdditionExpression +{ + var left = [self parseMultiplicationExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"+" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"-" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseBinaryExpression +{ + var left = [self parseAdditionExpression]; + + while (YES) + { + var right; + + if ([self scanString:@":=" intoString:NULL]) // assignment + { + // check left to be a variable? + right = [self parseAdditionExpression]; + // FIXME + } + else + { + return left; + } + } +} + +@end + +var CPRaiseParseError = function CPRaiseParseError(aScanner, target) +{ + [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; +} + +@import "CPCompoundPredicate.j" +@import "CPComparisonPredicate.j" +@import "CPExpression.j" +/* + * CPPredicate.j + * + * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) + * Copyright (c) 2005 Free Software Foundation. + * + * Created by cacaodev. + * Copyright 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPArray.j" +@import "CPException.j" +@import "CPNull.j" +@import "CPObject.j" +@import "CPScanner.j" +@import "CPSet.j" +@import "CPValue.j" +@import "CPCharacterSet.j" + +/*! + @ingroup foundation + @class CPPredicate + @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. + + You use predicates to represent logical conditions, used for describing objects in persistent stores + and in-memory filtering of objects. Although it is common to create predicates directly from instances + of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a + format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: + + - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" + - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" + - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") + - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. + + You can create predicates for relationships, such as: + + - group.name like "work*" + - ALL children.age > 12 + - ANY children.age > 12 + + You can create predicates for operations, such as @sum.items.price < 1000. + + You can also create predicates that include variables, so that the predicate can be pre-defined before + substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. +*/ + +@implementation CPPredicate : CPObject +{ +} + +/*! + Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. + @param format The format string for the new predicate. + @param … A comma-separated list of arguments to substitute into format. + @return A new predicate formed by creating a new string with format and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format, ... +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var args = Array.prototype.slice.call(arguments, 3); + return [self predicateWithFormat:arguments[2] argumentArray:args]; +} + +/*! + Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. + @param format The format string for the new predicate. + @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. + @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args +{ + if (!format) + [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; + + var s = [[CPPredicateScanner alloc] initWithString:format args:args], + p = [s parse]; + + return p; +} + +/*! + Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. + @param format The format string for the new predicate. + @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. + @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. +*/ ++ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList +{ + // UNIMPLEMENTED + return nil; +} + +/*! + Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. +*/ +- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Creates and returns a predicate that always evaluates to a given value. + @param value The value to which the new predicate should evaluate. + @return A predicate that always evaluates to value. +*/ ++ (CPPredicate)predicateWithValue:(BOOL)value +{ + return [[CPPredicate_BOOL alloc] initWithBool:value]; +} + +// Evaluating a Predicate +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. + @param object The object against which to evaluate the receiver. + @return YES if object matches the conditions specified by the receiver, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object +{ + // IMPLEMENTED BY SUBCLASSES +} + +/*! + Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. + @param object The object against which to evaluate the receiver. + @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. + @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. +*/ +- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables +{ + // IMPLEMENTED BY SUBCLASSES +} + +// Getting Format Information +/*! + Returns the receiver’s format string. + @return The receiver’s format string. +*/ +- (CPString)predicateFormat +{ + // IMPLEMENTED BY SUBCLASSES +} + +- (CPString)description +{ + return [self predicateFormat]; +} + +@end + +@implementation CPPredicate_BOOL : CPPredicate +{ + BOOL _value; +} + +- (id)initWithBool:(BOOL)value +{ + _value = value; + return self; +} + +- (BOOL)evaluateObject:(id)object +{ + return _value; +} + +- (CPString)predicateFormat +{ + return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; +} + +@end + + +@implementation CPArray (CPPredicate) + +- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPArray array], + i = 0; + + for (; i < count; i++) + { + var object = self[i]; + if ([predicate evaluateWithObject:object]) + result.push(object); + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (count--) + { + if (![predicate evaluateWithObject:self[count]]) + splice(count, 1); + } +} + +@end + +@implementation CPSet (CPPredicate) + +- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate +{ + var count = [self count], + result = [CPSet set], + i = 0; + + for (; i < count; i++) + { + var object = [self objectAtIndex:i]; + + if ([predicate evaluateWithObject:object]) + [result addObject:object]; + } + + return result; +} + +- (void)filterUsingPredicate:(CPPredicate)predicate +{ + var count = [self count]; + + while (--count >= 0) + { + var object = [self objectAtIndex:count]; + + if (![predicate evaluateWithObject:object]) + [self removeObjectAtIndex:count]; + } +} + +@end + +#define REFERENCE(variable) \ +function(newValue)\ +{\ + var oldValue = variable;\ + if (typeof newValue != 'undefined')\ + variable = newValue;\ + return oldValue;\ +} + +@implementation CPPredicateScanner : CPScanner +{ + CPEnumerator _args; + unsigned _retrieved; +} + +- (id)initWithString:(CPString)format args:(CPArray)args +{ + self = [super initWithString:format] + + if (self) + { + _args = [args objectEnumerator]; + } + return self; +} + +- (id)nextArg +{ + return [_args nextObject]; +} + +- (BOOL)scanPredicateKeyword:(CPString)key +{ + var loc = [self scanLocation]; + + [self setCaseSensitive:NO]; + if (![self scanString:key intoString:NULL]) + return NO; + + if ([self isAtEnd]) + return YES; + + var c = [[self string] characterAtIndex:[self scanLocation]]; + if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) + return YES; + + [self setScanLocation:loc]; + + return NO; +} + +- (CPPredicate)parse +{ + var r = nil; + + try + { + [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; + r = [self parsePredicate]; + } + catch(error) + { + CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); + } + finally + { + if (![self isAtEnd]) + { + var pstr = [self string], + loc = [self scanLocation]; + CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); + } + } + + return r; +} + +- (CPPredicate)parsePredicate +{ + return [self parseAnd]; +} + +- (CPPredicate)parseAnd +{ + var l = [self parseOr]; + + while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) + { + var r = [self parseOr]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseNot +{ + if ([self scanString:@"(" intoString:NULL]) + { + var r = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + return r; + } + + if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) + { + return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; + } + if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) + { + return [CPPredicate predicateWithValue:YES]; + } + if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) + { + return [CPPredicate predicateWithValue:NO]; + } + + return [self parseComparison]; +} + +- (CPPredicate)parseOr +{ + var l = [self parseNot]; + while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) + { + var r = [self parseNot]; + + if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) + { + if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObjectsFromArray:[r subpredicates]]; + } + else + { + [[r subpredicates] insertObject:l atIndex:0]; + l = r; + } + } + else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) + { + [[l subpredicates] addObject:r]; + } + else + { + l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; + } + } + return l; +} + +- (CPPredicate)parseComparison +{ + var modifier = CPDirectPredicateModifier, + type = 0, + opts = 0, + left, + right, + p, + negate = NO; + + if ([self scanPredicateKeyword:@"ANY"]) + { + modifier = CPAnyPredicateModifier; + } + else if ([self scanPredicateKeyword:@"ALL"]) + { + modifier = CPAllPredicateModifier; + } + else if ([self scanPredicateKeyword:@"NONE"]) + { + modifier = CPAnyPredicateModifier; + negate = YES; + } + else if ([self scanPredicateKeyword:@"SOME"]) + { + modifier = CPAllPredicateModifier; + negate = YES; + } + + left = [self parseExpression]; + if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) + { + type = CPNotEqualToPredicateOperatorType; + } + else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) + { + type = CPLessThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) + { + type = CPGreaterThanOrEqualToPredicateOperatorType; + } + else if ([self scanString:@"<" intoString:NULL]) + { + type = CPLessThanPredicateOperatorType; + } + else if ([self scanString:@">" intoString:NULL]) + { + type = CPGreaterThanPredicateOperatorType; + } + else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) + { + type = CPEqualToPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"MATCHES"]) + { + type = CPMatchesPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"LIKE"]) + { + type = CPLikePredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BEGINSWITH"]) + { + type = CPBeginsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"ENDSWITH"]) + { + type = CPEndsWithPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"IN"]) + { + type = CPInPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"CONTAINS"]) + { + type = CPContainsPredicateOperatorType; + } + else if ([self scanPredicateKeyword:@"BETWEEN"]) + { + type = CPBetweenPredicateOperatorType; + } + else + CPRaiseParseError(self, @"comparison predicate"); + + if ([self scanString:@"[cd]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; + } + else if ([self scanString:@"[c]" intoString:NULL]) + { + opts = CPCaseInsensitivePredicateOption; + } + else if ([self scanString:@"[d]" intoString:NULL]) + { + opts = CPDiacriticInsensitivePredicateOption; + } + + right = [self parseExpression]; + + p = [CPComparisonPredicate predicateWithLeftExpression:left + rightExpression:right + modifier:modifier + type:type + options:opts]; + + return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; +} + +- (CPExpression)parseExpression +{ + return [self parseBinaryExpression]; +} + +- (CPExpression)parseSimpleExpression +{ + var identifier, + location, + ident, + dbl; + + if ([self scanDouble:REFERENCE(dbl)]) + return [CPExpression expressionForConstantValue:dbl]; + + // FIXME: handle integer, hex constants, 0x 0o 0b + if ([self scanString:@"-" intoString:NULL]) + return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; + + if ([self scanString:@"(" intoString:NULL]) + { + var arg = [self parseExpression]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return arg; + } + + if ([self scanString:@"{" intoString:NULL]) + { + if ([self scanString:@"}" intoString:NULL]) + return [CPExpression expressionForConstantValue:a]; + + var a = []; + + [a addObject:[self parseExpression]]; + + while ([self scanString:@"," intoString:NULL]) + [a addObject:[self parseExpression]]; + + if (![self scanString:@"}" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForAggregate:a]; + } + + if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) + { + return [CPExpression expressionForConstantValue:[CPNull null]]; + } + if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; + } + if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) + { + return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; + } + if ([self scanPredicateKeyword:@"SELF"]) + { + return [CPExpression expressionForEvaluatedObject]; + } + + if ([self scanString:@"$" intoString:NULL]) + { + var variable = [self parseSimpleExpression]; + + if (![variable keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForVariable:variable]; + } + + location = [self scanLocation]; + + if ([self scanString:@"%" intoString:NULL]) + { + if ([self isAtEnd] == NO) + { + var c = [[self string] characterAtIndex:[self scanLocation]]; + + switch (c) + { + case '%':// '%%' is treated as '%' + location = [self scanLocation]; + break; + case 'K': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForKeyPath:[self nextArg]]; + case '@': + case 'c': + case 'C': + case 'd': + case 'D': + case 'i': + case 'o': + case 'O': + case 'u': + case 'U': + case 'x': + case 'X': + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + case 'h': + [self scanString:@"h" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + case 'q': + [self scanString:@"q" intoString:NULL]; + if ([self isAtEnd] == NO) + { + c = [[self string] characterAtIndex:[self scanLocation]]; + if (c == 'i' || c == 'u' || c == 'x' || c == 'X') + { + [self setScanLocation:[self scanLocation] + 1]; + return [CPExpression expressionForConstantValue:[self nextArg]]; + } + } + break; + } + } + + [self setScanLocation:location]; + } + + if ([self scanString:@"\"" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"\"" intoString:REFERENCE(str)]; + + if ([self scanString:@"\"" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"'" intoString:NULL]) + { + var skip = [self charactersToBeSkipped], + str = @""; + + [self setCharactersToBeSkipped:nil]; + [self scanUpToString:@"'" intoString:REFERENCE(str)]; + + if ([self scanString:@"'" intoString:NULL] == NO) + CPRaiseParseError(self, @"expression"); + + [self setCharactersToBeSkipped:skip]; + + return [CPExpression expressionForConstantValue:str]; + } + + if ([self scanString:@"@" intoString:NULL]) + { + var e = [self parseExpression]; + + if (![e keyPath]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; + } + + if ([self scanString:@"SUBQUERY" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var collection = [self parseExpression], + variableExpression, + subpredicate; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + variableExpression = [self parseExpression]; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + subpredicate = [self parsePredicate]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + } + + if ([self scanString:@"FUNCTION" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var args = [CPArray arrayWithObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; + } + + [self scanString:@"#" intoString:NULL]; + if (!identifier) + identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; + + if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) + CPRaiseParseError(self, @"expression"); + + return [CPExpression expressionForKeyPath:ident]; +} + +- (CPExpression)parseFunctionalExpression +{ + var left = [self parseSimpleExpression]; + + while (YES) + { + if ([self scanString:@"." intoString:NULL]) + { + var right = [self parseSimpleExpression], + expressionType = [right expressionType]; + + if (expressionType == CPKeyPathExpressionType) + left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; + else if (expressionType == CPVariableExpressionType) + left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; + else + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@"[" intoString:NULL]) + { + // index expression + if ([self scanPredicateKeyword:@"FIRST"]) + { + left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"LAST"]) + { + left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; + } + else if ([self scanPredicateKeyword:@"SIZE"]) + { + left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; + } + else + { + var index = [self parseExpression]; + left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; + } + + if (![self scanString:@"]" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + else if ([self scanString:@":" intoString:NULL]) + { + // function - this parser allows for (max)(a, b, c) to be properly + // recognized and even (%K)(a, b, c) if %K evaluates to "max" + + if (![left keyPath]) + CPRaiseParseError(self, @"expression"); + + var selector = [left keyPath] + @":", + args = []; + + if (![self scanString:@"(" intoString:NULL]) + { + var str; + [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; + + if (![self scanString:@":(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + selector += str + @":"; + } + + if (![self scanString:@")" intoString:NULL]) + { + [args addObject:[self parseExpression]]; + while ([self scanString:@"," intoString:NULL]) + [args addObject:[self parseExpression]]; + + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + } + + left = [CPExpression expressionForFunction:selector arguments:args]; + } + else if ([self scanString:@"UNION" intoString:NULL]) + { + left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"INTERSECT" intoString:NULL]) + { + left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; + } + else if ([self scanString:@"MINUS" intoString:NULL]) + { + left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; + } + else + { + // done with suffixes + return left; + } + } +} + +- (CPExpression)parsePowerExpression +{ + var left = [self parseFunctionalExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"**" intoString:NULL]) + { + right = [self parseFunctionalExpression]; + left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseMultiplicationExpression +{ + var left = [self parsePowerExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"*" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"/" intoString:NULL]) + { + right = [self parsePowerExpression]; + left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseAdditionExpression +{ + var left = [self parseMultiplicationExpression]; + + while (YES) + { + var right; + + if ([self scanString:@"+" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else if ([self scanString:@"-" intoString:NULL]) + { + right = [self parseMultiplicationExpression]; + left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; + } + else + { + return left; + } + } +} + +- (CPExpression)parseBinaryExpression +{ + var left = [self parseAdditionExpression]; + + while (YES) + { + var right; + + if ([self scanString:@":=" intoString:NULL]) // assignment + { + // check left to be a variable? + right = [self parseAdditionExpression]; + // FIXME + } + else + { + return left; + } + } +} + +@end + +var CPRaiseParseError = function CPRaiseParseError(aScanner, target) +{ + [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; +} + +@import "CPCompoundPredicate.j" +@import "CPComparisonPredicate.j" +@import "CPExpression.j" +/* + * CPPredicate.j + * + * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) + * Copyright (c) 2005 Free Software Foundation. + * + * Created by cacaodev. + * Copyright 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPArray.j" +@import "CPException.j" +@import "CPNull.j" +@import "CPObject.j" +@import "CPScanner.j" +@import "CPSet.j" +@import "CPValue.j" +@import "CPCharacterSet.j" + +/*! + @ingroup foundation + @class CPPredicate + @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. + + You use predicates to represent logical conditions, used for describing objects in persistent stores + and in-memory filtering of objects. Although it is common to create predicates directly from instances + of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a + format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: + + - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" + - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" + - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") + - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. + + You can create predicates for relationships, such as: + + - group.name like "work*" + - ALL children.age > 12 + - ANY children.age > 12 + + You can create predicates for operations, such as @sum.items.price < 1000. + + You can also create predicates that include variables, so that the predicate can be pre-defined before + substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. */ @implementation CPPredicate : CPObject From 634fa7b85030df6f5311ed773bf2bd41c4475729 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 18 May 2011 17:32:59 -0700 Subject: [PATCH 5/7] Stupid Uber Glory bundle duplicated the file a few times when stripping whitespace. --- Foundation/CPPredicate/CPPredicate.j | 2894 +------------------------- 1 file changed, 1 insertion(+), 2893 deletions(-) diff --git a/Foundation/CPPredicate/CPPredicate.j b/Foundation/CPPredicate/CPPredicate.j index 44c630821..1e99887ec 100644 --- a/Foundation/CPPredicate/CPPredicate.j +++ b/Foundation/CPPredicate/CPPredicate.j @@ -52,2899 +52,7 @@ - ALL children.age > 12 - ANY children.age > 12 - You can create predicates for operations, such as @sum.items.price < 1000. - - You can also create predicates that include variables, so that the predicate can be pre-defined before - substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. -*/ - -@implementation CPPredicate : CPObject -{ -} - -/*! - Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. - @param format The format string for the new predicate. - @param … A comma-separated list of arguments to substitute into format. - @return A new predicate formed by creating a new string with format and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format, ... -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var args = Array.prototype.slice.call(arguments, 3); - return [self predicateWithFormat:arguments[2] argumentArray:args]; -} - -/*! - Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. - @param format The format string for the new predicate. - @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. - @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - p = [s parse]; - - return p; -} - -/*! - Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. - @param format The format string for the new predicate. - @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. - @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList -{ - // UNIMPLEMENTED - return nil; -} - -/*! - Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. -*/ -- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Creates and returns a predicate that always evaluates to a given value. - @param value The value to which the new predicate should evaluate. - @return A predicate that always evaluates to value. -*/ -+ (CPPredicate)predicateWithValue:(BOOL)value -{ - return [[CPPredicate_BOOL alloc] initWithBool:value]; -} - -// Evaluating a Predicate -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. - @param object The object against which to evaluate the receiver. - @return YES if object matches the conditions specified by the receiver, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. - @param object The object against which to evaluate the receiver. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -// Getting Format Information -/*! - Returns the receiver’s format string. - @return The receiver’s format string. -*/ -- (CPString)predicateFormat -{ - // IMPLEMENTED BY SUBCLASSES -} - -- (CPString)description -{ - return [self predicateFormat]; -} - -@end - -@implementation CPPredicate_BOOL : CPPredicate -{ - BOOL _value; -} - -- (id)initWithBool:(BOOL)value -{ - _value = value; - return self; -} - -- (BOOL)evaluateObject:(id)object -{ - return _value; -} - -- (CPString)predicateFormat -{ - return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; -} - -@end - - -@implementation CPArray (CPPredicate) - -- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPArray array], - i = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; i < count; i++) - { - var object = [self objectAtIndex:i]; - - if ([predicate evaluateWithObject:object]) - [result addObject:object]; - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (--count >= 0) - { - var object = [self objectAtIndex:count]; - - if (![predicate evaluateWithObject:object]) - [self removeObjectAtIndex:count]; - } -} - -@end - -#define REFERENCE(variable) \ -function(newValue)\ -{\ - var oldValue = variable;\ - if (typeof newValue != 'undefined')\ - variable = newValue;\ - return oldValue;\ -} - -@implementation CPPredicateScanner : CPScanner -{ - CPEnumerator _args; - unsigned _retrieved; -} - -- (id)initWithString:(CPString)format args:(CPArray)args -{ - self = [super initWithString:format] - - if (self) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var c = [[self string] characterAtIndex:[self scanLocation]]; - if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) - return YES; - - [self setScanLocation:loc]; - - return NO; -} - -- (CPPredicate)parse -{ - var r = nil; - - try - { - [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; - r = [self parsePredicate]; - } - catch(error) - { - CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - return r; -} - -- (CPPredicate)parsePredicate -{ - return [self parseAnd]; -} - -- (CPPredicate)parseAnd -{ - var l = [self parseOr]; - - while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) - { - var r = [self parseOr]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"predicate"); - - return r; - } - - if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) - { - return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; - } - if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) - { - return [CPPredicate predicateWithValue:YES]; - } - if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) - { - return [CPPredicate predicateWithValue:NO]; - } - - return [self parseComparison]; -} - -- (CPPredicate)parseOr -{ - var l = [self parseNot]; - while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) - { - var r = [self parseNot]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = NO; - - if ([self scanPredicateKeyword:@"ANY"]) - { - modifier = CPAnyPredicateModifier; - } - else if ([self scanPredicateKeyword:@"ALL"]) - { - modifier = CPAllPredicateModifier; - } - else if ([self scanPredicateKeyword:@"NONE"]) - { - modifier = CPAnyPredicateModifier; - negate = YES; - } - else if ([self scanPredicateKeyword:@"SOME"]) - { - modifier = CPAllPredicateModifier; - negate = YES; - } - - left = [self parseExpression]; - if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) - { - type = CPNotEqualToPredicateOperatorType; - } - else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) - { - type = CPLessThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) - { - type = CPGreaterThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@"<" intoString:NULL]) - { - type = CPLessThanPredicateOperatorType; - } - else if ([self scanString:@">" intoString:NULL]) - { - type = CPGreaterThanPredicateOperatorType; - } - else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) - { - type = CPEqualToPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"MATCHES"]) - { - type = CPMatchesPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"LIKE"]) - { - type = CPLikePredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BEGINSWITH"]) - { - type = CPBeginsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"ENDSWITH"]) - { - type = CPEndsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"IN"]) - { - type = CPInPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"CONTAINS"]) - { - type = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - if ([self scanString:@"[cd]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; - } - else if ([self scanString:@"[c]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption; - } - else if ([self scanString:@"[d]" intoString:NULL]) - { - opts = CPDiacriticInsensitivePredicateOption; - } - - right = [self parseExpression]; - - p = [CPComparisonPredicate predicateWithLeftExpression:left - rightExpression:right - modifier:modifier - type:type - options:opts]; - - return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; -} - -- (CPExpression)parseExpression -{ - return [self parseBinaryExpression]; -} - -- (CPExpression)parseSimpleExpression -{ - var identifier, - location, - ident, - dbl; - - if ([self scanDouble:REFERENCE(dbl)]) - return [CPExpression expressionForConstantValue:dbl]; - - // FIXME: handle integer, hex constants, 0x 0o 0b - if ([self scanString:@"-" intoString:NULL]) - return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; - - if ([self scanString:@"(" intoString:NULL]) - { - var arg = [self parseExpression]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate:a]; - } - - if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) - { - return [CPExpression expressionForConstantValue:[CPNull null]]; - } - if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; - } - if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; - } - if ([self scanPredicateKeyword:@"SELF"]) - { - return [CPExpression expressionForEvaluatedObject]; - } - - if ([self scanString:@"$" intoString:NULL]) - { - var variable = [self parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - location = [self scanLocation]; - - if ([self scanString:@"%" intoString:NULL]) - { - if ([self isAtEnd] == NO) - { - var c = [[self string] characterAtIndex:[self scanLocation]]; - - switch (c) - { - case '%':// '%%' is treated as '%' - location = [self scanLocation]; - break; - case 'K': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForKeyPath:[self nextArg]]; - case '@': - case 'c': - case 'C': - case 'd': - case 'D': - case 'i': - case 'o': - case 'O': - case 'u': - case 'U': - case 'x': - case 'X': - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - case 'h': - [self scanString:@"h" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - case 'q': - [self scanString:@"q" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u' || c == 'x' || c == 'X') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - } - } - - [self setScanLocation:location]; - } - - if ([self scanString:@"\"" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // done with suffixes - return left; - } - } -} - -- (CPExpression)parsePowerExpression -{ - var left = [self parseFunctionalExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"**" intoString:NULL]) - { - right = [self parseFunctionalExpression]; - left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseAdditionExpression -{ - var left = [self parseMultiplicationExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"+" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseBinaryExpression -{ - var left = [self parseAdditionExpression]; - - while (YES) - { - var right; - - if ([self scanString:@":=" intoString:NULL]) // assignment - { - // check left to be a variable? - right = [self parseAdditionExpression]; - // FIXME - } - else - { - return left; - } - } -} - -@end - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.j" - -/*! - @ingroup foundation - @class CPPredicate - @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. - - You use predicates to represent logical conditions, used for describing objects in persistent stores - and in-memory filtering of objects. Although it is common to create predicates directly from instances - of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a - format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: - - - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - You can create predicates for operations, such as @sum.items.price < 1000. - - You can also create predicates that include variables, so that the predicate can be pre-defined before - substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. -*/ - -@implementation CPPredicate : CPObject -{ -} - -/*! - Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. - @param format The format string for the new predicate. - @param … A comma-separated list of arguments to substitute into format. - @return A new predicate formed by creating a new string with format and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format, ... -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var args = Array.prototype.slice.call(arguments, 3); - return [self predicateWithFormat:arguments[2] argumentArray:args]; -} - -/*! - Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. - @param format The format string for the new predicate. - @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. - @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - p = [s parse]; - - return p; -} - -/*! - Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. - @param format The format string for the new predicate. - @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. - @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList -{ - // UNIMPLEMENTED - return nil; -} - -/*! - Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. -*/ -- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Creates and returns a predicate that always evaluates to a given value. - @param value The value to which the new predicate should evaluate. - @return A predicate that always evaluates to value. -*/ -+ (CPPredicate)predicateWithValue:(BOOL)value -{ - return [[CPPredicate_BOOL alloc] initWithBool:value]; -} - -// Evaluating a Predicate -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. - @param object The object against which to evaluate the receiver. - @return YES if object matches the conditions specified by the receiver, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. - @param object The object against which to evaluate the receiver. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -// Getting Format Information -/*! - Returns the receiver’s format string. - @return The receiver’s format string. -*/ -- (CPString)predicateFormat -{ - // IMPLEMENTED BY SUBCLASSES -} - -- (CPString)description -{ - return [self predicateFormat]; -} - -@end - -@implementation CPPredicate_BOOL : CPPredicate -{ - BOOL _value; -} - -- (id)initWithBool:(BOOL)value -{ - _value = value; - return self; -} - -- (BOOL)evaluateObject:(id)object -{ - return _value; -} - -- (CPString)predicateFormat -{ - return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; -} - -@end - - -@implementation CPArray (CPPredicate) - -- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPArray array], - i = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; i < count; i++) - { - var object = [self objectAtIndex:i]; - - if ([predicate evaluateWithObject:object]) - [result addObject:object]; - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (--count >= 0) - { - var object = [self objectAtIndex:count]; - - if (![predicate evaluateWithObject:object]) - [self removeObjectAtIndex:count]; - } -} - -@end - -#define REFERENCE(variable) \ -function(newValue)\ -{\ - var oldValue = variable;\ - if (typeof newValue != 'undefined')\ - variable = newValue;\ - return oldValue;\ -} - -@implementation CPPredicateScanner : CPScanner -{ - CPEnumerator _args; - unsigned _retrieved; -} - -- (id)initWithString:(CPString)format args:(CPArray)args -{ - self = [super initWithString:format] - - if (self) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var c = [[self string] characterAtIndex:[self scanLocation]]; - if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) - return YES; - - [self setScanLocation:loc]; - - return NO; -} - -- (CPPredicate)parse -{ - var r = nil; - - try - { - [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; - r = [self parsePredicate]; - } - catch(error) - { - CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - return r; -} - -- (CPPredicate)parsePredicate -{ - return [self parseAnd]; -} - -- (CPPredicate)parseAnd -{ - var l = [self parseOr]; - - while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) - { - var r = [self parseOr]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"predicate"); - - return r; - } - - if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) - { - return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; - } - if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) - { - return [CPPredicate predicateWithValue:YES]; - } - if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) - { - return [CPPredicate predicateWithValue:NO]; - } - - return [self parseComparison]; -} - -- (CPPredicate)parseOr -{ - var l = [self parseNot]; - while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) - { - var r = [self parseNot]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = NO; - - if ([self scanPredicateKeyword:@"ANY"]) - { - modifier = CPAnyPredicateModifier; - } - else if ([self scanPredicateKeyword:@"ALL"]) - { - modifier = CPAllPredicateModifier; - } - else if ([self scanPredicateKeyword:@"NONE"]) - { - modifier = CPAnyPredicateModifier; - negate = YES; - } - else if ([self scanPredicateKeyword:@"SOME"]) - { - modifier = CPAllPredicateModifier; - negate = YES; - } - - left = [self parseExpression]; - if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) - { - type = CPNotEqualToPredicateOperatorType; - } - else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) - { - type = CPLessThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) - { - type = CPGreaterThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@"<" intoString:NULL]) - { - type = CPLessThanPredicateOperatorType; - } - else if ([self scanString:@">" intoString:NULL]) - { - type = CPGreaterThanPredicateOperatorType; - } - else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) - { - type = CPEqualToPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"MATCHES"]) - { - type = CPMatchesPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"LIKE"]) - { - type = CPLikePredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BEGINSWITH"]) - { - type = CPBeginsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"ENDSWITH"]) - { - type = CPEndsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"IN"]) - { - type = CPInPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"CONTAINS"]) - { - type = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - if ([self scanString:@"[cd]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; - } - else if ([self scanString:@"[c]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption; - } - else if ([self scanString:@"[d]" intoString:NULL]) - { - opts = CPDiacriticInsensitivePredicateOption; - } - - right = [self parseExpression]; - - p = [CPComparisonPredicate predicateWithLeftExpression:left - rightExpression:right - modifier:modifier - type:type - options:opts]; - - return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; -} - -- (CPExpression)parseExpression -{ - return [self parseBinaryExpression]; -} - -- (CPExpression)parseSimpleExpression -{ - var identifier, - location, - ident, - dbl; - - if ([self scanDouble:REFERENCE(dbl)]) - return [CPExpression expressionForConstantValue:dbl]; - - // FIXME: handle integer, hex constants, 0x 0o 0b - if ([self scanString:@"-" intoString:NULL]) - return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; - - if ([self scanString:@"(" intoString:NULL]) - { - var arg = [self parseExpression]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate:a]; - } - - if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) - { - return [CPExpression expressionForConstantValue:[CPNull null]]; - } - if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; - } - if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; - } - if ([self scanPredicateKeyword:@"SELF"]) - { - return [CPExpression expressionForEvaluatedObject]; - } - - if ([self scanString:@"$" intoString:NULL]) - { - var variable = [self parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - location = [self scanLocation]; - - if ([self scanString:@"%" intoString:NULL]) - { - if ([self isAtEnd] == NO) - { - var c = [[self string] characterAtIndex:[self scanLocation]]; - - switch (c) - { - case '%':// '%%' is treated as '%' - location = [self scanLocation]; - break; - case 'K': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForKeyPath:[self nextArg]]; - case '@': - case 'c': - case 'C': - case 'd': - case 'D': - case 'i': - case 'o': - case 'O': - case 'u': - case 'U': - case 'x': - case 'X': - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - case 'h': - [self scanString:@"h" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - case 'q': - [self scanString:@"q" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u' || c == 'x' || c == 'X') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - } - } - - [self setScanLocation:location]; - } - - if ([self scanString:@"\"" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // done with suffixes - return left; - } - } -} - -- (CPExpression)parsePowerExpression -{ - var left = [self parseFunctionalExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"**" intoString:NULL]) - { - right = [self parseFunctionalExpression]; - left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseAdditionExpression -{ - var left = [self parseMultiplicationExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"+" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseBinaryExpression -{ - var left = [self parseAdditionExpression]; - - while (YES) - { - var right; - - if ([self scanString:@":=" intoString:NULL]) // assignment - { - // check left to be a variable? - right = [self parseAdditionExpression]; - // FIXME - } - else - { - return left; - } - } -} - -@end - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.j" - -/*! - @ingroup foundation - @class CPPredicate - @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. - - You use predicates to represent logical conditions, used for describing objects in persistent stores - and in-memory filtering of objects. Although it is common to create predicates directly from instances - of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a - format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: - - - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - You can create predicates for operations, such as @sum.items.price < 1000. - - You can also create predicates that include variables, so that the predicate can be pre-defined before - substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. -*/ - -@implementation CPPredicate : CPObject -{ -} - -/*! - Creates and returns a new predicate formed by creating a new string with a given format and parsing the result. - @param format The format string for the new predicate. - @param … A comma-separated list of arguments to substitute into format. - @return A new predicate formed by creating a new string with format and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format, ... -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var args = Array.prototype.slice.call(arguments, 3); - return [self predicateWithFormat:arguments[2] argumentArray:args]; -} - -/*! - Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result. - @param format The format string for the new predicate. - @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array. - @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args -{ - if (!format) - [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"]; - - var s = [[CPPredicateScanner alloc] initWithString:format args:args], - p = [s parse]; - - return p; -} - -/*! - Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result. - @param format The format string for the new predicate. - @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list. - @return A new predicate by substituting the values in argList into predicateFormat and parsing the result. -*/ -+ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList -{ - // UNIMPLEMENTED - return nil; -} - -/*! - Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return A copy of the receiver with the receiver’s variables substituted by values specified in variables. -*/ -- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Creates and returns a predicate that always evaluates to a given value. - @param value The value to which the new predicate should evaluate. - @return A predicate that always evaluates to value. -*/ -+ (CPPredicate)predicateWithValue:(BOOL)value -{ - return [[CPPredicate_BOOL alloc] initWithBool:value]; -} - -// Evaluating a Predicate -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver. - @param object The object against which to evaluate the receiver. - @return YES if object matches the conditions specified by the receiver, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object -{ - // IMPLEMENTED BY SUBCLASSES -} - -/*! - Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary. - @param object The object against which to evaluate the receiver. - @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver. - @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO. -*/ -- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables -{ - // IMPLEMENTED BY SUBCLASSES -} - -// Getting Format Information -/*! - Returns the receiver’s format string. - @return The receiver’s format string. -*/ -- (CPString)predicateFormat -{ - // IMPLEMENTED BY SUBCLASSES -} - -- (CPString)description -{ - return [self predicateFormat]; -} - -@end - -@implementation CPPredicate_BOOL : CPPredicate -{ - BOOL _value; -} - -- (id)initWithBool:(BOOL)value -{ - _value = value; - return self; -} - -- (BOOL)evaluateObject:(id)object -{ - return _value; -} - -- (CPString)predicateFormat -{ - return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE"; -} - -@end - - -@implementation CPArray (CPPredicate) - -- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPArray array], - i = 0; - - for (; i < count; i++) - { - var object = self[i]; - if ([predicate evaluateWithObject:object]) - result.push(object); - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (count--) - { - if (![predicate evaluateWithObject:self[count]]) - splice(count, 1); - } -} - -@end - -@implementation CPSet (CPPredicate) - -- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate -{ - var count = [self count], - result = [CPSet set], - i = 0; - - for (; i < count; i++) - { - var object = [self objectAtIndex:i]; - - if ([predicate evaluateWithObject:object]) - [result addObject:object]; - } - - return result; -} - -- (void)filterUsingPredicate:(CPPredicate)predicate -{ - var count = [self count]; - - while (--count >= 0) - { - var object = [self objectAtIndex:count]; - - if (![predicate evaluateWithObject:object]) - [self removeObjectAtIndex:count]; - } -} - -@end - -#define REFERENCE(variable) \ -function(newValue)\ -{\ - var oldValue = variable;\ - if (typeof newValue != 'undefined')\ - variable = newValue;\ - return oldValue;\ -} - -@implementation CPPredicateScanner : CPScanner -{ - CPEnumerator _args; - unsigned _retrieved; -} - -- (id)initWithString:(CPString)format args:(CPArray)args -{ - self = [super initWithString:format] - - if (self) - { - _args = [args objectEnumerator]; - } - return self; -} - -- (id)nextArg -{ - return [_args nextObject]; -} - -- (BOOL)scanPredicateKeyword:(CPString)key -{ - var loc = [self scanLocation]; - - [self setCaseSensitive:NO]; - if (![self scanString:key intoString:NULL]) - return NO; - - if ([self isAtEnd]) - return YES; - - var c = [[self string] characterAtIndex:[self scanLocation]]; - if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c]) - return YES; - - [self setScanLocation:loc]; - - return NO; -} - -- (CPPredicate)parse -{ - var r = nil; - - try - { - [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]]; - r = [self parsePredicate]; - } - catch(error) - { - CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error); - } - finally - { - if (![self isAtEnd]) - { - var pstr = [self string], - loc = [self scanLocation]; - CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'"); - } - } - - return r; -} - -- (CPPredicate)parsePredicate -{ - return [self parseAnd]; -} - -- (CPPredicate)parseAnd -{ - var l = [self parseOr]; - - while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"]) - { - var r = [self parseOr]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseNot -{ - if ([self scanString:@"(" intoString:NULL]) - { - var r = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"predicate"); - - return r; - } - - if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"]) - { - return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]]; - } - if ([self scanPredicateKeyword:@"TRUEPREDICATE"]) - { - return [CPPredicate predicateWithValue:YES]; - } - if ([self scanPredicateKeyword:@"FALSEPREDICATE"]) - { - return [CPPredicate predicateWithValue:NO]; - } - - return [self parseComparison]; -} - -- (CPPredicate)parseOr -{ - var l = [self parseNot]; - while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"]) - { - var r = [self parseNot]; - - if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType) - { - if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObjectsFromArray:[r subpredicates]]; - } - else - { - [[r subpredicates] insertObject:l atIndex:0]; - l = r; - } - } - else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType) - { - [[l subpredicates] addObject:r]; - } - else - { - l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]]; - } - } - return l; -} - -- (CPPredicate)parseComparison -{ - var modifier = CPDirectPredicateModifier, - type = 0, - opts = 0, - left, - right, - p, - negate = NO; - - if ([self scanPredicateKeyword:@"ANY"]) - { - modifier = CPAnyPredicateModifier; - } - else if ([self scanPredicateKeyword:@"ALL"]) - { - modifier = CPAllPredicateModifier; - } - else if ([self scanPredicateKeyword:@"NONE"]) - { - modifier = CPAnyPredicateModifier; - negate = YES; - } - else if ([self scanPredicateKeyword:@"SOME"]) - { - modifier = CPAllPredicateModifier; - negate = YES; - } - - left = [self parseExpression]; - if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL]) - { - type = CPNotEqualToPredicateOperatorType; - } - else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL]) - { - type = CPLessThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL]) - { - type = CPGreaterThanOrEqualToPredicateOperatorType; - } - else if ([self scanString:@"<" intoString:NULL]) - { - type = CPLessThanPredicateOperatorType; - } - else if ([self scanString:@">" intoString:NULL]) - { - type = CPGreaterThanPredicateOperatorType; - } - else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL]) - { - type = CPEqualToPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"MATCHES"]) - { - type = CPMatchesPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"LIKE"]) - { - type = CPLikePredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BEGINSWITH"]) - { - type = CPBeginsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"ENDSWITH"]) - { - type = CPEndsWithPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"IN"]) - { - type = CPInPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"CONTAINS"]) - { - type = CPContainsPredicateOperatorType; - } - else if ([self scanPredicateKeyword:@"BETWEEN"]) - { - type = CPBetweenPredicateOperatorType; - } - else - CPRaiseParseError(self, @"comparison predicate"); - - if ([self scanString:@"[cd]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption; - } - else if ([self scanString:@"[c]" intoString:NULL]) - { - opts = CPCaseInsensitivePredicateOption; - } - else if ([self scanString:@"[d]" intoString:NULL]) - { - opts = CPDiacriticInsensitivePredicateOption; - } - - right = [self parseExpression]; - - p = [CPComparisonPredicate predicateWithLeftExpression:left - rightExpression:right - modifier:modifier - type:type - options:opts]; - - return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p; -} - -- (CPExpression)parseExpression -{ - return [self parseBinaryExpression]; -} - -- (CPExpression)parseSimpleExpression -{ - var identifier, - location, - ident, - dbl; - - if ([self scanDouble:REFERENCE(dbl)]) - return [CPExpression expressionForConstantValue:dbl]; - - // FIXME: handle integer, hex constants, 0x 0o 0b - if ([self scanString:@"-" intoString:NULL]) - return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]]; - - if ([self scanString:@"(" intoString:NULL]) - { - var arg = [self parseExpression]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return arg; - } - - if ([self scanString:@"{" intoString:NULL]) - { - if ([self scanString:@"}" intoString:NULL]) - return [CPExpression expressionForConstantValue:a]; - - var a = []; - - [a addObject:[self parseExpression]]; - - while ([self scanString:@"," intoString:NULL]) - [a addObject:[self parseExpression]]; - - if (![self scanString:@"}" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForAggregate:a]; - } - - if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"]) - { - return [CPExpression expressionForConstantValue:[CPNull null]]; - } - if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; - } - if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) - { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; - } - if ([self scanPredicateKeyword:@"SELF"]) - { - return [CPExpression expressionForEvaluatedObject]; - } - - if ([self scanString:@"$" intoString:NULL]) - { - var variable = [self parseSimpleExpression]; - - if (![variable keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForVariable:variable]; - } - - location = [self scanLocation]; - - if ([self scanString:@"%" intoString:NULL]) - { - if ([self isAtEnd] == NO) - { - var c = [[self string] characterAtIndex:[self scanLocation]]; - - switch (c) - { - case '%':// '%%' is treated as '%' - location = [self scanLocation]; - break; - case 'K': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForKeyPath:[self nextArg]]; - case '@': - case 'c': - case 'C': - case 'd': - case 'D': - case 'i': - case 'o': - case 'O': - case 'u': - case 'U': - case 'x': - case 'X': - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - case 'h': - [self scanString:@"h" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - case 'q': - [self scanString:@"q" intoString:NULL]; - if ([self isAtEnd] == NO) - { - c = [[self string] characterAtIndex:[self scanLocation]]; - if (c == 'i' || c == 'u' || c == 'x' || c == 'X') - { - [self setScanLocation:[self scanLocation] + 1]; - return [CPExpression expressionForConstantValue:[self nextArg]]; - } - } - break; - } - } - - [self setScanLocation:location]; - } - - if ([self scanString:@"\"" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"\"" intoString:REFERENCE(str)]; - - if ([self scanString:@"\"" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"'" intoString:NULL]) - { - var skip = [self charactersToBeSkipped], - str = @""; - - [self setCharactersToBeSkipped:nil]; - [self scanUpToString:@"'" intoString:REFERENCE(str)]; - - if ([self scanString:@"'" intoString:NULL] == NO) - CPRaiseParseError(self, @"expression"); - - [self setCharactersToBeSkipped:skip]; - - return [CPExpression expressionForConstantValue:str]; - } - - if ([self scanString:@"@" intoString:NULL]) - { - var e = [self parseExpression]; - - if (![e keyPath]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:[e keyPath] + "@"]; - } - - if ([self scanString:@"SUBQUERY" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var collection = [self parseExpression], - variableExpression, - subpredicate; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - variableExpression = [self parseExpression]; - - if (![self scanString:@"," intoString:NULL]) - CPRaiseParseError(self, @"expression"); - subpredicate = [self parsePredicate]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - return [[CPExpression_subquery alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; - } - - if ([self scanString:@"FUNCTION" intoString:NULL]) - { - if (![self scanString:@"(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - var args = [CPArray arrayWithObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)]; - } - - [self scanString:@"#" intoString:NULL]; - if (!identifier) - identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; - - if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)]) - CPRaiseParseError(self, @"expression"); - - return [CPExpression expressionForKeyPath:ident]; -} - -- (CPExpression)parseFunctionalExpression -{ - var left = [self parseSimpleExpression]; - - while (YES) - { - if ([self scanString:@"." intoString:NULL]) - { - var right = [self parseSimpleExpression], - expressionType = [right expressionType]; - - if (expressionType == CPKeyPathExpressionType) - left = [[CPExpression_keypath alloc] initWithOperand:left andKeyPath:[right keyPath]]; - else if (expressionType == CPVariableExpressionType) - left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]]; - else - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@"[" intoString:NULL]) - { - // index expression - if ([self scanPredicateKeyword:@"FIRST"]) - { - left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"LAST"]) - { - left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]]; - } - else if ([self scanPredicateKeyword:@"SIZE"]) - { - left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]]; - } - else - { - var index = [self parseExpression]; - left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]]; - } - - if (![self scanString:@"]" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - else if ([self scanString:@":" intoString:NULL]) - { - // function - this parser allows for (max)(a, b, c) to be properly - // recognized and even (%K)(a, b, c) if %K evaluates to "max" - - if (![left keyPath]) - CPRaiseParseError(self, @"expression"); - - var selector = [left keyPath] + @":", - args = []; - - if (![self scanString:@"(" intoString:NULL]) - { - var str; - [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)]; - - if (![self scanString:@":(" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - - selector += str + @":"; - } - - if (![self scanString:@")" intoString:NULL]) - { - [args addObject:[self parseExpression]]; - while ([self scanString:@"," intoString:NULL]) - [args addObject:[self parseExpression]]; - - if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); - } - - left = [CPExpression expressionForFunction:selector arguments:args]; - } - else if ([self scanString:@"UNION" intoString:NULL]) - { - left = [CPExpression expressionForUnionSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"INTERSECT" intoString:NULL]) - { - left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]]; - } - else if ([self scanString:@"MINUS" intoString:NULL]) - { - left = [CPExpression expressionForMinusSet:left with:[self parseExpression]]; - } - else - { - // done with suffixes - return left; - } - } -} - -- (CPExpression)parsePowerExpression -{ - var left = [self parseFunctionalExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"**" intoString:NULL]) - { - right = [self parseFunctionalExpression]; - left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseMultiplicationExpression -{ - var left = [self parsePowerExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"*" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"/" intoString:NULL]) - { - right = [self parsePowerExpression]; - left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseAdditionExpression -{ - var left = [self parseMultiplicationExpression]; - - while (YES) - { - var right; - - if ([self scanString:@"+" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else if ([self scanString:@"-" intoString:NULL]) - { - right = [self parseMultiplicationExpression]; - left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]]; - } - else - { - return left; - } - } -} - -- (CPExpression)parseBinaryExpression -{ - var left = [self parseAdditionExpression]; - - while (YES) - { - var right; - - if ([self scanString:@":=" intoString:NULL]) // assignment - { - // check left to be a variable? - right = [self parseAdditionExpression]; - // FIXME - } - else - { - return left; - } - } -} - -@end - -var CPRaiseParseError = function CPRaiseParseError(aScanner, target) -{ - [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; -} - -@import "CPCompoundPredicate.j" -@import "CPComparisonPredicate.j" -@import "CPExpression.j" -/* - * CPPredicate.j - * - * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/) - * Copyright (c) 2005 Free Software Foundation. - * - * Created by cacaodev. - * Copyright 2010. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import "CPArray.j" -@import "CPException.j" -@import "CPNull.j" -@import "CPObject.j" -@import "CPScanner.j" -@import "CPSet.j" -@import "CPValue.j" -@import "CPCharacterSet.j" - -/*! - @ingroup foundation - @class CPPredicate - @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. - - You use predicates to represent logical conditions, used for describing objects in persistent stores - and in-memory filtering of objects. Although it is common to create predicates directly from instances - of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a - format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include: - - - Simple comparisons, such as grade == "7" or firstName like "Shaffiq" - - Case/diacritic insensitive lookups, such as name contains[cd] "itroen" - - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley") - - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}. - - You can create predicates for relationships, such as: - - - group.name like "work*" - - ALL children.age > 12 - - ANY children.age > 12 - - You can create predicates for operations, such as @sum.items.price < 1000. + You can create predicates for operations, such as \@sum.items.price < 1000. You can also create predicates that include variables, so that the predicate can be pre-defined before substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. From 3937e66e06a5d970ec0be5b7a48da1791b241435 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Thu, 19 May 2011 16:46:41 -0700 Subject: [PATCH 6/7] Major fix to documentation generation: - It turns out doxygen would not generate documentation for the first method in a file (or sometimes any methods at all) if there was no ivar block or an empty ivar block. Now a dummy __doxygen__ ivar is inserted in empty blocks to force correct behavior. - Added a missing @endverbatim to CPExpression.j. - Tweaked class documentation for CPPredicate.j. --- Foundation/CPPredicate/CPExpression.j | 22 ++- Foundation/CPPredicate/CPPredicate.j | 2 +- .../preprocess/003.make_headers.rb | 162 ++++++++++-------- Tools/Documentation/support/massage_text.py | 2 + 4 files changed, 111 insertions(+), 77 deletions(-) diff --git a/Foundation/CPPredicate/CPExpression.j b/Foundation/CPPredicate/CPExpression.j index 9767933ae..d8f4edfcd 100644 --- a/Foundation/CPPredicate/CPExpression.j +++ b/Foundation/CPPredicate/CPExpression.j @@ -71,9 +71,12 @@ CPMinusSetExpressionType = 9; @class CPExpression @brief CPExpression is used to represent expressions in a predicate. - Comparison operations in an CPPredicate are based on two expressions, as represented by instances of the CPExpression class. Expressions are created for constant values, key paths, and so on. + Comparison operations in an CPPredicate are based on two expressions, as represented by instances of the CPExpression class. + Expressions are created for constant values, key paths, and so on. - Generally, anywhere in the CPExpression class hierarchy where there is composite API and subtypes that may only reasonably respond to a subset of that API, invoking a method that does not make sense for that subtype will cause an exception to be thrown. + Generally, anywhere in the CPExpression class hierarchy where there is composite API and subtypes + that may only reasonably respond to a subset of that API, invoking a method that does not make sense + for that subtype will cause an exception to be thrown. */ @implementation CPExpression : CPObject @@ -182,12 +185,15 @@ CPMinusSetExpressionType = 9; Returns a new expression that will invoke one of the predefined functions. @param function_name The name of the function to invoke. @param parameters An array containing CPExpression objects that will be used as parameters during the invocation of selector. - - For a selector taking no parameters, the array should be empty. For a selector taking one or more parameters, the array should contain one CPExpression object which will evaluate to an instance of the appropriate type for each parameter. - - If there is a mismatch between the number of parameters expected and the number you provide during evaluation, an exception may be raised or missing parameters may simply be replaced by nil (which occurs depends on how many parameters are provided, and whether you have over- or underflow). @return A new expression that invokes the function name using the parameters in parameters. + For a selector taking no parameters, the array should be empty. For a selector taking one or more parameters, + the array should contain one CPExpression object which will evaluate to an instance of the appropriate type for each parameter. + + If there is a mismatch between the number of parameters expected and the number you provide during evaluation, + an exception may be raised or missing parameters may simply be replaced by nil (which occurs depends on how many + parameters are provided, and whether you have over- or underflow). + The name parameter can be one of the following predefined functions: @verbatim name parameter array contents returns @@ -220,6 +226,7 @@ CPMinusSetExpressionType = 9; now: none [CPDate now] This method raises an exception immediately if the selector is invalid; it raises an exception at runtime if the parameters are incorrect. +@endverbatim */ + (CPExpression)expressionForFunction:(CPString)function_name arguments:(CPArray)parameters { @@ -232,7 +239,8 @@ CPMinusSetExpressionType = 9; @param selectorName The name of the method to be invoked. @param parameters An array containing CPExpression objects which can be evaluated to provide parameters for the method specified by name. @return An expression which will return the result of invoking the selector named name on the result of evaluating the target expression with the parameters specified by evaluating the elements of parameters. - See the description of expressionForFunction:arguments: for examples of how to construct the parameter array. + + See the description of \c expressionForFunction:arguments: for examples of how to construct the parameter array. */ + (CPExpression)expressionForFunction:(CPExpression)target selectorName:(CPString)selectorName arguments:(CPArray)parameters { diff --git a/Foundation/CPPredicate/CPPredicate.j b/Foundation/CPPredicate/CPPredicate.j index 1e99887ec..b12a2a62f 100644 --- a/Foundation/CPPredicate/CPPredicate.j +++ b/Foundation/CPPredicate/CPPredicate.j @@ -55,7 +55,7 @@ You can create predicates for operations, such as \@sum.items.price < 1000. You can also create predicates that include variables, so that the predicate can be pre-defined before - substituting concrete values at runtime with the evaluateWithObject:substitutionVariables: method. + substituting concrete values at runtime with the \c evaluateWithObject:substitutionVariables: method. */ @implementation CPPredicate : CPObject diff --git a/Tools/Documentation/preprocess/003.make_headers.rb b/Tools/Documentation/preprocess/003.make_headers.rb index 327df1ea0..777e0772f 100755 --- a/Tools/Documentation/preprocess/003.make_headers.rb +++ b/Tools/Documentation/preprocess/003.make_headers.rb @@ -7,7 +7,7 @@ ACCESSOR_GET_TEMPLATE = < \[implementation\]"), " ", re.compile(r"Static Public Member Functions"), "Class Methods", re.compile(r"Public Member Functions"), "Instance Methods", + re.compile(r"Protected Attributes"), "Instance Variables", re.compile(r"Member Function Documentation"), "Method Documentation", + re.compile(r"Member Data Documentation"), "Instance Variable Documentation", re.compile(r"(AppKit|Foundation)\.doc"), r"\1", re.compile(r"\s*\n(\s*\n){2}\s*(){2} \n\s*"), "" ] From aa7345935de4ffcd572560676b0d99ba55954fa9 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Thu, 19 May 2011 17:10:26 -0700 Subject: [PATCH 7/7] Oops, accessor methods weren't being synthesized, removed some cruft --- .../preprocess/003.make_headers.rb | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Tools/Documentation/preprocess/003.make_headers.rb b/Tools/Documentation/preprocess/003.make_headers.rb index 777e0772f..8b0cdf93b 100755 --- a/Tools/Documentation/preprocess/003.make_headers.rb +++ b/Tools/Documentation/preprocess/003.make_headers.rb @@ -16,8 +16,6 @@ Synthesized accessor method. EOS -ACCESSOR_GET_INTERFACE_TEMPLATE = "- (\#{ivarType})\#{getter}" - ACCESSOR_SET_TEMPLATE = <