xref: /core/basic/source/comp/scanner.cxx (revision a400c865)
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 #include <basiccharclass.hxx>
21 #include <scanner.hxx>
22 #include <sbintern.hxx>
23 #include <runtime.hxx>
24 
25 #include <basic/sberrors.hxx>
26 #include <i18nlangtag/lang.h>
27 #include <svl/numformat.hxx>
28 #include <svl/zforlist.hxx>
29 #include <rtl/character.hxx>
30 #include <o3tl/string_view.hxx>
31 #include <utility>
32 #include <vector>
33 
SbiScanner(OUString _aBuf,StarBASIC * p)34 SbiScanner::SbiScanner(OUString _aBuf, StarBASIC* p)
35     : aBuf(std::move(_aBuf))
36     , nLineIdx(-1)
37     , nSaveLineIdx(-1)
38     , pBasic(p)
39     , eScanType(SbxVARIANT)
40     , nVal(0)
41     , nSavedCol1(0)
42     , nCol(0)
43     , nErrors(0)
44     , nColLock(0)
45     , nBufPos(0)
46     , nLine(0)
47     , nCol1(0)
48     , nCol2(0)
49     , bSymbol(false)
50     , bNumber(false)
51     , bSpaces(false)
52     , bAbort(false)
53     , bHash(true)
54     , bError(false)
55     , bCompatible(false)
56     , bVBASupportOn(false)
57     , bPrevLineExtentsComment(false)
58     , bClosingUnderscore(false)
59     , bLineEndsWithWhitespace(false)
60     , bInStatement(false)
61 {
62 }
63 
LockColumn()64 void SbiScanner::LockColumn()
65 {
66     if( !nColLock++ )
67         nSavedCol1 = nCol1;
68 }
69 
UnlockColumn()70 void SbiScanner::UnlockColumn()
71 {
72     if( nColLock )
73         nColLock--;
74 }
75 
GenError(ErrCode code)76 void SbiScanner::GenError( ErrCode code )
77 {
78     if( GetSbData()->bBlockCompilerError )
79     {
80         bAbort = true;
81         return;
82     }
83     if( !bError )
84     {
85         bool bRes = true;
86         // report only one error per statement
87         bError = true;
88         if( pBasic )
89         {
90             // in case of EXPECTED or UNEXPECTED it always refers
91             // to the last token, so take the Col1 over
92             sal_Int32 nc = nColLock ? nSavedCol1 : nCol1;
93             if ( code.anyOf(
94                     ERRCODE_BASIC_EXPECTED,
95                     ERRCODE_BASIC_UNEXPECTED,
96                     ERRCODE_BASIC_SYMBOL_EXPECTED,
97                     ERRCODE_BASIC_LABEL_EXPECTED) )
98             {
99                     nc = nCol1;
100                     if( nc > nCol2 ) nCol2 = nc;
101             }
102             bRes = pBasic->CError( code, aError, nLine, nc, nCol2 );
103         }
104         bAbort = bAbort || !bRes  || ( code == ERRCODE_BASIC_NO_MEMORY || code == ERRCODE_BASIC_PROG_TOO_LARGE );
105     }
106     nErrors++;
107 }
108 
109 
110 // used by SbiTokenizer::MayBeLabel() to detect a label
DoesColonFollow()111 bool SbiScanner::DoesColonFollow()
112 {
113     if(nCol < aLine.getLength() && aLine[nCol] == ':')
114     {
115         ++nLineIdx; ++nCol;
116         return true;
117     }
118     else
119         return false;
120 }
121 
122 // test for legal suffix
GetSuffixType(sal_Unicode c)123 static SbxDataType GetSuffixType( sal_Unicode c )
124 {
125     switch (c)
126     {
127     case '%':
128         return SbxINTEGER;
129     case '&':
130         return SbxLONG;
131     case '!':
132         return SbxSINGLE;
133     case '#':
134         return SbxDOUBLE;
135     case '@':
136         return SbxCURRENCY;
137     case '$':
138         return SbxSTRING;
139     default:
140         return SbxVARIANT;
141     }
142 }
143 
144 // reading the next symbol into the variables aSym, nVal and eType
145 // return value is sal_False at EOF or errors
146 #define BUF_SIZE 80
147 
scanAlphanumeric()148 void SbiScanner::scanAlphanumeric()
149 {
150     sal_Int32 n = nCol;
151     while(nCol < aLine.getLength() && (BasicCharClass::isAlphaNumeric(aLine[nCol], bCompatible) || aLine[nCol] == '_'))
152     {
153         ++nLineIdx;
154         ++nCol;
155     }
156     aSym = aLine.copy(n, nCol - n);
157 }
158 
scanGoto()159 void SbiScanner::scanGoto()
160 {
161     sal_Int32 n = nCol;
162     while(n < aLine.getLength() && BasicCharClass::isWhitespace(aLine[n]))
163         ++n;
164 
165     if(n + 1 < aLine.getLength())
166     {
167         std::u16string_view aTemp = aLine.subView(n, 2);
168         if(o3tl::equalsIgnoreAsciiCase(aTemp, u"to"))
169         {
170             aSym = "goto";
171             nLineIdx += n + 2 - nCol;
172             nCol = n + 2;
173         }
174     }
175 }
176 
readLine()177 bool SbiScanner::readLine()
178 {
179     if(nBufPos >= aBuf.getLength())
180         return false;
181 
182     sal_Int32 n = nBufPos;
183     sal_Int32 nLen = aBuf.getLength();
184 
185     while(n < nLen && aBuf[n] != '\r' && aBuf[n] != '\n')
186         ++n;
187 
188     // Trim trailing whitespace
189     sal_Int32 nEnd = n;
190     while(nBufPos < nEnd && BasicCharClass::isWhitespace(aBuf[nEnd - 1]))
191         --nEnd;
192 
193     // tdf#149402 - check if line ends with a whitespace
194     bLineEndsWithWhitespace = (n > nEnd);
195     aLine = aBuf.copy(nBufPos, nEnd - nBufPos);
196 
197     // Fast-forward past the line ending
198     if(n + 1 < nLen && aBuf[n] == '\r' && aBuf[n + 1] == '\n')
199         n += 2;
200     else if(n < nLen)
201         ++n;
202 
203     nBufPos = n;
204     nLineIdx = 0;
205 
206     ++nLine;
207     nCol = nCol1 = nCol2 = 0;
208     nColLock = 0;
209 
210     return true;
211 }
212 
213 // Function to check if a string is a valid compiler directive
isValidCompilerDirective(std::u16string_view directive)214 static bool isValidCompilerDirective(std::u16string_view directive) {
215     static const std::vector<std::u16string_view> validDirectives = {
216         u"if", u"elseif", u"else", u"end", u"const"
217     };
218 
219     return std::any_of(validDirectives.begin(), validDirectives.end(), [&](const auto& valid) {
220         return o3tl::matchIgnoreAsciiCase(directive, valid);
221     });
222 }
223 
NextSym()224 bool SbiScanner::NextSym()
225 {
226     // memorize for the EOLN-case
227     sal_Int32 nOldLine = nLine;
228     sal_Int32 nOldCol1 = nCol1;
229     sal_Int32 nOldCol2 = nCol2;
230     sal_Unicode buf[ BUF_SIZE ], *p = buf;
231 
232     eScanType = SbxVARIANT;
233     aSym.clear();
234     bHash = bSymbol = bNumber = bSpaces = false;
235 
236     // read in line?
237     if (nLineIdx == -1)
238     {
239         if(!readLine())
240             return false;
241 
242         nOldLine = nLine;
243         nOldCol1 = nOldCol2 = 0;
244     }
245 
246     const sal_Int32 nLineIdxScanStart = nLineIdx;
247 
248     if(nCol < aLine.getLength() && BasicCharClass::isWhitespace(aLine[nCol]))
249     {
250         bSpaces = true;
251         while(nCol < aLine.getLength() && BasicCharClass::isWhitespace(aLine[nCol]))
252         {
253             ++nLineIdx;
254             ++nCol;
255         }
256     }
257 
258     nCol1 = nCol;
259 
260     // only blank line?
261     if(nCol >= aLine.getLength())
262         goto eoln;
263 
264     if( bPrevLineExtentsComment )
265         goto PrevLineCommentLbl;
266 
267     if(nCol < aLine.getLength() && aLine[nCol] == '#')
268     {
269         sal_Int32 nLineTempIdx = nLineIdx;
270         std::u16string_view candidate(aLine.subView(nCol + 1));
271 
272         do
273         {
274             nLineTempIdx++;
275         } while (nLineTempIdx < aLine.getLength() && !BasicCharClass::isWhitespace(aLine[nLineTempIdx])
276             && aLine[nLineTempIdx] != '#' && aLine[nLineTempIdx] != ',');
277         // leave it if it is a date literal - it will be handled later
278         if (nLineTempIdx >= aLine.getLength() || aLine[nLineTempIdx] != '#')
279         {
280             ++nLineIdx;
281             ++nCol;
282             //handle compiler directives (# is first non-space character)
283             if (nOldCol2 == 0)
284             {
285                 if (isValidCompilerDirective(candidate))
286                 {
287                     // Skip the whole line if starts with a hash and is a valid compiler directive
288                     nCol = 0;
289                     goto eoln;
290                 }
291                 else
292                 {
293                     GenError(ERRCODE_BASIC_SYNTAX);
294                 }
295             }
296             else
297                 bHash = true;
298         }
299     }
300 
301     // copy character if symbol
302     if(nCol < aLine.getLength() && (BasicCharClass::isAlpha(aLine[nCol], bCompatible) || aLine[nCol] == '_'))
303     {
304         // if there's nothing behind '_' , it's the end of a line!
305         if(nCol + 1 == aLine.getLength() && aLine[nCol] == '_')
306         {
307             // Note that nCol is not incremented here...
308             ++nLineIdx;
309             goto eoln;
310         }
311 
312         bSymbol = true;
313 
314         scanAlphanumeric();
315 
316         // Special handling for "go to"
317         if(nCol < aLine.getLength() && bCompatible && aSym.equalsIgnoreAsciiCase("go"))
318             scanGoto();
319 
320         // tdf#125637 - check for closing underscore
321         if (nCol == aLine.getLength() && aLine[nCol - 1] == '_')
322         {
323             bClosingUnderscore = true;
324         }
325         // type recognition?
326         // don't test the exclamation mark
327         // if there's a symbol behind it
328         else if((nCol >= aLine.getLength() || aLine[nCol] != '!') ||
329                 (nCol + 1 >= aLine.getLength() || !BasicCharClass::isAlpha(aLine[nCol + 1], bCompatible)))
330         {
331             if(nCol < aLine.getLength())
332             {
333                 SbxDataType t(GetSuffixType(aLine[nCol]));
334                 if( t != SbxVARIANT )
335                 {
336                     eScanType = t;
337                     ++nLineIdx;
338                     ++nCol;
339                 }
340             }
341         }
342     }
343 
344     // read in and convert if number
345     else if((nCol < aLine.getLength() && rtl::isAsciiDigit(aLine[nCol])) ||
346             (nCol + 1 < aLine.getLength() && aLine[nCol] == '.' && rtl::isAsciiDigit(aLine[nCol + 1])))
347     {
348         short exp = 0;
349         short dec = 0;
350         eScanType = SbxDOUBLE;
351         bool bScanError = false;
352         bool bBufOverflow = false;
353         // All this because of 'D' or 'd' floating point type, sigh...
354         while(!bScanError && nCol < aLine.getLength() && strchr("0123456789.DEde", aLine[nCol]))
355         {
356             // from 4.1.1996: buffer full? -> go on scanning empty
357             if( (p-buf) == (BUF_SIZE-1) )
358             {
359                 bBufOverflow = true;
360                 ++nLineIdx;
361                 ++nCol;
362                 continue;
363             }
364             // point or exponent?
365             if(aLine[nCol] == '.')
366             {
367                 if( ++dec > 1 )
368                     bScanError = true;
369                 else
370                     *p++ = '.';
371             }
372             else if(strchr("DdEe", aLine[nCol]))
373             {
374                 if (++exp > 1)
375                     bScanError = true;
376                 else
377                 {
378                     *p++ = 'E';
379                     if (nCol + 1 < aLine.getLength() && (aLine[nCol+1] == '+' || aLine[nCol+1] == '-'))
380                     {
381                         ++nLineIdx;
382                         ++nCol;
383                         if( (p-buf) == (BUF_SIZE-1) )
384                         {
385                             bBufOverflow = true;
386                             continue;
387                         }
388                         *p++ = aLine[nCol];
389                     }
390                 }
391             }
392             else
393             {
394                 *p++ = aLine[nCol];
395             }
396             ++nLineIdx;
397             ++nCol;
398         }
399         *p = 0;
400         aSym = p; bNumber = true;
401 
402         // For bad characters, scan and parse errors generate only one error.
403         ErrCode nError = ERRCODE_NONE;
404         if (bScanError)
405         {
406             --nLineIdx;
407             --nCol;
408             aError = OUString( aLine[nCol]);
409             nError = ERRCODE_BASIC_BAD_CHAR_IN_NUMBER;
410         }
411 
412         rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
413         const sal_Unicode* pParseEnd = buf;
414         nVal = rtl_math_uStringToDouble( buf, buf+(p-buf), '.', ',', &eStatus, &pParseEnd );
415         if (pParseEnd != buf+(p-buf))
416         {
417             // e.g. "12e" or "12e+", or with bScanError "12d"+"E".
418             sal_Int32 nChars = buf+(p-buf) - pParseEnd;
419             nLineIdx -= nChars;
420             nCol -= nChars;
421             // For bScanError, nLineIdx and nCol were already decremented, just
422             // add that character to the parse end.
423             if (bScanError)
424                 ++nChars;
425             // Copy error position from original string, not the buffer
426             // replacement where "12dE" => "12EE".
427             aError = aLine.copy( nCol, nChars);
428             nError = ERRCODE_BASIC_BAD_CHAR_IN_NUMBER;
429         }
430         else if (eStatus != rtl_math_ConversionStatus_Ok)
431         {
432             // Keep the scan error and character at position, if any.
433             if (!nError)
434                 nError = ERRCODE_BASIC_MATH_OVERFLOW;
435         }
436 
437         if (nError)
438             GenError( nError );
439 
440         if( !dec && !exp )
441         {
442             if( nVal >= SbxMININT && nVal <= SbxMAXINT )
443                 eScanType = SbxINTEGER;
444             else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
445                     eScanType = SbxLONG;
446         }
447 
448         if( bBufOverflow )
449             GenError( ERRCODE_BASIC_MATH_OVERFLOW );
450 
451         // type recognition?
452         if( nCol < aLine.getLength() )
453         {
454             SbxDataType t(GetSuffixType(aLine[nCol]));
455             if( t != SbxVARIANT )
456             {
457                 eScanType = t;
458                 ++nLineIdx;
459                 ++nCol;
460             }
461             // tdf#130476 - don't allow String trailing data type character with numbers
462             if ( t == SbxSTRING )
463             {
464                 GenError( ERRCODE_BASIC_SYNTAX );
465             }
466         }
467     }
468 
469     // Hex/octal number? Read in and convert:
470     else if(aLine.getLength() - nCol > 1 && aLine[nCol] == '&')
471     {
472         ++nLineIdx; ++nCol;
473         sal_Unicode base = 16;
474         sal_Unicode xch  = aLine[nCol];
475         ++nLineIdx; ++nCol;
476         switch( rtl::toAsciiUpperCase( xch ) )
477         {
478             case 'O':
479                 base = 8;
480                 break;
481             case 'H':
482                 break;
483             default :
484                 // treated as an operator
485                 --nLineIdx; --nCol; nCol1 = nCol-1;
486                 aSym = "&";
487                 return true;
488         }
489         bNumber = true;
490         // Hex literals are signed Integers ( as defined by basic
491         // e.g. -2,147,483,648 through 2,147,483,647 (signed)
492         sal_uInt64 lu = 0;
493         bool bOverflow = false;
494         while(nCol < aLine.getLength() && BasicCharClass::isAlphaNumeric(aLine[nCol], false))
495         {
496             sal_Unicode ch = rtl::toAsciiUpperCase(aLine[nCol]);
497             ++nLineIdx; ++nCol;
498             if( ((base == 16 ) && rtl::isAsciiHexDigit( ch ) ) ||
499                      ((base == 8) && rtl::isAsciiOctalDigit( ch )))
500             {
501                 int i = ch  - '0';
502                 if( i > 9 ) i -= 7;
503                 lu = ( lu * base ) + i;
504                 if( lu > SAL_MAX_UINT32 )
505                 {
506                     bOverflow = true;
507                 }
508             }
509             else
510             {
511                 aError = OUString(ch);
512                 GenError( ERRCODE_BASIC_BAD_CHAR_IN_NUMBER );
513             }
514         }
515 
516         // tdf#130476 - take into account trailing data type characters
517         if( nCol < aLine.getLength() )
518         {
519             SbxDataType t(GetSuffixType(aLine[nCol]));
520             if( t != SbxVARIANT )
521             {
522                 eScanType = t;
523                 ++nLineIdx;
524                 ++nCol;
525             }
526             // tdf#130476 - don't allow String trailing data type character with numbers
527             if ( t == SbxSTRING )
528             {
529                 GenError( ERRCODE_BASIC_SYNTAX );
530             }
531         }
532 
533         // tdf#130476 - take into account trailing data type characters
534         switch ( eScanType )
535         {
536             case SbxINTEGER:
537                 nVal = static_cast<double>( static_cast<sal_Int16>(lu) );
538                 if ( lu > SbxMAXUINT )
539                 {
540                     bOverflow = true;
541                 }
542                 break;
543             case SbxLONG: nVal = static_cast<double>( static_cast<sal_Int32>(lu) ); break;
544             case SbxVARIANT:
545             {
546                 // tdf#62326 - If the value of the hex string without explicit type character lies within
547                 // the range of 0x8000 (SbxMAXINT + 1) and 0xFFFF (SbxMAXUINT) inclusive, cast the value
548                 // to 16 bit in order to get signed integers, e.g., SbxMININT through SbxMAXINT
549                 sal_Int32 ls = (lu > SbxMAXINT && lu <= SbxMAXUINT) ? static_cast<sal_Int16>(lu) : static_cast<sal_Int32>(lu);
550                 eScanType = ( ls >= SbxMININT && ls <= SbxMAXINT ) ? SbxINTEGER : SbxLONG;
551                 nVal = static_cast<double>(ls);
552                 break;
553             }
554             default:
555                 nVal = static_cast<double>(lu);
556                 break;
557         }
558         if( bOverflow )
559             GenError( ERRCODE_BASIC_MATH_OVERFLOW );
560     }
561 
562     // Strings:
563     else if (nLineIdx < aLine.getLength() && (aLine[nLineIdx] == '"' || aLine[nLineIdx] == '['))
564     {
565         sal_Unicode cSep = aLine[nLineIdx];
566         if( cSep == '[' )
567         {
568             bSymbol = true;
569             cSep = ']';
570         }
571         sal_Int32 n = nCol + 1;
572         while (nLineIdx < aLine.getLength())
573         {
574             do
575             {
576                 nLineIdx++;
577                 nCol++;
578             }
579             while (nLineIdx < aLine.getLength() && (aLine[nLineIdx] != cSep));
580             if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == cSep)
581             {
582                 nLineIdx++; nCol++;
583                 if (nLineIdx >= aLine.getLength() || aLine[nLineIdx] != cSep || cSep == ']')
584                 {
585                     // If VBA Interop then doesn't eat the [] chars
586                     if ( cSep == ']' && bVBASupportOn )
587                         aSym = aLine.copy( n - 1, nCol - n  + 1);
588                     else
589                         aSym = aLine.copy( n, nCol - n - 1 );
590                     // get out duplicate string delimiters
591                     OUStringBuffer aSymBuf(aSym.getLength());
592                     for ( sal_Int32 i = 0, len = aSym.getLength(); i < len; ++i )
593                     {
594                         aSymBuf.append( aSym[i] );
595                         if ( aSym[i] == cSep && ( i+1 < len ) && aSym[i+1] == cSep )
596                             ++i;
597                     }
598                     aSym = aSymBuf.makeStringAndClear();
599                     if( cSep != ']' )
600                         eScanType = SbxSTRING;
601                     break;
602                 }
603             }
604             else
605             {
606                 aError = OUString(cSep);
607                 GenError( ERRCODE_BASIC_EXPECTED );
608             }
609         }
610     }
611 
612     // Date:
613     else if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == '#')
614     {
615         sal_Int32 n = nCol + 1;
616         do
617         {
618             nLineIdx++;
619             nCol++;
620         }
621         while (nLineIdx < aLine.getLength() && (aLine[nLineIdx] != '#'));
622         if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == '#')
623         {
624             nLineIdx++; nCol++;
625             aSym = aLine.copy( n, nCol - n - 1 );
626 
627             // parse date literal
628             std::shared_ptr<SvNumberFormatter> pFormatter;
629             if (GetSbData()->pInst)
630             {
631                 pFormatter = GetSbData()->pInst->GetNumberFormatter();
632             }
633             else
634             {
635                 sal_uInt32 nDummy;
636                 pFormatter = SbiInstance::PrepareNumberFormatter( nDummy, nDummy, nDummy );
637             }
638             sal_uInt32 nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US);
639             bool bSuccess = pFormatter->IsNumberFormat(aSym, nIndex, nVal);
640             if( bSuccess )
641             {
642                 SvNumFormatType nType_ = pFormatter->GetType(nIndex);
643                 if( !(nType_ & SvNumFormatType::DATE) )
644                     bSuccess = false;
645             }
646 
647             if (!bSuccess)
648                 GenError( ERRCODE_BASIC_CONVERSION );
649 
650             bNumber = true;
651             eScanType = SbxDOUBLE;
652         }
653         else
654         {
655             aError = OUString('#');
656             GenError( ERRCODE_BASIC_EXPECTED );
657         }
658     }
659     // invalid characters:
660     else if (nLineIdx < aLine.getLength() && aLine[nLineIdx] >= 0x7F)
661     {
662         GenError( ERRCODE_BASIC_SYNTAX ); nLineIdx++; nCol++;
663     }
664     // other groups:
665     else
666     {
667         sal_Int32 n = 1;
668         auto nChar = nLineIdx < aLine.getLength() ? aLine[nLineIdx] : 0;
669         ++nLineIdx;
670         if (nLineIdx < aLine.getLength())
671         {
672             switch (nChar)
673             {
674                 case '<': if( aLine[nLineIdx] == '>' || aLine[nLineIdx] == '=' ) n = 2; break;
675                 case '>': if( aLine[nLineIdx] == '=' ) n = 2; break;
676                 case ':': if( aLine[nLineIdx] == '=' ) n = 2; break;
677             }
678         }
679         aSym = aLine.copy(nCol, std::min(n, aLine.getLength() - nCol));
680         nLineIdx += n-1; nCol = nCol + n;
681     }
682 
683     nCol2 = nCol-1;
684 
685 PrevLineCommentLbl:
686 
687     if (bPrevLineExtentsComment ||
688         (eScanType != SbxSTRING &&
689         (aSym.startsWith("'") || aSym.equalsIgnoreAsciiCase("REM") || aSym.startsWith("#"))))
690     {
691         bPrevLineExtentsComment = false;
692         aSym = "REM";
693         sal_Int32 nLen = aLine.getLength() - nLineIdx;
694         // tdf#149402 - don't extend comment if line ends in a whitespace (BasicCharClass::isWhitespace)
695         if (bCompatible && !bLineEndsWithWhitespace && aLine[nLineIdx + nLen - 1] == '_'
696             && aLine[nLineIdx + nLen - 2] == ' ')
697             bPrevLineExtentsComment = true;
698         nCol2 = nCol2 + nLen;
699         nLineIdx = -1;
700     }
701 
702     if (nLineIdx == nLineIdxScanStart)
703     {
704         GenError( ERRCODE_BASIC_SYMBOL_EXPECTED );
705         return false;
706     }
707 
708     return true;
709 
710 
711 eoln:
712     if (nCol && aLine[--nLineIdx] == '_' && !bClosingUnderscore)
713     {
714         nLineIdx = -1;
715         bool bRes = NextSym();
716         if( aSym.startsWith(".") )
717         {
718             // object _
719             //    .Method
720             // ^^^  <- spaces is legal in MSO VBA
721             bSpaces = false;
722         }
723         return bRes;
724     }
725     else
726     {
727         nLineIdx = -1;
728         nLine = nOldLine;
729         nCol1 = nOldCol1;
730         nCol2 = nOldCol2;
731         aSym = "\n";
732         nColLock = 0;
733         bClosingUnderscore = false;
734         // tdf#149157 - break multiline continuation in a comment after a new line
735         bPrevLineExtentsComment = false;
736         return true;
737     }
738 }
739 
740 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
741