mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-01 00:21:43 +00:00
628 lines
16 KiB
C++
628 lines
16 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
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// 'Special' stuff
|
|
//
|
|
// if(), int statements, etc.
|
|
//
|
|
// 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.
|
|
//
|
|
//
|
|
|
|
#include "t_script.h"
|
|
|
|
//==========================================================================
|
|
//
|
|
// ending brace found in parsing
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::spec_brace()
|
|
{
|
|
if(BraceType != bracket_close) // only deal with closing } braces
|
|
return;
|
|
|
|
// if() requires nothing to be done
|
|
if(Section->type == st_if || Section->type == st_else)
|
|
return;
|
|
|
|
// if a loop, jump back to the start of the loop
|
|
if(Section->type == st_loop)
|
|
{
|
|
Rover = Script->SectionLoop(Section);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 'if' statement -- haleyjd: changed to bool for else/elseif
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FParser::spec_if()
|
|
{
|
|
int endtoken;
|
|
svalue_t eval;
|
|
|
|
|
|
if((endtoken = FindOperator(0, NumTokens-1, ")")) == -1)
|
|
{
|
|
script_error("parse error in if statement\n");
|
|
return false;
|
|
}
|
|
|
|
// 2 to skip past the 'if' and '('
|
|
EvaluateExpression(eval, 2, endtoken-1);
|
|
bool ifresult = !!intvalue(eval);
|
|
|
|
if(Section && BraceType == bracket_open && endtoken == NumTokens-1)
|
|
{
|
|
// {} braces
|
|
if(!ifresult) // skip to end of section
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
else if(ifresult) // if() without {} braces
|
|
{
|
|
// nothing to do ?
|
|
if(endtoken != NumTokens-1)
|
|
EvaluateExpression(eval, endtoken+1, NumTokens-1);
|
|
}
|
|
|
|
return ifresult;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 'elseif' statement
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FParser::spec_elseif(bool lastif)
|
|
{
|
|
int endtoken;
|
|
svalue_t eval;
|
|
|
|
if((endtoken = FindOperator(0, NumTokens-1, ")")) == -1)
|
|
{
|
|
script_error("parse error in elseif statement\n");
|
|
return false;
|
|
}
|
|
|
|
if(lastif)
|
|
{
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
return true;
|
|
}
|
|
// 2 to skip past the 'elseif' and '('
|
|
EvaluateExpression(eval, 2, endtoken-1);
|
|
bool ifresult = !!intvalue(eval);
|
|
|
|
if(Section && BraceType == bracket_open
|
|
&& endtoken == NumTokens-1)
|
|
{
|
|
// {} braces
|
|
if(!ifresult) // skip to end of section
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
else if(ifresult) // elseif() without {} braces
|
|
{
|
|
// nothing to do ?
|
|
if(endtoken != NumTokens-1)
|
|
EvaluateExpression(eval, endtoken+1, NumTokens-1);
|
|
}
|
|
|
|
return ifresult;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 'else' statement
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::spec_else(bool lastif)
|
|
{
|
|
if(lastif)
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// while() loop
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::spec_while()
|
|
{
|
|
int endtoken;
|
|
svalue_t eval;
|
|
|
|
if(!Section)
|
|
{
|
|
script_error("no {} section given for loop\n");
|
|
return;
|
|
}
|
|
|
|
if( (endtoken = FindOperator(0, NumTokens-1, ")")) == -1)
|
|
{
|
|
script_error("parse error in loop statement\n");
|
|
return;
|
|
}
|
|
|
|
EvaluateExpression(eval, 2, endtoken-1);
|
|
|
|
// skip if no longer valid
|
|
if(!intvalue(eval)) Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// for() loop
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::spec_for()
|
|
{
|
|
svalue_t eval;
|
|
int start;
|
|
int comma1, comma2; // token numbers of the seperating commas
|
|
|
|
if(!Section)
|
|
{
|
|
script_error("need {} delimiters for for()\n");
|
|
return;
|
|
}
|
|
|
|
// is a valid section
|
|
|
|
start = 2; // skip "for" and "(": start on third token(2)
|
|
|
|
// find the seperating commas first
|
|
|
|
if( (comma1 = FindOperator(start, NumTokens-1, ",")) == -1
|
|
|| (comma2 = FindOperator(comma1+1, NumTokens-1, ",")) == -1)
|
|
{
|
|
script_error("incorrect arguments to for()\n"); // haleyjd:
|
|
return; // said if()
|
|
}
|
|
|
|
// are we looping back from a previous loop?
|
|
if(Section == PrevSection)
|
|
{
|
|
// do the loop 'action' (third argument)
|
|
EvaluateExpression(eval, comma2+1, NumTokens-2);
|
|
|
|
// check if we should run the loop again (second argument)
|
|
EvaluateExpression(eval, comma1+1, comma2-1);
|
|
if(!intvalue(eval))
|
|
{
|
|
// stop looping
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// first time: starting the loop
|
|
// just evaluate the starting expression (first arg)
|
|
EvaluateExpression(eval, start, comma1-1);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Variable Creation
|
|
//
|
|
// called for each individual variable in a statement
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::CreateVariable(int newvar_type, DFsScript *newvar_script, int start, int stop)
|
|
{
|
|
if(TokenType[start] != name_)
|
|
{
|
|
script_error("invalid name for variable: '%s'\n", Tokens[start]);
|
|
return;
|
|
}
|
|
|
|
// check if already exists, only checking
|
|
// the current script
|
|
if(newvar_script->VariableForName (Tokens[start]))
|
|
{
|
|
// In Eternity this was fatal and in Legacy it was ignored
|
|
// So make this a warning.
|
|
Printf("FS: redefined symbol: '%s'\n", Tokens[start]);
|
|
return; // already one
|
|
}
|
|
|
|
// haleyjd: disallow mobj references in the hub script --
|
|
// they cause dangerous dangling references and are of no
|
|
// potential use
|
|
if(newvar_script != Script && newvar_type == svt_mobj)
|
|
{
|
|
script_error("cannot create mobj reference in hub script\n");
|
|
return;
|
|
}
|
|
|
|
newvar_script->NewVariable (Tokens[start], newvar_type);
|
|
|
|
if(stop != start)
|
|
{
|
|
svalue_t scratch;
|
|
EvaluateExpression(scratch, start, stop);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// divide a statement (without type prefix) into individual
|
|
// variables to create them using create_variable
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::ParseVarLine(int newvar_type, DFsScript *newvar_script, int start)
|
|
{
|
|
int starttoken = start, endtoken;
|
|
|
|
while(1)
|
|
{
|
|
endtoken = FindOperator(starttoken, NumTokens-1, ",");
|
|
if(endtoken == -1) break;
|
|
CreateVariable(newvar_type, newvar_script, starttoken, endtoken-1);
|
|
starttoken = endtoken+1; //start next after end of this one
|
|
}
|
|
// dont forget the last one
|
|
CreateVariable(newvar_type, newvar_script, starttoken, NumTokens-1);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// variable definition
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FParser::spec_variable()
|
|
{
|
|
int start = 0;
|
|
|
|
int newvar_type = -1; // init to -1
|
|
DFsScript *newvar_script = Script; // use current script
|
|
|
|
// check for 'hub' keyword to make a hub variable
|
|
if(!strcmp(Tokens[start], "hub"))
|
|
{
|
|
// The hub script doesn't work so it's probably safest to store the variable locally.
|
|
//newvar_script = &hub_script;
|
|
start++; // skip first token
|
|
}
|
|
|
|
// now find variable type
|
|
if(!strcmp(Tokens[start], "const"))
|
|
{
|
|
newvar_type = svt_const;
|
|
start++;
|
|
}
|
|
else if(!strcmp(Tokens[start], "string"))
|
|
{
|
|
newvar_type = svt_string;
|
|
start++;
|
|
}
|
|
else if(!strcmp(Tokens[start], "int"))
|
|
{
|
|
newvar_type = svt_int;
|
|
start++;
|
|
}
|
|
else if(!strcmp(Tokens[start], "mobj"))
|
|
{
|
|
newvar_type = svt_mobj;
|
|
start++;
|
|
}
|
|
else if(!strcmp(Tokens[start], "fixed") || !strcmp(Tokens[start], "float"))
|
|
{
|
|
newvar_type = svt_fixed;
|
|
start++;
|
|
}
|
|
else if(!strcmp(Tokens[start], "script")) // check for script creation
|
|
{
|
|
spec_script();
|
|
return true; // used tokens
|
|
}
|
|
|
|
// are we creating a new variable?
|
|
if(newvar_type != -1)
|
|
{
|
|
ParseVarLine(newvar_type, newvar_script, start);
|
|
return true; // used tokens
|
|
}
|
|
|
|
return false; // not used: try normal parsing
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// ADD SCRIPT
|
|
//
|
|
// when the level is first loaded, all the
|
|
// scripts are simply stored in the levelscript.
|
|
// before the level starts, this script is
|
|
// preprocessed and run like any other. This allows
|
|
// the individual scripts to be derived from the
|
|
// levelscript. When the interpreter detects the
|
|
// 'script' keyword this function is called
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::spec_script()
|
|
{
|
|
int scriptnum;
|
|
int datasize;
|
|
DFsScript *newscript;
|
|
|
|
scriptnum = 0;
|
|
|
|
if(!Section)
|
|
{
|
|
script_error("need seperators for newscript\n");
|
|
return;
|
|
}
|
|
|
|
// presume that the first token is "newscript"
|
|
|
|
if(NumTokens < 2)
|
|
{
|
|
script_error("need newscript number\n");
|
|
return;
|
|
}
|
|
|
|
svalue_t result;
|
|
EvaluateExpression(result, 1, NumTokens-1);
|
|
scriptnum = intvalue(result);
|
|
|
|
if(scriptnum < 0)
|
|
{
|
|
script_error("invalid newscript number\n");
|
|
return;
|
|
}
|
|
|
|
newscript = new DFsScript;
|
|
|
|
// add to scripts list of parent
|
|
Script->children[scriptnum] = newscript;
|
|
GC::WriteBarrier(Script, newscript);
|
|
|
|
// copy newscript data
|
|
// workout newscript size: -2 to ignore { and }
|
|
datasize = (Section->end_index - Section->start_index - 2);
|
|
|
|
// alloc extra 10 for safety
|
|
newscript->data = (char *)malloc(datasize+10);
|
|
|
|
// copy from parent newscript (levelscript)
|
|
// ignore first char which is {
|
|
memcpy(newscript->data, Script->SectionStart(Section) + 1, datasize);
|
|
|
|
// tack on a 0 to end the string
|
|
newscript->data[datasize] = '\0';
|
|
|
|
newscript->scriptnum = scriptnum;
|
|
newscript->parent = Script; // remember parent
|
|
|
|
// preprocess the newscript now
|
|
newscript->Preprocess();
|
|
|
|
// we dont want to run the newscript, only add it
|
|
// jump past the newscript in parsing
|
|
|
|
Rover = Script->SectionEnd(Section) + 1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// evaluate_function: once parse.c is pretty
|
|
// sure it has a function to run it calls
|
|
// this. evaluate_function makes sure that
|
|
// it is a function call first, then evaluates all
|
|
// the arguments given to the function.
|
|
// these are built into an argc/argv-style
|
|
// list. the function 'handler' is then called.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::EvaluateFunction(svalue_t &result, int start, int stop)
|
|
{
|
|
DFsVariable *func = NULL;
|
|
int startpoint, endpoint;
|
|
|
|
// the arguments need to be built locally in case of
|
|
// function returns as function arguments eg
|
|
// print("here is a random number: ", rnd() );
|
|
|
|
int argc;
|
|
svalue_t argv[MAXARGS];
|
|
|
|
if(TokenType[start] != function || TokenType[stop] != operator_
|
|
|| Tokens[stop][0] != ')' )
|
|
{
|
|
script_error("misplaced closing paren\n");
|
|
}
|
|
|
|
// all the functions are stored in the global script
|
|
else if( !(func = global_script->VariableForName (Tokens[start])) )
|
|
{
|
|
script_error("no such function: '%s'\n",Tokens[start]);
|
|
}
|
|
|
|
else if(func->type != svt_function && func->type != svt_linespec)
|
|
{
|
|
script_error("'%s' not a function\n", Tokens[start]);
|
|
}
|
|
|
|
// build the argument list
|
|
// use a C command-line style system rather than
|
|
// a system using a fixed length list
|
|
|
|
argc = 0;
|
|
endpoint = start + 2; // ignore the function name and first bracket
|
|
|
|
while(endpoint < stop)
|
|
{
|
|
startpoint = endpoint;
|
|
endpoint = FindOperator(startpoint, stop-1, ",");
|
|
|
|
// check for -1: no more ','s
|
|
if(endpoint == -1)
|
|
{ // evaluate the last expression
|
|
endpoint = stop;
|
|
}
|
|
if(endpoint-1 < startpoint)
|
|
break;
|
|
|
|
EvaluateExpression(argv[argc], startpoint, endpoint-1);
|
|
endpoint++; // skip the ','
|
|
argc++;
|
|
}
|
|
|
|
// store the arguments in the global arglist
|
|
t_argc = argc;
|
|
t_argv = argv;
|
|
|
|
// haleyjd: return values can propagate to void functions, so
|
|
// t_return needs to be cleared now
|
|
|
|
t_return.type = svt_int;
|
|
t_return.value.i = 0;
|
|
|
|
// now run the function
|
|
if (func->type == svt_function)
|
|
{
|
|
(this->*func->value.handler)();
|
|
}
|
|
else
|
|
{
|
|
RunLineSpecial(func->value.ls);
|
|
}
|
|
|
|
// return the returned value
|
|
result = t_return;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// structure dot (.) operator
|
|
// there are not really any structs in FraggleScript, it's
|
|
// just a different way of calling a function that looks
|
|
// nicer. ie
|
|
// a.b() = a.b = b(a)
|
|
// a.b(c) = b(a,c)
|
|
//
|
|
// this function is just based on the one above
|
|
//
|
|
//==========================================================================
|
|
|
|
void FParser::OPstructure(svalue_t &result, int start, int n, int stop)
|
|
{
|
|
DFsVariable *func = NULL;
|
|
|
|
// the arguments need to be built locally in case of
|
|
// function returns as function arguments eg
|
|
// print("here is a random number: ", rnd() );
|
|
|
|
int argc;
|
|
svalue_t argv[MAXARGS];
|
|
|
|
// all the functions are stored in the global script
|
|
if( !(func = global_script->VariableForName (Tokens[n+1])) )
|
|
{
|
|
script_error("no such function: '%s'\n",Tokens[n+1]);
|
|
}
|
|
|
|
else if(func->type != svt_function)
|
|
{
|
|
script_error("'%s' not a function\n", Tokens[n+1]);
|
|
}
|
|
|
|
// build the argument list
|
|
|
|
// add the left part as first arg
|
|
|
|
EvaluateExpression(argv[0], start, n-1);
|
|
argc = 1; // start on second argv
|
|
|
|
if(stop != n+1) // can be a.b not a.b()
|
|
{
|
|
int startpoint, endpoint;
|
|
|
|
// ignore the function name and first bracket
|
|
endpoint = n + 3;
|
|
|
|
while(endpoint < stop)
|
|
{
|
|
startpoint = endpoint;
|
|
endpoint = FindOperator(startpoint, stop-1, ",");
|
|
|
|
// check for -1: no more ','s
|
|
if(endpoint == -1)
|
|
{ // evaluate the last expression
|
|
endpoint = stop;
|
|
}
|
|
if(endpoint-1 < startpoint)
|
|
break;
|
|
|
|
EvaluateExpression(argv[argc], startpoint, endpoint-1);
|
|
endpoint++; // skip the ','
|
|
argc++;
|
|
}
|
|
}
|
|
|
|
// store the arguments in the global arglist
|
|
t_argc = argc;
|
|
t_argv = argv;
|
|
t_func = func->Name;
|
|
|
|
// haleyjd: return values can propagate to void functions, so
|
|
// t_return needs to be cleared now
|
|
|
|
t_return.type = svt_int;
|
|
t_return.value.i = 0;
|
|
|
|
// now run the function
|
|
(this->*func->value.handler)();
|
|
|
|
// return the returned value
|
|
result = t_return;
|
|
}
|
|
|
|
|