2019-12-23 09:53:58 +00:00
/* For code that originates from ZDoom the following applies:
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2005 - 2016 Randy Heit
* * Copyright 2005 - 2016 Christoph Oelckers
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
2019-10-23 23:20:58 +00:00
// HEADER FILES ------------------------------------------------------------
# include <string.h>
# include <stdlib.h>
2020-04-11 22:04:02 +00:00
# include "engineerrors.h"
2019-10-23 23:20:58 +00:00
# include "sc_man.h"
# include "cmdlib.h"
2021-10-30 08:51:03 +00:00
2019-10-23 23:20:58 +00:00
# include "printf.h"
# include "name.h"
2019-10-31 22:25:21 +00:00
# include "v_text.h"
2021-10-30 08:51:03 +00:00
2020-04-11 21:48:14 +00:00
# include "zstring.h"
2020-04-11 21:50:43 +00:00
# include "name.h"
2020-09-10 15:46:54 +00:00
# include <inttypes.h>
2020-04-11 21:54:33 +00:00
# include "filesystem.h"
2019-10-23 23:20:58 +00:00
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
2020-04-11 22:04:02 +00:00
void VersionInfo : : operator = ( const char * string )
{
char * endp ;
major = ( int16_t ) clamp < unsigned long long > ( strtoull ( string , & endp , 10 ) , 0 , USHRT_MAX ) ;
if ( * endp = = ' . ' )
{
minor = ( int16_t ) clamp < unsigned long long > ( strtoull ( endp + 1 , & endp , 10 ) , 0 , USHRT_MAX ) ;
if ( * endp = = ' . ' )
{
revision = ( int16_t ) clamp < unsigned long long > ( strtoull ( endp + 1 , & endp , 10 ) , 0 , USHRT_MAX ) ;
if ( * endp ! = 0 ) major = USHRT_MAX ;
}
else if ( * endp = = 0 )
{
revision = 0 ;
}
else major = USHRT_MAX ;
}
else if ( * endp = = 0 )
{
minor = revision = 0 ;
}
else major = USHRT_MAX ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner Constructor
//
//==========================================================================
2022-12-08 10:40:22 +00:00
FScanner : : FScanner ( TMap < FName , Symbol > * extsymbols ) : symbols ( extsymbols ? * extsymbols : mysymbols )
2019-10-23 23:20:58 +00:00
{
ScriptOpen = false ;
}
2020-04-11 22:04:02 +00:00
//==========================================================================
//
// FScanner OpenLumpNum Constructor
//
//==========================================================================
2022-12-08 10:40:22 +00:00
FScanner : : FScanner ( int lumpnum , TMap < FName , Symbol > * extsymbols ) : symbols ( extsymbols ? * extsymbols : mysymbols )
2020-04-11 22:04:02 +00:00
{
ScriptOpen = false ;
OpenLumpNum ( lumpnum ) ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner :: Open
//
//==========================================================================
void FScanner : : Open ( const char * name )
{
2020-04-11 22:04:02 +00:00
int lump = fileSystem . CheckNumForFullName ( name , true ) ;
if ( lump = = - 1 )
2019-10-23 23:20:58 +00:00
{
I_Error ( " Could not find script lump '%s' \n " , name ) ;
}
2020-04-11 22:04:02 +00:00
OpenLumpNum ( lump ) ;
2019-11-03 19:24:50 +00:00
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner :: OpenFile
//
2023-08-19 17:32:17 +00:00
// Loads a script from a file.
2019-10-23 23:20:58 +00:00
//
//==========================================================================
2020-04-11 22:04:02 +00:00
bool FScanner : : OpenFile ( const char * name )
2019-10-23 23:20:58 +00:00
{
Close ( ) ;
2020-04-11 22:04:02 +00:00
2019-10-30 23:41:56 +00:00
FileReader fr ;
2020-04-11 22:04:02 +00:00
if ( ! fr . OpenFile ( name ) ) return false ;
auto filesize = fr . GetLength ( ) ;
auto filebuff = fr . Read ( ) ;
2023-08-19 14:57:37 +00:00
if ( filebuff . size ( ) = = 0 & & filesize > 0 ) return false ;
2020-04-11 22:04:02 +00:00
2023-08-19 14:57:37 +00:00
ScriptBuffer = FString ( ( const char * ) filebuff . data ( ) , filesize ) ;
2019-10-23 23:20:58 +00:00
ScriptName = name ; // This is used for error messages so the full file name is preferable
LumpNum = - 1 ;
PrepareScript ( ) ;
2020-04-11 22:04:02 +00:00
return true ;
2019-10-23 23:20:58 +00:00
}
//==========================================================================
//
// FScanner :: OpenMem
//
// Prepares a script that is already in memory for parsing. The memory is
// copied, so you can do whatever you want with it after opening it.
//
//==========================================================================
void FScanner : : OpenMem ( const char * name , const char * buffer , int size )
{
OpenString ( name , FString ( buffer , size ) ) ;
}
//==========================================================================
//
// FScanner :: OpenString
//
// Like OpenMem, but takes a string directly.
//
//==========================================================================
void FScanner : : OpenString ( const char * name , FString buffer )
{
Close ( ) ;
ScriptBuffer = buffer ;
ScriptName = name ;
LumpNum = - 1 ;
PrepareScript ( ) ;
}
2020-04-11 22:04:02 +00:00
//==========================================================================
//
// FScanner :: OpenLumpNum
//
// Loads a script from the lump directory
//
//==========================================================================
void FScanner : : OpenLumpNum ( int lump )
{
Close ( ) ;
{
2023-08-20 00:15:57 +00:00
auto mem = fileSystem . OpenFileReader ( lump ) ;
auto buff = ScriptBuffer . LockNewBuffer ( mem . GetLength ( ) ) ;
mem . Read ( buff , mem . GetLength ( ) ) ;
buff [ mem . GetLength ( ) ] = 0 ;
ScriptBuffer . UnlockBuffer ( ) ;
2020-04-11 22:04:02 +00:00
}
2023-08-20 00:15:57 +00:00
ScriptName = fileSystem . GetFileFullPath ( lump ) . c_str ( ) ;
2020-04-11 22:04:02 +00:00
LumpNum = lump ;
PrepareScript ( ) ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner :: PrepareScript
//
// Prepares a script for parsing.
//
//==========================================================================
void FScanner : : PrepareScript ( )
{
2020-09-05 19:33:04 +00:00
// If the file got a UTF-8 byte order mark, remove that.
if ( ScriptBuffer . Len ( ) > 3 & & ScriptBuffer [ 0 ] = = ( char ) 0xEF & & ScriptBuffer [ 1 ] = = ( char ) 0xBB & & ScriptBuffer [ 2 ] = = ( char ) 0xBF )
{
ScriptBuffer = ScriptBuffer . Mid ( 3 ) ;
}
2019-10-23 23:20:58 +00:00
// The scanner requires the file to end with a '\n', so add one if
// it doesn't already.
2019-12-23 09:53:58 +00:00
if ( ScriptBuffer . Len ( ) = = 0 | | ScriptBuffer . Back ( ) ! = ' \n ' )
2019-10-23 23:20:58 +00:00
{
// If the last character in the buffer is a null character, change
// it to a newline. Otherwise, append a newline to the end.
2019-12-23 09:53:58 +00:00
if ( ScriptBuffer . Len ( ) > 0 & & ScriptBuffer . Back ( ) = = ' \0 ' )
2019-10-23 23:20:58 +00:00
{
ScriptBuffer . LockBuffer ( ) [ ScriptBuffer . Len ( ) - 1 ] = ' \n ' ;
ScriptBuffer . UnlockBuffer ( ) ;
}
else
{
ScriptBuffer + = ' \n ' ;
}
}
ScriptPtr = & ScriptBuffer [ 0 ] ;
ScriptEndPtr = & ScriptBuffer [ ScriptBuffer . Len ( ) ] ;
Line = 1 ;
End = false ;
ScriptOpen = true ;
String = StringBuffer ;
AlreadyGot = false ;
LastGotToken = false ;
LastGotPtr = NULL ;
LastGotLine = 1 ;
CMode = false ;
Escape = true ;
StateMode = 0 ;
StateOptions = false ;
StringBuffer [ 0 ] = ' \0 ' ;
BigStringBuffer = " " ;
}
//==========================================================================
//
// FScanner :: Close
//
//==========================================================================
void FScanner : : Close ( )
{
ScriptOpen = false ;
ScriptBuffer = " " ;
BigStringBuffer = " " ;
StringBuffer [ 0 ] = ' \0 ' ;
String = StringBuffer ;
}
//==========================================================================
//
// FScanner :: SavePos
//
// Saves the current script location for restoration later
//
//==========================================================================
const FScanner : : SavedPos FScanner : : SavePos ( )
{
SavedPos pos ;
CheckOpen ( ) ;
if ( End )
{
pos . SavedScriptPtr = NULL ;
}
else
{
pos . SavedScriptPtr = ScriptPtr ;
}
pos . SavedScriptLine = Line ;
return pos ;
}
//==========================================================================
//
// FScanner :: RestorePos
//
// Restores the previously saved script location
//
//==========================================================================
void FScanner : : RestorePos ( const FScanner : : SavedPos & pos )
{
if ( pos . SavedScriptPtr )
{
ScriptPtr = pos . SavedScriptPtr ;
Line = pos . SavedScriptLine ;
End = false ;
}
else
{
End = true ;
}
AlreadyGot = false ;
LastGotToken = false ;
Crossed = false ;
}
2020-09-10 15:46:54 +00:00
long long FScanner : : mystrtoll ( const char * p , char * * endp , int base )
{
// Do not treat a leading 0 as an octal identifier if so desired.
if ( NoOctals & & * p = = ' 0 ' & & p [ 1 ] ! = ' x ' & & p [ 1 ] ! = ' X ' & & base = = 0 ) base = 10 ;
return strtoll ( p , endp , base ) ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner :: isText
//
// Checks if this is a text file.
//
//==========================================================================
bool FScanner : : isText ( )
{
for ( unsigned int i = 0 ; i < ScriptBuffer . Len ( ) ; i + + )
{
int c = ScriptBuffer [ i ] ;
if ( c < ' ' & & c ! = ' \n ' & & c ! = ' \r ' & & c ! = ' \t ' ) return false ;
}
return true ;
}
//==========================================================================
//
// FScanner :: SetCMode
//
// Enables/disables C mode. In C mode, more characters are considered to
// be whole words than in non-C mode.
//
//==========================================================================
void FScanner : : SetCMode ( bool cmode )
{
CMode = cmode ;
}
//==========================================================================
//
// FScanner :: SetEscape
//
// Turns the escape sequence \" in strings on or off. If it's off, that
// means you can't include quotation marks inside strings.
//
//==========================================================================
void FScanner : : SetEscape ( bool esc )
{
Escape = esc ;
}
//==========================================================================
//
// FScanner :: SetStateMode
//
// Enters state mode. This mode is very permissive for identifiers, which
// it returns as TOK_NonWhitespace. The only character sequences that are
// not returned as such are these:
//
// * stop
// * wait
// * fail
// * loop
// * goto - Automatically exits state mode after it's seen.
// * :
// * ;
// * } - Automatically exits state mode after it's seen.
//
// Quoted strings are returned as TOK_NonWhitespace, minus the quotes. Once
// two consecutive sequences of TOK_NonWhitespace have been encountered
// (which would be the state's sprite and frame specifiers), nearly normal
// processing resumes, with the exception that various identifiers
// used for state options will be returned as tokens and not identifiers.
// This ends once a ';' or '{' character is encountered.
//
//==========================================================================
void FScanner : : SetStateMode ( bool stately )
{
StateMode = stately ? 2 : 0 ;
StateOptions = stately ;
}
//==========================================================================
//
// FScanner::ScanString
//
// Set tokens true if you want TokenType to be set.
//
//==========================================================================
bool FScanner : : ScanString ( bool tokens )
{
const char * marker , * tok ;
bool return_val ;
2020-09-12 08:23:47 +00:00
ParseError = false ;
2019-10-23 23:20:58 +00:00
CheckOpen ( ) ;
if ( AlreadyGot )
{
AlreadyGot = false ;
if ( ! tokens | | LastGotToken )
{
return true ;
}
ScriptPtr = LastGotPtr ;
Line = LastGotLine ;
}
Crossed = false ;
if ( ScriptPtr > = ScriptEndPtr )
{
End = true ;
return false ;
}
LastGotPtr = ScriptPtr ;
LastGotLine = Line ;
// In case the generated scanner does not use marker, avoid compiler warnings.
2020-10-16 05:12:35 +00:00
// marker;
2019-10-23 23:20:58 +00:00
# include "sc_man_scanner.h"
LastGotToken = tokens ;
return return_val ;
}
//==========================================================================
//
// FScanner :: GetString
//
//==========================================================================
bool FScanner : : GetString ( )
{
return ScanString ( false ) ;
}
2020-09-27 09:58:54 +00:00
//==========================================================================
2019-10-23 23:20:58 +00:00
//
// FScanner :: MustGetString
//
//==========================================================================
void FScanner : : MustGetString ( void )
{
if ( FScanner : : GetString ( ) = = false )
{
ScriptError ( " Missing string (unexpected end of file). " ) ;
}
}
//==========================================================================
//
// FScanner :: MustGetStringName
//
//==========================================================================
void FScanner : : MustGetStringName ( const char * name )
{
MustGetString ( ) ;
if ( Compare ( name ) = = false )
{
ScriptError ( " Expected '%s', got '%s'. " , name , String ) ;
}
}
//==========================================================================
//
// FScanner :: CheckString
//
// Checks if the next token matches the specified string. Returns true if
// it does. If it doesn't, it ungets it and returns false.
//
//==========================================================================
bool FScanner : : CheckString ( const char * name )
{
if ( GetString ( ) )
{
if ( Compare ( name ) )
{
return true ;
}
UnGet ( ) ;
}
return false ;
}
//==========================================================================
//
// FScanner :: GetToken
//
// Sets sc_Float, sc_Number, and sc_Name based on sc_TokenType.
//
//==========================================================================
2020-09-10 15:46:54 +00:00
bool FScanner : : GetToken ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
if ( ScanString ( true ) )
{
2020-04-11 21:48:14 +00:00
if ( TokenType = = TK_IntConst )
2019-10-23 23:20:58 +00:00
{
char * stopper ;
// Check for unsigned
if ( String [ StringLen - 1 ] = = ' u ' | | String [ StringLen - 1 ] = = ' U ' | |
String [ StringLen - 2 ] = = ' u ' | | String [ StringLen - 2 ] = = ' U ' )
{
TokenType = TK_UIntConst ;
2019-10-30 17:09:00 +00:00
BigNumber = ( int64_t ) strtoull ( String , & stopper , 0 ) ;
2020-04-11 21:48:14 +00:00
Number = ( int ) BigNumber ; // clamp<int64_t>(BigNumber, 0, UINT_MAX);
2019-10-23 23:20:58 +00:00
Float = ( unsigned ) Number ;
}
else
{
2020-09-10 15:46:54 +00:00
BigNumber = mystrtoll ( String , & stopper , 0 ) ;
2020-04-11 21:48:14 +00:00
Number = ( int ) BigNumber ; // clamp<int64_t>(BigNumber, 0, UINT_MAX);
2019-10-23 23:20:58 +00:00
Float = Number ;
}
}
else if ( TokenType = = TK_FloatConst )
{
char * stopper ;
Float = strtod ( String , & stopper ) ;
}
else if ( TokenType = = TK_StringConst )
{
2020-09-10 15:46:54 +00:00
StringLen = strbin ( const_cast < char * > ( String ) ) ;
}
else if ( TokenType = = TK_Identifier & & evaluate & & symbols . CountUsed ( ) > 0 )
{
auto sym = symbols . CheckKey ( String ) ;
if ( sym )
{
TokenType = sym - > tokenType ;
BigNumber = sym - > Number ;
Number = ( int ) sym - > Number ;
Float = sym - > Float ;
// String will retain the actual symbol name.
}
2019-10-23 23:20:58 +00:00
}
return true ;
}
return false ;
}
//==========================================================================
//
// FScanner :: MustGetAnyToken
//
//==========================================================================
2020-09-10 15:46:54 +00:00
void FScanner : : MustGetAnyToken ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
2020-09-10 15:46:54 +00:00
if ( GetToken ( evaluate ) = = false )
2019-10-23 23:20:58 +00:00
{
ScriptError ( " Missing token (unexpected end of file). " ) ;
}
}
//==========================================================================
//
// FScanner :: TokenMustBe
//
//==========================================================================
void FScanner : : TokenMustBe ( int token )
{
if ( TokenType ! = token )
{
FString tok1 = TokenName ( token ) ;
FString tok2 = TokenName ( TokenType , String ) ;
ScriptError ( " Expected %s but got %s instead. " , tok1 . GetChars ( ) , tok2 . GetChars ( ) ) ;
}
}
//==========================================================================
//
// FScanner :: MustGetToken
//
//==========================================================================
2020-09-10 15:46:54 +00:00
void FScanner : : MustGetToken ( int token , bool evaluate )
2019-10-23 23:20:58 +00:00
{
2020-09-10 15:46:54 +00:00
MustGetAnyToken ( evaluate ) ;
2019-10-23 23:20:58 +00:00
TokenMustBe ( token ) ;
}
//==========================================================================
//
// FScanner :: CheckToken
//
// Checks if the next token matches the specified token. Returns true if
// it does. If it doesn't, it ungets it and returns false.
//
//==========================================================================
2020-09-10 15:46:54 +00:00
bool FScanner : : CheckToken ( int token , bool evaluate )
2019-10-23 23:20:58 +00:00
{
2020-09-10 15:46:54 +00:00
if ( GetToken ( evaluate ) )
2019-10-23 23:20:58 +00:00
{
if ( TokenType = = token )
{
return true ;
}
UnGet ( ) ;
}
return false ;
}
//==========================================================================
//
// FScanner :: GetNumber
//
//==========================================================================
2020-09-12 08:23:47 +00:00
bool FScanner : : GetNumber ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
char * stopper ;
CheckOpen ( ) ;
if ( GetString ( ) )
{
if ( strcmp ( String , " MAXINT " ) = = 0 )
{
Number = INT_MAX ;
}
else
{
2020-09-10 15:46:54 +00:00
BigNumber = mystrtoll ( String , & stopper , 0 ) ;
2020-04-11 21:48:14 +00:00
Number = ( int ) BigNumber ; // clamp<int64_t>(BigNumber, 0, UINT_MAX);
2019-10-23 23:20:58 +00:00
if ( * stopper ! = 0 )
{
2020-09-12 08:23:47 +00:00
if ( evaluate & & symbols . CountUsed ( ) )
{
auto sym = symbols . CheckKey ( String ) ;
if ( sym & & sym - > tokenType = = TK_IntConst )
{
BigNumber = sym - > Number ;
Number = ( int ) sym - > Number ;
Float = sym - > Float ;
// String will retain the actual symbol name.
return true ;
}
}
2019-10-23 23:20:58 +00:00
ScriptError ( " SC_GetNumber: Bad numeric constant \" %s \" . " , String ) ;
2020-09-14 22:11:08 +00:00
return false ;
2019-10-23 23:20:58 +00:00
}
}
Float = Number ;
return true ;
}
else
{
return false ;
}
}
//==========================================================================
//
// FScanner :: MustGetNumber
//
//==========================================================================
2020-09-12 08:23:47 +00:00
void FScanner : : MustGetNumber ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
2020-09-12 08:23:47 +00:00
if ( GetNumber ( evaluate ) = = false )
2019-10-23 23:20:58 +00:00
{
ScriptError ( " Missing integer (unexpected end of file). " ) ;
}
}
//==========================================================================
//
// FScanner :: CheckNumber
//
// similar to GetNumber but ungets the token if it isn't a number
// and does not print an error
//
//==========================================================================
2020-09-12 08:23:47 +00:00
bool FScanner : : CheckNumber ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
char * stopper ;
if ( GetString ( ) )
{
if ( String [ 0 ] = = 0 )
{
UnGet ( ) ;
return false ;
}
else if ( strcmp ( String , " MAXINT " ) = = 0 )
{
2019-10-30 23:41:56 +00:00
BigNumber = INT64_MAX ;
2019-10-23 23:20:58 +00:00
Number = INT_MAX ;
}
else
{
2020-09-10 15:46:54 +00:00
BigNumber = mystrtoll ( String , & stopper , 0 ) ;
2020-04-11 21:48:14 +00:00
Number = ( int ) BigNumber ; // clamp<int64_t>(BigNumber, 0, UINT_MAX);
2019-10-23 23:20:58 +00:00
if ( * stopper ! = 0 )
{
2020-09-12 08:23:47 +00:00
if ( evaluate & & symbols . CountUsed ( ) )
{
auto sym = symbols . CheckKey ( String ) ;
if ( sym & & sym - > tokenType = = TK_IntConst )
{
BigNumber = sym - > Number ;
Number = ( int ) sym - > Number ;
Float = sym - > Float ;
// String will retain the actual symbol name.
return true ;
}
}
2019-10-23 23:20:58 +00:00
UnGet ( ) ;
return false ;
}
}
Float = Number ;
return true ;
}
else
{
return false ;
}
}
//==========================================================================
//
// FScanner :: CheckFloat
//
// [GRB] Same as SC_CheckNumber, only for floats
//
//==========================================================================
2020-09-12 08:23:47 +00:00
bool FScanner : : CheckFloat ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
char * stopper ;
if ( GetString ( ) )
{
if ( String [ 0 ] = = 0 )
{
UnGet ( ) ;
return false ;
}
2021-12-30 09:30:21 +00:00
2019-10-23 23:20:58 +00:00
Float = strtod ( String , & stopper ) ;
if ( * stopper ! = 0 )
{
2020-09-12 08:23:47 +00:00
if ( evaluate & & symbols . CountUsed ( ) )
{
auto sym = symbols . CheckKey ( String ) ;
if ( sym & & sym - > tokenType = = TK_IntConst & & sym - > tokenType ! = TK_FloatConst )
{
BigNumber = sym - > Number ;
Number = ( int ) sym - > Number ;
Float = sym - > Float ;
// String will retain the actual symbol name.
return true ;
}
}
2019-10-23 23:20:58 +00:00
UnGet ( ) ;
return false ;
}
return true ;
}
else
{
return false ;
}
}
//==========================================================================
//
// FScanner :: GetFloat
//
//==========================================================================
2020-09-12 08:23:47 +00:00
bool FScanner : : GetFloat ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
char * stopper ;
CheckOpen ( ) ;
if ( GetString ( ) )
{
Float = strtod ( String , & stopper ) ;
if ( * stopper ! = 0 )
{
2020-09-12 08:23:47 +00:00
if ( evaluate & & symbols . CountUsed ( ) )
{
auto sym = symbols . CheckKey ( String ) ;
if ( sym & & sym - > tokenType = = TK_IntConst & & sym - > tokenType ! = TK_FloatConst )
{
BigNumber = sym - > Number ;
Number = ( int ) sym - > Number ;
Float = sym - > Float ;
// String will retain the actual symbol name.
return true ;
}
}
2019-10-23 23:20:58 +00:00
ScriptError ( " SC_GetFloat: Bad numeric constant \" %s \" . " , String ) ;
2020-09-14 22:11:08 +00:00
return false ;
2019-10-23 23:20:58 +00:00
}
Number = ( int ) Float ;
return true ;
}
else
{
return false ;
}
}
//==========================================================================
//
// FScanner :: MustGetFloat
//
//==========================================================================
2020-09-12 08:23:47 +00:00
void FScanner : : MustGetFloat ( bool evaluate )
2019-10-23 23:20:58 +00:00
{
2020-09-12 08:23:47 +00:00
if ( GetFloat ( evaluate ) = = false )
2019-10-23 23:20:58 +00:00
{
ScriptError ( " Missing floating-point number (unexpected end of file). " ) ;
}
}
//==========================================================================
//
// FScanner :: UnGet
//
// Assumes there is a valid string in String.
//
//==========================================================================
void FScanner : : UnGet ( )
{
AlreadyGot = true ;
AlreadyGotLine = LastGotLine ; // in case of an error we want the line of the last token.
}
//==========================================================================
//
// FScanner :: MatchString
//
// Returns the index of the first match to String from the passed
// array of strings, or -1 if not found.
//
//==========================================================================
int FScanner : : MatchString ( const char * const * strings , size_t stride )
{
int i ;
assert ( stride % sizeof ( const char * ) = = 0 ) ;
stride / = sizeof ( const char * ) ;
for ( i = 0 ; * strings ! = NULL ; i + + )
{
if ( Compare ( * strings ) )
{
return i ;
}
strings + = stride ;
}
return - 1 ;
}
//==========================================================================
//
// FScanner :: MustMatchString
//
//==========================================================================
int FScanner : : MustMatchString ( const char * const * strings , size_t stride )
{
int i ;
i = MatchString ( strings , stride ) ;
if ( i = = - 1 )
{
ScriptError ( " Unknown keyword '%s' " , String ) ;
}
return i ;
}
//==========================================================================
//
// FScanner :: Compare
//
//==========================================================================
bool FScanner : : Compare ( const char * text )
{
return ( stricmp ( text , String ) = = 0 ) ;
}
2019-12-23 09:53:58 +00:00
//==========================================================================
//
// Convenience helpers that parse an entire number including a leading minus or plus sign
//
//==========================================================================
2020-09-10 15:46:54 +00:00
bool FScanner : : ScanValue ( bool allowfloat , bool evaluate )
2019-12-23 09:53:58 +00:00
{
bool neg = false ;
2020-09-10 15:46:54 +00:00
if ( ! GetToken ( evaluate ) )
2019-12-23 09:53:58 +00:00
{
return false ;
}
if ( TokenType = = ' - ' | | TokenType = = ' + ' )
{
neg = TokenType = = ' - ' ;
2020-09-10 15:46:54 +00:00
if ( ! GetToken ( evaluate ) )
2019-12-23 09:53:58 +00:00
{
return false ;
}
}
2020-06-23 22:40:22 +00:00
if ( TokenType = = TK_FloatConst & & ! allowfloat )
2019-12-23 09:53:58 +00:00
return false ;
2020-06-23 22:40:22 +00:00
if ( TokenType ! = TK_IntConst & & TokenType ! = TK_FloatConst )
{
auto d = constants . CheckKey ( String ) ;
if ( ! d ) return false ;
if ( ! allowfloat & & int64_t ( * d ) ! = * d ) return false ;
2020-10-07 14:02:49 +00:00
BigNumber = int64_t ( * d ) ;
Number = int ( * d ) ;
2020-06-23 22:40:22 +00:00
Float = * d ;
2019-12-23 09:53:58 +00:00
}
if ( neg )
{
2020-06-23 22:40:22 +00:00
BigNumber = - BigNumber ;
2019-12-23 09:53:58 +00:00
Number = - Number ;
Float = - Float ;
}
return true ;
}
2020-09-10 15:46:54 +00:00
bool FScanner : : CheckValue ( bool allowfloat , bool evaluate )
2019-12-23 09:53:58 +00:00
{
auto savedstate = SavePos ( ) ;
2020-09-10 15:46:54 +00:00
bool res = ScanValue ( allowfloat , evaluate ) ;
2019-12-23 09:53:58 +00:00
if ( ! res ) RestorePos ( savedstate ) ;
return res ;
}
2020-09-10 15:46:54 +00:00
void FScanner : : MustGetValue ( bool allowfloat , bool evaluate )
2019-12-23 09:53:58 +00:00
{
2020-09-10 15:46:54 +00:00
if ( ! ScanValue ( allowfloat , evaluate ) ) ScriptError ( allowfloat ? " Numeric constant expected " : " Integer constant expected " ) ;
2019-12-23 09:53:58 +00:00
}
bool FScanner : : CheckBoolToken ( )
{
if ( CheckToken ( TK_True ) )
{
Number = 1 ;
Float = 1 ;
return true ;
}
if ( CheckToken ( TK_False ) )
{
Number = 0 ;
Float = 0 ;
return true ;
}
return false ;
}
void FScanner : : MustGetBoolToken ( )
{
if ( ! CheckBoolToken ( ) )
ScriptError ( " Expected true or false " ) ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// FScanner :: TokenName
//
// Returns the name of a token.
//
//==========================================================================
FString FScanner : : TokenName ( int token , const char * string )
{
static const char * const names [ ] =
{
# define xx(sym,str) str,
# include "sc_man_tokens.h"
} ;
FString work ;
if ( token > ' ' & & token < 256 )
{
work = ' \' ' ;
work + = token ;
work + = ' \' ' ;
}
else if ( token > = TK_Identifier & & token < TK_LastToken )
{
work = names [ token - TK_Identifier ] ;
if ( string ! = NULL & & token > = TK_Identifier & & token < = TK_FloatConst )
{
work + = ' ' ;
char quote = ( token = = TK_StringConst ) ? ' " ' : ' \' ' ;
work + = quote ;
work + = string ;
work + = quote ;
}
}
else
{
2021-12-24 08:56:02 +00:00
work . Format ( " Unknown(%d) " , token ) ;
2019-10-23 23:20:58 +00:00
}
return work ;
}
//==========================================================================
//
// FScanner::GetMessageLine
//
//==========================================================================
int FScanner : : GetMessageLine ( )
{
return AlreadyGot ? AlreadyGotLine : Line ;
}
//==========================================================================
//
// FScanner::ScriptError
//
//==========================================================================
void FScanner : : ScriptError ( const char * message , . . . )
{
FString composed ;
if ( message = = NULL )
{
composed = " Bad syntax. " ;
}
else
{
va_list arglist ;
va_start ( arglist , message ) ;
composed . VFormat ( message , arglist ) ;
va_end ( arglist ) ;
}
2020-09-12 08:23:47 +00:00
if ( NoFatalErrors )
{
Printf ( TEXTCOLOR_RED " Script error, \" %s \" " TEXTCOLOR_RED " line %d: \n " TEXTCOLOR_RED " %s \n " , ScriptName . GetChars ( ) ,
AlreadyGot ? AlreadyGotLine : Line , composed . GetChars ( ) ) ;
return ;
}
2019-10-23 23:20:58 +00:00
I_Error ( " Script error, \" %s \" line %d: \n %s \n " , ScriptName . GetChars ( ) ,
AlreadyGot ? AlreadyGotLine : Line , composed . GetChars ( ) ) ;
}
//==========================================================================
//
// FScanner::ScriptMessage
//
//==========================================================================
void FScanner : : ScriptMessage ( const char * message , . . . )
{
FString composed ;
if ( message = = NULL )
{
composed = " Bad syntax. " ;
}
else
{
va_list arglist ;
va_start ( arglist , message ) ;
composed . VFormat ( message , arglist ) ;
va_end ( arglist ) ;
}
2019-12-23 09:53:58 +00:00
Printf ( TEXTCOLOR_RED " Script error, \" %s \" " TEXTCOLOR_RED " line %d: \n " TEXTCOLOR_RED " %s \n " , ScriptName . GetChars ( ) ,
2019-10-23 23:20:58 +00:00
AlreadyGot ? AlreadyGotLine : Line , composed . GetChars ( ) ) ;
}
//==========================================================================
//
// FScanner :: CheckOpen
//
//==========================================================================
void FScanner : : CheckOpen ( )
{
if ( ScriptOpen = = false )
{
2020-04-15 11:35:16 +00:00
I_Error ( " SC_ call before SC_Open(). " ) ;
2019-10-23 23:20:58 +00:00
}
}
2020-09-10 15:46:54 +00:00
//==========================================================================
//
//
//
//==========================================================================
void FScanner : : AddSymbol ( const char * name , int64_t value )
{
Symbol sym ;
sym . tokenType = TK_IntConst ;
2022-12-08 10:40:22 +00:00
sym . Number = value ;
2020-10-07 14:02:49 +00:00
sym . Float = double ( value ) ;
2020-09-10 15:46:54 +00:00
symbols . Insert ( name , sym ) ;
}
//==========================================================================
//
//
//
//==========================================================================
void FScanner : : AddSymbol ( const char * name , uint64_t value )
{
Symbol sym ;
sym . tokenType = TK_UIntConst ;
sym . Number = value ;
2020-10-24 15:30:47 +00:00
sym . Float = ( double ) value ;
2020-09-10 15:46:54 +00:00
symbols . Insert ( name , sym ) ;
}
//==========================================================================
//
//
//
//==========================================================================
2020-09-10 22:18:13 +00:00
void FScanner : : SkipToEndOfBlock ( )
{
int depth = 0 ;
while ( 1 )
{
2020-09-14 22:11:08 +00:00
MustGetString ( ) ; // this will abort if it reaches the end of the file
if ( Compare ( " { " ) ) depth + + ;
else if ( Compare ( " } " ) )
2020-09-10 22:18:13 +00:00
{
depth - - ;
if ( depth < 0 ) return ;
}
}
}
//==========================================================================
//
//
//
//==========================================================================
2020-09-10 15:46:54 +00:00
void FScanner : : AddSymbol ( const char * name , double value )
{
Symbol sym ;
sym . tokenType = TK_FloatConst ;
sym . Number = ( int64_t ) value ;
sym . Float = value ;
symbols . Insert ( name , sym ) ;
}
2021-04-07 19:59:47 +00:00
//==========================================================================
//
//
//
//==========================================================================
int FScanner : : StartBraces ( FScanner : : SavedPos * braceend )
{
if ( CheckString ( " { " ) )
{
auto here = SavePos ( ) ;
SkipToEndOfBlock ( ) ;
* braceend = SavePos ( ) ;
RestorePos ( here ) ;
return 0 ;
}
else
{
ScriptError ( " '{' expected " ) ;
return - 1 ;
}
}
//==========================================================================
//
//
//
//==========================================================================
bool FScanner : : FoundEndBrace ( FScanner : : SavedPos & braceend )
{
auto here = SavePos ( ) ;
return here . SavedScriptPtr > = braceend . SavedScriptPtr ;
}
2019-12-23 09:53:58 +00:00
//==========================================================================
//
// a class that remembers a parser position
//
//==========================================================================
int FScriptPosition : : ErrorCounter ;
int FScriptPosition : : WarnCounter ;
2020-04-11 22:04:02 +00:00
int FScriptPosition : : Developer ;
2019-12-23 09:53:58 +00:00
bool FScriptPosition : : StrictErrors ; // makes all OPTERROR messages real errors.
bool FScriptPosition : : errorout ; // call I_Error instead of printing the error itself.
FScriptPosition : : FScriptPosition ( FString fname , int line )
{
2020-04-11 22:04:02 +00:00
FileName = fname ;
2019-12-23 09:53:58 +00:00
ScriptLine = line ;
}
FScriptPosition : : FScriptPosition ( FScanner & sc )
{
FileName = sc . ScriptName ;
ScriptLine = sc . GetMessageLine ( ) ;
}
FScriptPosition & FScriptPosition : : operator = ( FScanner & sc )
{
FileName = sc . ScriptName ;
ScriptLine = sc . GetMessageLine ( ) ;
return * this ;
}
//==========================================================================
//
// FScriptPosition::Message
//
//==========================================================================
void FScriptPosition : : Message ( int severity , const char * message , . . . ) const
{
FString composed ;
2020-04-11 22:04:02 +00:00
if ( severity = = MSG_DEBUGLOG & & Developer < DMSG_NOTIFY ) return ;
if ( severity = = MSG_DEBUGERROR & & Developer < DMSG_ERROR ) return ;
if ( severity = = MSG_DEBUGWARN & & Developer < DMSG_WARNING ) return ;
if ( severity = = MSG_DEBUGMSG & & Developer < DMSG_NOTIFY ) return ;
2019-12-23 09:53:58 +00:00
if ( severity = = MSG_OPTERROR )
{
2020-04-11 22:04:02 +00:00
severity = StrictErrors ? MSG_ERROR : MSG_WARNING ;
2019-12-23 09:53:58 +00:00
}
// This is mainly for catching the error with an exception handler.
if ( severity = = MSG_ERROR & & errorout ) severity = MSG_FATAL ;
if ( message = = NULL )
{
composed = " Bad syntax. " ;
}
else
{
va_list arglist ;
va_start ( arglist , message ) ;
composed . VFormat ( message , arglist ) ;
va_end ( arglist ) ;
}
const char * type = " " ;
const char * color ;
int level = PRINT_HIGH ;
switch ( severity )
{
default :
return ;
case MSG_WARNING :
case MSG_DEBUGWARN :
case MSG_DEBUGERROR : // This is intentionally not being printed as an 'error', the difference to MSG_DEBUGWARN is only the severity level at which it gets triggered.
WarnCounter + + ;
type = " warning " ;
color = TEXTCOLOR_ORANGE ;
break ;
case MSG_ERROR :
ErrorCounter + + ;
type = " error " ;
color = TEXTCOLOR_RED ;
break ;
case MSG_MESSAGE :
case MSG_DEBUGMSG :
type = " message " ;
color = TEXTCOLOR_GREEN ;
break ;
case MSG_DEBUGLOG :
case MSG_LOG :
type = " message " ;
level = PRINT_LOG ;
color = " " ;
break ;
case MSG_FATAL :
I_Error ( " Script error, \" %s \" line %d: \n %s \n " ,
FileName . GetChars ( ) , ScriptLine , composed . GetChars ( ) ) ;
return ;
}
Printf ( level , " %sScript %s, \" %s \" line %d: \n %s%s \n " ,
color , type , FileName . GetChars ( ) , ScriptLine , color , composed . GetChars ( ) ) ;
}
2019-10-23 23:20:58 +00:00
//==========================================================================
//
// ParseHex
//
//==========================================================================
2019-12-23 09:53:58 +00:00
int ParseHex ( const char * hex , FScriptPosition * sc )
2019-10-23 23:20:58 +00:00
{
2019-12-23 09:53:58 +00:00
const char * str ;
2019-10-23 23:20:58 +00:00
int num ;
num = 0 ;
str = hex ;
while ( * str )
{
num < < = 4 ;
if ( * str > = ' 0 ' & & * str < = ' 9 ' )
num + = * str - ' 0 ' ;
else if ( * str > = ' a ' & & * str < = ' f ' )
num + = 10 + * str - ' a ' ;
else if ( * str > = ' A ' & & * str < = ' F ' )
num + = 10 + * str - ' A ' ;
else {
2019-12-23 09:53:58 +00:00
if ( sc ) sc - > Message ( MSG_WARNING , " Bad hex number: %s " , hex ) ;
else Printf ( " Bad hex number: %s \n " , hex ) ;
2019-10-23 23:20:58 +00:00
return 0 ;
}
str + + ;
}
return num ;
}
2019-12-23 09:53:58 +00:00