mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-12-11 21:00:53 +00:00
446 lines
12 KiB
C++
446 lines
12 KiB
C++
|
// Emacs style mode select -*- C++ -*-
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Copyright(C) 2000 Simon Howard
|
||
|
//
|
||
|
// This program 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.
|
||
|
//
|
||
|
// This program 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 this program; if not, write to the Free Software
|
||
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Preprocessor.
|
||
|
//
|
||
|
// The preprocessor must be called when the script is first loaded.
|
||
|
// It performs 2 functions:
|
||
|
// 1: blank out comments (which could be misinterpreted)
|
||
|
// 2: makes a list of all the sections held within {} braces
|
||
|
// 3: 'dry' runs the script: goes thru each statement and
|
||
|
// sets the types of all the DFsSection's in the script
|
||
|
// 4: Saves locations of all goto() labels
|
||
|
//
|
||
|
// the system of DFsSection's is pretty horrible really, but it works
|
||
|
// and its probably the only way i can think of of saving scripts
|
||
|
// half-way thru running
|
||
|
//
|
||
|
// By Simon Howard
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
//
|
||
|
// FraggleScript is from SMMU which is under the GPL. Technically,
|
||
|
// therefore, combining the FraggleScript code with the non-free
|
||
|
// ZDoom code is a violation of the GPL.
|
||
|
//
|
||
|
// As this may be a problem for you, I hereby grant an exception to my
|
||
|
// copyright on the SMMU source (including FraggleScript). You may use
|
||
|
// any code from SMMU in (G)ZDoom, provided that:
|
||
|
//
|
||
|
// * For any binary release of the port, the source code is also made
|
||
|
// available.
|
||
|
// * The copyright notice is kept on any file containing my code.
|
||
|
//
|
||
|
//
|
||
|
|
||
|
/* includes ************************/
|
||
|
|
||
|
#include "t_script.h"
|
||
|
#include "i_system.h"
|
||
|
#include "w_wad.h"
|
||
|
#include "farchive.h"
|
||
|
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// {} sections
|
||
|
//
|
||
|
// during preprocessing all of the {} sections
|
||
|
// are found. these are stored in a hash table
|
||
|
// according to their offset in the script.
|
||
|
// functions here deal with creating new sections
|
||
|
// and finding them from a given offset.
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
IMPLEMENT_POINTY_CLASS(DFsSection)
|
||
|
DECLARE_POINTER(next)
|
||
|
END_POINTERS
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
void DFsSection::Serialize(FArchive &ar)
|
||
|
{
|
||
|
Super::Serialize(ar);
|
||
|
ar << type << start_index << end_index << loop_index << next;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
char *DFsScript::SectionStart(const DFsSection *sec)
|
||
|
{
|
||
|
return data + sec->start_index;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
char *DFsScript::SectionEnd(const DFsSection *sec)
|
||
|
{
|
||
|
return data + sec->end_index;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
char *DFsScript::SectionLoop(const DFsSection *sec)
|
||
|
{
|
||
|
return data + sec->loop_index;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
void DFsScript::ClearSections()
|
||
|
{
|
||
|
for(int i=0;i<SECTIONSLOTS;i++)
|
||
|
{
|
||
|
DFsSection * var = sections[i];
|
||
|
while(var)
|
||
|
{
|
||
|
DFsSection *next = var->next;
|
||
|
var->Destroy();
|
||
|
var = next;
|
||
|
}
|
||
|
sections[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// create section
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
DFsSection *DFsScript::NewSection(const char *brace)
|
||
|
{
|
||
|
int n = section_hash(brace);
|
||
|
DFsSection *newsec = new DFsSection;
|
||
|
|
||
|
newsec->start_index = MakeIndex(brace);
|
||
|
newsec->next = sections[n];
|
||
|
sections[n] = newsec;
|
||
|
GC::WriteBarrier(this, newsec);
|
||
|
return newsec;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// find a Section from the location of the starting { brace
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
DFsSection *DFsScript::FindSectionStart(const char *brace)
|
||
|
{
|
||
|
int n = section_hash(brace);
|
||
|
DFsSection *current = sections[n];
|
||
|
|
||
|
// use the hash table: check the appropriate hash chain
|
||
|
|
||
|
while(current)
|
||
|
{
|
||
|
if(SectionStart(current) == brace) return current;
|
||
|
current = current->next;
|
||
|
}
|
||
|
|
||
|
return NULL; // not found
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// find a Section from the location of the closing } brace
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
DFsSection *DFsScript::FindSectionEnd(const char *brace)
|
||
|
{
|
||
|
int n;
|
||
|
|
||
|
// hash table is no use, they are hashed according to
|
||
|
// the offset of the starting brace
|
||
|
|
||
|
// we have to go through every entry to find from the
|
||
|
// ending brace
|
||
|
|
||
|
for(n=0; n<SECTIONSLOTS; n++) // check all sections in all chains
|
||
|
{
|
||
|
DFsSection *current = sections[n];
|
||
|
|
||
|
while(current)
|
||
|
{
|
||
|
if(SectionEnd(current) == brace) return current; // found it
|
||
|
current = current->next;
|
||
|
}
|
||
|
}
|
||
|
return NULL; // not found
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// preproocessor main loop
|
||
|
//
|
||
|
// This works by recursion. when a { opening
|
||
|
// brace is found, another instance of the
|
||
|
// function is called for the data inside
|
||
|
// the {} section.
|
||
|
// At the same time, the sections are noted
|
||
|
// down and hashed. Goto() labels are noted
|
||
|
// down, and comments are blanked out
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
char *DFsScript::ProcessFindChar(char *datap, char find)
|
||
|
{
|
||
|
while(*datap)
|
||
|
{
|
||
|
if(*datap==find) return datap;
|
||
|
if(*datap=='\"') // found a quote: ignore stuff in it
|
||
|
{
|
||
|
datap++;
|
||
|
while(*datap && *datap != '\"')
|
||
|
{
|
||
|
// escape sequence ?
|
||
|
if(*datap=='\\') datap++;
|
||
|
datap++;
|
||
|
}
|
||
|
// error: end of script in a constant
|
||
|
if(!*datap) return NULL;
|
||
|
}
|
||
|
|
||
|
// comments: blank out
|
||
|
|
||
|
if(*datap=='/' && *(datap+1)=='*') // /* -- */ comment
|
||
|
{
|
||
|
while(*datap && (*datap != '*' || *(datap+1) != '/') )
|
||
|
{
|
||
|
*datap=' '; datap++;
|
||
|
}
|
||
|
if(*datap)
|
||
|
*datap = *(datap+1) = ' '; // blank the last bit
|
||
|
else
|
||
|
{
|
||
|
// script terminated in comment
|
||
|
script_error("script terminated inside comment\n");
|
||
|
}
|
||
|
}
|
||
|
if(*datap=='/' && *(datap+1)=='/') // // -- comment
|
||
|
{
|
||
|
while(*datap != '\n')
|
||
|
{
|
||
|
*datap=' '; datap++; // blank out
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/********** labels ****************/
|
||
|
|
||
|
// labels are also found during the
|
||
|
// preprocessing. these are of the form
|
||
|
//
|
||
|
// label_name:
|
||
|
//
|
||
|
// and are used for the goto function.
|
||
|
// goto labels are stored as variables.
|
||
|
|
||
|
if(*datap==':' && scriptnum != -1) // not in global scripts
|
||
|
{
|
||
|
char *labelptr = datap-1;
|
||
|
|
||
|
while(!isop(*labelptr)) labelptr--;
|
||
|
|
||
|
FString labelname(labelptr+1, strcspn(labelptr+1, ":"));
|
||
|
|
||
|
if (labelname.Len() == 0)
|
||
|
{
|
||
|
Printf(PRINT_BOLD,"Script %d: ':' encountrered in incorrect position!\n",scriptnum);
|
||
|
}
|
||
|
|
||
|
DFsVariable *newlabel = NewVariable(labelname, svt_label);
|
||
|
newlabel->value.i = MakeIndex(labelptr);
|
||
|
}
|
||
|
|
||
|
if(*datap=='{') // { -- } sections: add 'em
|
||
|
{
|
||
|
DFsSection *newsec = NewSection(datap);
|
||
|
|
||
|
newsec->type = st_empty;
|
||
|
// find the ending } and save
|
||
|
char * theend = ProcessFindChar(datap+1, '}');
|
||
|
if(!theend)
|
||
|
{ // brace not found
|
||
|
// This is fatal because it will cause a crash later
|
||
|
// if the game isn't terminated.
|
||
|
I_Error("Script %d: section error: no ending brace\n", scriptnum);
|
||
|
}
|
||
|
|
||
|
newsec->end_index = MakeIndex(theend);
|
||
|
// continue from the end of the section
|
||
|
datap = theend;
|
||
|
}
|
||
|
datap++;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// second stage parsing
|
||
|
//
|
||
|
// second stage preprocessing considers the script
|
||
|
// in terms of tokens rather than as plain data.
|
||
|
//
|
||
|
// we 'dry' run the script: go thru each statement and
|
||
|
// collect types for Sections
|
||
|
//
|
||
|
// this is an important thing to do, it cannot be done
|
||
|
// at runtime for 2 reasons:
|
||
|
// 1. gotos() jumping inside loops will pass thru
|
||
|
// the end of the loop
|
||
|
// 2. savegames. loading a script saved inside a
|
||
|
// loop will let it pass thru the loop
|
||
|
//
|
||
|
// this is basically a cut-down version of the normal
|
||
|
// parsing loop.
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
void DFsScript::DryRunScript()
|
||
|
{
|
||
|
char *end = data + len;
|
||
|
char *rover = data;
|
||
|
|
||
|
// allocate space for the tokens
|
||
|
FParser parse(this);
|
||
|
try
|
||
|
{
|
||
|
while(rover < end && *rover)
|
||
|
{
|
||
|
rover = parse.GetTokens(rover);
|
||
|
|
||
|
if(!parse.NumTokens) continue;
|
||
|
|
||
|
if(parse.Section && parse.TokenType[0] == function)
|
||
|
{
|
||
|
if(!strcmp(parse.Tokens[0], "if"))
|
||
|
{
|
||
|
parse.Section->type = st_if;
|
||
|
continue;
|
||
|
}
|
||
|
else if(!strcmp(parse.Tokens[0], "elseif")) // haleyjd: SoM's else code
|
||
|
{
|
||
|
parse.Section->type = st_elseif;
|
||
|
continue;
|
||
|
}
|
||
|
else if(!strcmp(parse.Tokens[0], "else"))
|
||
|
{
|
||
|
parse.Section->type = st_else;
|
||
|
continue;
|
||
|
}
|
||
|
else if(!strcmp(parse.Tokens[0], "while") ||
|
||
|
!strcmp(parse.Tokens[0], "for"))
|
||
|
{
|
||
|
parse.Section->type = st_loop;
|
||
|
parse.Section->loop_index = MakeIndex(parse.LineStart);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (CFsError err)
|
||
|
{
|
||
|
parse.ErrorMessage(err.msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// main preprocess function
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
void DFsScript::Preprocess()
|
||
|
{
|
||
|
len = (int)strlen(data);
|
||
|
ProcessFindChar(data, 0); // fill in everything
|
||
|
DryRunScript();
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
//
|
||
|
// FraggleScript allows 'including' of other lumps.
|
||
|
// we divert input from the current script (normally
|
||
|
// levelscript) to a seperate lump. This of course
|
||
|
// first needs to be preprocessed to remove comments
|
||
|
// etc.
|
||
|
//
|
||
|
// parse an 'include' lump
|
||
|
//
|
||
|
//==========================================================================
|
||
|
|
||
|
void DFsScript::ParseInclude(char *lumpname)
|
||
|
{
|
||
|
int lumpnum;
|
||
|
char *lump;
|
||
|
|
||
|
if((lumpnum = Wads.CheckNumForName(lumpname)) == -1)
|
||
|
{
|
||
|
I_Error("include lump '%s' not found!\n", lumpname);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int lumplen=Wads.LumpLength(lumpnum);
|
||
|
lump=new char[lumplen+10];
|
||
|
Wads.ReadLump(lumpnum,lump);
|
||
|
|
||
|
lump[lumplen]=0;
|
||
|
|
||
|
// preprocess the include
|
||
|
// we assume that it does not include sections or labels or
|
||
|
// other nasty things
|
||
|
ProcessFindChar(lump, 0);
|
||
|
|
||
|
// now parse the lump
|
||
|
FParser parse(this);
|
||
|
parse.Run(lump, lump, lump+lumplen);
|
||
|
|
||
|
// free the lump
|
||
|
delete[] lump;
|
||
|
}
|
||
|
|