tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / basic / source / comp / loops.cxx
bloba7e818dfd2bcd25945403c2720493c01b3b71eb2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <parser.hxx>
22 #include <memory>
24 #include <basic/sberrors.hxx>
25 #include <basic/sbmod.hxx>
27 #include <rtl/ustrbuf.hxx>
29 static bool EndsIfBranch(SbiToken eTok)
31 return eTok == ELSEIF || eTok == ELSE || eTok == ENDIF;
34 // Single-line IF and Multiline IF
36 void SbiParser::If()
38 sal_uInt32 nEndLbl;
39 SbiToken eTok = NIL;
40 // ignore end-tokens
41 SbiExpression aCond( this );
42 aCond.Gen();
43 TestToken( THEN );
44 if( IsEoln( Next() ) )
46 // At the end of each block a jump to ENDIF must be inserted,
47 // so that the condition is not evaluated again at ELSEIF.
48 // The table collects all jump points.
49 #define JMP_TABLE_SIZE 100
50 sal_uInt32 pnJmpToEndLbl[JMP_TABLE_SIZE]; // 100 ELSEIFs allowed
51 sal_uInt16 iJmp = 0; // current table index
53 // multiline IF
54 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
55 eTok = Peek();
56 while (!EndsIfBranch(eTok) && !bAbort && Parse())
58 eTok = Peek();
59 if( IsEof() )
61 Error( ERRCODE_BASIC_BAD_BLOCK, IF ); bAbort = true; return;
64 while( eTok == ELSEIF )
66 // jump to ENDIF in case of a successful IF/ELSEIF
67 if( iJmp >= JMP_TABLE_SIZE )
69 Error( ERRCODE_BASIC_PROG_TOO_LARGE ); bAbort = true; return;
71 pnJmpToEndLbl[iJmp++] = aGen.Gen( SbiOpcode::JUMP_, 0 );
73 Next();
74 aGen.BackChain( nEndLbl );
76 aGen.Statement();
77 auto pCond = std::make_unique<SbiExpression>( this );
78 pCond->Gen();
79 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
80 pCond.reset();
81 TestToken( THEN );
82 eTok = Peek();
83 while (!EndsIfBranch(eTok) && !bAbort && Parse())
85 eTok = Peek();
86 if( IsEof() )
88 Error( ERRCODE_BASIC_BAD_BLOCK, ELSEIF ); bAbort = true; return;
92 if( eTok == ELSE )
94 Next();
95 sal_uInt32 nElseLbl = nEndLbl;
96 nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 );
97 aGen.BackChain( nElseLbl );
99 aGen.Statement();
100 StmntBlock( ENDIF );
102 else if( eTok == ENDIF )
103 Next();
106 while( iJmp > 0 )
108 iJmp--;
109 aGen.BackChain( pnJmpToEndLbl[iJmp] );
112 else
114 // single line IF
115 bSingleLineIf = true;
116 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
117 Push( eCurTok );
118 // tdf#128263: update push positions to correctly restore in Next()
119 nPLine = nLine;
120 nPCol1 = nCol1;
121 nPCol2 = nCol2;
123 while( !bAbort )
125 if( !Parse() ) break;
126 eTok = Peek();
127 if( eTok == ELSE || eTok == EOLN || eTok == REM )
128 break;
130 if( eTok == ELSE )
132 Next();
133 sal_uInt32 nElseLbl = nEndLbl;
134 nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 );
135 aGen.BackChain( nElseLbl );
136 while( !bAbort )
138 if( !Parse() ) break;
139 eTok = Peek();
140 if( eTok == EOLN || eTok == REM )
141 break;
144 bSingleLineIf = false;
146 aGen.BackChain( nEndLbl );
149 // ELSE/ELSEIF/ENDIF without IF
151 void SbiParser::NoIf()
153 Error( ERRCODE_BASIC_NO_IF );
154 StmntBlock( ENDIF );
157 // DO WHILE...LOOP
158 // DO ... LOOP WHILE
160 void SbiParser::DoLoop()
162 sal_uInt32 nStartLbl = aGen.GetPC();
163 OpenBlock( DO );
164 SbiToken eTok = Next();
165 if( IsEoln( eTok ) )
167 // DO ... LOOP [WHILE|UNTIL expr]
168 StmntBlock( LOOP );
169 eTok = Next();
170 if( eTok == UNTIL || eTok == WHILE )
172 SbiExpression aExpr( this );
173 aExpr.Gen();
174 aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPF_ : SbiOpcode::JUMPT_, nStartLbl );
175 } else
176 if (eTok == EOLN || eTok == REM)
177 aGen.Gen (SbiOpcode::JUMP_, nStartLbl);
178 else
179 Error( ERRCODE_BASIC_EXPECTED, WHILE );
181 else
183 // DO [WHILE|UNTIL expr] ... LOOP
184 if( eTok == UNTIL || eTok == WHILE )
186 SbiExpression aCond( this );
187 aCond.Gen();
189 sal_uInt32 nEndLbl = aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPT_ : SbiOpcode::JUMPF_, 0 );
190 StmntBlock( LOOP );
191 TestEoln();
192 aGen.Gen( SbiOpcode::JUMP_, nStartLbl );
193 aGen.BackChain( nEndLbl );
195 CloseBlock();
198 // WHILE ... WEND
200 void SbiParser::While()
202 SbiExpression aCond( this );
203 sal_uInt32 nStartLbl = aGen.GetPC();
204 aCond.Gen();
205 sal_uInt32 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 );
206 StmntBlock( WEND );
207 aGen.Gen( SbiOpcode::JUMP_, nStartLbl );
208 aGen.BackChain( nEndLbl );
211 // FOR var = expr TO expr STEP
213 void SbiParser::For()
215 bool bForEach = ( Peek() == EACH );
216 if( bForEach )
217 Next();
218 SbiExpression aLvalue( this, SbOPERAND );
219 if (!aLvalue.IsVariable())
221 bAbort = true;
222 return; // the error is already set in SbiExpression ctor
224 aLvalue.Gen(); // variable on the Stack
226 if( bForEach )
228 TestToken( IN_ );
229 SbiExpression aCollExpr( this, SbOPERAND );
230 aCollExpr.Gen(); // Collection var to for stack
231 TestEoln();
232 aGen.Gen( SbiOpcode::INITFOREACH_ );
234 else
236 TestToken( EQ );
237 SbiExpression aStartExpr( this );
238 aStartExpr.Gen();
239 TestToken( TO );
240 SbiExpression aStopExpr( this );
241 aStopExpr.Gen();
242 if( Peek() == STEP )
244 Next();
245 SbiExpression aStepExpr( this );
246 aStepExpr.Gen();
248 else
250 SbiExpression aOne( this, 1, SbxINTEGER );
251 aOne.Gen();
253 TestEoln();
254 // The stack has all 4 elements now: variable, start, end, increment
255 // bind start value
256 aGen.Gen( SbiOpcode::INITFOR_ );
259 sal_uInt32 nLoop = aGen.GetPC();
260 // do tests, maybe free the stack
261 sal_uInt32 nEndTarget = aGen.Gen( SbiOpcode::TESTFOR_, 0 );
262 OpenBlock( FOR );
263 StmntBlock( NEXT );
264 aGen.Gen( SbiOpcode::NEXT_ );
265 aGen.Gen( SbiOpcode::JUMP_, nLoop );
266 // are there variables after NEXT?
267 if( Peek() == SYMBOL )
269 SbiExpression aVar( this, SbOPERAND );
270 if( aVar.GetRealVar() != aLvalue.GetRealVar() )
271 Error( ERRCODE_BASIC_EXPECTED, aLvalue.GetRealVar()->GetName() );
273 aGen.BackChain( nEndTarget );
274 CloseBlock();
277 // WITH .. END WITH
279 namespace
281 // Generate a '{_with_library.module_offset} = rVar'
282 // Use the {_with_library.module_offset} in OpenBlock
283 // The name of the variable can't be used by user: a name like [{_with_library.module_offset}]
284 // is valid, but not without the square brackets
285 struct WithLocalVar
287 WithLocalVar(SbiParser& rParser, SbiExpression& rVar)
288 : m_rParser(rParser)
289 , m_aWithParent(createLocalVar(rParser))
291 // Assignment
292 m_aWithParent.Gen();
293 rVar.Gen();
294 m_rParser.aGen.Gen(SbiOpcode::PUTC_);
297 ~WithLocalVar()
299 // {_with_library.module_offset} = Nothing
300 m_aWithParent.Gen();
301 m_rParser.aGen.Gen(SbiOpcode::RTL_, m_rParser.aGblStrings.Add(u"Nothing"_ustr), SbxOBJECT);
302 m_rParser.aGen.Gen(SbiOpcode::PUTC_);
305 static SbiExpression createLocalVar(SbiParser& rParser)
307 // Create the unique name
308 OUStringBuffer moduleName(rParser.aGen.GetModule().GetName());
309 for (auto parent = rParser.aGen.GetModule().GetParent(); parent;
310 parent = parent->GetParent())
311 moduleName.insert(0, parent->GetName() + ".");
313 OUString uniqueName
314 = "{_with_" + moduleName + "_" + OUString::number(rParser.aGen.GetOffset()) + "}";
315 while (rParser.pPool->Find(uniqueName) != nullptr)
317 static sal_Int64 unique_suffix;
318 uniqueName = "{_with_" + moduleName + "_" + OUString::number(rParser.aGen.GetOffset())
319 + "_" + OUString::number(unique_suffix++) + "}";
321 SbiSymDef* pWithParentDef = new SbiSymDef(uniqueName);
322 pWithParentDef->SetType(SbxOBJECT);
323 rParser.pPool->Add(pWithParentDef);
325 // DIM local variable: work with Option Explicit
326 rParser.aGen.Gen(SbiOpcode::LOCAL_, pWithParentDef->GetId(), pWithParentDef->GetType());
328 return SbiExpression(&rParser, *pWithParentDef);
331 SbiParser& m_rParser;
332 SbiExpression m_aWithParent;
336 void SbiParser::With()
338 SbiExpression aVar( this, SbOPERAND );
340 SbiExprNode *pNode = aVar.GetExprNode()->GetRealNode();
341 if (!pNode)
342 return;
343 SbiSymDef* pDef = pNode->GetVar();
344 // Variant, from 27.6.1997, #41090: empty -> must be Object
345 if( pDef->GetType() == SbxVARIANT || pDef->GetType() == SbxEMPTY )
346 pDef->SetType( SbxOBJECT );
347 else if( pDef->GetType() != SbxOBJECT )
348 Error( ERRCODE_BASIC_NEEDS_OBJECT );
350 pNode->SetType( SbxOBJECT );
352 std::optional<WithLocalVar> oLocalVar;
353 if (pDef->GetProcDef())
354 oLocalVar.emplace(*this, aVar);
356 OpenBlock(NIL, oLocalVar ? oLocalVar->m_aWithParent.GetExprNode() : aVar.GetExprNode());
357 StmntBlock( ENDWITH );
358 CloseBlock();
361 // LOOP/NEXT/WEND without construct
363 void SbiParser::BadBlock()
365 if( eEndTok )
366 Error( ERRCODE_BASIC_BAD_BLOCK, eEndTok );
367 else
368 Error( ERRCODE_BASIC_BAD_BLOCK, u"Loop/Next/Wend"_ustr );
371 // On expr Goto/Gosub n,n,n...
373 void SbiParser::OnGoto()
375 SbiExpression aCond( this );
376 aCond.Gen();
377 sal_uInt32 nLabelsTarget = aGen.Gen( SbiOpcode::ONJUMP_, 0 );
378 SbiToken eTok = Next();
379 if( eTok != GOTO && eTok != GOSUB )
381 Error( ERRCODE_BASIC_EXPECTED, u"GoTo/GoSub"_ustr );
382 eTok = GOTO;
385 sal_uInt32 nLbl = 0;
388 Next(); // get label
389 if( MayBeLabel() )
391 sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
392 aGen.Gen( SbiOpcode::JUMP_, nOff );
393 nLbl++;
395 else Error( ERRCODE_BASIC_LABEL_EXPECTED );
397 while( !bAbort && TestComma() );
398 if( eTok == GOSUB )
399 nLbl |= 0x8000;
400 aGen.Patch( nLabelsTarget, nLbl );
403 // GOTO/GOSUB
405 void SbiParser::Goto()
407 SbiOpcode eOp = eCurTok == GOTO ? SbiOpcode::JUMP_ : SbiOpcode::GOSUB_;
408 Next();
409 if( MayBeLabel() )
411 sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
412 aGen.Gen( eOp, nOff );
414 else Error( ERRCODE_BASIC_LABEL_EXPECTED );
417 // RETURN [label]
419 void SbiParser::Return()
421 Next();
422 if( MayBeLabel() )
424 sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
425 aGen.Gen( SbiOpcode::RETURN_, nOff );
427 else aGen.Gen( SbiOpcode::RETURN_, 0 );
430 // SELECT CASE
432 void SbiParser::Select()
434 TestToken( CASE );
435 SbiExpression aCase( this );
436 SbiToken eTok = NIL;
437 aCase.Gen();
438 aGen.Gen( SbiOpcode::CASE_ );
439 TestEoln();
440 sal_uInt32 nNextTarget = 0;
441 sal_uInt32 nDoneTarget = 0;
442 bool bElse = false;
444 while( !bAbort )
446 eTok = Next();
447 if( eTok == CASE )
449 if( nNextTarget )
451 aGen.BackChain( nNextTarget );
452 nNextTarget = 0;
454 aGen.Statement();
456 bool bDone = false;
457 sal_uInt32 nTrueTarget = 0;
458 if( Peek() == ELSE )
460 // CASE ELSE
461 Next();
462 bElse = true;
464 else while( !bDone )
466 if( bElse )
467 Error( ERRCODE_BASIC_SYNTAX );
468 SbiToken eTok2 = Peek();
469 if( eTok2 == IS || ( eTok2 >= EQ && eTok2 <= GE ) )
470 { // CASE [IS] operator expr
471 if( eTok2 == IS )
472 Next();
473 eTok2 = Peek();
474 if( eTok2 < EQ || eTok2 > GE )
475 Error( ERRCODE_BASIC_SYNTAX );
476 else Next();
477 SbiExpression aCompare( this );
478 aCompare.Gen();
479 nTrueTarget = aGen.Gen(
480 SbiOpcode::CASEIS_, nTrueTarget,
481 sal::static_int_cast< sal_uInt16 >(
482 SbxEQ + ( eTok2 - EQ ) ) );
484 else
485 { // CASE expr | expr TO expr
486 SbiExpression aCase1( this );
487 aCase1.Gen();
488 if( Peek() == TO )
490 // CASE a TO b
491 Next();
492 SbiExpression aCase2( this );
493 aCase2.Gen();
494 nTrueTarget = aGen.Gen( SbiOpcode::CASETO_, nTrueTarget );
496 else
497 // CASE a
498 nTrueTarget = aGen.Gen( SbiOpcode::CASEIS_, nTrueTarget, SbxEQ );
501 if( Peek() == COMMA ) Next();
502 else
504 TestEoln();
505 bDone = true;
509 if( !bElse )
511 nNextTarget = aGen.Gen( SbiOpcode::JUMP_, nNextTarget );
512 aGen.BackChain( nTrueTarget );
514 // build the statement body
515 while( !bAbort )
517 eTok = Peek();
518 if( eTok == CASE || eTok == ENDSELECT )
519 break;
520 if( !Parse() ) goto done;
521 eTok = Peek();
522 if( eTok == CASE || eTok == ENDSELECT )
523 break;
525 if( !bElse )
526 nDoneTarget = aGen.Gen( SbiOpcode::JUMP_, nDoneTarget );
528 else if( !IsEoln( eTok ) )
529 break;
531 done:
532 if( eTok != ENDSELECT )
533 Error( ERRCODE_BASIC_EXPECTED, ENDSELECT );
534 if( nNextTarget )
535 aGen.BackChain( nNextTarget );
536 aGen.BackChain( nDoneTarget );
537 aGen.Gen( SbiOpcode::ENDCASE_ );
540 // ON Error/Variable
542 void SbiParser::On()
544 SbiToken eTok = Peek();
545 OUString aString = SbiTokenizer::Symbol(eTok);
546 if (aString.equalsIgnoreAsciiCase("ERROR"))
548 eTok = ERROR_; // Error comes as SYMBOL
550 if( eTok != ERROR_ && eTok != LOCAL )
552 OnGoto();
554 else
556 if( eTok == LOCAL )
558 Next();
560 Next (); // no more TestToken, as there'd be an error otherwise
562 Next(); // get token after error
563 if( eCurTok == GOTO )
565 // ON ERROR GOTO label|0
566 Next();
567 bool bError_ = false;
568 if( MayBeLabel() )
570 if( eCurTok == NUMBER && !nVal )
572 aGen.Gen( SbiOpcode::STDERROR_ );
574 else
576 sal_uInt32 nOff = pProc->GetLabels().Reference( aSym );
577 aGen.Gen( SbiOpcode::ERRHDL_, nOff );
580 else if( eCurTok == MINUS )
582 Next();
583 if( eCurTok == NUMBER && nVal == 1 )
585 aGen.Gen( SbiOpcode::STDERROR_ );
587 else
589 bError_ = true;
592 if( bError_ )
594 Error( ERRCODE_BASIC_LABEL_EXPECTED );
597 else if( eCurTok == RESUME )
599 TestToken( NEXT );
600 aGen.Gen( SbiOpcode::NOERROR_ );
602 else Error( ERRCODE_BASIC_EXPECTED, u"GoTo/Resume"_ustr );
606 // RESUME [0]|NEXT|label
608 void SbiParser::Resume()
610 sal_uInt32 nLbl;
612 switch( Next() )
614 case EOS:
615 case EOLN:
616 aGen.Gen( SbiOpcode::RESUME_, 0 );
617 break;
618 case NEXT:
619 aGen.Gen( SbiOpcode::RESUME_, 1 );
620 Next();
621 break;
622 case NUMBER:
623 if( !nVal )
625 aGen.Gen( SbiOpcode::RESUME_, 0 );
626 break;
628 [[fallthrough]];
629 case SYMBOL:
630 if( MayBeLabel() )
632 nLbl = pProc->GetLabels().Reference( aSym );
633 aGen.Gen( SbiOpcode::RESUME_, nLbl );
634 Next();
635 break;
637 [[fallthrough]];
638 default:
639 Error( ERRCODE_BASIC_LABEL_EXPECTED );
643 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */