1+ define ( function ( require , exports , module ) {
2+ 'use strict' ;
3+
4+ var Utils = require ( 'utils' ) ,
5+ Condition = require ( 'condition' ) ,
6+ Operator = require ( 'operator' ) ;
7+
8+ function Expression ( text ) {
9+ this . operators = [ /*Operator*/ ] ;
10+ this . conditions = [ /*Condition*/ ] ;
11+ this . truePaths = [ /*{ condition, result }*/ ] ;
12+
13+ // trim surrounding space and parenthesis pairs
14+ var textToParse = Utils . trimParenthesisPairs ( text ) ;
15+ var topLevelParenthesis = Utils . findTopLevelParenthesis ( textToParse ) ;
16+
17+ var textChunks = [ ] ,
18+ lastPosition = 0 ,
19+ i , j , k , l , m ; // iterators
20+
21+ // Break the text into sub-expressions and top-level expressions
22+ // TODO: Identify when a ! precedes an Expression, and pass that into the constructor
23+ if ( topLevelParenthesis . length === 0 ) {
24+ // There are no sub-expressions to extract. Store the entire string
25+ textChunks . push ( textToParse ) ;
26+ } else {
27+ for ( i = 0 ; i < topLevelParenthesis . length ; i ++ ) {
28+ // Store the text between previous chunk and start of this Expression
29+ textChunks . push ( textToParse . substring ( lastPosition , topLevelParenthesis [ i ] . start ) ) ;
30+ // Store the sub-expression
31+ textChunks . push ( new Expression ( textToParse . substring ( topLevelParenthesis [ i ] . start , topLevelParenthesis [ i ] . end + 1 ) ) ) ;
32+ // Advance the pointer
33+ lastPosition = topLevelParenthesis [ i ] . end + 1 ;
34+ }
35+ // Store any trailing text
36+ if ( lastPosition < textToParse . length - 1 ) {
37+ textChunks . push ( textToParse . substring ( lastPosition ) ) ;
38+ }
39+ }
40+
41+ var conditionChunks = [ ] ,
42+ matchAndOr = new RegExp (
43+ '(\\s|\\b)(?=' + Utils . tokensAndOr . join ( '|' ) + ')'
44+ , 'ig' ) ,
45+ captureLeadingAnd = new RegExp (
46+ '^(' + Utils . tokensAnd . join ( '|' ) + ')'
47+ , 'ig' ) ,
48+ captureLeadingOr = new RegExp (
49+ '^(' + Utils . tokensOr . join ( '|' ) + ')'
50+ , 'ig' ) ,
51+ condition , leadingAndMatch , leadingOrMatch ;
52+
53+ // TODO: Identify when the condition is preceded by a ! or has a negative comparison
54+ for ( j = 0 ; j < textChunks . length ; j ++ ) {
55+ // If this chunk is a sub-expression, just store it without parsing
56+ if ( textChunks [ j ] instanceof Expression ) {
57+ this . conditions . push ( textChunks [ j ] ) ;
58+ } else {
59+ conditionChunks = textChunks [ j ] . split ( matchAndOr ) ;
60+ for ( k = 0 ; k < conditionChunks . length ; k ++ ) {
61+ condition = conditionChunks [ k ] ;
62+
63+ // Determine if an AND operator or an OR operator was found.
64+ // If so, store which was found and then remove it.
65+ if ( ( leadingAndMatch = condition . match ( captureLeadingAnd ) ) !== null ) {
66+ this . operators . push ( new Operator ( true ) ) ; // AND operator
67+ condition = condition . substring ( leadingAndMatch [ 0 ] . length ) ;
68+ } else if ( ( leadingOrMatch = condition . match ( captureLeadingOr ) ) !== null ) {
69+ this . operators . push ( new Operator ( false ) ) ; // OR operator
70+ condition = condition . substring ( leadingOrMatch [ 0 ] . length ) ;
71+ }
72+
73+ // Store anything that's not still empty.
74+ condition = condition . trim ( ) ;
75+ if ( condition !== '' ) {
76+ this . conditions . push ( new Condition ( condition ) ) ;
77+ }
78+ }
79+ }
80+ }
81+
82+ this . hasMixedOperators = Utils . hasMixedOperators ( this . operators ) ;
83+
84+ // Calculate the combinations of Conditions that will resolve to true
85+ var truePath ;
86+ if ( ! this . hasMixedOperators ) { // TODO: Can we calculate when operators are mixed?
87+ if ( this . conditions . length === 1 ) {
88+ // There's only one condition, so it must be true
89+ this . truePaths . push ( [ { condition : this . conditions [ 0 ] , result : true } ] ) ;
90+ } else {
91+ if ( this . operators [ 0 ] . isAnd ( ) ) {
92+ // Create one truePath where every condition is true
93+ truePath = [ ] ;
94+ for ( l = 0 ; l < this . conditions . length ; l ++ ) {
95+ truePath . push ( { condition : this . conditions [ l ] , result : true } ) ;
96+ }
97+ this . truePaths . push ( truePath ) ;
98+ } else {
99+ // Create a separate truePath for each Condition where one is true
100+ for ( l = 0 ; l < this . conditions . length ; l ++ ) {
101+ truePath = [ ] ;
102+ for ( m = 0 ; m < this . conditions . length ; m ++ ) {
103+ truePath . push ( { condition : this . conditions [ m ] , result : ( l === m ) } ) ;
104+ }
105+ this . truePaths . push ( truePath ) ;
106+ }
107+ }
108+ }
109+ }
110+
111+ return this ;
112+ }
113+
114+ Expression . prototype . expandTruePaths = function ( ) {
115+
116+ var replaceSubExpressionWithTruePath = function ( appendInto , toAppend , subExpressionPos ) {
117+ var resultToInsert = appendInto [ subExpressionPos ] . result ,
118+ tempAppendInfo , tempToAppend , i , spliceArgs ;
119+
120+ // Create a copy of the objects so that modifications will not leak in or out by reference
121+ tempAppendInfo = Utils . cloneDeep ( appendInto ) ;
122+ tempToAppend = Utils . cloneDeep ( toAppend ) ;
123+
124+ // If the Expression does not need to be true in this path, set all its conditions to false
125+ // TODO: Move this to the (expandedTruePaths[k][i].result === false) condition
126+ if ( ! resultToInsert ) {
127+ for ( i = 0 ; i < tempToAppend . length ; i ++ ) {
128+ tempToAppend [ i ] . result = false ;
129+ }
130+ }
131+
132+ spliceArgs = [ subExpressionPos , 1 ] . concat ( tempToAppend ) ;
133+ Array . prototype . splice . apply ( tempAppendInfo , spliceArgs ) ;
134+
135+ return tempAppendInfo ;
136+ } ;
137+
138+ var expandedTruePaths = Utils . cloneDeep ( this . truePaths ) ,
139+ i , j , k , kMax , subTruePaths , tempTruePath ;
140+
141+ // Iterate through the first truePath, as a template of the conditions and sub-expressions.
142+ // Step through it backwards so expanded paths will not throw off the upcoming indicies.
143+ for ( i = this . truePaths [ 0 ] . length - 1 ; i >= 0 ; i -- ) {
144+ if ( this . truePaths [ 0 ] [ i ] . condition instanceof Expression ) {
145+ subTruePaths = this . truePaths [ 0 ] [ i ] . condition . expandTruePaths ( ) ;
146+
147+ // Cross-apply the child true paths to the one for this Expression
148+ for ( k = expandedTruePaths . length - 1 ; k >= 0 ; k -- ) {
149+
150+ if ( expandedTruePaths [ k ] [ i ] . result === false ) {
151+ // If the sub-expression doesn't need to be true, then we can replace it with a single entry where all conditions are false
152+ tempTruePath = replaceSubExpressionWithTruePath ( expandedTruePaths [ k ] , subTruePaths [ 0 ] , i ) ;
153+ // Append the new true path after the existing one. Because k counts down, it won't be processed.
154+ expandedTruePaths . splice ( k + 1 , 0 , tempTruePath ) ;
155+ } else {
156+ // The sub-expression needs to be true, so insert each of its true paths
157+ for ( j = subTruePaths . length - 1 ; j >= 0 ; j -- ) {
158+ // Update this true path to replace the sub-expression with its expanded conditions
159+ tempTruePath = replaceSubExpressionWithTruePath ( expandedTruePaths [ k ] , subTruePaths [ j ] , i ) ;
160+ // Append the new true path after the existing one. Because k counts down, it won't be processed.
161+ expandedTruePaths . splice ( k + 1 , 0 , tempTruePath ) ;
162+ }
163+ }
164+
165+ // Remove the sub-expression, now that it's been replaced above by the expansion(s)
166+ expandedTruePaths . splice ( k , 1 ) ;
167+
168+ }
169+ }
170+ }
171+
172+ return expandedTruePaths ;
173+ } ;
174+
175+ /*Expression.prototype.toArray = function() {
176+ var toArray = [],
177+ i;
178+ for(i = 0; i < this.conditions.length; i++) {
179+ toArray.push(this.conditions[i]);
180+ if(i < this.operators.length) {
181+ toArray.push(this.operators[i]);
182+ }
183+ }
184+ return toArray;
185+ };*/
186+
187+ return Expression ;
188+
189+ } ) ;
0 commit comments