// 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 #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.setDouble(atof(Tokens[n])); } 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*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; tempscriptnum, 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