/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2012 Warzone 2100 Project Warzone 2100 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. Warzone 2100 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 Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ %{ /* * script.l * * Script file lexer. */ #include #include "lib/framework/frame.h" #include "lib/framework/string_ext.h" #include "lib/script/interpreter.h" #include "lib/script/parse.h" #include "lib/script/script.h" /* Get the Yacc definitions */ #if defined (WZ_CC_MSVC) #include "script_parser.hpp" #else #include "script_parser.h" #endif // fwrite declared with warn_unused_result, resulting in mysterious errors in "%%" on some distros. static inline bool no_warn_unused_result(int ignore) { if (ignore) {} return true; } #define fwrite(a, b, c, d) no_warn_unused_result(fwrite(a, b, c, d)) /* Maximum length for any TEXT value */ #ifndef YYLMAX #define YYLMAX 255 #endif #if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION == 33 extern int scr_get_lineno(void); extern FILE *scr_get_in(void); extern FILE *scr_get_out(void); extern int scr_get_leng(void); extern char *scr_get_text(void); extern void scr_set_lineno(int line_number); extern void scr_set_in(FILE* in_str); extern void scr_set_out(FILE* out_str); extern int scr_get_debug(void); extern void scr_set_debug(int bdebug); extern int scr_lex_destroy(void); #endif /* Store for any string values */ static char aText[TEXT_BUFFERS][YYLMAX]; static UDWORD currText=0; // Note if we are in a comment static bool inComment = false; /* FLEX include buffer stack */ static YY_BUFFER_STATE include_stack[MAX_SCR_INCLUDE_DEPTH]; /* FLEX define buffer stack */ static YY_BUFFER_STATE macro_stack[MAX_SCR_MACRO_DEPTH]; static SCR_MACRO scr_macro[MAX_SCR_MACROS]; /* Script include files stack */ static unsigned int scr_include_stack_ptr = 0; static PHYSFS_file* pScrInputFiles[MAX_SCR_INCLUDE_DEPTH]; /* Line count stack used with script includes */ static unsigned int scrInclLine[MAX_SCR_INCLUDE_DEPTH]; /* Script defines stack */ static unsigned int scr_num_macros; /* Number of macros defined so far */ static int scr_macro_stack_ptr; /* Pointer to the current flex macro input buffer */ static char *pScrMacroBuffer[MAX_SCR_MACRO_DEPTH]; #undef YY_INPUT #define YY_INPUT(buf,result,max_size) \ { \ if (PHYSFS_eof(pScrInputFiles[scr_include_stack_ptr])) \ { \ buf[0] = EOF; \ result = YY_NULL; \ } \ else \ { \ result = PHYSFS_read(pScrInputFiles[scr_include_stack_ptr], buf, 1, max_size); \ if (result == -1) \ { \ buf[0] = EOF; \ result = YY_NULL; \ } \ } \ } static YYSTYPE dummy; /* Get the token type for a variable symbol */ static SDWORD scriptGetVarToken(VAR_SYMBOL *psVar) { bool object; // See if this is an object pointer if (!asScrTypeTab || psVar->type < VAL_USERTYPESTART) { object = false; } else { object = asScrTypeTab[psVar->type - VAL_USERTYPESTART].accessType == AT_OBJECT; } if (psVar->storage == ST_OBJECT) { /* This is an object member variable */ if (object) { RULE("current variable is OBJ_OBJVAR"); return OBJ_OBJVAR; } else { switch (psVar->type) { case VAL_BOOL: return BOOL_OBJVAR; break; case VAL_INT: // case VAL_FLOAT: RULE( "current variable is NUM_OBJVAR"); return NUM_OBJVAR; break; default: RULE( "current variable is USER_OBJVAR"); return USER_OBJVAR; break; } } } else if (psVar->dimensions > 0) { /* This is an array variable */ if (object) { return OBJ_ARRAY; } else { switch (psVar->type) { case VAL_BOOL: return BOOL_ARRAY; break; case VAL_FLOAT: return FLOAT_ARRAY; break; case VAL_INT: return NUM_ARRAY; break; default: return VAR_ARRAY; break; } } } else { /* This is a standard variable */ if (object) { RULE( "current variable is OBJ_VAR"); return OBJ_VAR; } else { RULE( "ordinary var"); switch (psVar->type) { case VAL_BOOL: return BOOL_VAR; break; case VAL_FLOAT: RULE( "VAL_FLOAT"); return FLOAT_VAR; break; case VAL_INT: return NUM_VAR; break; case VAL_STRING: return STRING_VAR; break; default: return VAR; break; } } } } /* Get the token type for a constant symbol */ static SDWORD scriptGetConstToken(CONST_SYMBOL *psConst) { bool object; // See if this is an object constant if (!asScrTypeTab || psConst->type < VAL_USERTYPESTART) { object = false; } else { object = asScrTypeTab[psConst->type - VAL_USERTYPESTART].accessType == AT_OBJECT; } switch (psConst->type) { case VAL_BOOL: return BOOL_CONSTANT; break; // case VAL_FLOAT: case VAL_INT: return NUM_CONSTANT; break; case VAL_STRING: return STRING_CONSTANT; break; default: if (object) { //debug(LOG_SCRIPT, "scriptGetConstToken: OBJ_CONSTANT"); return OBJ_CONSTANT; } else { return USER_CONSTANT; } break; } } /* Get the token type for a function symbol */ static SDWORD scriptGetFuncToken(FUNC_SYMBOL *psFunc) { bool object; // See if this is an object pointer if(psFunc->type >= VAL_USERTYPESTART) { object = asScrTypeTab[psFunc->type - VAL_USERTYPESTART].accessType == AT_OBJECT; if (object) { return OBJ_FUNC; } } switch (psFunc->type) { case VAL_BOOL: return BOOL_FUNC; break; case VAL_FLOAT: return FLOAT_FUNC; break; case VAL_INT: return NUM_FUNC; break; case VAL_STRING: return STRING_FUNC; break; case VAL_VOID: return FUNC; break; default: return USER_FUNC; break; } } /* Get the token type for a custom function symbol */ static SDWORD scriptGetCustomFuncToken(EVENT_SYMBOL *psFunc) { bool object; // See if this is an object pointer if (!asScrTypeTab || psFunc->retType < VAL_USERTYPESTART) { object = false; } else { object = asScrTypeTab[psFunc->retType - VAL_USERTYPESTART].accessType == AT_OBJECT; } if (object) { return OBJ_FUNC_CUST; } else { switch (psFunc->retType) { case VAL_BOOL: return BOOL_FUNC_CUST; break; case VAL_FLOAT: RULE( "current function is FLOAT_FUNC_CUST"); return FLOAT_FUNC_CUST; break; case VAL_INT: RULE( "current function is NUM_FUNC_CUST"); return NUM_FUNC_CUST; break; case VAL_STRING: return STRING_FUNC_CUST; break; case VAL_VOID: return VOID_FUNC_CUST; break; default: return USER_FUNC_CUST; break; } } } /* Look up defined macro and return new define buffer */ static bool scriptLoopUpMacro(const char *pMacro, char **ppMacroBody) { unsigned int i; for (i = 0; i < scr_num_macros; ++i) { if(strcmp(pMacro, &(scr_macro[i].scr_define_macro[0]) ) == 0) { *ppMacroBody = (char *)malloc(MAXSTRLEN); strcpy( *ppMacroBody, &(scr_macro[i].scr_define_body[0]) ); //copy macro body into buffer so we can process it //*pMacroBody = &(scr_macro[i].scr_define_body[0]); //return macro body return true; } } return false; } /* Return to the previous macto buffer */ static void popMacroStack(void) { ASSERT(scr_macro_stack_ptr <= MAX_SCR_MACRO_DEPTH, "FLEX: macro stack pointer out of bounds: %d", scr_macro_stack_ptr); /* Free current macro buffer */ free(pScrMacroBuffer[scr_macro_stack_ptr]); pScrMacroBuffer[scr_macro_stack_ptr] = NULL; /* Delete flex buffer */ yy_delete_buffer( YY_CURRENT_BUFFER ); /* Switch one level lower in the macro stack */ yy_switch_to_buffer( macro_stack[scr_macro_stack_ptr - 1] ); scr_macro_stack_ptr--; } /* Store macro name */ static void storeMacroName(const char *pMacroName) { ASSERT(scr_num_macros < MAX_SCR_MACROS, "FLEX: macro count out of bounds: %u", scr_num_macros); /* Make sure this name isn't already reserved */ if((scriptLookUpType(pMacroName, &dummy.tval)) || (scriptLookUpVariable(pMacroName, &dummy.vSymbol)) || (scriptLookUpConstant(pMacroName, &dummy.cSymbol)) || (scriptLookUpFunction(pMacroName, &dummy.fSymbol)) || (scriptLookUpCustomFunction(pMacroName, &dummy.eSymbol)) || (scriptLookUpTrigger(pMacroName, &dummy.tSymbol)) || (scriptLookUpEvent(pMacroName, &dummy.eSymbol)) || (scriptLookUpCallback(pMacroName, &dummy.cbSymbol))) { scr_error("FLEX: macro name '%s' is already reserved", pMacroName); } /* Store define macro */ sstrcpy(scr_macro[scr_num_macros].scr_define_macro, pMacroName); } /* Store macro body */ static void storeMacroBody(const char *pMacroBody) { ASSERT(scr_num_macros < MAX_SCR_MACROS, "FLEX: macro count out of bounds: %u", scr_num_macros); /* Store macro body */ sstrcpy(scr_macro[scr_num_macros].scr_define_body, pMacroBody); scr_num_macros++; } /* include directive encountered - push current buffer, set up new one */ static void pushInclude(const char *pIncludePath) { PHYSFS_file* newInput; if ( scr_include_stack_ptr >= MAX_SCR_INCLUDE_DEPTH ){ scr_error("FLEX: Includes nested too deeply" ); } if(!PHYSFS_exists(pIncludePath)){ scr_error("FLEX: '%s' include doesn't exist", pIncludePath ); } /* Open include */ newInput = PHYSFS_openRead(pIncludePath); if(!newInput){ scr_error("FLEX: Couldn't open include: '%s'\n%s", pIncludePath, PHYSFS_getLastError() ); } /* Push current flex buffer */ include_stack[scr_include_stack_ptr] = YY_CURRENT_BUFFER; scr_include_stack_ptr++; ASSERT(scr_include_stack_ptr < MAX_SCR_INCLUDE_DEPTH, "FLEX: scr_include_stack_ptr out of bounds: %u", scr_include_stack_ptr); pScrInputFiles[scr_include_stack_ptr] = newInput; } %} %option prefix="scr_" %option nounput %option yylineno %option never-interactive %x COMMENT %x SLCOMMENT %x QUOTE %x _MACRO %x _INCL %x _INCL_END_QUOTE %x _DEF_MACRO %x _DEF_BODY_START %x _DEF_BODY _IDENTIFIER[a-zA-Z_][0-9_a-zA-Z_]* %% /* Match keywords */ wait return WAIT; every return EVERY; trigger return TRIGGER; event return EVENT; inactive return INACTIVE; init return INITIALISE; link return LINK; ref return REF; return return RET; function return lexFUNCTION; "("int")"|"("INT")" { RULE(yytext); return TO_INT_CAST; } "("float")"|"("FLOAT")" { RULE(yytext); return TO_FLOAT_CAST; } public { scr_lval.stype = ST_PUBLIC; return STORAGE; } private { scr_lval.stype = ST_PRIVATE; return STORAGE; } local { scr_lval.stype = ST_LOCAL; return STORAGE; } while return WHILE; if return IF; else return ELSE; exit return EXIT; pause return PAUSE; /* Match type keywords */ void|VOID { scr_lval.tval = VAL_VOID; return _VOID; } string|STRING { scr_lval.tval = VAL_STRING; return TYPE; } bool|BOOL { scr_lval.tval = VAL_BOOL; return TYPE; } int|INT { scr_lval.tval = VAL_INT; return TYPE; } float|FLOAT { scr_lval.tval = VAL_FLOAT; return TYPE; } /* Match boolean values */ true|TRUE { scr_lval.bval = true; return BOOLEAN_T; } false|FALSE { scr_lval.bval = false; return BOOLEAN_T; } /* Match increment/decrement operators */ "++" {RULE("++"); return _INC; } "--" {RULE("++"); return _DEC; } /* Match boolean operators */ "==" return BOOLEQUAL; "!=" return NOTEQUAL; ">=" return GREATEQUAL; "<=" return LESSEQUAL; ">" return GREATER; "<" return LESS; and|AND|"&&" return _AND; or|OR|"||" return _OR; not|NOT|"!" return _NOT; /* Start of a #define */ "#define"[ \t]+ { BEGIN (_DEF_MACRO); } /* Start of the #define macro name */ <_DEF_MACRO>{_IDENTIFIER} { RULE("FLEX: matched define macro: '#define %s'", scr_text); /* Store macro name */ storeMacroName(scr_text); BEGIN (_DEF_BODY_START); } <_DEF_BODY_START>[ \t]+ { BEGIN (_DEF_BODY); } /* Eat space between macro and body */ <_DEF_BODY>[^\r\n]* { /* Anything until the end of the line is macro body */ RULE("FLEX: matched define body: '#define %s %s' (%d)", scr_macro[scr_num_macros].scr_define_macro, scr_text, scr_num_macros + 1); /* Store macro body */ storeMacroBody(scr_text); BEGIN (INITIAL); } /* Handle Includes */ "#include"[ \t]+\" { BEGIN (_INCL); } /* Match '#include' and skip whitespace until quotation */ <_INCL>[^ \t\n\"]+ { /* got the include file name */ RULE("FLEX: Opening include '%s' (scr_include_stack_ptr: %u)", scr_text, scr_include_stack_ptr); /* Set up new input buffer, push current one to stack */ pushInclude(scr_text); /* Match the ending quote */ BEGIN(_INCL_END_QUOTE); } <_INCL_END_QUOTE>\" { int line; char *text; YY_BUFFER_STATE includeBuffer; ASSERT(scr_include_stack_ptr < MAX_SCR_INCLUDE_DEPTH, "FLEX: scr_include_stack_ptr out of bounds: %u", scr_include_stack_ptr); ASSERT(pScrInputFiles[scr_include_stack_ptr] != NULL, "FLEX: failed to load include"); RULE("FLEX: storing line number for include %u: ", scr_include_stack_ptr - 1, scr_lineno); /* Store the current line number in the current input buffer */ scriptGetErrorData(&line, &text); scrInclLine[scr_include_stack_ptr - 1] = line; scr_lineno = 1; //we are at the beginning of the first line in a new input now /* Create a new YY_BUFFER_STATE for this file */ includeBuffer = yy_create_buffer(NULL, YY_BUF_SIZE); /* Switch to the new buffer */ yy_switch_to_buffer(includeBuffer); BEGIN (INITIAL); } <> { RULE("FLEX: EOF: scr_include_stack_ptr: %u; scr_macro_stack_ptr: %d", scr_include_stack_ptr, scr_macro_stack_ptr); ASSERT(pScrInputFiles[scr_include_stack_ptr] != NULL, "FLEX: can't unload, buffer is NULL"); if ( scr_include_stack_ptr == 0 ) { /* We finished scanning top level script or macro */ if(scr_macro_stack_ptr > 0) { RULE("FLEX: Finished parsing macro %d", scr_macro_stack_ptr); popMacroStack(); } else //both stacks are empty { RULE("FLEX: Finished parsing all input"); yyterminate(); } } else { /* First finish scanning all macros */ if(scr_macro_stack_ptr > 0) { RULE("FLEX: Finished parsing macro %d in include %d", scr_macro_stack_ptr, scr_include_stack_ptr); popMacroStack(); } else //no macros, return to previous include { RULE("FLEX: returning from parsing include %u", scr_include_stack_ptr); /* Free include buffer */ PHYSFS_close(pScrInputFiles[scr_include_stack_ptr]); pScrInputFiles[scr_include_stack_ptr] = NULL; /* Delete flex buffer */ yy_delete_buffer( YY_CURRENT_BUFFER ); /* Pop previous script file */ yy_switch_to_buffer( include_stack[scr_include_stack_ptr - 1] ); ASSERT(scr_include_stack_ptr != 0, "Can't restore line number of theprevious flex input"); /* Restore line count of the previous buffer from the line count stack */ scr_lineno = scrInclLine[scr_include_stack_ptr - 1]; RULE("FLEX: restoring line number for include %u: ", scr_include_stack_ptr, scr_lineno); /* Go to the previous flex input buffer */ scr_include_stack_ptr--; } } } /* Match floating point numbers */ -?[0-9]*"."[0-9]+ { RULE( "float matched '%s'", scr_text); scr_lval.fval = (float)atof(scr_text); return FLOAT_T; } /* Match integer numbers */ -?[0-9]+ { scr_lval.ival = atol(scr_text); return INTEGER; } /* Match identifiers */ {_IDENTIFIER} { char* tmpMacroBuffer; RULE( "looking up '%s'", scr_text); /* See if this identifier has been defined as a type */ if (scriptLookUpType(scr_text, &scr_lval.tval)) //STRUCTURESTAT etc { RULE( "'%s' is a user type", scr_text); return TYPE; } /* See if this identifier has been defined as a variable */ else if (scriptLookUpVariable(scr_text, &scr_lval.vSymbol)) { RULE( "'%s' is a var", scr_text); return scriptGetVarToken(scr_lval.vSymbol); } /* See if this identifier has been defined as a constant */ else if (scriptLookUpConstant(scr_text, &scr_lval.cSymbol)) { RULE( "'%s' is a constant", scr_text); return scriptGetConstToken(scr_lval.cSymbol); } /* See if this identifier has been defined as a function */ else if (scriptLookUpFunction(scr_text, &scr_lval.fSymbol)) { RULE( "'%s' is a function", scr_text); return scriptGetFuncToken(scr_lval.fSymbol); } /* See if this identifier has been defined as a custom function */ else if (scriptLookUpCustomFunction(scr_text, &scr_lval.eSymbol)) { RULE( "'%s' is a cust func", scr_text); return scriptGetCustomFuncToken(scr_lval.eSymbol); } else if (scriptLookUpTrigger(scr_text, &scr_lval.tSymbol)) { RULE( "'%s' is a trigger", scr_text); return TRIG_SYM; } else if (scriptLookUpEvent(scr_text, &scr_lval.eSymbol)) { RULE( "'%s' is an event", scr_text); return EVENT_SYM; } else if (scriptLookUpCallback(scr_text, &scr_lval.cbSymbol)) { RULE( "'%s' is a callback", scr_text); return CALLBACK_SYM; } else if(scriptLoopUpMacro(scr_text, &tmpMacroBuffer)) { RULE( "'%s' is a macro", scr_text); ASSERT(tmpMacroBuffer != NULL, "FLEX: failed to allocate memory for macro buffer %d", scr_macro_stack_ptr); ASSERT(scr_macro_stack_ptr >= 0 && scr_macro_stack_ptr < MAX_SCR_MACRO_DEPTH, "FLEX: flex macro buffer pointer out of bounds: %d", scr_macro_stack_ptr ); /* Push current buffer we are processing; * can be either macro buffer, include buffer or top level script */ macro_stack[scr_macro_stack_ptr] = YY_CURRENT_BUFFER; scr_macro_stack_ptr++; pScrMacroBuffer[scr_macro_stack_ptr] = tmpMacroBuffer; // Assign to the new input buffer /* Feed flex with the new macro buffer */ yy_switch_to_buffer( yy_scan_string( pScrMacroBuffer[scr_macro_stack_ptr] ) ); } else { RULE( "'%s' is an ident", scr_text); sstrcpy(aText[currText], scr_text); scr_lval.sval = aText[currText]; currText = (currText + 1) % TEXT_BUFFERS; return IDENT; } } /* Strip macros */ "#" { BEGIN (_MACRO); } <_MACRO>\n { BEGIN (INITIAL); } <_MACRO>. ; /* Match quoted text */ \" { BEGIN (QUOTE); } \" { BEGIN (INITIAL); } [^\"\n]* { sstrcpy(aText[currText], scr_text); scr_lval.sval = aText[currText]; currText = (currText + 1) % TEXT_BUFFERS; //debug(LOG_SCRIPT, "%s is QTEXT", scr_text); return QTEXT; } /* Skip white space */ [ \t\n\x0d\x0a] ; /* Strip comments */ "/*" { inComment=true; BEGIN (COMMENT); } "*/" | "*/"\n { inComment=false; BEGIN (INITIAL); } . | \n ; /* Strip single line comments */ "//" { BEGIN (SLCOMMENT); } \n { BEGIN (INITIAL); } [^\n]* ; /* Match anything that's been missed and pass it as a char */ . return scr_text[0]; %% /* Set the current input file for the lexer */ void scriptSetInputFile(PHYSFS_file* fileHandle) { YY_BUFFER_STATE inputScript; unsigned int i; /* Reset the lexer in case it's been used before */ scr__flush_buffer( YY_CURRENT_BUFFER ); /* Reset include stack */ scr_include_stack_ptr = 0; /* Initialize include input files */ for(i = 0; i < MAX_SCR_INCLUDE_DEPTH; ++i) { pScrInputFiles[i] = NULL; scrInclLine[i] = 0; } /* Set the initial input buffer */ pScrInputFiles[0] = fileHandle; inputScript = yy_create_buffer(NULL, YY_BUF_SIZE); yy_switch_to_buffer(inputScript); /* Reset number of macros */ scr_num_macros = 0; /* Reset macro stack */ scr_macro_stack_ptr = 0; /* Initialize macro input buffers */ for(i = 0; i < MAX_SCR_MACRO_DEPTH; ++i) { pScrMacroBuffer[i] = NULL; } } void scriptGetErrorData(int *pLine, char **ppText) { *pLine = scr_lineno; *ppText = scr_text; } int scr_wrap(void) { if (inComment) { debug( LOG_ERROR, "Warning: reached end of file in a comment" ); abort(); } return 1; } /* Older GNU Flex versions don't define yylex_destroy() * (and neither define a subminor version) */ #if !defined(YY_FLEX_SUBMINOR_VERSION) || (YY_FLEX_SUBMINOR_VERSION < 9) int scr_lex_destroy(void) { /* For non-reentrant C scanner only. */ yy_delete_buffer(YY_CURRENT_BUFFER); yy_init = 1; return 0; } #endif