/* * 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" @import "CPComparisonPredicate_Constants.j" @import "CPCompoundPredicate_Constants.j" @import "_CPExpression.j" @class CPCompoundPredicate @class CPComparisonPredicate @class _CPKeyPathExpression @class _CPSubqueryExpression /*! @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 \c 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)isEqual:(id)anObject { if (self === anObject) return YES; if (anObject === nil || self.isa !== anObject.isa || _value !== [anObject evaluateWithObject:nil]) return NO; return YES; } - (BOOL)evaluateWithObject:(id)object { return _value; } - (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables { 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 objectAtIndex:i]; if ([predicate evaluateWithObject:object]) result.push(object); } return result; } - (void)filterUsingPredicate:(CPPredicate)predicate { var count = [self count]; while (count--) { if (![predicate evaluateWithObject:[self objectAtIndex:count]]) self.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 @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:@ref(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]) { var a = []; if ([self scanString:@"}" intoString:NULL]) return [CPExpression expressionForConstantValue:a]; [a addObject:[self parseExpression]]; while ([self scanString:@"," intoString:NULL]) [a addObject:[self parseExpression]]; if (![self scanString:@"}" intoString:NULL]) 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:@ref(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:@ref(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 [[_CPSubqueryExpression 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:@ref(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 = [[_CPKeyPathExpression 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:@ref(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(aScanner, target) { [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]]; };