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