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