1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
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
41 SbiExpression
aCond( this );
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
54 nEndLbl
= aGen
.Gen( SbiOpcode::JUMPF_
, 0 );
56 while (!EndsIfBranch(eTok
) && !bAbort
&& Parse())
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 );
74 aGen
.BackChain( nEndLbl
);
77 auto pCond
= std::make_unique
<SbiExpression
>( this );
79 nEndLbl
= aGen
.Gen( SbiOpcode::JUMPF_
, 0 );
83 while (!EndsIfBranch(eTok
) && !bAbort
&& Parse())
88 Error( ERRCODE_BASIC_BAD_BLOCK
, ELSEIF
); bAbort
= true; return;
95 sal_uInt32 nElseLbl
= nEndLbl
;
96 nEndLbl
= aGen
.Gen( SbiOpcode::JUMP_
, 0 );
97 aGen
.BackChain( nElseLbl
);
102 else if( eTok
== ENDIF
)
109 aGen
.BackChain( pnJmpToEndLbl
[iJmp
] );
115 bSingleLineIf
= true;
116 nEndLbl
= aGen
.Gen( SbiOpcode::JUMPF_
, 0 );
118 // tdf#128263: update push positions to correctly restore in Next()
125 if( !Parse() ) break;
127 if( eTok
== ELSE
|| eTok
== EOLN
|| eTok
== REM
)
133 sal_uInt32 nElseLbl
= nEndLbl
;
134 nEndLbl
= aGen
.Gen( SbiOpcode::JUMP_
, 0 );
135 aGen
.BackChain( nElseLbl
);
138 if( !Parse() ) break;
140 if( eTok
== EOLN
|| eTok
== REM
)
144 bSingleLineIf
= false;
146 aGen
.BackChain( nEndLbl
);
149 // ELSE/ELSEIF/ENDIF without IF
151 void SbiParser::NoIf()
153 Error( ERRCODE_BASIC_NO_IF
);
160 void SbiParser::DoLoop()
162 sal_uInt32 nStartLbl
= aGen
.GetPC();
164 SbiToken eTok
= Next();
167 // DO ... LOOP [WHILE|UNTIL expr]
170 if( eTok
== UNTIL
|| eTok
== WHILE
)
172 SbiExpression
aExpr( this );
174 aGen
.Gen( eTok
== UNTIL
? SbiOpcode::JUMPF_
: SbiOpcode::JUMPT_
, nStartLbl
);
176 if (eTok
== EOLN
|| eTok
== REM
)
177 aGen
.Gen (SbiOpcode::JUMP_
, nStartLbl
);
179 Error( ERRCODE_BASIC_EXPECTED
, WHILE
);
183 // DO [WHILE|UNTIL expr] ... LOOP
184 if( eTok
== UNTIL
|| eTok
== WHILE
)
186 SbiExpression
aCond( this );
189 sal_uInt32 nEndLbl
= aGen
.Gen( eTok
== UNTIL
? SbiOpcode::JUMPT_
: SbiOpcode::JUMPF_
, 0 );
192 aGen
.Gen( SbiOpcode::JUMP_
, nStartLbl
);
193 aGen
.BackChain( nEndLbl
);
200 void SbiParser::While()
202 SbiExpression
aCond( this );
203 sal_uInt32 nStartLbl
= aGen
.GetPC();
205 sal_uInt32 nEndLbl
= aGen
.Gen( SbiOpcode::JUMPF_
, 0 );
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
);
218 SbiExpression
aLvalue( this, SbOPERAND
);
219 if (!aLvalue
.IsVariable())
222 return; // the error is already set in SbiExpression ctor
224 aLvalue
.Gen(); // variable on the Stack
229 SbiExpression
aCollExpr( this, SbOPERAND
);
230 aCollExpr
.Gen(); // Collection var to for stack
232 aGen
.Gen( SbiOpcode::INITFOREACH_
);
237 SbiExpression
aStartExpr( this );
240 SbiExpression
aStopExpr( this );
245 SbiExpression
aStepExpr( this );
250 SbiExpression
aOne( this, 1, SbxINTEGER
);
254 // The stack has all 4 elements now: variable, start, end, increment
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 );
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
);
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
287 WithLocalVar(SbiParser
& rParser
, SbiExpression
& rVar
)
289 , m_aWithParent(createLocalVar(rParser
))
294 m_rParser
.aGen
.Gen(SbiOpcode::PUTC_
);
299 // {_with_library.module_offset} = Nothing
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() + ".");
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();
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
);
361 // LOOP/NEXT/WEND without construct
363 void SbiParser::BadBlock()
366 Error( ERRCODE_BASIC_BAD_BLOCK
, eEndTok
);
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 );
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
);
391 sal_uInt32 nOff
= pProc
->GetLabels().Reference( aSym
);
392 aGen
.Gen( SbiOpcode::JUMP_
, nOff
);
395 else Error( ERRCODE_BASIC_LABEL_EXPECTED
);
397 while( !bAbort
&& TestComma() );
400 aGen
.Patch( nLabelsTarget
, nLbl
);
405 void SbiParser::Goto()
407 SbiOpcode eOp
= eCurTok
== GOTO
? SbiOpcode::JUMP_
: SbiOpcode::GOSUB_
;
411 sal_uInt32 nOff
= pProc
->GetLabels().Reference( aSym
);
412 aGen
.Gen( eOp
, nOff
);
414 else Error( ERRCODE_BASIC_LABEL_EXPECTED
);
419 void SbiParser::Return()
424 sal_uInt32 nOff
= pProc
->GetLabels().Reference( aSym
);
425 aGen
.Gen( SbiOpcode::RETURN_
, nOff
);
427 else aGen
.Gen( SbiOpcode::RETURN_
, 0 );
432 void SbiParser::Select()
435 SbiExpression
aCase( this );
438 aGen
.Gen( SbiOpcode::CASE_
);
440 sal_uInt32 nNextTarget
= 0;
441 sal_uInt32 nDoneTarget
= 0;
451 aGen
.BackChain( nNextTarget
);
457 sal_uInt32 nTrueTarget
= 0;
467 Error( ERRCODE_BASIC_SYNTAX
);
468 SbiToken eTok2
= Peek();
469 if( eTok2
== IS
|| ( eTok2
>= EQ
&& eTok2
<= GE
) )
470 { // CASE [IS] operator expr
474 if( eTok2
< EQ
|| eTok2
> GE
)
475 Error( ERRCODE_BASIC_SYNTAX
);
477 SbiExpression
aCompare( this );
479 nTrueTarget
= aGen
.Gen(
480 SbiOpcode::CASEIS_
, nTrueTarget
,
481 sal::static_int_cast
< sal_uInt16
>(
482 SbxEQ
+ ( eTok2
- EQ
) ) );
485 { // CASE expr | expr TO expr
486 SbiExpression
aCase1( this );
492 SbiExpression
aCase2( this );
494 nTrueTarget
= aGen
.Gen( SbiOpcode::CASETO_
, nTrueTarget
);
498 nTrueTarget
= aGen
.Gen( SbiOpcode::CASEIS_
, nTrueTarget
, SbxEQ
);
501 if( Peek() == COMMA
) Next();
511 nNextTarget
= aGen
.Gen( SbiOpcode::JUMP_
, nNextTarget
);
512 aGen
.BackChain( nTrueTarget
);
514 // build the statement body
518 if( eTok
== CASE
|| eTok
== ENDSELECT
)
520 if( !Parse() ) goto done
;
522 if( eTok
== CASE
|| eTok
== ENDSELECT
)
526 nDoneTarget
= aGen
.Gen( SbiOpcode::JUMP_
, nDoneTarget
);
528 else if( !IsEoln( eTok
) )
532 if( eTok
!= ENDSELECT
)
533 Error( ERRCODE_BASIC_EXPECTED
, ENDSELECT
);
535 aGen
.BackChain( nNextTarget
);
536 aGen
.BackChain( nDoneTarget
);
537 aGen
.Gen( SbiOpcode::ENDCASE_
);
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
)
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
567 bool bError_
= false;
570 if( eCurTok
== NUMBER
&& !nVal
)
572 aGen
.Gen( SbiOpcode::STDERROR_
);
576 sal_uInt32 nOff
= pProc
->GetLabels().Reference( aSym
);
577 aGen
.Gen( SbiOpcode::ERRHDL_
, nOff
);
580 else if( eCurTok
== MINUS
)
583 if( eCurTok
== NUMBER
&& nVal
== 1 )
585 aGen
.Gen( SbiOpcode::STDERROR_
);
594 Error( ERRCODE_BASIC_LABEL_EXPECTED
);
597 else if( eCurTok
== RESUME
)
600 aGen
.Gen( SbiOpcode::NOERROR_
);
602 else Error( ERRCODE_BASIC_EXPECTED
, u
"GoTo/Resume"_ustr
);
606 // RESUME [0]|NEXT|label
608 void SbiParser::Resume()
616 aGen
.Gen( SbiOpcode::RESUME_
, 0 );
619 aGen
.Gen( SbiOpcode::RESUME_
, 1 );
625 aGen
.Gen( SbiOpcode::RESUME_
, 0 );
632 nLbl
= pProc
->GetLabels().Reference( aSym
);
633 aGen
.Gen( SbiOpcode::RESUME_
, nLbl
);
639 Error( ERRCODE_BASIC_LABEL_EXPECTED
);
643 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */