diff --git a/Foundation/CPPredicate/CPExpression.j b/Foundation/CPPredicate/CPExpression.j index 06ed6b0ba..10ca2b598 100644 --- a/Foundation/CPPredicate/CPExpression.j +++ b/Foundation/CPPredicate/CPExpression.j @@ -28,3 +28,5 @@ @import "_CPAggregateExpression.j" @import "_CPSetExpression.j" @import "_CPSubqueryExpression.j" +@import "_CPBlockExpression.j" +@import "_CPConditionalExpression.j" diff --git a/Foundation/CPPredicate/_CPAggregateExpression.j b/Foundation/CPPredicate/_CPAggregateExpression.j index 688166398..2216c959c 100644 --- a/Foundation/CPPredicate/_CPAggregateExpression.j +++ b/Foundation/CPPredicate/_CPAggregateExpression.j @@ -25,7 +25,7 @@ @implementation _CPAggregateExpression : CPExpression { - CPArray _aggregate; + CPArray _aggregate @accessors(getter=collection); } - (id)initWithAggregate:(CPArray)collection @@ -48,11 +48,6 @@ return YES; } -- (id)collection -{ - return _aggregate; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { var eval_array = [CPArray array], diff --git a/Foundation/CPPredicate/_CPBlockExpression.j b/Foundation/CPPredicate/_CPBlockExpression.j new file mode 100644 index 000000000..299f250ee --- /dev/null +++ b/Foundation/CPPredicate/_CPBlockExpression.j @@ -0,0 +1,83 @@ +/* + * _CPBlockExpression.j + * + * Created by cacaodev. + * Copyright 2015. + * + * 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 "_CPExpression.j" + +@implementation _CPBlockExpression : CPExpression +{ + Function _block @accessors(getter=expressionBlock); + CPArray _arguments @accessors(getter=arguments); +} + +- (id)initWithBlock:(Function)aBlock arguments:(CPArray)arguments +{ + self = [super initWithExpressionType:CPBlockExpressionType]; + + if (self) + { + _block = aBlock; + _arguments = arguments; + } + + return self; +} + +- (BOOL)isEqual:(id)object +{ + if (self === object) + return YES; + + if (object === nil || object.isa !== self.isa || [object expressionBlock] !== _block || ![[object arguments] isEqual:_arguments]) + return NO; + + return YES; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var args = []; + + [_arguments enumerateObjectsUsingBlock:function(exp, idx) + { + [args addObject:[exp expressionValueWithObject:object context:context]]; + }]; + + return _block(object, args, context); +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)bindings +{ + var args = []; + + [_arguments enumerateObjectsUsingBlock:function(exp, idx) + { + [args addObject:[exp _expressionWithSubstitutionVariables:bindings]]; + }]; + + return [[_CPBlockExpression alloc] initWithBlock:_block arguments:args]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"Block(function, %@)", [_arguments description]]; +} + +@end \ No newline at end of file diff --git a/Foundation/CPPredicate/_CPConditionalExpression.j b/Foundation/CPPredicate/_CPConditionalExpression.j new file mode 100644 index 000000000..8fc544580 --- /dev/null +++ b/Foundation/CPPredicate/_CPConditionalExpression.j @@ -0,0 +1,78 @@ +/* + * _CPConditionalExpression.j + * + * Created by cacaodev. + * Copyright 2015. + * + * 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 "_CPPredicate.j" +@import "_CPExpression.j" + +@implementation _CPConditionalExpression : CPExpression +{ + CPPredicate _predicate @accessors(getter=predicate); + CPExpression _trueExpression @accessors(getter=trueExpression); + CPExpression _falseExpression @accessors(getter=falseExpression); +} + +- (id)initWithPredicate:(CPPredicate)aPredicate trueExpression:(CPExpression)trueExpression falseExpression:(CPExpression)falseExpression +{ + self = [super initWithExpressionType:CPConditionalExpressionType]; + + if (self) + { + _predicate = aPredicate; + _trueExpression = trueExpression; + _falseExpression = falseExpression; + } + + return self; +} + +- (BOOL)isEqual:(id)object +{ + if (self === object) + return YES; + + if (object === nil || object.isa !== self.isa || ![[object predicate] isEqual:_predicate] || ![[object trueExpression] isEqual:_trueExpression] || ![[object falseExpression] isEqual:_falseExpression]) + return NO; + + return YES; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var eval = [_predicate evaluateWithObject:object substitutionVariables:context], + exp = eval ? _trueExpression : _falseExpression; + + return [exp expressionValueWithObject:object context:context]; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)bindings +{ + var predicate = [_predicate predicateWithSubstitutionVariables:bindings], + trueExp = [_trueExpression _expressionWithSubstitutionVariables:bindings], + falseExp = [_falseExpression _expressionWithSubstitutionVariables:bindings]; + + return [[_CPConditionalExpression alloc] initWithPredicate:predicate trueExpression:trueExp falseExpression:falseExp]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"TERNARY(%@,%@,%@)", [_predicate predicateFormat], [_trueExpression description], [_falseExpression description]]; +} + +@end \ No newline at end of file diff --git a/Foundation/CPPredicate/_CPConstantValueExpression.j b/Foundation/CPPredicate/_CPConstantValueExpression.j index 397ce6371..1c1bbbce9 100644 --- a/Foundation/CPPredicate/_CPConstantValueExpression.j +++ b/Foundation/CPPredicate/_CPConstantValueExpression.j @@ -27,7 +27,7 @@ @implementation _CPConstantValueExpression : CPExpression { - id _value; + id _value @accessors(getter=constantValue); } - (id)initWithValue:(id)value @@ -51,11 +51,6 @@ return YES; } -- (id)constantValue -{ - return _value; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { return _value; diff --git a/Foundation/CPPredicate/_CPExpression.j b/Foundation/CPPredicate/_CPExpression.j index 5a9561cb7..8a671c5b2 100644 --- a/Foundation/CPPredicate/_CPExpression.j +++ b/Foundation/CPPredicate/_CPExpression.j @@ -65,6 +65,14 @@ CPIntersectSetExpressionType = 8; An expression that combines two nested expression results by set subtraction. */ CPMinusSetExpressionType = 9; +/*! + An expression that returns the result of evaluating a block. +*/ +CPBlockExpressionType = 10; +/*! + An expression that returns an expression that depends on the evaluation of a predicate. +*/ +CPConditionalExpressionType = 11; /*! @ingroup foundation @@ -259,6 +267,32 @@ CPMinusSetExpressionType = 9; return [[_CPSubqueryExpression alloc] initWithExpression:expression usingIteratorVariable:variable predicate:predicate]; } +/*! + Returns Creates an NSExpression object that will use the Block for evaluating objects. + @param aBlock The Block is applied to the object to be evaluated. + +The Block takes three arguments and returns a value: + +evaluatedObject +The object to be evaluated. +expressions +An array of predicate expressions that evaluates to a collection. +context +A dictionary that the expression can use to store temporary state for one predicate evaluation. + +@discussion Note that context is mutable, and that it can only be accessed during the evaluation of the expression. +@param arguments An array containing NSExpression objects that will be used as parameters during the invocation of the block. +*/ ++ (CPExpression)expressionForBlock:(Function)aBlock arguments:(CPArray)args +{ + return [[_CPBlockExpression alloc] initWithBlock:aBlock arguments:args]; +} + ++ (CPExpression)expressionForConditional:(CPPredicate)aPredicate trueExpression:(CPExpression)trueExpression falseExpression:(CPExpression)falseExpression +{ + return [[_CPConditionalExpression alloc] initWithPredicate:aPredicate trueExpression:trueExpression falseExpression:falseExpression]; +} + // Getting Information About an Expression /*! Returns the expression type for the receiver. @@ -316,7 +350,7 @@ CPMinusSetExpressionType = 9; /*! Returns the arguments for the receiver. - @return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression. + @return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression or as a parameter of the block of a block expression. This method raises an exception if it is not applicable to the receiver. */ - (CPArray)arguments @@ -337,8 +371,8 @@ CPMinusSetExpressionType = 9; } /*! - Returns the predicate in a subquery expression. - @return The predicate in a subquery expression.. + Returns the predicate in a subquery expression or a conditional expression. + @return The predicate in a subquery expression or a conditional expression. This method raises an exception if it is not applicable to the receiver. */ - (CPPredicate)predicate @@ -380,6 +414,39 @@ CPMinusSetExpressionType = 9; return nil; } +/*! + Returns the block of a block expression. + @return The block of a block expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (Function)expressionBlock +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + +/*! + Returns the true expression of a conditional expression. + @return The true expression of a conditional expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (CPExpression)trueExpression +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + +/*! + Returns the false expression of a conditional expression. + @return The false expression of a conditional expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (CPExpression)falseExpression +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + - (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables { return self; diff --git a/Foundation/CPPredicate/_CPFunctionExpression.j b/Foundation/CPPredicate/_CPFunctionExpression.j index 394c93612..32790d4e4 100644 --- a/Foundation/CPPredicate/_CPFunctionExpression.j +++ b/Foundation/CPPredicate/_CPFunctionExpression.j @@ -28,9 +28,9 @@ @implementation _CPFunctionExpression : CPExpression { - CPExpression _operand; + CPExpression _operand @accessors(getter=operand); SEL _selector; - CPArray _arguments; + CPArray _arguments @accessors(getter=arguments); int _argc; int _maxargs; } @@ -88,16 +88,6 @@ return [self _function]; } -- (CPArray)arguments -{ - return _arguments; -} - -- (CPExpression)operand -{ - return _operand; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { var target = [_operand expressionValueWithObject:object context:context], diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j index 56bfcd643..34001c415 100644 --- a/Foundation/CPPredicate/_CPPredicate.j +++ b/Foundation/CPPredicate/_CPPredicate.j @@ -744,16 +744,43 @@ 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, @"predicate"); + + return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + } + + if ([self scanString:@"TERNARY" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var predicate = [self parsePredicate], + trueExpression, + falseExpression; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + trueExpression = [self parseExpression]; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + falseExpression = [self parseExpression]; + if (![self scanString:@")" intoString:NULL]) CPRaiseParseError(self, @"expression"); - return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + return [CPExpression expressionForConditional:predicate trueExpression:trueExpression falseExpression:falseExpression]; } if ([self scanString:@"FUNCTION" intoString:NULL]) diff --git a/Foundation/CPPredicate/_CPSetExpression.j b/Foundation/CPPredicate/_CPSetExpression.j index a0e9df3bb..707486b32 100644 --- a/Foundation/CPPredicate/_CPSetExpression.j +++ b/Foundation/CPPredicate/_CPSetExpression.j @@ -25,8 +25,8 @@ @implementation _CPSetExpression : CPExpression { - CPExpression _left; - CPExpression _right; + CPExpression _left @accessors(getter=leftExpression); + CPExpression _right @accessors(getter=rightExpression); } - (id)initWithType:(int)type left:(CPExpression)left right:(CPExpression)right @@ -88,16 +88,6 @@ return self; } -- (CPExpression)leftExpression -{ - return _left; -} - -- (CPExpression)rightExpression -{ - return _right; -} - - (CPString)description { var desc; diff --git a/Foundation/CPPredicate/_CPSubqueryExpression.j b/Foundation/CPPredicate/_CPSubqueryExpression.j index e8f648403..d69483d2c 100644 --- a/Foundation/CPPredicate/_CPSubqueryExpression.j +++ b/Foundation/CPPredicate/_CPSubqueryExpression.j @@ -26,9 +26,9 @@ @implementation _CPSubqueryExpression : CPExpression { - CPExpression _collection; + CPExpression _collection @accessors(getter=collection); CPExpression _variableExpression; - CPPredicate _subpredicate; + CPPredicate _subpredicate @accessors(getter=predicate); } - (id)initWithExpression:(CPExpression)collection usingIteratorVariable:(CPString)variable predicate:(CPPredicate)subpredicate @@ -81,21 +81,11 @@ return YES; } -- (CPExpression)collection -{ - return _collection; -} - - (id)copy { return [[_CPSubqueryExpression alloc] initWithExpression:[_collection copy] usingIteratorExpression:[_variableExpression copy] predicate:[_subpredicate copy]]; } -- (CPPredicate)predicate -{ - return _subpredicate; -} - - (CPString)description { return [self predicateFormat]; diff --git a/Foundation/CPPredicate/_CPVariableExpression.j b/Foundation/CPPredicate/_CPVariableExpression.j index 910ef23d5..a33911222 100644 --- a/Foundation/CPPredicate/_CPVariableExpression.j +++ b/Foundation/CPPredicate/_CPVariableExpression.j @@ -29,7 +29,7 @@ @implementation _CPVariableExpression : CPExpression { - CPString _variable; + CPString _variable @accessors(getter=variable); } - (id)initWithVariable:(CPString)variable @@ -54,11 +54,6 @@ return YES; } -- (CPString)variable -{ - return _variable; -} - - (id)expressionValueWithObject:object context:(CPDictionary)context { var expression = [self _expressionWithSubstitutionVariables:context]; diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j index ff89760c0..3a9840e96 100644 --- a/Tests/Foundation/CPPredicateTest.j +++ b/Tests/Foundation/CPPredicateTest.j @@ -79,6 +79,17 @@ var expression_minusset = [CPExpression expressionForMinusSet:set with:array]; [self assertNotNull:expression_minusset message:"MinusSet Expression should not be nil"]; + + var expression_block = [CPExpression expressionForBlock:function(obj, args, bindings) + { + return obj; + } arguments:nil]; + + [self assertNotNull:expression_block message:"Block Expression should not be nil"]; + + var expression_conditional = [CPExpression expressionForConditional:[CPPredicate predicateWithValue:YES] trueExpression:expression_minusset falseExpression:expression_block]; + + [self assertNotNull:expression_conditional message:"Conditional Expression should not be nil"]; } - (void)testSetExpressionEvaluation @@ -147,6 +158,27 @@ [self assertTrue:([eval isEqual:expected]) message:"'" + [expression predicateFormat] + "' result is "+ eval + "but should be " + expected]; } +- (void)testBlockExpressionEvaluation +{ + var block = function(obj, args, bindings) + { + return [obj stringByAppendingString:[args componentsJoinedByString:"-"]]; + }; + + var expression = [CPExpression expressionForBlock:block arguments:[[CPExpression expressionForConstantValue:"A"], [CPExpression expressionForConstantValue:"B"]]]; + + var eval = [expression expressionValueWithObject:"OBJ" context:@{}]; + [self assertTrue:([eval isEqual:"OBJA-B"]) message:"'" + [expression description] + "' result is "+ eval + "but should be " + "OBJA-B"]; +} + +- (void)testConditionalExpressionEvaluation +{ + var expression = [CPExpression expressionForConditional:[CPPredicate predicateWithFormat:"SELF = $variable"] trueExpression:[CPExpression expressionForConstantValue:"TRUE_EXP"] falseExpression:[CPExpression expressionForConstantValue:"FALSE_EXP"]]; + + var eval = [expression expressionValueWithObject:"OBJ" context:@{"variable":"OBJ"}]; + [self assertTrue:([eval isEqual:"TRUE_EXP"]) message:"'" + [expression description] + "' result is "+ eval + "but should be " + "TRUE_EXP"]; +} + - (void)testOptions { var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"àa"] rightExpression:[CPExpression expressionForConstantValue:@"aà"] modifier:CPDirectPredicateModifier type:CPLikePredicateOperatorType options:3]; @@ -381,6 +413,9 @@ predicate = [CPPredicate predicateWithFormat:@"a UNION {'b'} = {'a','b'}"]; var left = [[predicate leftExpression] expressionValueWithObject:object context:nil]; [self assertTrue:[left isEqualToSet:result] message:"Expression eval " + left + " should be " + result]; + + predicate = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[SIZE] = 1, 'Single', Record1.Name) = 'John'"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:"Predicate " + predicate + " should evaluate to TRUE"]; } - (void)testExpressionAndPredicateIsEqual @@ -439,6 +474,10 @@ pred1 = [CPPredicate predicateWithFormat:@"a = 'a' AND b = 'b'"]; pred2 = [CPPredicate predicateWithFormat:@"a = 'a' AND b = 'b'"]; [self assert:pred1 equals:pred2]; + + pred1 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + pred2 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + [self assert:pred1 equals:pred2]; } - (void)testExpressionAndPredicateIsNotEqualToNil @@ -490,6 +529,10 @@ [self assert:pred1 notEqual:nil]; + pred1 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + + [self assert:pred1 notEqual:nil]; + pred1 = [CPPredicate predicateWithFormat:@"$x CONTAINS 'a'"]; [self assert:pred1 notEqual:nil];