/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_basic.hxx"

#include "sbcomp.hxx"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#if defined UNX
#include <stdlib.h>
#else
#include <math.h>   // atof()
#endif
#include <rtl/math.hxx>
#include <vcl/svapp.hxx>
#include <unotools/charclass.hxx>

#include <runtime.hxx>

SbiScanner::SbiScanner( const ::rtl::OUString& rBuf, StarBASIC* p ) : aBuf( rBuf )
{
	pBasic   = p;
	pLine    = NULL;
	nVal	 = 0;
	eScanType = SbxVARIANT;
	nErrors  = 0;
	nBufPos  = 0;
	nCurCol1 = 0;
	nSavedCol1 = 0;
	nColLock = 0;
	nLine	 = 0;
	nCol1	 = 0;
	nCol2	 = 0;
	nCol     = 0;
	bError	 =
	bAbort   =
	bSpaces  =
	bNumber  =
	bSymbol  =
	bUsedForHilite =
	bCompatible = 
	bVBASupportOn = 
	bPrevLineExtentsComment = sal_False;
	bHash    =
	bErrors  = sal_True;
}

SbiScanner::~SbiScanner()
{}

void SbiScanner::LockColumn()
{
	if( !nColLock++ )
		nSavedCol1 = nCol1;
}

void SbiScanner::UnlockColumn()
{
	if( nColLock )
		nColLock--;
}

void SbiScanner::GenError( SbError code )
{
    if( GetSbData()->bBlockCompilerError )
    {
        bAbort = sal_True;
        return;
    }
	if( !bError && bErrors )
	{
		sal_Bool bRes = sal_True;
		// Nur einen Fehler pro Statement reporten
		bError = sal_True;
		if( pBasic )
		{
			// Falls EXPECTED oder UNEXPECTED kommen sollte, bezieht es sich
			// immer auf das letzte Token, also die Col1 uebernehmen
			sal_uInt16 nc = nColLock ? nSavedCol1 : nCol1;
			switch( code )
			{
				case SbERR_EXPECTED:
				case SbERR_UNEXPECTED:
				case SbERR_SYMBOL_EXPECTED:
				case SbERR_LABEL_EXPECTED:
					nc = nCol1;
					if( nc > nCol2 ) nCol2 = nc;
					break;
			}
			bRes = pBasic->CError( code, aError, nLine, nc, nCol2 );
		}
		bAbort |= !bRes |
			 ( code == SbERR_NO_MEMORY || code == SbERR_PROG_TOO_LARGE );
	}
	if( bErrors )
		nErrors++;
}

// Falls sofort ein Doppelpunkt folgt, wird sal_True zurueckgeliefert.
// Wird von SbiTokenizer::MayBeLabel() verwendet, um einen Label zu erkennen

sal_Bool SbiScanner::DoesColonFollow()
{
	if( pLine && *pLine == ':' )
	{
		pLine++; nCol++; return sal_True;
	}
	else return sal_False;
}

// Testen auf ein legales Suffix

static SbxDataType GetSuffixType( sal_Unicode c )
{
	static String aSuffixesStr = String::CreateFromAscii( "%&!#@ $" );
	if( c )
	{
		sal_uInt32 n = aSuffixesStr.Search( c );
		if( STRING_NOTFOUND != n && c != ' ' )
			return SbxDataType( (sal_uInt16) n + SbxINTEGER );
	}
	return SbxVARIANT;
}

// Einlesen des naechsten Symbols in die Variablen aSym, nVal und eType
// Returnwert ist sal_False bei EOF oder Fehlern
#define BUF_SIZE 80

namespace {

/** Returns true, if the passed character is a white space character. */
inline bool lclIsWhitespace( sal_Unicode cChar )
{
    return (cChar == ' ') || (cChar == '\t') || (cChar == '\f');
}

} // namespace

sal_Bool SbiScanner::NextSym()
{
	// Fuer den EOLN-Fall merken
	sal_uInt16 nOldLine = nLine;
	sal_uInt16 nOldCol1 = nCol1;
	sal_uInt16 nOldCol2 = nCol2;
	sal_Unicode buf[ BUF_SIZE ], *p = buf;
	bHash = sal_False;

	eScanType = SbxVARIANT;
	aSym.Erase();
	bSymbol =
	bNumber = bSpaces = sal_False;

	// Zeile einlesen?
	if( !pLine )
	{
		sal_Int32 n = nBufPos;
		sal_Int32 nLen = aBuf.getLength();
		if( nBufPos >= nLen )
			return sal_False;
		const sal_Unicode* p2 = aBuf.getStr();
		p2 += n;
		while( ( n < nLen ) && ( *p2 != '\n' ) && ( *p2 != '\r' ) )
			p2++, n++;
        // #163944# ignore trailing whitespace
        sal_Int32 nCopyEndPos = n;
        while( (nBufPos < nCopyEndPos) && lclIsWhitespace( aBuf[ nCopyEndPos - 1 ] ) )
            --nCopyEndPos;
		aLine = aBuf.copy( nBufPos, nCopyEndPos - nBufPos );
		if( n < nLen )
        {
		    if( *p2 == '\r' && *( p2+1 ) == '\n' )
			    n += 2;
		    else
			    n++;
        }
		nBufPos = n;
        pLine = aLine.getStr();
		nOldLine = ++nLine;
		nCol = nCol1 = nCol2 = nOldCol1 = nOldCol2 = 0;
		nColLock = 0;
	}

	// Leerstellen weg:
	while( lclIsWhitespace( *pLine ) )
		pLine++, nCol++, bSpaces = sal_True;

	nCol1 = nCol;

	// nur Leerzeile?
	if( !*pLine )
		goto eoln;

	if( bPrevLineExtentsComment )
		goto PrevLineCommentLbl;

	if( *pLine == '#' )
	{
		pLine++;
		nCol++;
		bHash = sal_True;
	}

	// Symbol? Dann Zeichen kopieren.
    if( BasicSimpleCharClass::isAlpha( *pLine, bCompatible ) || *pLine == '_' )
	{
		// Wenn nach '_' nichts kommt, ist es ein Zeilenabschluss!
		if(	*pLine == '_' && !*(pLine+1) )
		{	pLine++;
			goto eoln;	}
		bSymbol = sal_True;
		short n = nCol;
		for ( ; (BasicSimpleCharClass::isAlphaNumeric( *pLine, bCompatible ) || ( *pLine == '_' ) ); pLine++ )
			nCol++;
		aSym = aLine.copy( n, nCol - n );

		// Special handling for "go to"
		if( bCompatible && *pLine && aSym.EqualsIgnoreCaseAscii( "go" ) )
		{
			const sal_Unicode* pTestLine = pLine;
			short nTestCol = nCol;
			while( lclIsWhitespace( *pTestLine ) )
			{
				pTestLine++;
				nTestCol++;
			}

			if( *pTestLine && *(pTestLine + 1) )
			{
				String aTestSym = aLine.copy( nTestCol, 2 );
				if( aTestSym.EqualsIgnoreCaseAscii( "to" ) )
				{
					aSym = String::CreateFromAscii( "goto" );
					pLine = pTestLine + 2;
					nCol = nTestCol + 2;
				}
			}
		}

		// Abschliessendes '_' durch Space ersetzen, wenn Zeilenende folgt
		// (sonst falsche Zeilenfortsetzung)
		if(	!bUsedForHilite && !*pLine && *(pLine-1) == '_' )
		{
			aSym.GetBufferAccess();		// #109693 force copy if necessary
			*((sal_Unicode*)(pLine-1)) = ' ';		// cast wegen const
		}
		// Typkennung?
		// Das Ausrufezeichen bitte nicht testen, wenn
		// danach noch ein Symbol anschliesst
		else if( *pLine != '!' || !BasicSimpleCharClass::isAlpha( pLine[ 1 ], bCompatible ) )
		{
			SbxDataType t = GetSuffixType( *pLine );
			if( t != SbxVARIANT )
			{
				eScanType = t;
				pLine++;
				nCol++;
			}
		}
	}

	// Zahl? Dann einlesen und konvertieren.
	else if( BasicSimpleCharClass::isDigit( *pLine & 0xFF )
		|| ( *pLine == '.' && BasicSimpleCharClass::isDigit( *(pLine+1) & 0xFF ) ) )
	{
		short exp = 0;
		short comma = 0;
		short ndig = 0;
		short ncdig = 0;
		eScanType = SbxDOUBLE;
		sal_Bool bBufOverflow = sal_False;
		while( strchr( "0123456789.DEde", *pLine ) && *pLine )
		{
			// AB 4.1.1996: Buffer voll? -> leer weiter scannen
			if( (p-buf) == (BUF_SIZE-1) )
			{
				bBufOverflow = sal_True;
				pLine++, nCol++;
				continue;
			}
			// Komma oder Exponent?
			if( *pLine == '.' )
			{
				if( ++comma > 1 )
				{
					pLine++; nCol++; continue;
				}
				else *p++ = *pLine++, nCol++;
			}
			else if( strchr( "DdEe", *pLine ) )
			{
				if (++exp > 1)
				{
					pLine++; nCol++; continue;
				}
//              if( toupper( *pLine ) == 'D' )
//                  eScanType = SbxDOUBLE;
				*p++ = 'E'; pLine++; nCol++;
				// Vorzeichen hinter Exponent?
				if( *pLine == '+' )
					pLine++, nCol++;
				else
				if( *pLine == '-' )
					*p++ = *pLine++, nCol++;
			}
			else
			{
				*p++ = *pLine++, nCol++;
				if( comma && !exp ) ncdig++;
			}
			if (!exp) ndig++;
		}
		*p = 0;
		aSym = p; bNumber = sal_True;
		// Komma, Exponent mehrfach vorhanden?
		if( comma > 1 || exp > 1 )
		{	aError = '.';
			GenError( SbERR_BAD_CHAR_IN_NUMBER );	}

		// #57844 Lokalisierte Funktion benutzen
	    nVal = rtl_math_uStringToDouble( buf, buf+(p-buf), '.', ',', NULL, NULL );
		// ALT: nVal = atof( buf );

		ndig = ndig - comma;
		if( !comma && !exp )
		{
			if( nVal >= SbxMININT && nVal <= SbxMAXINT )
				eScanType = SbxINTEGER;
			else
			if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
				eScanType = SbxLONG;
		}
		if( bBufOverflow )
			GenError( SbERR_MATH_OVERFLOW );
		// zu viele Zahlen fuer SINGLE?
//      if (ndig > 15 || ncdig > 6)
//          eScanType = SbxDOUBLE;
//      else
//      if( nVal > SbxMAXSNG || nVal < SbxMINSNG )
//          eScanType = SbxDOUBLE;

		// Typkennung?
		SbxDataType t = GetSuffixType( *pLine );
		if( t != SbxVARIANT )
		{
			eScanType = t;
			pLine++;
			nCol++;
		}
	}

	// Hex/Oktalzahl? Einlesen und konvertieren:
	else if( *pLine == '&' )
	{
		pLine++; nCol++;
		sal_Unicode cmp1[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', 0 };
		sal_Unicode cmp2[] = { '0', '1', '2', '3', '4', '5', '6', '7', 0 };
		sal_Unicode *cmp = cmp1;
		//char *cmp = "0123456789ABCDEF";
		sal_Unicode base = 16;
		sal_Unicode ndig = 8;
		sal_Unicode xch  = *pLine++ & 0xFF; nCol++;
		switch( toupper( xch ) )
		{
			case 'O':
				cmp = cmp2; base = 8; ndig = 11; break;
				//cmp = "01234567"; base = 8; ndig = 11; break;
			case 'H':
				break;
			default :
				// Wird als Operator angesehen
				pLine--; nCol--; nCol1 = nCol-1; aSym = '&'; return SYMBOL;
		}
		bNumber = sal_True;
		long l = 0;
		int i;
		sal_Bool bBufOverflow = sal_False;
		while( BasicSimpleCharClass::isAlphaNumeric( *pLine & 0xFF, bCompatible ) )
		{
			sal_Unicode ch = sal::static_int_cast< sal_Unicode >(
                toupper( *pLine & 0xFF ) );
			pLine++; nCol++;
			// AB 4.1.1996: Buffer voll, leer weiter scannen
			if( (p-buf) == (BUF_SIZE-1) )
				bBufOverflow = sal_True;
			else if( String( cmp ).Search( ch ) != STRING_NOTFOUND )
			//else if( strchr( cmp, ch ) )
				*p++ = ch;
			else
			{
				aError = ch;
				GenError( SbERR_BAD_CHAR_IN_NUMBER );
			}
		}
		*p = 0;
		for( p = buf; *p; p++ )
		{
			i = (*p & 0xFF) - '0';
			if( i > 9 ) i -= 7;
			l = ( l * base ) + i;
			if( !ndig-- )
			{
				GenError( SbERR_MATH_OVERFLOW ); break;
			}
		}
		if( *pLine == '&' ) pLine++, nCol++;
		nVal = (double) l;
		eScanType = ( l >= SbxMININT && l <= SbxMAXINT ) ? SbxINTEGER : SbxLONG;
		if( bBufOverflow )
			GenError( SbERR_MATH_OVERFLOW );
	}

	// Strings:
	else if( *pLine == '"' || *pLine == '[' )
	{
		sal_Unicode cSep = *pLine;
		if( cSep == '[' )
			bSymbol = sal_True, cSep = ']';
		short n = nCol+1;
		while( *pLine )
		{
			do pLine++, nCol++;
			while( *pLine && ( *pLine != cSep ) );
			if( *pLine == cSep )
			{
				pLine++; nCol++;
				if( *pLine != cSep || cSep == ']' ) break;
			} else aError = cSep, GenError( SbERR_EXPECTED );
		}
		// If VBA Interop then doen't eat the [] chars	
		if ( cSep == ']' && bVBASupportOn )
			aSym = aLine.copy( n - 1, nCol - n  + 1);
		else
			aSym = aLine.copy( n, nCol - n - 1 );
		// Doppelte Stringbegrenzer raus
		String s( cSep );
		s += cSep;
		sal_uInt16 nIdx = 0;
		do 
		{
			nIdx = aSym.Search( s, nIdx );
			if( nIdx == STRING_NOTFOUND )
				break;
			aSym.Erase( nIdx, 1 );
			nIdx++;
		} 
		while( true );
		if( cSep != ']' )
			eScanType = ( cSep == '#' ) ? SbxDATE : SbxSTRING;
	}
	// ungueltige Zeichen:
	else if( ( *pLine & 0xFF ) >= 0x7F )
	{
		GenError( SbERR_SYNTAX ); pLine++; nCol++;
	}
	// andere Gruppen:
	else
	{
		short n = 1;
		switch( *pLine++ )
		{
			case '<': if( *pLine == '>' || *pLine == '=' ) n = 2; break;
			case '>': if( *pLine == '=' ) n = 2; break;
			case ':': if( *pLine == '=' ) n = 2; break;
		}
		aSym = aLine.copy( nCol, n );
		pLine += n-1; nCol = nCol + n;
	}

	nCol2 = nCol-1;

PrevLineCommentLbl:
	// Kommentar?
	if( bPrevLineExtentsComment || (eScanType != SbxSTRING &&
		( aSym.GetBuffer()[0] == '\'' || aSym.EqualsIgnoreCaseAscii( "REM" ) ) ) )
	{
		bPrevLineExtentsComment = sal_False;
		aSym = String::CreateFromAscii( "REM" );
		sal_uInt16 nLen = String( pLine ).Len();
		if( bCompatible && pLine[ nLen - 1 ] == '_' && pLine[ nLen - 2 ] == ' ' )
			bPrevLineExtentsComment = sal_True;
		nCol2 = nCol2 + nLen;
		pLine = NULL;
	}
	return sal_True;

	// Sonst Zeilen-Ende: aber bitte auf '_' testen, ob die
	// Zeile nicht weitergeht!
eoln:
	if( nCol && *--pLine == '_' )
	{
		pLine = NULL; 
		bool bRes = NextSym();
		if( bVBASupportOn && aSym.GetBuffer()[0] == '.' )
		{
			// object _
			//    .Method
			// ^^^  <- spaces is legal in MSO VBA
			OSL_TRACE("*** resetting bSpaces***"); 
			bSpaces = sal_False; 
		}
		return bRes;
	}
	else
	{
		pLine = NULL;
		nLine = nOldLine;
		nCol1 = nOldCol1;
		nCol2 = nOldCol2;
		aSym = '\n';
		nColLock = 0;
		return sal_True;
	}
}

LetterTable BasicSimpleCharClass::aLetterTable;

LetterTable::LetterTable( void )
{
	for( int i = 0 ; i < 256 ; ++i )
		IsLetterTab[i] = false;

	IsLetterTab[0xC0] = true;	// À , CAPITAL LETTER A WITH GRAVE ACCENT
	IsLetterTab[0xC1] = true;	// Á , CAPITAL LETTER A WITH ACUTE ACCENT
	IsLetterTab[0xC2] = true;	// Â , CAPITAL LETTER A WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xC3] = true;	// Ã , CAPITAL LETTER A WITH TILDE
	IsLetterTab[0xC4] = true;	// Ä , CAPITAL LETTER A WITH DIAERESIS
	IsLetterTab[0xC5] = true;	// Å , CAPITAL LETTER A WITH RING ABOVE
	IsLetterTab[0xC6] = true;	// Æ , CAPITAL LIGATURE AE
	IsLetterTab[0xC7] = true;	// Ç , CAPITAL LETTER C WITH CEDILLA
	IsLetterTab[0xC8] = true;	// È , CAPITAL LETTER E WITH GRAVE ACCENT
	IsLetterTab[0xC9] = true;	// É , CAPITAL LETTER E WITH ACUTE ACCENT
	IsLetterTab[0xCA] = true;	// Ê , CAPITAL LETTER E WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xCB] = true;	// Ë , CAPITAL LETTER E WITH DIAERESIS
	IsLetterTab[0xCC] = true;	// Ì , CAPITAL LETTER I WITH GRAVE ACCENT
	IsLetterTab[0xCD] = true;	// Í , CAPITAL LETTER I WITH ACUTE ACCENT
	IsLetterTab[0xCE] = true;	// Î , CAPITAL LETTER I WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xCF] = true;	// Ï , CAPITAL LETTER I WITH DIAERESIS
	IsLetterTab[0xD0] = true;	// Ð , CAPITAL LETTER ETH
	IsLetterTab[0xD1] = true;	// Ñ , CAPITAL LETTER N WITH TILDE
	IsLetterTab[0xD2] = true;	// Ò , CAPITAL LETTER O WITH GRAVE ACCENT
	IsLetterTab[0xD3] = true;	// Ó , CAPITAL LETTER O WITH ACUTE ACCENT
	IsLetterTab[0xD4] = true;	// Ô , CAPITAL LETTER O WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xD5] = true;	// Õ , CAPITAL LETTER O WITH TILDE
	IsLetterTab[0xD6] = true;	// Ö , CAPITAL LETTER O WITH DIAERESIS
	IsLetterTab[0xD8] = true;	// Ø , CAPITAL LETTER O WITH STROKE
	IsLetterTab[0xD9] = true;	// Ù , CAPITAL LETTER U WITH GRAVE ACCENT
	IsLetterTab[0xDA] = true;	// Ú , CAPITAL LETTER U WITH ACUTE ACCENT
	IsLetterTab[0xDB] = true;	// Û , CAPITAL LETTER U WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xDC] = true;	// Ü , CAPITAL LETTER U WITH DIAERESIS
	IsLetterTab[0xDD] = true;	// Ý , CAPITAL LETTER Y WITH ACUTE ACCENT
	IsLetterTab[0xDE] = true;	// Þ , CAPITAL LETTER THORN
	IsLetterTab[0xDF] = true;	// ß , SMALL LETTER SHARP S
	IsLetterTab[0xE0] = true;	// à , SMALL LETTER A WITH GRAVE ACCENT
	IsLetterTab[0xE1] = true;	// á , SMALL LETTER A WITH ACUTE ACCENT
	IsLetterTab[0xE2] = true;	// â , SMALL LETTER A WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xE3] = true;	// ã , SMALL LETTER A WITH TILDE
	IsLetterTab[0xE4] = true;	// ä , SMALL LETTER A WITH DIAERESIS
	IsLetterTab[0xE5] = true;	// å , SMALL LETTER A WITH RING ABOVE
	IsLetterTab[0xE6] = true;	// æ , SMALL LIGATURE AE
	IsLetterTab[0xE7] = true;	// ç , SMALL LETTER C WITH CEDILLA
	IsLetterTab[0xE8] = true;	// è , SMALL LETTER E WITH GRAVE ACCENT
	IsLetterTab[0xE9] = true;	// é , SMALL LETTER E WITH ACUTE ACCENT
	IsLetterTab[0xEA] = true;	// ê , SMALL LETTER E WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xEB] = true;	// ë , SMALL LETTER E WITH DIAERESIS
	IsLetterTab[0xEC] = true;	// ì , SMALL LETTER I WITH GRAVE ACCENT
	IsLetterTab[0xED] = true;	// í , SMALL LETTER I WITH ACUTE ACCENT
	IsLetterTab[0xEE] = true;	// î , SMALL LETTER I WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xEF] = true;	// ï , SMALL LETTER I WITH DIAERESIS
	IsLetterTab[0xF0] = true;	// ð , SMALL LETTER ETH
	IsLetterTab[0xF1] = true;	// ñ , SMALL LETTER N WITH TILDE
	IsLetterTab[0xF2] = true;	// ò , SMALL LETTER O WITH GRAVE ACCENT
	IsLetterTab[0xF3] = true;	// ó , SMALL LETTER O WITH ACUTE ACCENT
	IsLetterTab[0xF4] = true;	// ô , SMALL LETTER O WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xF5] = true;	// õ , SMALL LETTER O WITH TILDE
	IsLetterTab[0xF6] = true;	// ö , SMALL LETTER O WITH DIAERESIS
	IsLetterTab[0xF8] = true;	// ø , SMALL LETTER O WITH OBLIQUE BAR
	IsLetterTab[0xF9] = true;	// ù , SMALL LETTER U WITH GRAVE ACCENT
	IsLetterTab[0xFA] = true;	// ú , SMALL LETTER U WITH ACUTE ACCENT
	IsLetterTab[0xFB] = true;	// û , SMALL LETTER U WITH CIRCUMFLEX ACCENT
	IsLetterTab[0xFC] = true;	// ü , SMALL LETTER U WITH DIAERESIS
	IsLetterTab[0xFD] = true;	// ý , SMALL LETTER Y WITH ACUTE ACCENT
	IsLetterTab[0xFE] = true;	// þ , SMALL LETTER THORN
	IsLetterTab[0xFF] = true;	// ÿ , SMALL LETTER Y WITH DIAERESIS
}

bool LetterTable::isLetterUnicode( sal_Unicode c )
{
	static CharClass* pCharClass = NULL;
	if( pCharClass == NULL )
		pCharClass = new CharClass( Application::GetSettings().GetLocale() );
	String aStr( c );
	bool bRet = pCharClass->isLetter( aStr, 0 );
	return bRet;
}
