gzdoom/src/fragglescript/t_spec.cpp
2016-03-01 09:47:10 -06:00

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;
}