1 #include <valarray>
2 #include <sstream>
3 #include "ctokenizer.h"
4 using namespace std;
5 
6 namespace pictcli_constraints
7 {
8 
9 //
10 // handled by parseConstraint()
11 //
12 #define TEXT_TokenKeywordIf             L"IF"
13 #define TEXT_TokenKeywordThen           L"THEN"
14 #define TEXT_TokenKeywordElse           L"ELSE"
15 
16 //
17 // handled by getValueSet()
18 //
19 #define TEXT_TokenValueSetOpen          L"{"
20 #define TEXT_TokenValueSetSeparator     L","
21 #define TEXT_TokenValueSetClose         L"}"
22 
23 //
24 // handled by getParameterName()
25 //
26 // defined in cp.h as it's used by csolver.cpp
27 #define TEXT_TokenParameterNameOpen     L"["
28 #define TEXT_TokenParameterNameClose    L"]"
29 
30 //
31 // handled by parseCondition() and getFunction()
32 //
33 #define TEXT_TokenParenthesisOpen       L"("
34 #define TEXT_TokenParenthesisClose      L")"
35 
36 //
37 // handled by getFunction()
38 //
39 #define TEXT_FunctionIsNegativeParam    L"ISNEGATIVE"
40 #define TEXT_FunctionIsPositiveParam    L"ISPOSITIVE"
41 
42 //
43 // handled by parseTerm()
44 //
45 #define TEXT_TokenQuotes                L"\""
46 
47 //
48 // handled by getRelation()
49 //
50 #define TEXT_TokenRelationEQ            L"="
51 #define TEXT_TokenRelationNE            L"<>"
52 #define TEXT_TokenRelationLT            L"<"
53 #define TEXT_TokenRelationLE            L"<="
54 #define TEXT_TokenRelationGT            L">"
55 #define TEXT_TokenRelationGE            L">="
56 #define TEXT_TokenRelationIN            L"IN"
57 #define TEXT_TokenRelationLIKE          L"LIKE"
58 
59 //
60 // handled by getLogicalOper()
61 //
62 #define TEXT_TokenLogicalOperAND        L"AND"
63 #define TEXT_TokenLogicalOperOR         L"OR"
64 
65 //
66 // not handled by any function because of grammar; used directly
67 //
68 #define TEXT_TokenLogicalOperNOT        L"NOT"
69 
70 //
71 // Special characters recognized within a string
72 //
73 #define TEXT_SpecialCharMarker          L'\\'
74 
75 //
76 // create an array of special characters, then populate valarray with it
77 //
78 const wchar_t SpecialCharacters[] = { TEXT_SpecialCharMarker, L'"', L']' };
79 
80 //
81 //
82 //
Tokenize()83 void ConstraintsTokenizer::Tokenize()
84 {
85     _tokenLists.clear();
86 
87     while( _currentPosition < _constraintsText.end() )
88     {
89         CTokenList tokenList;
90         parseConstraint( tokenList );
91         _tokenLists.push_back( tokenList );
92 
93         skipWhiteChars();
94     }
95 }
96 
97 //
98 //
99 //
cleanUpTokenLists()100 void ConstraintsTokenizer::cleanUpTokenLists()
101 {
102     for( auto & tokenList : _tokenLists )
103         for( auto & token : tokenList )
104             delete( token );
105 }
106 
107 
108 //
109 // Parses a constraint:
110 //
111 // constraint   ::= IF <clause> THEN <term>;
112 //                  IF <clause> THEN <term> ELSE <term>;
113 //                  <parameter_name> <relation> <parameter_name>;
114 //
parseConstraint(IN OUT CTokenList & tokens)115 void ConstraintsTokenizer::parseConstraint( IN OUT CTokenList& tokens )
116 {
117     skipWhiteChars();
118 
119     // save position in case a new token is created
120     wstring::iterator position = _currentPosition;
121 
122     // IF <clause> THEN <clause> ELSE <clause>
123     // <clause>
124     if ( isNextSubstring( wstring(TEXT_TokenKeywordIf)) )
125     {
126         CToken* tokenKeywordIf = new CToken( TokenType_KeywordIf, position );
127         tokens.push_back( tokenKeywordIf );
128 
129         skipWhiteChars();
130         parseClause( tokens );
131 
132         skipWhiteChars();
133         position = _currentPosition;
134         if ( isNextSubstring( charArrToStr( TEXT_TokenKeywordThen )))
135         {
136             CToken* tokenKeywordThen = new CToken( TokenType_KeywordThen, position );
137             tokens.push_back( tokenKeywordThen );
138         }
139         else
140         {
141             throw CSyntaxError( SyntaxErrType_NoKeywordThen, _currentPosition );
142         }
143     }
144 
145     // evaluate the THEN part
146     parseClause( tokens );
147 
148     // evaluate the ELSE part
149     skipWhiteChars();
150     position = _currentPosition;
151     if ( isNextSubstring( charArrToStr( TEXT_TokenKeywordElse )))
152     {
153         CToken* tokenKeywordElse = new CToken( TokenType_KeywordElse, position );
154         tokens.push_back( tokenKeywordElse );
155 
156         parseClause( tokens );
157     }
158 
159     // all forms of contraints should end with a termination marker
160     skipWhiteChars();
161     position = _currentPosition;
162     if ( ! isNextSubstring ( charArrToStr( TEXT_TokenConstraintEnd )))
163     {
164         throw CSyntaxError( SyntaxErrType_NoConstraintEnd, _currentPosition );
165     }
166 
167     // some functions are like macros so do the expansions on the token list
168     doPostParseExpansions( tokens );
169 }
170 
171 //
172 // Parses a clause:
173 //
174 // clause       ::= <condition>
175 //                  <condition> <logical_operator> <clause>
176 //
parseClause(IN OUT CTokenList & tokens)177 void ConstraintsTokenizer::parseClause( IN OUT CTokenList& tokens )
178 {
179     skipWhiteChars();
180     parseCondition( tokens );
181 
182     // getLogicalOper() may change the current position so preserve it for token creation
183     skipWhiteChars();
184     wstring::iterator position = _currentPosition;
185 
186     LogicalOper logicalOper = getLogicalOper();
187     if ( LogicalOper_Unknown != logicalOper )
188     {
189         CToken* token = new CToken( logicalOper, position );
190         tokens.push_back( token );
191 
192         skipWhiteChars();
193         parseClause( tokens );
194     }
195 }
196 
197 //
198 // Parses a condition:
199 //
200 // condition    ::= <term>
201 //                  (<clause>)
202 //                  NOT <clause>
203 //
parseCondition(IN OUT CTokenList & tokens)204 void ConstraintsTokenizer::parseCondition( IN OUT CTokenList& tokens )
205 {
206     skipWhiteChars();
207     wstring::iterator position = _currentPosition;
208 
209     // (<clause>)
210     if ( isNextSubstring( charArrToStr( TEXT_TokenParenthesisOpen )))
211     {
212         CToken* token = new CToken( TokenType_ParenthesisOpen, position );;
213         tokens.push_back( token );
214 
215         skipWhiteChars();
216         parseClause( tokens );
217 
218         skipWhiteChars();
219         position = _currentPosition;
220         if ( isNextSubstring( charArrToStr( TEXT_TokenParenthesisClose )))
221         {
222             token = new CToken( TokenType_ParenthesisClose, position );
223             tokens.push_back( token );
224         }
225         else
226         {
227             throw CSyntaxError( SyntaxErrType_NoEndParenthesis, _currentPosition );
228         }
229     }
230 
231     // NOT <clause>
232     else if ( isNextSubstring( charArrToStr( TEXT_TokenLogicalOperNOT )))
233     {
234         CToken* token = new CToken( LogicalOper_NOT, position );
235         tokens.push_back( token );
236 
237         skipWhiteChars();
238         parseClause( tokens );
239     }
240 
241     // <term>
242     else
243     {
244         parseTerm( tokens );
245     }
246 }
247 
248 //
249 // Parses a term:
250 //
251 // term         ::= <parameter_name> <relation> <value>
252 //                  <parameter_name> LIKE <string>
253 //                  <parameter_name> IN {<value_set>}
254 //                  <parameter_name> <relation> <parameter_name>
255 //                  {functions on term level}
256 //
parseTerm(IN OUT CTokenList & tokens)257 void ConstraintsTokenizer::parseTerm( IN OUT CTokenList& tokens )
258 {
259     skipWhiteChars();
260     wstring::iterator position = _currentPosition;
261 
262     // check whether it's one of the functions
263     CFunction *function = getFunction();
264     if( NULL != function )
265     {
266         CToken* token;
267         try
268         {
269             token = new CToken( function, position );
270         }
271         catch( ... )
272         {
273             delete( function );
274             throw;
275         }
276         tokens.push_back( token );
277     }
278 
279     // if not, parse anything that starts with para_name
280     else
281     {
282         wstring paramName = getParameterName();
283         CParameters::iterator found = _model.findParamByName( paramName );
284 
285         CParameter* param = NULL;
286         if ( found != _model.Parameters.end() )
287         {
288             param = &*found;
289         }
290 
291         skipWhiteChars();
292         Relation relation = getRelation();
293 
294         skipWhiteChars();
295 
296         CTerm* term = NULL;
297         switch( relation )
298         {
299             case Relation_IN:
300             case Relation_NOT_IN:
301             {
302                 CValueSet* valueSet = new CValueSet;
303 
304                 if ( ! isNextSubstring( charArrToStr( TEXT_TokenValueSetOpen )))
305                 {
306                     throw CSyntaxError( SyntaxErrType_NoValueSetOpen, _currentPosition );
307                 }
308 
309                 try
310                 {
311                     getValueSet( *valueSet );
312                 }
313                 catch( ... )
314                 {
315                     delete( valueSet );
316                     throw;
317                 }
318 
319                 skipWhiteChars();
320                 if ( ! isNextSubstring( charArrToStr( TEXT_TokenValueSetClose )))
321                 {
322                     throw CSyntaxError( SyntaxErrType_NoValueSetClose, _currentPosition );
323                 }
324 
325                 // raw text of a term
326                 wstring rawText;
327                 rawText.assign( position, _currentPosition );
328 
329                 try
330                 {
331                     term = new CTerm( param, relation, SyntaxTermDataType_ValueSet, valueSet, rawText );
332                 }
333                 catch( ... )
334                 {
335                     delete( valueSet );
336                     throw;
337                 }
338                 break;
339             }
340 
341             // At this point the relation LIKE is treated as an ordinary relation
342             //   despite the fact it can only have a string as an argument on
343             //   the right-side. It will be verified later during parsing.
344             default:
345             {
346                 if ( isNextSubstring( charArrToStr( TEXT_TokenParameterNameOpen ), true ))
347                 {
348                     wstring paramName2 = getParameterName();
349 
350                     //
351                     // look up parameters by their names and return references
352                     //
353                     CParameter *param2 = NULL;
354                     found = _model.findParamByName( paramName2 );
355                     if ( found != _model.Parameters.end() )
356                     {
357                         param2 = &*found;
358                     }
359 
360                     wstring rawText;
361                     rawText.assign( position, _currentPosition );
362 
363                     term = new CTerm( param, relation, SyntaxTermDataType_ParameterName, param2, rawText );
364                 }
365                 else
366                 {
367                     CValue* value = getValue();
368 
369                     // raw text of a term
370                     wstring rawText;
371                     rawText.assign( position, _currentPosition );
372 
373                     try
374                     {
375                         term = new CTerm( param, relation, SyntaxTermDataType_Value, value, rawText );
376                     }
377                     catch( ... )
378                     {
379                         delete( value );
380                         throw;
381                     }
382                 }
383                 break;
384             }
385         }
386 
387         // now create token of type 'term'; this token has data
388         CToken* token;
389         try
390         {
391             token = new CToken( term, position );
392         }
393         catch( ... )
394         {
395             delete( term );
396             throw;
397         }
398         tokens.push_back( token );
399     }
400 }
401 
402 //
403 // Parses a function
404 //
405 // <term> ::= IsNegative(<parameter_name>)
406 //
407 // Returns a CFunction object if in fact a function was parsed
408 // or NULL otherwise
409 //
getFunction()410 CFunction *ConstraintsTokenizer::getFunction()
411 {
412     skipWhiteChars();
413     wstring::iterator position = _currentPosition;
414 
415     FunctionType type = FunctionTypeUnknown;
416 
417     if ( isNextSubstring( charArrToStr( TEXT_FunctionIsNegativeParam )))
418     {
419         type = FunctionTypeIsNegativeParam;
420     }
421     else if ( isNextSubstring( charArrToStr( TEXT_FunctionIsPositiveParam )))
422     {
423         type = FunctionTypeIsPositiveParam;
424     }
425     else
426     {
427         return NULL;
428     }
429 
430     // opening bracket
431     if ( ! isNextSubstring( charArrToStr( TEXT_TokenParenthesisOpen )))
432     {
433         throw CSyntaxError( SyntaxErrType_FunctionNoParenthesisOpen, _currentPosition );
434     }
435 
436     // get the parameter name
437     skipWhiteChars();
438     wstring paramName = getString( charArrToStr( TEXT_TokenParenthesisClose ));
439     CParameters::iterator found = _model.findParamByName( paramName );
440 
441     CParameter* param = NULL;
442     if ( found != _model.Parameters.end() )
443     {
444         param = &*found;
445     }
446 
447     if ( ! isNextSubstring( charArrToStr( TEXT_TokenParenthesisClose )))
448     {
449         throw CSyntaxError( SyntaxErrType_FunctionNoParenthesisClose, _currentPosition );
450     }
451 
452     // now create a CFunction and return it
453     wstring rawText;
454     rawText.assign( position, _currentPosition );
455 
456     CFunction* function = new CFunction( type, FunctionDataType_Parameter, param, paramName, rawText );
457 
458     return( function );
459 }
460 
461 //
462 // Returns a CValue.
463 //
464 // Note: allocates memory, caller is supposed to free it
465 //
getValue()466 CValue* ConstraintsTokenizer::getValue()
467 {
468     CValue* value;
469 
470     // value is either string or number,
471     // a string always begins with quotes so check for it first
472     if ( isNextSubstring( charArrToStr( TEXT_TokenQuotes )))
473     {
474         wstring text;
475         text = getString( charArrToStr( TEXT_TokenQuotes ));
476         if (! isNextSubstring( charArrToStr( TEXT_TokenQuotes )))
477         {
478             throw CSyntaxError( SyntaxErrType_UnexpectedEndOfString, _currentPosition );
479         }
480 
481         value = new CValue( text );
482     }
483     else
484     {
485         double number = getNumber();
486         value = new CValue( number );
487     }
488 
489     return( value );
490 }
491 
492 //
493 // Parses a valueset
494 //
495 // value_set        ::= <value>
496 //                      <value>,<value_set>
497 //
getValueSet(OUT CValueSet & valueSet)498 void ConstraintsTokenizer::getValueSet( OUT CValueSet& valueSet )
499 {
500     skipWhiteChars();
501 
502     CValue* value = getValue();
503     valueSet.push_back( *value );
504     delete( value );
505 
506     skipWhiteChars();
507     if ( isNextSubstring( charArrToStr( TEXT_TokenValueSetSeparator )))
508     {
509         skipWhiteChars();
510         getValueSet( valueSet );
511     }
512 }
513 
514 //
515 // Returns the next relation; order of comparisons is important
516 //
getRelation()517 Relation ConstraintsTokenizer::getRelation()
518 {
519     if     ( isNextSubstring( charArrToStr( TEXT_TokenRelationEQ     ))) return ( Relation_EQ   );
520     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationNE     ))) return ( Relation_NE   );
521     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationLE     ))) return ( Relation_LE   );
522     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationGE     ))) return ( Relation_GE   );
523     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationGT     ))) return ( Relation_GT   );
524     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationLT     ))) return ( Relation_LT   );
525     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationIN     ))) return ( Relation_IN   );
526     else if( isNextSubstring( charArrToStr( TEXT_TokenRelationLIKE   ))) return ( Relation_LIKE );
527     else if( isNextSubstring( charArrToStr( TEXT_TokenLogicalOperNOT )))
528     {
529         skipWhiteChars();
530         if     ( isNextSubstring( charArrToStr( TEXT_TokenRelationIN   ))) return ( Relation_NOT_IN   );
531         else if( isNextSubstring( charArrToStr( TEXT_TokenRelationLIKE ))) return ( Relation_NOT_LIKE );
532         else throw CSyntaxError( SyntaxErrType_UnknownRelation, _currentPosition );
533     }
534     else throw CSyntaxError( SyntaxErrType_UnknownRelation, _currentPosition );
535 
536     assert( false );
537     return ( Relation_Unknown );
538 }
539 
540 //
541 // Returns the next logical operator; doesn't handle NOT as it's parsed directly.
542 //
getLogicalOper()543 LogicalOper ConstraintsTokenizer::getLogicalOper()
544 {
545     if      ( isNextSubstring( charArrToStr( TEXT_TokenLogicalOperAND ))) return ( LogicalOper_AND );
546     else if ( isNextSubstring( charArrToStr( TEXT_TokenLogicalOperOR  ))) return ( LogicalOper_OR );
547     else return ( LogicalOper_Unknown );
548 }
549 
550 //
551 // Parses parameter name
552 //
getParameterName()553 wstring ConstraintsTokenizer::getParameterName()
554 {
555     wstring name;
556 
557     // look for opening marker
558     if ( ! ( isNextSubstring( charArrToStr( TEXT_TokenParameterNameOpen ))))
559     {
560         throw CSyntaxError( SyntaxErrType_NoParameterNameOpen, _currentPosition );
561     }
562 
563     // retrive text
564     name = getString( charArrToStr( TEXT_TokenParameterNameClose ));
565 
566     // look for closing marker
567     if ( ! isNextSubstring( charArrToStr( TEXT_TokenParameterNameClose )))
568     {
569         throw CSyntaxError( SyntaxErrType_NoParameterNameClose, _currentPosition );
570     }
571 
572     return( name );
573 }
574 
575 //
576 // Returns a number; reads from a string stream.
577 //
getNumber()578 double ConstraintsTokenizer::getNumber()
579 {
580     // declare new stream from text we'd like to parse
581     //  then try to get numeric value preserving old and new
582     //  position within a stream to properly update cursor
583     wstring substring( _currentPosition, _constraintsText.end() );
584     wistringstream ist( substring );
585 
586     unsigned int positionBefore = (unsigned int) ist.tellg();
587 
588     double number;
589     ist>>number;
590 
591     if (ist.rdstate() & ios::failbit)
592     {
593         throw CSyntaxError( SyntaxErrType_NotNumericValue, _currentPosition );
594     }
595 
596     // success, update current cursor position
597     unsigned int difference  =  (unsigned int) ist.tellg() - positionBefore;
598     _currentPosition += difference;
599 
600     return ( number );
601 }
602 
603 //
604 // Reads next characters considering them part of string
605 // Terminator is the enclosing char, typically a "
606 //
getString(IN const wstring & terminator)607 wstring ConstraintsTokenizer::getString( IN const wstring& terminator )
608 {
609     wstring ret;
610 
611     assert( 1 == terminator.size() );
612     wchar_t terminatingChar = terminator[ 0 ];
613 
614     wchar_t readChar;
615 
616     while( true )
617     {
618         // get next character, function throws error when there are no chars left
619         readChar = peekNextChar();
620 
621         // string ends properly terminated
622         if ( terminatingChar ==  readChar )
623         {
624             movePosition( -1 );
625             break;
626         }
627         // handle special characters
628         else if ( TEXT_SpecialCharMarker == readChar )
629         {
630             wchar_t nextChar = peekNextChar();
631 
632             bool found = false;
633             for( auto specialChar : SpecialCharacters )
634             {
635                 if( nextChar == specialChar ) found = true;
636             }
637             if( !found ) throw CSyntaxError( SyntaxErrType_UnknownSpecialChar, _currentPosition );
638 
639             // found a special character; append to resulting string in literal form
640             ret += nextChar;
641         }
642         // regular character: append to resulting string
643         else
644         {
645             ret += readChar;
646         }
647     }
648 
649     return( ret );
650 }
651 
652 //
653 // Skips all whitespace characters on and after current position.
654 //
skipWhiteChars()655 void ConstraintsTokenizer::skipWhiteChars()
656 {
657     // probe next character; the function throws error when there are no chars left),
658     try
659     {
660         while ( true )
661         {
662             wchar_t nextChar = peekNextChar();
663 
664             if ( ! ( iswspace ( nextChar )   // all white space characters
665                   || iswcntrl ( nextChar ))) // CRLF
666             {
667                 movePosition( -1 );
668                 break;
669             }
670         }
671     }
672     // there's nothing wrong with encountering the end of string here;
673     // other errors should be thrown to callers
674     catch ( CSyntaxError e )
675     {
676         if ( SyntaxErrType_UnexpectedEndOfString != e.Type )
677         {
678             throw e;
679         }
680     }
681 }
682 
683 //
684 // Returns the next character updating the current position
685 // Throws when no more characters are left
686 //
peekNextChar()687 wchar_t ConstraintsTokenizer::peekNextChar()
688 {
689     if ( _currentPosition >= _constraintsText.end() )
690     {
691         throw CSyntaxError( SyntaxErrType_UnexpectedEndOfString, _currentPosition );
692     }
693     return( *( _currentPosition++ ) );
694 }
695 
696 //
697 // If texts match, returns True and also updates the current cursor position
698 // (unless explicitly requested not to)
699 //
isNextSubstring(IN const wstring & text,IN bool dontMoveCursor)700 bool ConstraintsTokenizer::isNextSubstring( IN const wstring& text, IN bool dontMoveCursor )
701 {
702     skipWhiteChars();
703 
704     // Some STL implementations throw when text2 passed to 'equal' is shorter than text1.
705     // Checking for the sizes first should help.
706     bool textsMatch = false;
707 
708     if( distance( _currentPosition, _constraintsText.end() ) >= (int) text.size() )
709     {
710         textsMatch = equal ( text.begin(), text.end(), _currentPosition,
711                              []( wchar_t c1, wchar_t c2 ) { return ( toupper( c1 ) == toupper( c2 ) ); }
712                            );
713     }
714 
715     if ( textsMatch && ! dontMoveCursor )
716     {
717         _currentPosition += text.length();
718     }
719 
720     return ( textsMatch );
721 }
722 
723 //
724 //
725 //
movePosition(IN int count)726 void ConstraintsTokenizer::movePosition( IN int count )
727 {
728     wstring::iterator newPosition = _currentPosition + count;
729 
730     if ( newPosition < _constraintsText.begin() )
731     {
732         newPosition = _constraintsText.begin();
733     }
734     else if ( newPosition >= _constraintsText.end() )
735     {
736         newPosition = _constraintsText.end();
737     }
738     _currentPosition = newPosition;
739 }
740 
741 //
742 // Expands "macros", there are two macros curently:
743 //   IsNegative() == ( IsNegative(p1) or  IsNegative(p2) or  ... )
744 //   IsPositive() == ( IsPositive(p1) and IsPositive(p2) and ... )
745 //
doPostParseExpansions(IN OUT CTokenList & tokens)746 void ConstraintsTokenizer::doPostParseExpansions( IN OUT CTokenList& tokens )
747 {
748     CTokenList::iterator i_token = tokens.begin();
749     while( i_token != tokens.end() )
750     {
751         switch( (*i_token)->Type )
752         {
753         case TokenType_Function:
754             {
755             CFunction *function = (CFunction*) (*i_token)->Function;
756 
757             if(( function->Type == FunctionTypeIsNegativeParam
758               || function->Type == FunctionTypeIsPositiveParam )
759               && function->DataText.empty() )
760             {
761                 // deallocate the current token
762                 // we don't have to deallocate Data because in this case it is always NULL
763                 assert( function->Data == NULL );
764 
765                 // save positionInText and rawText and reuse it in all new tokens
766                 wstring::iterator oldPosInText = (*i_token)->PositionInText;
767                 FunctionType      oldType      = function->Type;
768                 wstring           oldRawText   = function->RawText;
769 
770                 delete(*i_token);
771                 i_token = tokens.erase( i_token );
772 
773                 // (
774                 CToken* newToken = new CToken( TokenType_ParenthesisOpen, oldPosInText );
775                 tokens.insert( i_token, newToken );
776 
777                 for( CParameters::iterator i_param =  _model.Parameters.begin();
778                                            i_param != _model.Parameters.end();
779                                          ++i_param )
780                 {
781                     if ( i_param->ResultParam ) continue;
782 
783                     if( i_param != _model.Parameters.begin() )
784                     {
785                         // logical operator OR or AND
786                         newToken = new CToken( oldType == FunctionTypeIsNegativeParam ? LogicalOper_OR : LogicalOper_AND,
787                                                oldPosInText );
788                         tokens.insert( i_token, newToken );
789                     }
790 
791                     // IsNegative(param) / IsPositive(param)
792                     CFunction* newFunction = new CFunction( oldType, FunctionDataType_Parameter,
793                                                             &*i_param, i_param->Name, oldRawText );
794                     newToken = new CToken( newFunction, oldPosInText );
795                     tokens.insert( i_token, newToken );
796                 }
797 
798                 // )
799                 newToken = new CToken( TokenType_ParenthesisClose, oldPosInText );
800                 tokens.insert( i_token, newToken );
801             }
802             else // it's not IsNegative() or IsPositive()
803             {
804                 ++i_token;
805             }
806             break;
807             }
808         default:
809             {
810             ++i_token;
811             break;
812             }
813         }
814     }
815 }
816 
817 }
818