From 07b6e2b9f15c7e307434f98a5381f1477ea6c79c Mon Sep 17 00:00:00 2001 From: cacaodev Date: Sun, 27 Dec 2015 15:40:32 +0100 Subject: [PATCH] CPExpression +expressionForConditional:trueExpression:falseExpression: Conditional Expression : an expression using different expressions for evaluation, depending of a predicate result. Support for predicate parsing: ```TERNARY(predicate, trueExpression, falseExpression)``` With init, equal, evaluation, parsing tests. --- Foundation/CPPredicate/CPExpression.j | 1 + .../CPPredicate/_CPConditionalExpression.j | 84 +++++++++++++++++++ Foundation/CPPredicate/_CPExpression.j | 9 ++ Foundation/CPPredicate/_CPPredicate.j | 26 +++++- Tests/Foundation/CPPredicateTest.j | 23 +++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 Foundation/CPPredicate/_CPConditionalExpression.j diff --git a/Foundation/CPPredicate/CPExpression.j b/Foundation/CPPredicate/CPExpression.j index 12ad45a74..10ca2b598 100644 --- a/Foundation/CPPredicate/CPExpression.j +++ b/Foundation/CPPredicate/CPExpression.j @@ -29,3 +29,4 @@ @import "_CPSetExpression.j" @import "_CPSubqueryExpression.j" @import "_CPBlockExpression.j" +@import "_CPConditionalExpression.j" diff --git a/Foundation/CPPredicate/_CPConditionalExpression.j b/Foundation/CPPredicate/_CPConditionalExpression.j new file mode 100644 index 000000000..6e636ec29 --- /dev/null +++ b/Foundation/CPPredicate/_CPConditionalExpression.j @@ -0,0 +1,84 @@ +/* + * _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; + CPExpression _trueExpression; + CPExpression _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; +} + +- (CPPredicate)predicate +{ + return _predicate; +} + +- (CPExpression)trueExpression +{ + return _trueExpression; +} + +- (CPExpression)falseExpression +{ + return _falseExpression; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var eval = [_predicate evaluateWithObject:object substitutionVariables:context]; + var exp = eval ? _trueExpression : _falseExpression; + + return [exp expressionValueWithObject:object context:context]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"TERNARY(%@,%@,%@)", [_predicate predicateFormat], [_trueExpression description], [_falseExpression description]]; +} + +@end \ No newline at end of file diff --git a/Foundation/CPPredicate/_CPExpression.j b/Foundation/CPPredicate/_CPExpression.j index 02fb98801..b26707fc0 100644 --- a/Foundation/CPPredicate/_CPExpression.j +++ b/Foundation/CPPredicate/_CPExpression.j @@ -69,6 +69,10 @@ 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 @@ -284,6 +288,11 @@ A dictionary that the expression can use to store temporary state for one predic 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. diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j index 56bfcd643..57192ab65 100644 --- a/Foundation/CPPredicate/_CPPredicate.j +++ b/Foundation/CPPredicate/_CPPredicate.j @@ -751,11 +751,35 @@ subpredicate = [self parsePredicate]; if (![self scanString:@")" intoString:NULL]) - CPRaiseParseError(self, @"expression"); + 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, + trueExpression, + falseExpression; + + predicate = [self parsePredicate]; + 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 [CPExpression expressionForConditional:predicate trueExpression:trueExpression falseExpression:falseExpression]; + } + if ([self scanString:@"FUNCTION" intoString:NULL]) { if (![self scanString:@"(" intoString:NULL]) diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j index 2d6f8210a..470cbc722 100644 --- a/Tests/Foundation/CPPredicateTest.j +++ b/Tests/Foundation/CPPredicateTest.j @@ -86,6 +86,10 @@ } 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 @@ -167,6 +171,14 @@ [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]; @@ -401,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 @@ -459,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 @@ -510,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];