1469 lines
31 KiB
C
1469 lines
31 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2007 ©
|
|
// parselib.c - script files parser
|
|
//=======================================================================
|
|
|
|
#include "launch.h"
|
|
|
|
#define MAX_TOKEN_LENGTH MAX_SYSPATH
|
|
|
|
// number sub-types
|
|
enum
|
|
{
|
|
NT_BINARY = BIT(0),
|
|
NT_OCTAL = BIT(1),
|
|
NT_DECIMAL = BIT(2),
|
|
NT_HEXADECIMAL = BIT(3),
|
|
NT_INTEGER = BIT(4),
|
|
NT_FLOAT = BIT(5),
|
|
NT_UNSIGNED = BIT(6),
|
|
NT_LONG = BIT(7),
|
|
NT_SINGLE = BIT(8),
|
|
NT_DOUBLE = BIT(9),
|
|
NT_EXTENDED = BIT(10),
|
|
};
|
|
|
|
// punctuation sub-types
|
|
enum
|
|
{
|
|
PT_RSHIFT_ASSIGN = 1,
|
|
PT_LSHIFT_ASSIGN,
|
|
PT_PARAMETERS,
|
|
PT_PRECOMPILER_MERGE,
|
|
PT_LOGIC_AND,
|
|
PT_LOGIC_OR,
|
|
PT_LOGIC_GEQUAL,
|
|
PT_LOGIC_LEQUAL,
|
|
PT_LOGIC_EQUAL,
|
|
PT_LOGIC_NOTEQUAL,
|
|
PT_MUL_ASSIGN,
|
|
PT_DIV_ASSIGN,
|
|
PT_MOD_ASSIGN,
|
|
PT_ADD_ASSIGN,
|
|
PT_SUB_ASSIGN,
|
|
PT_INCREMENT,
|
|
PT_DECREMENT,
|
|
PT_BINARY_AND_ASSIGN,
|
|
PT_BINARY_OR_ASSIGN,
|
|
PT_BINARY_XOR_ASSIGN,
|
|
PT_RSHIFT,
|
|
PT_LSHIFT,
|
|
PT_POINTER_REFERENCE,
|
|
PT_CPP_1,
|
|
PT_CPP_2,
|
|
PT_MUL,
|
|
PT_DIV,
|
|
PT_MOD,
|
|
PT_ADD,
|
|
PT_SUB,
|
|
PT_ASSIGN,
|
|
PT_BINARY_AND,
|
|
PT_BINARY_OR,
|
|
PT_BINARY_XOR,
|
|
PT_BINARY_NOT,
|
|
PT_LOGIC_NOT,
|
|
PT_LOGIC_GREATER,
|
|
PT_LOGIC_LESS,
|
|
PT_REFERENCE,
|
|
PT_COLON,
|
|
PT_COMMA,
|
|
PT_SEMICOLON,
|
|
PT_QUESTION_MARK,
|
|
PT_BRACE_OPEN,
|
|
PT_BRACE_CLOSE,
|
|
PT_BRACKET_OPEN,
|
|
PT_BRACKET_CLOSE,
|
|
PT_PARENTHESIS_OPEN,
|
|
PT_PARENTHESIS_CLOSE,
|
|
PT_PRECOMPILER,
|
|
PT_DOLLAR,
|
|
PT_BACKSLASH,
|
|
PT_MAXCOUNT,
|
|
};
|
|
|
|
// default punctuation table
|
|
static punctuation_t ps_punctuationsTable[] =
|
|
{
|
|
{">>=", PT_RSHIFT_ASSIGN },
|
|
{"<<=", PT_LSHIFT_ASSIGN },
|
|
{"...", PT_PARAMETERS },
|
|
{"##", PT_PRECOMPILER_MERGE},
|
|
{"&&", PT_LOGIC_AND },
|
|
{"||", PT_LOGIC_OR },
|
|
{">=", PT_LOGIC_GEQUAL },
|
|
{"<=", PT_LOGIC_LEQUAL },
|
|
{"==", PT_LOGIC_EQUAL },
|
|
{"!=", PT_LOGIC_NOTEQUAL },
|
|
{"*=", PT_MUL_ASSIGN },
|
|
{"/=", PT_DIV_ASSIGN },
|
|
{"%=", PT_MOD_ASSIGN },
|
|
{"+=", PT_ADD_ASSIGN },
|
|
{"-=", PT_SUB_ASSIGN },
|
|
{"++", PT_INCREMENT },
|
|
{"--", PT_DECREMENT },
|
|
{"&=", PT_BINARY_AND_ASSIGN},
|
|
{"|=", PT_BINARY_OR_ASSIGN },
|
|
{"^=", PT_BINARY_XOR_ASSIGN},
|
|
{">>", PT_RSHIFT },
|
|
{"<<", PT_LSHIFT },
|
|
{"->", PT_POINTER_REFERENCE},
|
|
{"::", PT_CPP_1 },
|
|
{".*", PT_CPP_2 },
|
|
{"*", PT_MUL },
|
|
{"/", PT_DIV },
|
|
{"%", PT_MOD },
|
|
{"+", PT_ADD },
|
|
{"-", PT_SUB },
|
|
{"=", PT_ASSIGN },
|
|
{"&", PT_BINARY_AND },
|
|
{"|", PT_BINARY_OR },
|
|
{"^", PT_BINARY_XOR },
|
|
{"~", PT_BINARY_NOT },
|
|
{"!", PT_LOGIC_NOT },
|
|
{">", PT_LOGIC_GREATER },
|
|
{"<", PT_LOGIC_LESS },
|
|
{".", PT_REFERENCE },
|
|
{":", PT_COLON },
|
|
{",", PT_COMMA },
|
|
{";", PT_SEMICOLON },
|
|
{"?", PT_QUESTION_MARK },
|
|
{"{", PT_BRACE_OPEN },
|
|
{"}", PT_BRACE_CLOSE },
|
|
{"[", PT_BRACKET_OPEN },
|
|
{"]", PT_BRACKET_CLOSE },
|
|
{"(", PT_PARENTHESIS_OPEN },
|
|
{")", PT_PARENTHESIS_CLOSE},
|
|
{"#", PT_PRECOMPILER },
|
|
{"$", PT_DOLLAR },
|
|
{"\\", PT_BACKSLASH },
|
|
{NULL, 0}
|
|
};
|
|
|
|
/*
|
|
=================
|
|
PS_NumberValue
|
|
=================
|
|
*/
|
|
static void PS_NumberValue( token_t *token )
|
|
{
|
|
char *string = token->string;
|
|
double fraction = 0.1, power = 1.0;
|
|
bool negative = false;
|
|
int i, exponent = 0;
|
|
|
|
token->floatValue = 0.0;
|
|
token->integerValue = 0;
|
|
|
|
if( token->type != TT_NUMBER )
|
|
return;
|
|
|
|
// if a decimal number
|
|
if( token->subType & NT_DECIMAL )
|
|
{
|
|
// if a floating point number
|
|
if( token->subType & NT_FLOAT )
|
|
{
|
|
while( *string && *string != '.' && *string != 'e' && *string != 'E' )
|
|
token->floatValue = token->floatValue * 10.0 + (double)(*string++ - '0');
|
|
|
|
if( *string == '.' )
|
|
{
|
|
string++;
|
|
while( *string && *string != 'e' && *string != 'E' )
|
|
{
|
|
token->floatValue = token->floatValue + (double)(*string++ - '0') * fraction;
|
|
fraction *= 0.1;
|
|
}
|
|
}
|
|
|
|
if( *string == 'e' || *string == 'E' )
|
|
{
|
|
string++;
|
|
|
|
if( *string == '+' )
|
|
{
|
|
string++;
|
|
negative = false;
|
|
}
|
|
else if( *string == '-' )
|
|
{
|
|
string++;
|
|
negative = true;
|
|
}
|
|
|
|
while( *string ) exponent = exponent * 10 + (*string++ - '0');
|
|
|
|
for( i = 0; i < exponent; i++ ) power *= 10.0;
|
|
|
|
if( negative ) token->floatValue /= power;
|
|
else token->floatValue *= power;
|
|
}
|
|
token->integerValue = (uint)token->floatValue;
|
|
return;
|
|
}
|
|
|
|
// if an integer number
|
|
if( token->subType & NT_INTEGER )
|
|
{
|
|
while( *string ) token->integerValue = token->integerValue * 10 + (*string++ - '0');
|
|
token->floatValue = (double)token->integerValue;
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if a binary number
|
|
if( token->subType & NT_BINARY )
|
|
{
|
|
string += 2;
|
|
while( *string ) token->integerValue = (token->integerValue<<1) + (*string++ - '0');
|
|
token->floatValue = (double)token->integerValue;
|
|
return;
|
|
}
|
|
|
|
// if an octal number
|
|
if( token->subType & NT_OCTAL )
|
|
{
|
|
string += 1;
|
|
while( *string ) token->integerValue = (token->integerValue<<3) + (*string++ - '0');
|
|
token->floatValue = (double)token->integerValue;
|
|
return;
|
|
}
|
|
|
|
// if a hexadecimal number
|
|
if( token->subType & NT_HEXADECIMAL )
|
|
{
|
|
string += 2;
|
|
while( *string )
|
|
{
|
|
if( *string >= 'a' && *string <= 'f' )
|
|
token->integerValue = (token->integerValue<<4) + (*string++ - 'a' + 10);
|
|
else if( *string >= 'A' && *string <= 'F' )
|
|
token->integerValue = (token->integerValue<<4) + (*string++ - 'A' + 10);
|
|
else token->integerValue = (token->integerValue<<4) + (*string++ - '0');
|
|
}
|
|
token->floatValue = (double)token->integerValue;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadWhiteSpace
|
|
=================
|
|
*/
|
|
static bool PS_ReadWhiteSpace( script_t *script, scFlags_t flags )
|
|
{
|
|
char *text;
|
|
int line;
|
|
bool hasNewLines = false;
|
|
|
|
// backup text and line
|
|
text = script->text;
|
|
line = script->line;
|
|
|
|
while( 1 )
|
|
{
|
|
// skip whitespace
|
|
while( *script->text <= ' ' )
|
|
{
|
|
if( !*script->text )
|
|
{
|
|
script->text = NULL;
|
|
return false;
|
|
}
|
|
if( *script->text == '\n' )
|
|
{
|
|
script->line++;
|
|
hasNewLines = true;
|
|
}
|
|
script->text++;
|
|
}
|
|
|
|
// if newlines are not allowed, restore text and line
|
|
if( hasNewLines && !(flags & SC_ALLOW_NEWLINES))
|
|
{
|
|
script->text = text;
|
|
script->line = line;
|
|
return false;
|
|
}
|
|
|
|
// QuArk comments: ;TX1, //TX1, #TX1
|
|
if(( flags & SC_COMMENT_SEMICOLON ) && (*script->text == ';' || *script->text == '#' ))
|
|
{
|
|
if( script->text[1] == 'T' && script->text[2] == 'X' )
|
|
script->TXcommand = script->text[3]; // QuArK TX#"-style comment
|
|
while( *script->text && *script->text != '\n' )
|
|
script->text++;
|
|
continue;
|
|
}
|
|
|
|
// skip // comments
|
|
if( *script->text == '/' && script->text[1] == '/' )
|
|
{
|
|
if( *script->text == '/' ) *script->text++;
|
|
if( script->text[1] == 'T' && script->text[2] == 'X' )
|
|
script->TXcommand = script->text[3]; // QuArK TX#"-style comment
|
|
while( *script->text && *script->text != '\n' )
|
|
script->text++;
|
|
continue;
|
|
}
|
|
|
|
// skip /* */ comments
|
|
if( *script->text == '/' && script->text[1] == '*' )
|
|
{
|
|
script->text += 2;
|
|
while( *script->text && (*script->text != '*' || script->text[1] != '/' ))
|
|
{
|
|
if( *script->text == '\n' )
|
|
script->line++;
|
|
script->text++;
|
|
}
|
|
if( *script->text ) script->text += 2;
|
|
continue;
|
|
}
|
|
|
|
// an actual token
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadEscapeChar
|
|
=================
|
|
*/
|
|
static bool PS_ReadEscapeChar( script_t *script, scFlags_t flags, char *ch )
|
|
{
|
|
int value;
|
|
|
|
script->text++;
|
|
|
|
switch( *script->text )
|
|
{
|
|
case 'a':
|
|
*ch = '\a';
|
|
break;
|
|
case 'b':
|
|
*ch = '\b';
|
|
break;
|
|
case 'f':
|
|
*ch = '\f';
|
|
break;
|
|
case 'n':
|
|
*ch = '\n';
|
|
break;
|
|
case 'r':
|
|
*ch = '\r';
|
|
break;
|
|
case 't':
|
|
*ch = '\t';
|
|
break;
|
|
case 'v':
|
|
*ch = '\v';
|
|
break;
|
|
case '\"':
|
|
*ch = '\"';
|
|
break;
|
|
case '\'':
|
|
*ch = '\'';
|
|
break;
|
|
case '\\':
|
|
*ch = '\\';
|
|
break;
|
|
case '\?':
|
|
*ch = '\?';
|
|
break;
|
|
case 'x':
|
|
script->text++;
|
|
|
|
for( value = 0; ; script->text++ )
|
|
{
|
|
if( *script->text >= 'a' && *script->text <= 'f' )
|
|
value = (value<<4) + (*script->text - 'a' + 10);
|
|
else if( *script->text >= 'A' && *script->text <= 'F' )
|
|
value = (value<<4) + (*script->text - 'A' + 10 );
|
|
else if( *script->text >= '0' && *script->text <= '9' )
|
|
value = (value<<4) + (*script->text - '0' );
|
|
else break;
|
|
}
|
|
|
|
script->text--;
|
|
|
|
if( value > 0xFF )
|
|
{
|
|
PS_ScriptError( script, flags, "too large value in escape character" );
|
|
return false;
|
|
}
|
|
*ch = value;
|
|
break;
|
|
default:
|
|
if( *script->text < '0' || *script->text > '9' )
|
|
{
|
|
PS_ScriptError( script, flags, "unknown escape character" );
|
|
return false;
|
|
}
|
|
|
|
for( value = 0; ; script->text++ )
|
|
{
|
|
if( *script->text >= '0' && *script->text <= '9' )
|
|
value = value * 10 + (*script->text - '0');
|
|
else break;
|
|
}
|
|
|
|
script->text--;
|
|
|
|
if( value > 0xFF )
|
|
{
|
|
PS_ScriptError( script, flags, "too large value in escape character" );
|
|
return false;
|
|
}
|
|
|
|
*ch = value;
|
|
break;
|
|
}
|
|
script->text++;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadGeneric
|
|
=================
|
|
*/
|
|
static bool PS_ReadGeneric( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
token->type = TT_GENERIC;
|
|
token->subType = 0;
|
|
token->line = script->line;
|
|
|
|
while( 1 )
|
|
{
|
|
if( *script->text <= ' ' ) break;
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "string longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadString
|
|
=================
|
|
*/
|
|
static bool PS_ReadString( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
char *text;
|
|
int line;
|
|
|
|
token->type = TT_STRING;
|
|
token->subType = 0;
|
|
|
|
token->line = script->line;
|
|
script->text++;
|
|
|
|
while( 1 )
|
|
{
|
|
if( !*script->text )
|
|
{
|
|
PS_ScriptError( script, flags, "missing trailing quote" );
|
|
return false;
|
|
}
|
|
|
|
if( *script->text == '\n' )
|
|
{
|
|
PS_ScriptError( script, flags, "newline inside string" );
|
|
return false;
|
|
}
|
|
|
|
if( *script->text == '\"' )
|
|
{
|
|
script->text++;
|
|
|
|
if( !(flags & SC_ALLOW_STRINGCONCAT))
|
|
break;
|
|
|
|
text = script->text;
|
|
line = script->line;
|
|
|
|
if( PS_ReadWhiteSpace( script, flags ))
|
|
{
|
|
if( *script->text == '\"' )
|
|
{
|
|
script->text++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
script->text = text;
|
|
script->line = line;
|
|
break;
|
|
}
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "string longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
|
|
if(( flags & SC_ALLOW_ESCAPECHARS) && *script->text == '\\' )
|
|
{
|
|
if( !PS_ReadEscapeChar( script, flags, &token->string[token->length] ))
|
|
return false;
|
|
token->length++;
|
|
continue;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadLine
|
|
=================
|
|
*/
|
|
static bool PS_ReadLine( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
token->type = TT_STRING;
|
|
token->subType = 0;
|
|
|
|
token->line = script->line;
|
|
|
|
while( 1 )
|
|
{
|
|
if( !*script->text )
|
|
{
|
|
PS_ScriptError( script, flags, "missing trailing quote" );
|
|
return false;
|
|
}
|
|
|
|
if( *script->text == '\n' )
|
|
{
|
|
script->line++;
|
|
break; // end of line
|
|
}
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "string longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
token->string[token->length] = 0;
|
|
token->line = script->line;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadLiteral
|
|
=================
|
|
*/
|
|
static bool PS_ReadLiteral( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
char *text;
|
|
int line;
|
|
|
|
token->type = TT_LITERAL;
|
|
token->line = script->line;
|
|
token->subType = 0;
|
|
script->text++;
|
|
|
|
while( 1 )
|
|
{
|
|
if( !*script->text )
|
|
{
|
|
PS_ScriptError( script, flags, "missing trailing quote" );
|
|
return false;
|
|
}
|
|
|
|
if( *script->text == '\n' )
|
|
{
|
|
PS_ScriptError( script, flags, "newline inside literal" );
|
|
return false;
|
|
}
|
|
|
|
if( *script->text == '\'' )
|
|
{
|
|
script->text++;
|
|
|
|
if(!(flags & SC_ALLOW_STRINGCONCAT))
|
|
break;
|
|
|
|
text = script->text;
|
|
line = script->line;
|
|
|
|
if( PS_ReadWhiteSpace( script, flags ))
|
|
{
|
|
if( *script->text == '\'' )
|
|
{
|
|
script->text++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
script->text = text;
|
|
script->line = line;
|
|
break;
|
|
}
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "literal longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
|
|
if((flags & SC_ALLOW_ESCAPECHARS) && *script->text == '\\' )
|
|
{
|
|
if( !PS_ReadEscapeChar( script, flags, &token->string[token->length] ))
|
|
return false;
|
|
token->length++;
|
|
continue;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadNumber
|
|
=================
|
|
*/
|
|
static bool PS_ReadNumber( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
bool hasDot = false;
|
|
int c;
|
|
|
|
token->type = TT_NUMBER;
|
|
token->subType = 0;
|
|
|
|
token->line = script->line;
|
|
|
|
if( *script->text == '0' && script->text[1] != '.' )
|
|
{
|
|
if( script->text[1] == 'b' || script->text[1] == 'B' )
|
|
{
|
|
token->string[token->length++] = *script->text++;
|
|
token->string[token->length++] = *script->text++;
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if( c < '0' || c > '1' )
|
|
break;
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "binary number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
token->subType |= (NT_BINARY|NT_INTEGER);
|
|
}
|
|
else if( script->text[1] == 'x' || script->text[1] == 'X' )
|
|
{
|
|
token->string[token->length++] = *script->text++;
|
|
token->string[token->length++] = *script->text++;
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if((c < 'a' || c > 'f') && (c < 'A' || c > 'F') && (c < '0' || c > '9'))
|
|
break;
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "hexadecimal number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
token->subType |= (NT_HEXADECIMAL|NT_INTEGER);
|
|
}
|
|
else
|
|
{
|
|
token->string[token->length++] = *script->text++;
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if( c < '0' || c > '7' )
|
|
break;
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "octal number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
token->subType |= (NT_OCTAL|NT_INTEGER);
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
return true;
|
|
}
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if( c == '.' )
|
|
{
|
|
if( hasDot ) break;
|
|
hasDot = true;
|
|
}
|
|
else if( c < '0' || c > '9' )
|
|
break;
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
if( hasDot || (*script->text == 'e' || *script->text == 'E'))
|
|
{
|
|
token->subType |= (NT_DECIMAL|NT_FLOAT);
|
|
|
|
if( *script->text == 'e' || *script->text == 'E' )
|
|
{
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
|
|
if( *script->text == '+' || *script->text == '-' )
|
|
{
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if( c < '0' || c > '9' ) break;
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "number longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
}
|
|
|
|
if( *script->text == 'f' || *script->text == 'F' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_SINGLE;
|
|
}
|
|
else if( *script->text == 'l' || *script->text == 'L' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_EXTENDED;
|
|
}
|
|
else token->subType |= NT_DOUBLE;
|
|
}
|
|
else
|
|
{
|
|
token->subType |= (NT_DECIMAL|NT_INTEGER);
|
|
|
|
if( *script->text == 'u' || *script->text == 'U' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_UNSIGNED;
|
|
|
|
if( *script->text == 'l' || *script->text == 'L' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_LONG;
|
|
}
|
|
}
|
|
else if( *script->text == 'l' || *script->text == 'L' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_LONG;
|
|
|
|
if( *script->text == 'u' || *script->text == 'U' )
|
|
{
|
|
script->text++;
|
|
token->subType |= NT_UNSIGNED;
|
|
}
|
|
}
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadName
|
|
=================
|
|
*/
|
|
static bool PS_ReadName( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
int c;
|
|
|
|
token->type = TT_NAME;
|
|
token->subType = 0;
|
|
|
|
token->line = script->line;
|
|
|
|
while( 1 )
|
|
{
|
|
c = *script->text;
|
|
|
|
if( flags & SC_ALLOW_PATHNAMES )
|
|
{
|
|
if((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_'
|
|
&& c != '/' && c != '\\' && c != ':' && c != '.' && c != '+' && c != '-')
|
|
break;
|
|
}
|
|
else if( flags & SC_ALLOW_PATHNAMES2 )
|
|
{
|
|
if((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')
|
|
&& c != '_' && c != '/' && c != '\\' && c != ':' && c != '.' && c != '+' && c != '@'
|
|
&& c != '-' && c != '{' && c != '!' && c != '$' && c != '&' && c != '~' )
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_')
|
|
break;
|
|
}
|
|
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "name longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = *script->text++;
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadPunctuation
|
|
=================
|
|
*/
|
|
static bool PS_ReadPunctuation( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
punctuation_t *punctuation;
|
|
int i, len;
|
|
|
|
for( i = 0; script->punctuations[i].name; i++ )
|
|
{
|
|
punctuation = &script->punctuations[i];
|
|
|
|
for( len = 0; punctuation->name[len] && script->text[len]; len++ )
|
|
{
|
|
if( punctuation->name[len] != script->text[len] )
|
|
break;
|
|
}
|
|
|
|
if( !punctuation->name[len] )
|
|
{
|
|
script->text += len;
|
|
token->type = TT_PUNCTUATION;
|
|
token->subType = punctuation->type;
|
|
token->line = script->line;
|
|
|
|
for( i = 0; i < len; i++ )
|
|
{
|
|
if( token->length == MAX_TOKEN_LENGTH - 1 )
|
|
{
|
|
PS_ScriptError( script, flags, "punctuation longer than %i symbols", MAX_TOKEN_LENGTH );
|
|
return false;
|
|
}
|
|
token->string[token->length++] = punctuation->name[i];
|
|
}
|
|
|
|
token->string[token->length] = 0;
|
|
PS_NumberValue( token );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ReadToken
|
|
|
|
Reads a token from the script
|
|
=================
|
|
*/
|
|
bool PS_ReadToken( script_t *script, scFlags_t flags, token_t *token )
|
|
{
|
|
token_t dummy;
|
|
|
|
// parselib requries token always
|
|
if( !token ) token = &dummy;
|
|
|
|
// if there is a token available (from PS_FreeToken)
|
|
if( script->tokenAvailable )
|
|
{
|
|
script->tokenAvailable = false;
|
|
Mem_Copy( token, &script->token, sizeof( token_t ));
|
|
return true;
|
|
}
|
|
|
|
// clear token
|
|
token->type = TT_EMPTY;
|
|
token->subType = 0;
|
|
token->line = 1;
|
|
token->string[0] = 0;
|
|
token->length = 0;
|
|
token->floatValue = 0.0;
|
|
token->integerValue = 0;
|
|
|
|
// make sure incoming data is valid
|
|
if( !script->text ) return false;
|
|
|
|
// skip whitespace and comments
|
|
if( !PS_ReadWhiteSpace( script, flags ))
|
|
return false;
|
|
|
|
// if we just want to parse a generic string separated by spaces
|
|
if( flags & SC_PARSE_GENERIC )
|
|
{
|
|
// if it is a string
|
|
if( flags & SC_PARSE_LINE )
|
|
{
|
|
if( PS_ReadLine( script, flags, token ))
|
|
return true;
|
|
}
|
|
else if( *script->text == '\"' )
|
|
{
|
|
if( PS_ReadString( script, flags, token ))
|
|
return true;
|
|
}
|
|
// if it is a literal
|
|
else if( *script->text == '\'' )
|
|
{
|
|
if( PS_ReadLiteral( script, flags, token ))
|
|
return true;
|
|
}
|
|
// check for a generic string
|
|
else if( PS_ReadGeneric( script, flags, token ))
|
|
return true;
|
|
}
|
|
// If it is a string
|
|
else if( *script->text == '\"' )
|
|
{
|
|
if( PS_ReadString( script, flags, token ))
|
|
return true;
|
|
}
|
|
// if it is a literal
|
|
else if( *script->text == '\'' )
|
|
{
|
|
if( PS_ReadLiteral( script, flags, token ))
|
|
return true;
|
|
}
|
|
// old pathnames must be grab before numbers because they contains '+', '-' and other unexpected symbols
|
|
else if((flags & SC_ALLOW_PATHNAMES2) && (*script->text == '/' || *script->text == '\\' || *script->text == ':' || *script->text == '.'
|
|
|| *script->text == '$' || *script->text == '-' || *script->text == '+' || *script->text == '!' || *script->text == '{' )) // damn prefix!
|
|
{
|
|
if( PS_ReadName( script, flags, token ))
|
|
return true;
|
|
}
|
|
// if it is a number
|
|
else if((*script->text >= '0' && *script->text <= '9') || (*script->text == '.' && (script->text[1] >= '0' && script->text[1] <= '9')))
|
|
{
|
|
if( PS_ReadNumber( script, flags, token ))
|
|
return true;
|
|
}
|
|
// if it is a name
|
|
else if((*script->text >= 'a' && *script->text <= 'z') || (*script->text >= 'A' && *script->text <= 'Z') || *script->text == '_')
|
|
{
|
|
if( PS_ReadName( script, flags, token ))
|
|
return true;
|
|
}
|
|
// check for a path name if needed
|
|
else if((flags & SC_ALLOW_PATHNAMES) && (*script->text == '/' || *script->text == '\\' || *script->text == ':' || *script->text == '.'))
|
|
{
|
|
if( PS_ReadName( script, flags, token ))
|
|
return true;
|
|
}
|
|
// check for a punctuation
|
|
else if( PS_ReadPunctuation( script, flags, token ))
|
|
return true;
|
|
|
|
// couldn't parse a token
|
|
token->type = TT_EMPTY;
|
|
token->subType = 0;
|
|
token->line = 1;
|
|
token->string[0] = 0;
|
|
token->length = 0;
|
|
token->floatValue = 0.0;
|
|
token->integerValue = 0;
|
|
|
|
PS_ScriptError( script, flags, "couldn't read token" );
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_SaveToken
|
|
=================
|
|
*/
|
|
void PS_SaveToken( script_t *script, token_t *token )
|
|
{
|
|
script->tokenAvailable = true;
|
|
Mem_Copy( &script->token, token, sizeof( token_t ));
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PARSER SIMPLY USER INTERFACE
|
|
|
|
=============================================================================
|
|
*/
|
|
/*
|
|
=================
|
|
PS_GetString
|
|
=================
|
|
*/
|
|
bool PS_GetString( script_t *script, int flags, char *value, size_t size )
|
|
{
|
|
token_t token;
|
|
|
|
if( !PS_ReadToken( script, flags|SC_ALLOW_PATHNAMES, &token ))
|
|
return false;
|
|
|
|
if( value && size ) com.strncpy( value, token.string, size );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_GetDouble
|
|
=================
|
|
*/
|
|
bool PS_GetDouble( script_t *script, int flags, double *value )
|
|
{
|
|
token_t token;
|
|
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type == TT_PUNCTUATION && !com.stricmp( token.string, "-" ))
|
|
{
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type != TT_NUMBER )
|
|
{
|
|
PS_ScriptError( script, flags, "expected float value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
if( value ) *value = -token.floatValue;
|
|
return true;
|
|
}
|
|
|
|
if( token.type != TT_NUMBER )
|
|
{
|
|
PS_ScriptError( script, flags, "expected float value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
|
|
if( value ) *value = token.floatValue;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_GetFloat
|
|
=================
|
|
*/
|
|
bool PS_GetFloat( script_t *script, int flags, float *value )
|
|
{
|
|
token_t token;
|
|
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type == TT_PUNCTUATION && !com.stricmp( token.string, "-" ))
|
|
{
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type != TT_NUMBER )
|
|
{
|
|
PS_ScriptError( script, flags, "expected float value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
if( value ) *value = -((float)token.floatValue);
|
|
return true;
|
|
}
|
|
|
|
if( token.type != TT_NUMBER )
|
|
{
|
|
PS_ScriptError( script, flags, "expected float value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
|
|
if( value ) *value = (float)token.floatValue;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_GetUnsigned
|
|
=================
|
|
*/
|
|
bool PS_GetUnsigned( script_t *script, int flags, uint *value )
|
|
{
|
|
token_t token;
|
|
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type != TT_NUMBER || !(token.subType & NT_INTEGER ))
|
|
{
|
|
PS_ScriptError( script, flags, "expected integer value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
|
|
if( value ) *value = token.integerValue;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_GetInteger
|
|
=================
|
|
*/
|
|
bool PS_GetInteger( script_t *script, int flags, int *value )
|
|
{
|
|
token_t token;
|
|
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type == TT_PUNCTUATION && !com.stricmp( token.string, "-" ))
|
|
{
|
|
if( !PS_ReadToken( script, flags, &token ))
|
|
return false;
|
|
|
|
if( token.type != TT_NUMBER || !(token.subType & NT_INTEGER ))
|
|
{
|
|
PS_ScriptError( script, flags, "expected integer value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
|
|
if( value ) *value = -((int)token.integerValue);
|
|
return true;
|
|
}
|
|
|
|
if( token.type != TT_NUMBER || !(token.subType & NT_INTEGER))
|
|
{
|
|
PS_ScriptError( script, flags, "expected integer value, found '%s'", token.string );
|
|
return false;
|
|
}
|
|
|
|
if( value ) *value = (int)token.integerValue;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_SkipWhiteSpace
|
|
|
|
Skips until a printable character is found
|
|
=================
|
|
*/
|
|
void PS_SkipWhiteSpace( script_t *script )
|
|
{
|
|
// make sure incoming data is valid
|
|
if( !script->text ) return;
|
|
|
|
while( 1 )
|
|
{
|
|
// skip whitespace
|
|
while( *script->text <= ' ' )
|
|
{
|
|
if( !*script->text )
|
|
{
|
|
script->text = NULL;
|
|
return;
|
|
}
|
|
|
|
if( *script->text == '\n' )
|
|
script->line++;
|
|
script->text++;
|
|
}
|
|
|
|
// skip // comments
|
|
if( *script->text == '/' && script->text[1] == '/' )
|
|
{
|
|
while( *script->text && *script->text != '\n' )
|
|
script->text++;
|
|
continue;
|
|
}
|
|
|
|
// skip /* */ comments
|
|
if( *script->text == '/' && script->text[1] == '*' )
|
|
{
|
|
script->text += 2;
|
|
|
|
while( *script->text && (*script->text != '*' || script->text[1] != '/' ))
|
|
{
|
|
if( *script->text == '\n' )
|
|
script->line++;
|
|
script->text++;
|
|
}
|
|
if( *script->text ) script->text += 2;
|
|
continue;
|
|
}
|
|
// an actual token
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_SkipRestOfLine
|
|
|
|
Skips until a new line is found
|
|
=================
|
|
*/
|
|
void PS_SkipRestOfLine( script_t *script )
|
|
{
|
|
token_t token;
|
|
|
|
while( 1 )
|
|
{
|
|
if( !PS_ReadToken( script, 0, &token ))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_SkipBracedSection
|
|
|
|
Skips until a matching close brace is found.
|
|
Internal brace depths are properly skipped.
|
|
=================
|
|
*/
|
|
void PS_SkipBracedSection( script_t *script, int depth )
|
|
{
|
|
token_t token;
|
|
|
|
do {
|
|
if( !PS_ReadToken( script, SC_ALLOW_NEWLINES, &token ))
|
|
break;
|
|
|
|
// HACKHACK: allow pathnames that can contain '{' symbol
|
|
// and invoke wrong count of braced sections :(
|
|
if( !com.stricmp( "map", token.string ))
|
|
if( !PS_ReadToken( script, SC_ALLOW_PATHNAMES2, &token ))
|
|
break;
|
|
|
|
if( token.type == TT_PUNCTUATION )
|
|
{
|
|
if( !com.stricmp( token.string, "{" ))
|
|
depth++;
|
|
else if( !com.stricmp( token.string, "}" ))
|
|
depth--;
|
|
}
|
|
} while( depth );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ScriptError
|
|
=================
|
|
*/
|
|
void PS_ScriptError( script_t *script, scFlags_t flags, const char *fmt, ... )
|
|
{
|
|
string errorstring;
|
|
va_list argPtr;
|
|
|
|
if(!(flags & SC_PRINT_ERRORS)) return;
|
|
|
|
va_start( argPtr, fmt );
|
|
com_vsnprintf( errorstring, MAX_STRING, fmt, argPtr );
|
|
va_end( argPtr );
|
|
|
|
MsgDev( D_ERROR, "source '%s', line %i: %s\n", script->name, script->line, errorstring );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ScriptWarning
|
|
=================
|
|
*/
|
|
void PS_ScriptWarning( script_t *script, scFlags_t flags, const char *fmt, ... )
|
|
{
|
|
string warnstring;
|
|
va_list argPtr;
|
|
|
|
if(!(flags & SC_PRINT_WARNINGS)) return;
|
|
|
|
va_start( argPtr, fmt );
|
|
com.vsnprintf( warnstring, MAX_STRING, fmt, argPtr );
|
|
va_end( argPtr );
|
|
|
|
MsgDev( D_WARN, "source '%s', line %i: %s\n", script->name, script->line, warnstring );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_SetPunctuationsTable
|
|
=================
|
|
*/
|
|
void PS_SetPunctuationsTable( script_t *script, punctuation_t *punctuationsTable )
|
|
{
|
|
if( punctuationsTable )
|
|
script->punctuations = punctuationsTable;
|
|
else script->punctuations = ps_punctuationsTable;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_ResetScript
|
|
=================
|
|
*/
|
|
void PS_ResetScript( script_t *script )
|
|
{
|
|
script->text = script->buffer;
|
|
script->line = 1;
|
|
|
|
script->tokenAvailable = false;
|
|
script->token.type = TT_EMPTY;
|
|
script->token.subType = 0;
|
|
script->token.line = 1;
|
|
script->token.string[0] = 0;
|
|
script->token.length = 0;
|
|
script->token.floatValue = 0.0;
|
|
script->token.integerValue = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_EndOfScript
|
|
=================
|
|
*/
|
|
bool PS_EndOfScript( script_t *script )
|
|
{
|
|
if( !script || !script->text )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static script_t *PS_NewScript( const char *filename, const char *buffer, size_t size )
|
|
{
|
|
script_t *script;
|
|
|
|
if( !buffer || size <= 0 ) return NULL;
|
|
|
|
// allocate the script_t
|
|
script = Mem_Alloc( Sys.scriptpool, sizeof( script_t ));
|
|
com.strncpy( script->name, filename, sizeof( script->name ));
|
|
script->buffer = Mem_Alloc( Sys.scriptpool, size + 1 ); // escape char
|
|
|
|
Mem_Copy( script->buffer, buffer, size );
|
|
script->text = script->buffer;
|
|
script->allocated = true;
|
|
script->size = size;
|
|
script->line = 1;
|
|
|
|
script->punctuations = ps_punctuationsTable;
|
|
script->tokenAvailable = false;
|
|
script->token.type = TT_EMPTY;
|
|
script->token.subType = 0;
|
|
script->token.line = 1;
|
|
script->token.string[0] = 0;
|
|
script->token.length = 0;
|
|
script->token.floatValue = 0.0;
|
|
script->token.integerValue = 0;
|
|
|
|
return script;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_LoadScriptFile
|
|
=================
|
|
*/
|
|
script_t *PS_LoadScript( const char *filename, const char *buf, size_t size )
|
|
{
|
|
script_t *script;
|
|
|
|
if( !buf || size <= 0 )
|
|
{
|
|
// trying to get script from disk
|
|
buf = FS_LoadFile( filename, &size );
|
|
script = PS_NewScript( filename, buf, size );
|
|
if( buf ) Mem_Free((char *)buf );
|
|
}
|
|
else script = PS_NewScript( filename, buf, size ); // from memory
|
|
|
|
return script;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PS_FreeScript
|
|
=================
|
|
*/
|
|
void PS_FreeScript( script_t *script )
|
|
{
|
|
if( !script )
|
|
{
|
|
MsgDev( D_WARN, "PS_FreeScript: trying to free NULL script\n" );
|
|
return;
|
|
}
|
|
|
|
if( script->allocated )
|
|
Mem_Free( script->buffer );
|
|
Mem_Free( script ); // himself
|
|
} |