gzdoom/src/fragglescript/t_parse.cpp
Christoph Oelckers fae8ed444c - merged 3dfloors2 branch into trunk.
SVN r3124 (trunk)
2011-01-29 11:09:38 +00:00

746 lines
19 KiB
C++

// Emacs style mode select -*- C++ -*-
//----------------------------------------------------------------------------
//
// Copyright(C) 2000 Simon Howard
// Copyright(C) 2002-2008 Christoph Oelckers
//
// 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
//
//--------------------------------------------------------------------------
//
// Parsing.
//
// Takes lines of code, or groups of lines and runs them.
// The main core of FraggleScript
//
// 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 <stdarg.h>
#include "t_script.h"
#include "s_sound.h"
#include "v_text.h"
#include "c_cvars.h"
#include "i_system.h"
CVAR(Bool, script_debug, false, 0)
/************ Divide into tokens **************/
#define isnum(c) ( ((c)>='0' && (c)<='9') || (c)=='.')
//==========================================================================
//
// NextToken: end this token, go onto the next
//
//==========================================================================
void FParser::NextToken()
{
if(Tokens[NumTokens-1][0] || TokenType[NumTokens-1]==string_)
{
NumTokens++;
Tokens[NumTokens-1] = Tokens[NumTokens-2] + strlen(Tokens[NumTokens-2]) + 1;
Tokens[NumTokens-1][0] = 0;
}
// get to the next token, ignoring spaces, newlines,
// useless chars, comments etc
while(1)
{
// empty whitespace
if(*Rover && (*Rover==' ' || *Rover<32))
{
while((*Rover==' ' || *Rover<32) && *Rover) Rover++;
}
// end-of-script?
if(!*Rover)
{
if(Tokens[0][0])
{
// line contains text, but no semicolon: an error
script_error("missing ';'\n");
}
// empty line, end of command-list
return;
}
break; // otherwise
}
if(NumTokens>1 && *Rover == '(' && TokenType[NumTokens-2] == name_)
TokenType[NumTokens-2] = function;
if(*Rover == '{' || *Rover == '}')
{
if(*Rover == '{')
{
BraceType = bracket_open;
Section = Script->FindSectionStart(Rover);
}
else // closing brace
{
BraceType = bracket_close;
Section = Script->FindSectionEnd(Rover);
}
if(!Section)
{
I_Error("section not found!\n");
return;
}
}
else if(*Rover == ':') // label
{
// ignore the label : reset
NumTokens = 1;
Tokens[0][0] = 0; TokenType[NumTokens-1] = name_;
Rover++; // ignore
}
else if(*Rover == '\"')
{
TokenType[NumTokens-1] = string_;
if(TokenType[NumTokens-2] == string_) NumTokens--; // join strings
Rover++;
}
else
{
TokenType[NumTokens-1] = isop(*Rover) ? operator_ : isnum(*Rover) ? number : name_;
}
}
//==========================================================================
//
// return an escape sequence (prefixed by a '\')
// do not use all C escape sequences
//
//==========================================================================
static char escape_sequence(char c)
{
if(c == 'n') return '\n';
if(c == '\\') return '\\';
if(c == '"') return '"';
if(c == '?') return '?';
if(c == 'a') return '\a'; // alert beep
if(c == 't') return '\t'; //tab
return c;
}
//==========================================================================
//
// add_char: add one character to the current token
//
//==========================================================================
static void add_char(char *tokn, char c)
{
char *out = tokn + strlen(tokn);
out[0] = c;
out[1] = 0;
}
//==========================================================================
//
// get_tokens.
// Take a string, break it into tokens.
//
// individual tokens are stored inside the tokens[] array
// tokentype is also used to hold the type for each token:
//
// name: a piece of text which starts with an alphabet letter.
// probably a variable name. Some are converted into
// function types later on in find_brackets
// number: a number. like '12' or '1337'
// operator: an operator such as '&&' or '+'. All FraggleScript
// operators are either one character, or two character
// (if 2 character, 2 of the same char or ending in '=')
// string: a text string that was enclosed in quote "" marks in
// the original text
// unset: shouldn't ever end up being set really.
// function: a function name (found in second stage parsing)
//
//==========================================================================
char *FParser::GetTokens(char *s)
{
char *tokn = NULL;
Rover = s;
NumTokens = 1;
Tokens[0][0] = 0; TokenType[NumTokens-1] = name_;
Section = NULL; // default to no section found
NextToken();
LineStart = Rover; // save the start
if(*Rover)
{
while(1)
{
tokn = Tokens[NumTokens-1];
if(Section)
{
// a { or } section brace has been found
break; // stop parsing now
}
else if(TokenType[NumTokens-1] != string_)
{
if(*Rover == ';') break; // check for end of command ';'
}
switch(TokenType[NumTokens-1])
{
case unset:
case string_:
while(*Rover != '\"') // dedicated loop for speed
{
if(*Rover == '\\') // escape sequences
{
Rover++;
if (*Rover>='0' && *Rover<='9')
{
add_char(tokn, TEXTCOLOR_ESCAPE);
add_char(tokn, *Rover+'A'-'0');
}
else add_char(tokn, escape_sequence(*Rover));
}
else
add_char(tokn, *Rover);
Rover++;
}
Rover++;
NextToken(); // end of this token
continue;
case operator_:
// all 2-character operators either end in '=' or
// are 2 of the same character
// do not allow 2-characters for brackets '(' ')'
// which are still being considered as operators
// operators are only 2-char max, do not need
// a seperate loop
if((*tokn && *Rover != '=' && *Rover!=*tokn) ||
*tokn == '(' || *tokn == ')')
{
// end of operator
NextToken();
continue;
}
add_char(tokn, *Rover);
break;
case number:
// haleyjd: 8-17
// add while number chars are read
while(isnum(*Rover)) // dedicated loop
add_char(tokn, *Rover++);
NextToken();
continue;
case name_:
// add the chars
while(!isop(*Rover)) // dedicated loop
add_char(tokn, *Rover++);
NextToken();
continue;
default:
break;
}
Rover++;
}
}
// check for empty last token
if(!tokn || !tokn[0])
{
NumTokens = NumTokens - 1;
}
Rover++;
return Rover;
}
//==========================================================================
//
// PrintTokens: add one character to the current token
//
//==========================================================================
void FParser::PrintTokens() // DEBUG
{
int i;
for (i = 0; i < NumTokens; i++)
{
Printf("\n'%s' \t\t --", Tokens[i]);
switch (TokenType[i])
{
case string_:
Printf("string");
break;
case operator_:
Printf("operator");
break;
case name_:
Printf("name");
break;
case number:
Printf("number");
break;
case unset:
Printf("duh");
break;
case function:
Printf("function name");
break;
}
}
Printf("\n");
if (Section)
Printf("current section: offset %i\n", Section->start_index);
}
//==========================================================================
//
// Parses a block of script code
//
//==========================================================================
void FParser::Run(char *rover, char *data, char *end)
{
Rover = rover;
try
{
PrevSection = NULL; // clear it
while(*Rover) // go through the script executing each statement
{
// past end of script?
if(Rover > end)
break;
PrevSection = Section; // store from prev. statement
// get the line and tokens
GetTokens(Rover);
if(!NumTokens)
{
if(Section) // no tokens but a brace
{
// possible } at end of loop:
// refer to spec.c
spec_brace();
}
continue; // continue to next statement
}
if(script_debug) PrintTokens(); // debug
RunStatement(); // run the statement
}
}
catch (const CFsError &err)
{
ErrorMessage(err.msg);
}
catch (const CFsTerminator &)
{
// The script has signalled that it wants to be terminated in an orderly fashion.
}
}
//==========================================================================
//
// decide what to do with it
//
// NB this stuff is a bit hardcoded:
// it could be nicer really but i'm
// aiming for speed
//
// if() and while() will be mistaken for functions
// during token processing
//
//==========================================================================
void FParser::RunStatement()
{
if(TokenType[0] == function)
{
if(!strcmp(Tokens[0], "if"))
{
Script->lastiftrue = spec_if();
return;
}
else if(!strcmp(Tokens[0], "elseif"))
{
if(!PrevSection ||
(PrevSection->type != st_if &&
PrevSection->type != st_elseif))
{
script_error("elseif statement without if\n");
return;
}
Script->lastiftrue = spec_elseif(Script->lastiftrue);
return;
}
else if(!strcmp(Tokens[0], "else"))
{
if(!PrevSection ||
(PrevSection->type != st_if &&
PrevSection->type != st_elseif))
{
script_error("else statement without if\n");
return;
}
spec_else(Script->lastiftrue);
Script->lastiftrue = true;
return;
}
else if(!strcmp(Tokens[0], "while"))
{
spec_while();
return;
}
else if(!strcmp(Tokens[0], "for"))
{
spec_for();
return;
}
}
else if(TokenType[0] == name_)
{
// NB: goto is a function so is not here
// Allow else without '()'
if (!strcmp(Tokens[0], "else"))
{
if(!PrevSection ||
(PrevSection->type != st_if &&
PrevSection->type != st_elseif))
{
script_error("else statement without if\n");
return;
}
spec_else(Script->lastiftrue);
Script->lastiftrue = true;
return;
}
// if a variable declaration, return now
if(spec_variable()) return;
}
// just a plain expression
svalue_t scratch;
EvaluateExpression(scratch,0, NumTokens-1);
}
/***************** Evaluating Expressions ************************/
//==========================================================================
//
// find a token, ignoring things in brackets
//
//==========================================================================
int FParser::FindOperator(int start, int stop, const char *value)
{
int i;
int bracketlevel = 0;
for(i=start; i<=stop; i++)
{
// only interested in operators
if(TokenType[i] != operator_) continue;
// use bracketlevel to check the number of brackets
// which we are inside
bracketlevel += Tokens[i][0]=='(' ? 1 :
Tokens[i][0]==')' ? -1 : 0;
// only check when we are not in brackets
if(!bracketlevel && !strcmp(value, Tokens[i]))
return i;
}
return -1;
}
//==========================================================================
//
// go through tokens the same as find_operator, but backwards
//
//==========================================================================
int FParser::FindOperatorBackwards(int start, int stop, const char *value)
{
int i;
int bracketlevel = 0;
for(i=stop; i>=start; i--) // check backwards
{
// operators only
if(TokenType[i] != operator_) continue;
// use bracketlevel to check the number of brackets
// which we are inside
bracketlevel += Tokens[i][0]=='(' ? -1 :
Tokens[i][0]==')' ? 1 : 0;
// only check when we are not in brackets
// if we find what we want, return it
if(!bracketlevel && !strcmp(value, Tokens[i]))
return i;
}
return -1;
}
//==========================================================================
//
// simple_evaluate is used once evalute_expression gets to the level
// where it is evaluating just one token
//
// converts number tokens into svalue_ts and returns
// the same with string tokens
// name tokens are considered to be variables and
// attempts are made to find the value of that variable
// command tokens are executed (does not return a svalue_t)
//
//==========================================================================
void FParser::SimpleEvaluate(svalue_t &returnvar, int n)
{
DFsVariable *var;
switch(TokenType[n])
{
case string_:
returnvar.type = svt_string;
returnvar.string = Tokens[n];
break;
case number:
if(strchr(Tokens[n], '.'))
{
returnvar.type = svt_fixed;
returnvar.value.f = (fixed_t)(atof(Tokens[n]) * FRACUNIT);
}
else
{
returnvar.type = svt_int;
returnvar.value.i = atoi(Tokens[n]);
}
break;
case name_:
var = Script->FindVariable(Tokens[n]);
if(!var)
{
script_error("unknown variable '%s'\n", Tokens[n]);
}
else var->GetValue(returnvar);
default:
break;
}
}
//==========================================================================
//
// pointless_brackets checks to see if there are brackets surrounding
// an expression. eg. "(2+4)" is the same as just "2+4"
//
// because of the recursive nature of evaluate_expression, this function is
// neccesary as evaluating expressions such as "2*(2+4)" will inevitably
// lead to evaluating "(2+4)"
//
//==========================================================================
void FParser::PointlessBrackets(int *start, int *stop)
{
int bracket_level, i;
// check that the start and end are brackets
while(Tokens[*start][0] == '(' && Tokens[*stop][0] == ')')
{
bracket_level = 0;
// confirm there are pointless brackets..
// if they are, bracket_level will only get to 0
// at the last token
// check up to <*stop rather than <=*stop to ignore
// the last token
for(i = *start; i<*stop; i++)
{
if(TokenType[i] != operator_) continue; // ops only
bracket_level += (Tokens[i][0] == '(');
bracket_level -= (Tokens[i][0] == ')');
if(bracket_level == 0) return; // stop if braces stop before end
}
// move both brackets in
*start = *start + 1;
*stop = *stop - 1;
}
}
//==========================================================================
//
// evaluate_expresion is the basic function used to evaluate
// a FraggleScript expression.
// start and stop denote the tokens which are to be evaluated.
//
// works by recursion: it finds operators in the expression
// (checking for each in turn), then splits the expression into
// 2 parts, left and right of the operator found.
// The handler function for that particular operator is then
// called, which in turn calls evaluate_expression again to
// evaluate each side. When it reaches the level of being asked
// to evaluate just 1 token, it calls simple_evaluate
//
//==========================================================================
void FParser::EvaluateExpression(svalue_t &result, int start, int stop)
{
int i, n;
// possible pointless brackets
if(TokenType[start] == operator_ && TokenType[stop] == operator_)
PointlessBrackets(&start, &stop);
if(start == stop) // only 1 thing to evaluate
{
SimpleEvaluate(result, start);
return;
}
// go through each operator in order of precedence
for(i=0; i<num_operators; i++)
{
// check backwards for the token. it has to be
// done backwards for left-to-right reading: eg so
// 5-3-2 is (5-3)-2 not 5-(3-2)
if (operators[i].direction==forward)
{
n = FindOperatorBackwards(start, stop, operators[i].string);
}
else
{
n = FindOperator(start, stop, operators[i].string);
}
if( n != -1)
{
// call the operator function and evaluate this chunk of tokens
(this->*operators[i].handler)(result, start, n, stop);
return;
}
}
if(TokenType[start] == function)
{
EvaluateFunction(result, start, stop);
return;
}
// error ?
{
FString tempstr;
for(i=start; i<=stop; i++) tempstr << Tokens[i] << ' ';
script_error("couldnt evaluate expression: %s\n",tempstr.GetChars());
}
}
//==========================================================================
//
// intercepts an error message and inserts script/line information
//
//==========================================================================
void FParser::ErrorMessage(FString msg)
{
int linenum = 0;
// find the line number
if(Rover >= Script->data && Rover <= Script->data+Script->len)
{
char *temp;
for(temp = Script->data; temp<LineStart; temp++)
if(*temp == '\n') linenum++; // count EOLs
}
//lineinfo.Format("Script %d, line %d: ", Script->scriptnum, linenum);
I_Error("Script %d, line %d: %s", Script->scriptnum, linenum, msg.GetChars());
}
//==========================================================================
//
// throws an error message
//
//==========================================================================
void script_error(const char *s, ...)
{
FString composed;
va_list args;
va_start(args, s);
composed.VFormat(s, args);
throw CFsError(composed);
}
// EOF