xref: /core/basic/source/comp/loops.cxx (revision ab9b67bb)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <parser.hxx>
22 #include <memory>
23 
24 // Single-line IF and Multiline IF
25 
26 void SbiParser::If()
27 {
28     sal_uInt32 nEndLbl;
29     SbiToken eTok = NIL;
30     // ignore end-tokens
31     SbiExpression aCond( this );
32     aCond.Gen();
33     TestToken( THEN );
34     if( IsEoln( Next() ) )
35     {
36         // At the end of each block a jump to ENDIF must be inserted,
37         // so that the condition is not evaluated again at ELSEIF.
38         // The table collects all jump points.
39 #define JMP_TABLE_SIZE 100
40         sal_uInt32 pnJmpToEndLbl[JMP_TABLE_SIZE];   // 100 ELSEIFs allowed
41         sal_uInt16 iJmp = 0;                        // current table index
42 
43         // multiline IF
44         nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
45         eTok = Peek();
46         while( !( eTok == ELSEIF || eTok == ELSE || eTok == ENDIF ) &&
47                 !bAbort && Parse() )
48         {
49             eTok = Peek();
50             if( IsEof() )
51             {
52                 Error( ERRCODE_BASIC_BAD_BLOCK, IF ); bAbort = true; return;
53             }
54         }
55         while( eTok == ELSEIF )
56         {
57             // jump to ENDIF in case of a successful IF/ELSEIF
58             if( iJmp >= JMP_TABLE_SIZE )
59             {
60                 Error( ERRCODE_BASIC_PROG_TOO_LARGE );  bAbort = true;  return;
61             }
62             pnJmpToEndLbl[iJmp++] = aGen.Gen( SbiOpcode::JUMP_, 0 );
63 
64             Next();
65             aGen.BackChain( nEndLbl );
66 
67             aGen.Statement();
68             std::unique_ptr<SbiExpression> pCond(new SbiExpression( this ));
69             pCond->Gen();
70             nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
71             pCond.reset();
72             TestToken( THEN );
73             eTok = Peek();
74             while( !( eTok == ELSEIF || eTok == ELSE || eTok == ENDIF ) &&
75                     !bAbort && Parse() )
76             {
77                 eTok = Peek();
78                 if( IsEof() )
79                 {
80                     Error( ERRCODE_BASIC_BAD_BLOCK, ELSEIF );  bAbort = true; return;
81                 }
82             }
83         }
84         if( eTok == ELSE )
85         {
86             Next();
87             sal_uInt32 nElseLbl = nEndLbl;
88             nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 );
89             aGen.BackChain( nElseLbl );
90 
91             aGen.Statement();
92             StmntBlock( ENDIF );
93         }
94         else if( eTok == ENDIF )
95             Next();
96 
97 
98         while( iJmp > 0 )
99         {
100             iJmp--;
101             aGen.BackChain( pnJmpToEndLbl[iJmp] );
102         }
103     }
104     else
105     {
106         // single line IF
107         bSingleLineIf = true;
108         nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
109         Push( eCurTok );
110         while( !bAbort )
111         {
112             if( !Parse() ) break;
113             eTok = Peek();
114             if( eTok == ELSE || eTok == EOLN || eTok == REM )
115                 break;
116         }
117         if( eTok == ELSE )
118         {
119             Next();
120             sal_uInt32 nElseLbl = nEndLbl;
121             nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 );
122             aGen.BackChain( nElseLbl );
123             while( !bAbort )
124             {
125                 if( !Parse() ) break;
126                 eTok = Peek();
127                 if( eTok == EOLN || eTok == REM )
128                     break;
129             }
130         }
131         bSingleLineIf = false;
132     }
133     aGen.BackChain( nEndLbl );
134 }
135 
136 // ELSE/ELSEIF/ENDIF without IF
137 
138 void SbiParser::NoIf()
139 {
140     Error( ERRCODE_BASIC_NO_IF );
141     StmntBlock( ENDIF );
142 }
143 
144 // DO WHILE...LOOP
145 // DO ... LOOP WHILE
146 
147 void SbiParser::DoLoop()
148 {
149     sal_uInt32 nStartLbl = aGen.GetPC();
150     OpenBlock( DO );
151     SbiToken eTok = Next();
152     if( IsEoln( eTok ) )
153     {
154         // DO ... LOOP [WHILE|UNTIL expr]
155         StmntBlock( LOOP );
156         eTok = Next();
157         if( eTok == UNTIL || eTok == WHILE )
158         {
159             SbiExpression aExpr( this );
160             aExpr.Gen();
161             aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPF_ : SbiOpcode::JUMPT_, nStartLbl );
162         } else
163             if (eTok == EOLN || eTok == REM)
164                 aGen.Gen (SbiOpcode::JUMP_, nStartLbl);
165             else
166                 Error( ERRCODE_BASIC_EXPECTED, WHILE );
167     }
168     else
169     {
170         // DO [WHILE|UNTIL expr] ... LOOP
171         if( eTok == UNTIL || eTok == WHILE )
172         {
173             SbiExpression aCond( this );
174             aCond.Gen();
175         }
176         sal_uInt32 nEndLbl = aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPT_ : SbiOpcode::JUMPF_, 0 );
177         StmntBlock( LOOP );
178         TestEoln();
179         aGen.Gen( SbiOpcode::JUMP_, nStartLbl );
180         aGen.BackChain( nEndLbl );
181     }
182     CloseBlock();
183 }
184 
185 // WHILE ... WEND
186 
187 void SbiParser::While()
188 {
189     SbiExpression aCond( this );
190     sal_uInt32 nStartLbl = aGen.GetPC();
191     aCond.Gen();
192     sal_uInt32 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
193     StmntBlock( WEND );
194     aGen.Gen( SbiOpcode::JUMP_, nStartLbl );
195     aGen.BackChain( nEndLbl );
196 }
197 
198 // FOR var = expr TO expr STEP
199 
200 void SbiParser::For()
201 {
202     bool bForEach = ( Peek() == EACH );
203     if( bForEach )
204         Next();
205     SbiExpression aLvalue( this, SbOPERAND );
206     aLvalue.Gen();      // variable on the Stack
207 
208     if( bForEach )
209     {
210         TestToken( IN_ );
211         SbiExpression aCollExpr( this, SbOPERAND );
212         aCollExpr.Gen();    // Collection var to for stack
213         TestEoln();
214         aGen.Gen( SbiOpcode::INITFOREACH_ );
215     }
216     else
217     {
218         TestToken( EQ );
219         SbiExpression aStartExpr( this );
220         aStartExpr.Gen();
221         TestToken( TO );
222         SbiExpression aStopExpr( this );
223         aStopExpr.Gen();
224         if( Peek() == STEP )
225         {
226             Next();
227             SbiExpression aStepExpr( this );
228             aStepExpr.Gen();
229         }
230         else
231         {
232             SbiExpression aOne( this, 1, SbxINTEGER );
233             aOne.Gen();
234         }
235         TestEoln();
236         // The stack has all 4 elements now: variable, start, end, increment
237         // bind start value
238         aGen.Gen( SbiOpcode::INITFOR_ );
239     }
240 
241     sal_uInt32 nLoop = aGen.GetPC();
242     // do tests, maybe free the stack
243     sal_uInt32 nEndTarget = aGen.Gen( SbiOpcode::TESTFOR_, 0 );
244     OpenBlock( FOR );
245     StmntBlock( NEXT );
246     aGen.Gen( SbiOpcode::NEXT_ );
247     aGen.Gen( SbiOpcode::JUMP_, nLoop );
248     // are there variables after NEXT?
249     if( Peek() == SYMBOL )
250     {
251         SbiExpression aVar( this, SbOPERAND );
252         if( aVar.GetRealVar() != aLvalue.GetRealVar() )
253             Error( ERRCODE_BASIC_EXPECTED, aLvalue.GetRealVar()->GetName() );
254     }
255     aGen.BackChain( nEndTarget );
256     CloseBlock();
257 }
258 
259 // WITH .. END WITH
260 
261 void SbiParser::With()
262 {
263     SbiExpression aVar( this, SbOPERAND );
264 
265     SbiExprNode *pNode = aVar.GetExprNode()->GetRealNode();
266     if (!pNode)
267         return;
268     SbiSymDef* pDef = pNode->GetVar();
269     // Variant, from 27.6.1997, #41090: empty -> must be Object
270     if( pDef->GetType() == SbxVARIANT || pDef->GetType() == SbxEMPTY )
271         pDef->SetType( SbxOBJECT );
272     else if( pDef->GetType() != SbxOBJECT )
273         Error( ERRCODE_BASIC_NEEDS_OBJECT );
274 
275 
276     pNode->SetType( SbxOBJECT );
277 
278     OpenBlock( NIL, aVar.GetExprNode() );
279     StmntBlock( ENDWITH );
280     CloseBlock();
281 }
282 
283 // LOOP/NEXT/WEND without construct
284 
285 void SbiParser::BadBlock()
286 {
287     if( eEndTok )
288         Error( ERRCODE_BASIC_BAD_BLOCK, eEndTok );
289     else
290         Error( ERRCODE_BASIC_BAD_BLOCK, "Loop/Next/Wend" );
291 }
292 
293 // On expr Goto/Gosub n,n,n...
294 
295 void SbiParser::OnGoto()
296 {
297     SbiExpression aCond( this );
298     aCond.Gen();
299     sal_uInt32 nLabelsTarget = aGen.Gen( SbiOpcode::ONJUMP_, 0 );
300     SbiToken eTok = Next();
301     if( eTok != GOTO && eTok != GOSUB )
302     {
303         Error( ERRCODE_BASIC_EXPECTED, "GoTo/GoSub" );
304         eTok = GOTO;
305     }
306 
307     sal_uInt32 nLbl = 0;
308     do
309     {
310         Next(); // get label
311         if( MayBeLabel() )
312         {
313             sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
314             aGen.Gen( SbiOpcode::JUMP_, nOff );
315             nLbl++;
316         }
317         else Error( ERRCODE_BASIC_LABEL_EXPECTED );
318     }
319     while( !bAbort && TestComma() );
320     if( eTok == GOSUB )
321         nLbl |= 0x8000;
322     aGen.Patch( nLabelsTarget, nLbl );
323 }
324 
325 // GOTO/GOSUB
326 
327 void SbiParser::Goto()
328 {
329     SbiOpcode eOp = eCurTok == GOTO ? SbiOpcode::JUMP_ : SbiOpcode::GOSUB_;
330     Next();
331     if( MayBeLabel() )
332     {
333         sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
334         aGen.Gen( eOp, nOff );
335     }
336     else Error( ERRCODE_BASIC_LABEL_EXPECTED );
337 }
338 
339 // RETURN [label]
340 
341 void SbiParser::Return()
342 {
343     Next();
344     if( MayBeLabel() )
345     {
346         sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
347         aGen.Gen( SbiOpcode::RETURN_, nOff );
348     }
349     else aGen.Gen( SbiOpcode::RETURN_, 0 );
350 }
351 
352 // SELECT CASE
353 
354 void SbiParser::Select()
355 {
356     TestToken( CASE );
357     SbiExpression aCase( this );
358     SbiToken eTok = NIL;
359     aCase.Gen();
360     aGen.Gen( SbiOpcode::CASE_ );
361     TestEoln();
362     sal_uInt32 nNextTarget = 0;
363     sal_uInt32 nDoneTarget = 0;
364     bool bElse = false;
365 
366     while( !bAbort )
367     {
368         eTok = Next();
369         if( eTok == CASE )
370         {
371             if( nNextTarget )
372             {
373                 aGen.BackChain( nNextTarget );
374                 nNextTarget = 0;
375             }
376             aGen.Statement();
377 
378             bool bDone = false;
379             sal_uInt32 nTrueTarget = 0;
380             if( Peek() == ELSE )
381             {
382                 // CASE ELSE
383                 Next();
384                 bElse = true;
385             }
386             else while( !bDone )
387             {
388                 if( bElse )
389                     Error( ERRCODE_BASIC_SYNTAX );
390                 SbiToken eTok2 = Peek();
391                 if( eTok2 == IS || ( eTok2 >= EQ && eTok2 <= GE ) )
392                 {   // CASE [IS] operator expr
393                     if( eTok2 == IS )
394                         Next();
395                     eTok2 = Peek();
396                     if( eTok2 < EQ || eTok2 > GE )
397                         Error( ERRCODE_BASIC_SYNTAX );
398                     else Next();
399                     SbiExpression aCompare( this );
400                     aCompare.Gen();
401                     nTrueTarget = aGen.Gen(
402                         SbiOpcode::CASEIS_, nTrueTarget,
403                         sal::static_int_cast< sal_uInt16 >(
404                             SbxEQ + ( eTok2 - EQ ) ) );
405                 }
406                 else
407                 {   // CASE expr | expr TO expr
408                     SbiExpression aCase1( this );
409                     aCase1.Gen();
410                     if( Peek() == TO )
411                     {
412                         // CASE a TO b
413                         Next();
414                         SbiExpression aCase2( this );
415                         aCase2.Gen();
416                         nTrueTarget = aGen.Gen( SbiOpcode::CASETO_, nTrueTarget );
417                     }
418                     else
419                         // CASE a
420                         nTrueTarget = aGen.Gen( SbiOpcode::CASEIS_, nTrueTarget, SbxEQ );
421 
422                 }
423                 if( Peek() == COMMA ) Next();
424                 else
425                 {
426                     TestEoln();
427                     bDone = true;
428                 }
429             }
430 
431             if( !bElse )
432             {
433                 nNextTarget = aGen.Gen( SbiOpcode::JUMP_, nNextTarget );
434                 aGen.BackChain( nTrueTarget );
435             }
436             // build the statement body
437             while( !bAbort )
438             {
439                 eTok = Peek();
440                 if( eTok == CASE || eTok == ENDSELECT )
441                     break;
442                 if( !Parse() ) goto done;
443                 eTok = Peek();
444                 if( eTok == CASE || eTok == ENDSELECT )
445                     break;
446             }
447             if( !bElse )
448                 nDoneTarget = aGen.Gen( SbiOpcode::JUMP_, nDoneTarget );
449         }
450         else if( !IsEoln( eTok ) )
451             break;
452     }
453 done:
454     if( eTok != ENDSELECT )
455         Error( ERRCODE_BASIC_EXPECTED, ENDSELECT );
456     if( nNextTarget )
457         aGen.BackChain( nNextTarget );
458     aGen.BackChain( nDoneTarget );
459     aGen.Gen( SbiOpcode::ENDCASE_ );
460 }
461 
462 // ON Error/Variable
463 
464 void SbiParser::On()
465 {
466     SbiToken eTok = Peek();
467     OUString aString = SbiTokenizer::Symbol(eTok);
468     if (aString.equalsIgnoreAsciiCase("ERROR"))
469     {
470         eTok = ERROR_; // Error comes as SYMBOL
471     }
472     if( eTok != ERROR_ && eTok != LOCAL )
473     {
474         OnGoto();
475     }
476     else
477     {
478         if( eTok == LOCAL )
479         {
480             Next();
481         }
482         Next (); // no more TestToken, as there'd be an error otherwise
483 
484         Next(); // get token after error
485         if( eCurTok == GOTO )
486         {
487             // ON ERROR GOTO label|0
488             Next();
489             bool bError_ = false;
490             if( MayBeLabel() )
491             {
492                 if( eCurTok == NUMBER && !nVal )
493                 {
494                     aGen.Gen( SbiOpcode::STDERROR_ );
495                 }
496                 else
497                 {
498                     sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
499                     aGen.Gen( SbiOpcode::ERRHDL_, nOff );
500                 }
501             }
502             else if( eCurTok == MINUS )
503             {
504                 Next();
505                 if( eCurTok == NUMBER && nVal == 1 )
506                 {
507                     aGen.Gen( SbiOpcode::STDERROR_ );
508                 }
509                 else
510                 {
511                     bError_ = true;
512                 }
513             }
514             if( bError_ )
515             {
516                 Error( ERRCODE_BASIC_LABEL_EXPECTED );
517             }
518         }
519         else if( eCurTok == RESUME )
520         {
521             TestToken( NEXT );
522             aGen.Gen( SbiOpcode::NOERROR_ );
523         }
524         else Error( ERRCODE_BASIC_EXPECTED, "GoTo/Resume" );
525     }
526 }
527 
528 // RESUME [0]|NEXT|label
529 
530 void SbiParser::Resume()
531 {
532     sal_uInt32 nLbl;
533 
534     switch( Next() )
535     {
536         case EOS:
537         case EOLN:
538             aGen.Gen( SbiOpcode::RESUME_, 0 );
539             break;
540         case NEXT:
541             aGen.Gen( SbiOpcode::RESUME_, 1 );
542             Next();
543             break;
544         case NUMBER:
545             if( !nVal )
546             {
547                 aGen.Gen( SbiOpcode::RESUME_, 0 );
548                 break;
549             }
550             [[fallthrough]];
551         case SYMBOL:
552             if( MayBeLabel() )
553             {
554                 nLbl = pProc->GetLabels().Reference( aSym );
555                 aGen.Gen( SbiOpcode::RESUME_, nLbl );
556                 Next();
557                 break;
558             }
559             [[fallthrough]];
560         default:
561             Error( ERRCODE_BASIC_LABEL_EXPECTED );
562     }
563 }
564 
565 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
566