Files

1302 lines
24 KiB
C++

/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tiki_parse.cpp : TIKI Script
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "../corepp/tiki.h"
TikiScript *TikiScript::currentScript;
TikiScript::~TikiScript()
{
Close();
}
TikiScript::TikiScript()
{
error = false;
buffer = NULL;
script_p = NULL;
end_p = NULL;
line = 0;
releaseBuffer = false;
tokenready = false;
memset(token, 0, sizeof(token));
include = NULL;
parent = NULL;
nummacros = 0;
mark_pos = 0;
path[0] = 0;
}
/*
==============
=
= Close
=
==============
*/
void TikiScript::Close(void)
{
Uninclude();
if (this->releaseBuffer) {
if (this->buffer) {
TIKI_Free(this->buffer);
}
}
buffer = NULL;
script_p = NULL;
end_p = NULL;
line = 0;
error = false;
releaseBuffer = false;
tokenready = false;
nummacros = 0;
memset(token, 0, sizeof(token));
mark_pos = 0;
}
/*
==============
=
= Filename
=
==============
*/
const char *TikiScript::Filename()
{
return filename;
}
/*
==============
=
= GetLineNumber
=
==============
*/
int TikiScript::GetLineNumber()
{
return line;
}
/*
==============
=
= Reset
=
==============
*/
void TikiScript::Reset(void)
{
Uninclude();
nummacros = 0;
error = 0;
line = 1;
tokenready = 0;
mark_pos = 0;
script_p = buffer;
}
/*
==============
=
= SkipToEOL
=
==============
*/
qboolean TikiScript::SkipToEOL(void)
{
if (script_p >= end_p) {
return true;
}
while (*script_p != TOKENEOL) {
if (script_p >= end_p) {
return true;
}
script_p++;
}
return false;
}
/*
==============
=
= SafeCheckOverflow
=
==============
*/
qboolean TikiScript::SafeCheckOverflow(void)
{
return script_p >= end_p;
}
/*
==============
=
= CheckOverflow
=
==============
*/
void TikiScript::CheckOverflow(void)
{
if (script_p >= end_p) {
this->error = 1;
TIKI_DPrintf("End of tiki file reached prematurely reading %s\n", Filename());
TIKI_DPrintf(" This may be caused by having a tiki file command at the end of the file\n");
TIKI_DPrintf(" without an 'end' at the end of the tiki file.\n");
}
}
/*
==============
=
= SkipWhiteSpace
=
==============
*/
void TikiScript::SkipWhiteSpace(qboolean crossline)
{
//
// skip space
//
while (!SafeCheckOverflow()) {
if (*script_p <= TOKENSPACE) {
if (*script_p == TOKENEOL) {
if (!crossline) {
TIKI_DPrintf("Line %i is incomplete in file %s\n", line, Filename());
return;
}
line++;
}
script_p++;
} else {
if (!AtComment()) {
return;
}
if (AtExtendedComment()) {
SkipExtendedComment();
} else {
if (!crossline) {
TIKI_DPrintf("Line %i is incomplete in file %s\n", line, Filename());
return;
}
SkipToEOL();
}
}
}
}
/*
==============
=
= AtComment
=
==============
*/
qboolean TikiScript::AtComment(void)
{
if (script_p >= end_p) {
return false;
}
if (*script_p == TOKENCOMMENT) {
return true;
}
if (*script_p == TOKENCOMMENT2) {
return true;
}
// Two or more character comment specifiers
if ((script_p + 1) >= end_p) {
return false;
}
if ((*script_p == '/') && ((*(script_p + 1) == '/') || (*(script_p + 1) == '*'))) {
return true;
}
return false;
}
/*
==============
=
= AtExtendedComment
=
==============
*/
qboolean TikiScript::AtExtendedComment(void)
{
if (script_p >= end_p) {
return false;
}
// Two or more character comment specifiers
if ((script_p + 1) >= end_p) {
return false;
}
if ((*(script_p) == '/') && (*(script_p + 1) == '*')) {
return true;
}
return false;
}
/*
==============
=
= SkipExtendedComment
=
==============
*/
void TikiScript::SkipExtendedComment(void)
{
CheckOverflow();
if (error) {
return;
}
while ((script_p + 1) < end_p) {
if ((*(script_p) == '*') && (*(script_p + 1) == '/')) {
script_p = script_p + 2;
return;
}
if (*script_p == '\n') {
line++;
}
script_p++;
}
}
/*
==============
=
= AtCommand
=
==============
*/
qboolean TikiScript::AtCommand(void)
{
if (script_p >= end_p) {
return false;
}
// Two or more character comment specifiers
if ((script_p + 1) >= end_p) {
return false;
}
if ((*script_p == '$') && (*(script_p + 1) != '$')) {
return true;
}
return false;
}
/*
==============
=
= Uninclude
=
==============
*/
void TikiScript::Uninclude(void)
{
if (include) {
currentScript = this;
include->Close();
delete include;
include = NULL;
}
}
/*
==============
=
= AddMacro
=
==============
*/
void TikiScript::AddMacro(const char *name, const char *expansion)
{
int i;
for (i = 0; i < nummacros; i++) {
if (!stricmp(name, macros[i].name)) {
return;
}
}
if (nummacros >= TIKI_MAXMACROS) {
TIKI_DPrintf("Too many %cdefine in file %s\n", TOKENSPECIAL, Filename());
} else {
Q_strncpyz(macros[nummacros].name, name, sizeof(macros[nummacros].name));
Q_strncpyz(macros[nummacros].macro, expansion, sizeof(macros[nummacros].macro));
nummacros++;
}
// Add the macro to parent
if (parent) {
parent->AddMacro(name, expansion);
}
}
/*
==============
=
= ProcessCommand
=
==============
*/
qboolean TikiScript::ProcessCommand(qboolean crossline)
{
char dummy;
int i;
size_t len;
char command[256];
char argument1[256];
char argument2[256];
sscanf(script_p, "%c%s %s %s\n", &dummy, command, argument1, argument2);
if (!stricmp(command, "define")) {
AddMacro(argument1, argument2);
SkipToEOL();
SkipWhiteSpace(crossline);
} else if (!stricmp(command, "include")) {
SkipToEOL();
SkipWhiteSpace(crossline);
include = new TikiScript;
if (include->LoadFile(argument1, qfalse)) {
include->SkipNonToken(crossline);
for (i = 0; i < nummacros; i++) {
include->AddMacro(macros[i].name, macros[i].macro);
}
include->parent = this;
} else {
TIKI_Error(
"^~^~^ Cannot find include file '%s' in %s on line %d\n", argument1, Filename(), GetLineNumber()
);
Uninclude();
}
} else if (!stricmp(command, "path")) {
Q_strncpyz(path, argument1, sizeof(path));
len = strlen(path);
if (path[len - 1] != '/' && path[len - 1] != '\\') {
Q_strcat(path, sizeof(path), "/");
}
SkipToEOL();
SkipWhiteSpace(crossline);
} else {
return false;
}
return true;
}
/*
==============
=
= Completed
=
==============
*/
qboolean TikiScript::Completed(void)
{
return script_p >= end_p;
}
/*
==============
=
= FindMacro
=
==============
*/
const char *TikiScript::FindMacro(const char *macro)
{
int i;
for (i = 0; i < nummacros; i++) {
if (!stricmp(macro, macros[i].name)) {
return macros[i].macro;
}
}
return NULL;
}
/*
==============
=
= SkipNonToken
=
==============
*/
void TikiScript::SkipNonToken(qboolean crossline)
{
//
// skip space and comments
//
SkipWhiteSpace(crossline);
while (!error) {
if (!AtCommand()) {
break;
}
if (!ProcessCommand(crossline)) {
break;
}
if (include) {
break;
}
}
}
/*
==============
=
= TokenAvailable
=
==============
*/
qboolean TikiScript::TokenAvailable(qboolean crossline)
{
if (include) {
qboolean ret = include->TokenAvailable(crossline);
if (ret || !crossline || !include->Completed()) {
return ret;
}
Uninclude();
}
if (tokenready) {
return true;
}
while (!SafeCheckOverflow()) {
if (*script_p <= TOKENSPACE) {
if (*script_p == TOKENEOL) {
if (!crossline) {
return false;
}
line++;
}
script_p++;
} else {
if (!AtComment()) {
return true;
}
if (AtExtendedComment()) {
SkipExtendedComment();
} else {
if (!crossline) {
return false;
}
SkipToEOL();
}
}
}
return false;
}
/*
==============
=
= CommentAvailable
=
==============
*/
qboolean TikiScript::CommentAvailable(qboolean crossline)
{
const char *searchptr;
searchptr = script_p;
if (searchptr >= end_p) {
return false;
}
while (*searchptr <= TOKENSPACE) {
if ((*searchptr == TOKENEOL) && (!crossline)) {
return false;
}
searchptr++;
if (searchptr >= end_p) {
return false;
}
}
return true;
}
/*
==============
=
= UnGetToken
=
==============
*/
void TikiScript::UnGetToken(void)
{
if (include) {
include->UnGetToken();
} else {
tokenready = true;
}
}
/*
==============
=
= AtString
=
==============
*/
qboolean TikiScript::AtString(qboolean crossline)
{
TikiScript *i;
//
// skip space
//
i = this;
while (1) {
i->SkipNonToken(crossline);
if (!i->include) {
break;
}
i = i->include;
}
return (!i->error) && (*i->script_p == '"');
}
/*
==============
=
= GetToken
=
==============
*/
const char *TikiScript::GetToken(qboolean crossline)
{
TikiScript *i;
const char *macro_start;
const char *macro_end;
char *token_p;
int len;
const char *subst;
char macro[256];
char temptoken[256];
i = this;
while (1) {
if (i->include) {
const char *ret;
ret = i->include->GetToken(crossline);
if (*ret || !crossline || !i->include->Completed()) {
return ret;
}
i->Uninclude();
}
if (i->tokenready) {
i->tokenready = false;
currentScript = i;
return i->token;
}
i->SkipNonToken(crossline);
if (i->error) {
return "";
}
if (!i->include) {
break;
}
i = i->include;
}
token_p = (char *)i->script_p;
if (*token_p == '"') {
currentScript = i;
return i->GetString(crossline);
}
macro_start = NULL;
macro_end = NULL;
token_p = i->token;
while ((*i->script_p > TOKENSPACE) && !i->AtComment()) {
if ((*i->script_p == '\\') && (i->script_p < (end_p - 1))) {
i->script_p++;
switch (*i->script_p) {
case 'n':
*token_p++ = '\n';
break;
case 'r':
*token_p++ = '\n';
break;
case '\'':
*token_p++ = '\'';
break;
case '\"':
*token_p++ = '\"';
break;
case '\\':
*token_p++ = '\\';
break;
default:
*token_p++ = *i->script_p;
break;
}
i->script_p++;
} else {
if (*i->script_p == '$') {
if (macro_start) {
macro_end = token_p;
} else {
macro_start = token_p;
}
}
*token_p++ = *i->script_p++;
}
if (token_p == &i->token[TIKI_MAXTOKEN]) {
TIKI_DPrintf("Token too large on line %i in file %s\n", i->line, i->Filename());
break;
}
if (i->script_p == i->end_p) {
break;
}
}
*token_p = 0;
if (macro_start) {
if (!macro_end) {
macro_end = token_p;
}
len = (macro_end - 1) - (macro_start + 1);
if (len == -1) {
if (macro_start - i->token != 0) {
memcpy(temptoken, i->token, macro_start - i->token);
}
temptoken[macro_start - i->token] = '$';
temptoken[macro_start - i->token + 1] = 0;
if (*(macro_end + 1)) {
strcat(temptoken, macro_end + 1);
}
Q_strncpyz(i->token, temptoken, sizeof(i->token));
} else {
memcpy(macro, (macro_start + 1), len + 1);
macro[len + 1] = 0;
subst = i->FindMacro(macro);
if (subst) {
if (macro_start - i->token != 0) {
memcpy(temptoken, i->token, macro_start - i->token);
}
temptoken[macro_start - i->token] = 0;
strcat(temptoken, subst);
if (*(macro_end + 1)) {
strcat(temptoken, macro_end + 1);
}
Q_strncpyz(i->token, temptoken, sizeof(i->token));
}
}
}
i->currentScript = i;
return i->token;
}
/*
==============
=
= GetLine
=
==============
*/
const char *TikiScript::GetLine(qboolean crossline)
{
TikiScript *i;
const char *start;
int size;
i = this;
while (1) {
if (i->include) {
const char *ret;
ret = i->include->GetToken(crossline);
if (*ret || !crossline || !i->include->Completed()) {
return ret;
}
i->Uninclude();
}
if (i->tokenready) {
i->tokenready = false;
return i->token;
}
if (!i->include) {
break;
}
i = i->include;
}
if (i->error) {
return "";
}
//
// copy token
//
start = i->script_p;
SkipToEOL();
size = i->script_p - start;
if (size < (TIKI_MAXTOKEN - 1)) {
memcpy(i->token, start, size);
i->token[size] = '\0';
} else {
TIKI_DPrintf("Token too large on line %i in file %s\n", i->line, i->filename);
}
return i->token;
}
/*
==============
=
= GetAndIgnoreLine
=
==============
*/
const char *TikiScript::GetAndIgnoreLine(qboolean crossline)
{
const char *start;
int size;
if (include) {
const char *ret;
ret = include->GetAndIgnoreLine(crossline);
if (*ret || !crossline || !include->Completed()) {
return ret;
}
Uninclude();
}
if (tokenready) {
tokenready = false;
return token;
}
//
// skip space
//
SkipWhiteSpace(crossline);
if (error) {
return "";
}
//
// copy token
//
start = script_p;
SkipToEOL();
size = script_p - start;
if (size < (TIKI_MAXTOKEN - 1)) {
memcpy(token, start, size);
token[size] = '\0';
} else {
TIKI_DPrintf("Token too large on line %i in file %s\n", line, filename);
}
return token;
}
/*
==============
=
= GetRaw
=
==============
*/
const char *TikiScript::GetRaw(void)
{
const char *start;
int size;
if (include) {
const char *ret;
ret = include->GetRaw();
if (*ret || !include->Completed()) {
return ret;
}
Uninclude();
}
//
// skip white space
//
SkipWhiteSpace(true);
if (error) {
return "";
}
//
// copy token
//
start = script_p;
SkipToEOL();
size = script_p - start;
if (size < (TIKI_MAXTOKEN - 1)) {
memset(token, 0, sizeof(token));
memcpy(token, start, size);
} else {
TIKI_DPrintf("Token too large on line %i in file %s\n", line, filename);
}
return token;
}
/*
==============
=
= GetString
=
==============
*/
const char *TikiScript::GetString(qboolean crossline)
{
TikiScript *i;
int startline;
char *token_p;
i = this;
while (1) {
if (i->include) {
const char *ret;
ret = i->include->GetString(crossline);
if (*ret || !crossline || !i->include->Completed()) {
return ret;
}
i->Uninclude();
}
// is a token already waiting?
if (i->tokenready) {
i->tokenready = false;
return i->token;
}
i->SkipNonToken(crossline);
if (!i->include) {
break;
}
i = i->include;
}
if (i->error) {
return "";
}
if (*i->script_p != '"') {
TIKI_DPrintf("Expecting string on line %i in file %s\n", i->line, i->filename);
return i->token;
}
i->script_p++;
startline = i->line;
token_p = i->token;
while (*i->script_p != '"') {
if (*i->script_p == TOKENEOL) {
TIKI_DPrintf("Line %i is incomplete while reading string in file %s\n", i->line, i->filename);
return i->token;
}
if ((*i->script_p == '\\') && (i->script_p < (end_p - 1))) {
i->script_p++;
switch (*i->script_p) {
case 'n':
*token_p++ = '\n';
break;
case 'r':
*token_p++ = '\n';
break;
case '\'':
*token_p++ = '\'';
break;
case '\"':
*token_p++ = '\"';
break;
case '\\':
*token_p++ = '\\';
break;
default:
*token_p++ = *i->script_p;
break;
}
i->script_p++;
} else {
*token_p++ = *i->script_p++;
}
if (i->script_p >= i->end_p) {
TIKI_DPrintf(
"End of token file reached prematurely while reading string on\n"
"line %d in file %s\n",
startline,
i->filename
);
}
if (token_p == &i->token[TIKI_MAXTOKEN]) {
TIKI_DPrintf("String too large on line %i in file %s\n", i->line, i->filename);
}
}
*token_p = 0;
// skip last quote
i->script_p++;
return i->token;
}
/*
==============
=
= GetSpecific
=
==============
*/
qboolean TikiScript::GetSpecific(const char *string)
{
if (include) {
qboolean ret = include->GetSpecific(string);
if (ret || !include->Completed()) {
return ret;
}
}
do {
if (!TokenAvailable(true)) {
return false;
}
GetToken(true);
} while (strcmp(token, string));
return true;
}
/*
==============
=
= GetInteger
=
==============
*/
int TikiScript::GetInteger(qboolean crossline)
{
return atoi(GetToken(crossline));
}
/*
==============
=
= GetDouble
=
==============
*/
double TikiScript::GetDouble(qboolean crossline)
{
return atof(GetToken(crossline));
}
/*
==============
=
= GetFloat
=
==============
*/
float TikiScript::GetFloat(qboolean crossline)
{
return (float)GetDouble(crossline);
}
/*
==============
=
= GetVector
=
==============
*/
void TikiScript::GetVector(qboolean crossline, float *vec)
{
vec[0] = GetFloat(crossline);
vec[1] = GetFloat(crossline);
vec[2] = GetFloat(crossline);
}
/*
==============
=
= LinesInFile
=
==============
*/
int TikiScript::LinesInFile(void)
{
qboolean temp_tokenready;
const char *temp_script_p;
int temp_line;
char temp_token[TIKI_MAXTOKEN];
int numentries;
temp_tokenready = tokenready;
temp_script_p = script_p;
temp_line = line;
Q_strncpyz(temp_token, token, sizeof(temp_token));
numentries = 0;
Reset();
while (TokenAvailable(true)) {
GetLine(true);
numentries++;
}
tokenready = temp_tokenready;
script_p = temp_script_p;
line = temp_line;
Q_strncpyz(token, temp_token, sizeof(token));
return numentries;
}
/*
==============
=
= Parse
=
==============
*/
void TikiScript::Parse(char *data, int length, const char *name)
{
Close();
buffer = data;
Reset();
this->length = length;
end_p = script_p + length;
strncpy(filename, name, TIKI_MAXTOKEN);
}
/*
==============
=
= LoadFile
=
==============
*/
qboolean TikiScript::LoadFile(const char *name, qboolean quiet)
{
int length;
char *buf;
Close();
length = TIKI_ReadFileEx(name, (void **)&buf, quiet);
if (length < 0) {
if (!quiet) {
TIKI_DPrintf("Tiki:LoadFile Couldn't load %s\n", name);
}
return false;
}
// create our own space
buffer = (char *)TIKI_Alloc(length + 1);
// copy the file over to our space
memcpy(buffer, buf, length);
buffer[length] = 0;
// free the file
TIKI_FreeFile(buf);
Parse(buffer, length, name);
releaseBuffer = true;
if (error) {
return false;
}
return true;
}
/*
==============
=
= Token
=
==============
*/
const char *TikiScript::Token(void)
{
if (include) {
return include->token;
} else {
return token;
}
}
/*
==============
=
= MarkPos
=
==============
*/
void TikiScript::MarkPos(void)
{
mark[mark_pos].mark_script_p = script_p;
mark[mark_pos].mark_tokenready = tokenready;
Q_strncpyz(mark[mark_pos].mark_token, token, sizeof(mark[mark_pos].mark_token));
mark_pos++;
}
/*
==============
=
= ReturnToMark
=
==============
*/
void TikiScript::ReturnToMark(void)
{
if (mark_pos <= 0) {
return;
}
mark_pos--;
script_p = mark[mark_pos].mark_script_p;
tokenready = mark[mark_pos].mark_tokenready;
memcpy(token, mark[mark_pos].mark_token, sizeof(token));
}
/*
==============
=
= ReplaceLineWithWhitespace
=
==============
*/
void TikiScript::ReplaceLineWithWhitespace(bool deleteFromStartOfLine)
{
char *change_p = (char *)script_p;
if (deleteFromStartOfLine && *(change_p - 1) != '\n') {
while (*(change_p - 1) != TOKENEOL && change_p > buffer) {
change_p--;
}
}
if (*change_p != TOKENEOL) {
if (change_p >= end_p) {
script_p = change_p;
return;
}
do {
*change_p++ = TOKENSPACE;
} while (*change_p != TOKENEOL && change_p < end_p);
}
if (change_p < end_p) {
script_p = change_p + 1;
line++;
} else {
script_p = change_p;
}
}
/*
==============
=
= GetParentToken
=
==============
*/
const char *TikiScript::GetParentToken(void)
{
if (parent) {
return parent->token;
} else {
return "";
}
}