/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2020 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules 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 3 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, see . */ #define HERCULES_CORE #include "config/core.h" // RENEWAL, RENEWAL_ASPD, RENEWAL_CAST, RENEWAL_DROP, RENEWAL_EDP, RENEWAL_EXP, RENEWAL_LVDMG, SCRIPT_CALLFUNC_CHECK, SECURE_NPCTIMEOUT, SECURE_NPCTIMEOUT_INTERVAL #include "script.h" #include "map/atcommand.h" #include "map/battle.h" #include "map/battleground.h" #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" #include "map/clan.h" #include "map/clif.h" #include "map/date.h" #include "map/elemental.h" #include "map/guild.h" #include "map/homunculus.h" #include "map/instance.h" #include "map/intif.h" #include "map/itemdb.h" #include "map/log.h" #include "map/mail.h" #include "map/map.h" #include "map/mapreg.h" #include "map/mercenary.h" #include "map/messages.h" #include "map/mob.h" #include "map/npc.h" #include "map/party.h" #include "map/path.h" #include "map/pc.h" #include "map/pet.h" #include "map/pet.h" #include "map/quest.h" #include "map/refine.h" #include "map/skill.h" #include "map/status.h" #include "map/status.h" #include "map/storage.h" #include "map/unit.h" #include "map/achievement.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/db.h" #include "common/memmgr.h" #include "common/md5calc.h" #include "common/mmo.h" // NEW_CARTS #include "common/nullpo.h" #include "common/random.h" #include "common/showmsg.h" #include "common/socket.h" // usage: getcharip #include "common/sql.h" #include "common/strlib.h" #include "common/sysinfo.h" #include "common/timer.h" #include "common/utils.h" #include "common/HPM.h" #include #include #include #include #include #ifndef WIN32 #include #endif static struct script_interface script_s; struct script_interface *script; static inline int GETVALUE(const struct script_buf *buf, int i) __attribute__((nonnull (1))); static inline int GETVALUE(const struct script_buf *buf, int i) { Assert_ret(VECTOR_LENGTH(*buf) > i + 2); return (int)MakeDWord(MakeWord(VECTOR_INDEX(*buf, i), VECTOR_INDEX(*buf, i+1)), MakeWord(VECTOR_INDEX(*buf, i+2), 0)); } static inline void SETVALUE(struct script_buf *buf, int i, int n) __attribute__((nonnull (1))); static inline void SETVALUE(struct script_buf *buf, int i, int n) { Assert_retv(VECTOR_LENGTH(*buf) > i + 2); VECTOR_INDEX(*buf, i) = GetByte(n, 0); VECTOR_INDEX(*buf, i+1) = GetByte(n, 1); VECTOR_INDEX(*buf, i+2) = GetByte(n, 2); } static const char *script_op2name(int op) { #define RETURN_OP_NAME(type) case type: return #type switch( op ) { RETURN_OP_NAME(C_NOP); RETURN_OP_NAME(C_POS); RETURN_OP_NAME(C_INT); RETURN_OP_NAME(C_PARAM); RETURN_OP_NAME(C_FUNC); RETURN_OP_NAME(C_STR); RETURN_OP_NAME(C_CONSTSTR); RETURN_OP_NAME(C_ARG); RETURN_OP_NAME(C_NAME); RETURN_OP_NAME(C_EOL); RETURN_OP_NAME(C_RETINFO); RETURN_OP_NAME(C_USERFUNC); RETURN_OP_NAME(C_USERFUNC_POS); RETURN_OP_NAME(C_REF); RETURN_OP_NAME(C_LSTR); // operators RETURN_OP_NAME(C_OP3); RETURN_OP_NAME(C_LOR); RETURN_OP_NAME(C_LAND); RETURN_OP_NAME(C_LE); RETURN_OP_NAME(C_LT); RETURN_OP_NAME(C_GE); RETURN_OP_NAME(C_GT); RETURN_OP_NAME(C_EQ); RETURN_OP_NAME(C_NE); RETURN_OP_NAME(C_XOR); RETURN_OP_NAME(C_OR); RETURN_OP_NAME(C_AND); RETURN_OP_NAME(C_ADD); RETURN_OP_NAME(C_SUB); RETURN_OP_NAME(C_MUL); RETURN_OP_NAME(C_POW); RETURN_OP_NAME(C_DIV); RETURN_OP_NAME(C_MOD); RETURN_OP_NAME(C_NEG); RETURN_OP_NAME(C_LNOT); RETURN_OP_NAME(C_NOT); RETURN_OP_NAME(C_R_SHIFT); RETURN_OP_NAME(C_L_SHIFT); RETURN_OP_NAME(C_ADD_POST); RETURN_OP_NAME(C_SUB_POST); RETURN_OP_NAME(C_ADD_PRE); RETURN_OP_NAME(C_SUB_PRE); RETURN_OP_NAME(C_RE_EQ); RETURN_OP_NAME(C_RE_NE); default: ShowDebug("script_op2name: unexpected op=%d\n", op); return "???"; } #undef RETURN_OP_NAME } #ifdef SCRIPT_DEBUG_DUMP_STACK static void script_dump_stack(struct script_state *st) { int i; nullpo_retv(st); ShowMessage("\tstart = %d\n", st->start); ShowMessage("\tend = %d\n", st->end); ShowMessage("\tdefsp = %d\n", st->stack->defsp); ShowMessage("\tsp = %d\n", st->stack->sp); for( i = 0; i < st->stack->sp; ++i ) { struct script_data* data = &st->stack->stack_data[i]; ShowMessage("\t[%d] %s", i, script->op2name(data->type)); switch( data->type ) { case C_INT: case C_POS: ShowMessage(" %d\n", data->u.num); break; case C_STR: case C_CONSTSTR: ShowMessage(" \"%s\"\n", data->u.str); break; case C_NAME: ShowMessage(" \"%s\" (id=%d ref=%p subtype=%s)\n", reference_getname(data), data->u.num, data->ref, script->op2name(script->str_data[data->u.num].type)); break; case C_RETINFO: { struct script_retinfo* ri = data->u.ri; ShowMessage(" %p {scope.vars=%p, scope.arrays=%p, script=%p, pos=%d, nargs=%d, defsp=%d}\n", ri, ri->scope.vars, ri->scope.arrays, ri->script, ri->pos, ri->nargs, ri->defsp); } break; default: ShowMessage("\n"); break; } } } #endif /// Reports on the console the src of a script error. static void script_reportsrc(struct script_state *st) { struct block_list* bl; nullpo_retv(st); if( st->oid == 0 ) return; //Can't report source. bl = map->id2bl(st->oid); if( bl == NULL ) return; switch( bl->type ) { case BL_NPC: { const struct npc_data *nd = BL_UCCAST(BL_NPC, bl); if (bl->m >= 0) ShowDebug("Source (NPC): %s at %s (%d,%d)\n", nd->name, map->list[bl->m].name, bl->x, bl->y); else ShowDebug("Source (NPC): %s (invisible/not on a map)\n", nd->name); } break; default: if( bl->m >= 0 ) ShowDebug("Source (Non-NPC type %u): name %s at %s (%d,%d)\n", bl->type, clif->get_bl_name(bl), map->list[bl->m].name, bl->x, bl->y); else ShowDebug("Source (Non-NPC type %u): name %s (invisible/not on a map)\n", bl->type, clif->get_bl_name(bl)); break; } } /// Reports on the console information about the script data. static void script_reportdata(struct script_data *data) { if( data == NULL ) return; switch( data->type ) { case C_NOP:// no value ShowDebug("Data: nothing (nil)\n"); break; case C_INT:// number ShowDebug("Data: number value=%"PRId64"\n", data->u.num); break; case C_STR: case C_CONSTSTR:// string if( data->u.str ) { ShowDebug("Data: string value=\"%s\"\n", data->u.str); } else { ShowDebug("Data: string value=NULL\n"); } break; case C_NAME:// reference if( reference_tovariable(data) ) {// variable const char* name = reference_getname(data); ShowDebug("Data: variable name='%s' index=%u\n", name, reference_getindex(data)); } else if( reference_toconstant(data) ) {// constant ShowDebug("Data: constant name='%s' value=%d\n", reference_getname(data), reference_getconstant(data)); } else if( reference_toparam(data) ) {// param ShowDebug("Data: param name='%s' type=%d\n", reference_getname(data), reference_getparamtype(data)); } else {// ??? ShowDebug("Data: reference name='%s' type=%s\n", reference_getname(data), script->op2name(data->type)); ShowDebug("Please report this!!! - script->str_data.type=%s\n", script->op2name(script->str_data[reference_getid(data)].type)); } break; case C_POS:// label ShowDebug("Data: label pos=%"PRId64"\n", data->u.num); break; default: ShowDebug("Data: %s\n", script->op2name(data->type)); break; } } /// Reports on the console information about the current built-in function. static void script_reportfunc(struct script_state *st) { int params, id; struct script_data* data; if (!script_hasdata(st,0)) { // no stack return; } data = script_getdata(st,0); if (!data_isreference(data) || script->str_data[reference_getid(data)].type != C_FUNC) { // script currently not executing a built-in function or corrupt stack return; } id = reference_getid(data); params = script_lastdata(st)-1; if (params > 0) { int i; ShowDebug("Function: %s (%d parameter%s):\n", script->get_str(id), params, ( params == 1 ) ? "" : "s"); for (i = 2; i <= script_lastdata(st); i++) { script->reportdata(script_getdata(st,i)); } } else { ShowDebug("Function: %s (no parameters)\n", script->get_str(id)); } } /*========================================== * Output error message *------------------------------------------*/ static void disp_error_message2(const char *mes, const char *pos, int report) __attribute__((nonnull (1))) analyzer_noreturn; static void disp_error_message2(const char *mes, const char *pos, int report) { script->error_msg = aStrdup(mes); script->error_pos = pos; script->error_report = report; longjmp( script->error_jump, 1 ); } #define disp_error_message(mes,pos) (disp_error_message2((mes),(pos),1)) static void disp_warning_message(const char *mes, const char *pos) { script->warning(script->parser_current_src,script->parser_current_file,script->parser_current_line,mes,pos); } /// Checks event parameter validity static void check_event(struct script_state *st, const char *evt) { if( evt && evt[0] && !stristr(evt, "::On") ) { ShowWarning("NPC event parameter deprecated! Please use 'NPCNAME::OnEVENT' instead of '%s'.\n", evt); script->reportsrc(st); } } /*========================================== * Hashes the input string *------------------------------------------*/ static unsigned int calc_hash(const char *p) { unsigned int h; nullpo_ret(p); #if defined(SCRIPT_HASH_DJB2) h = 5381; while( *p ) // hash*33 + c h = ( h << 5 ) + h + ((unsigned char)(*p++)); #elif defined(SCRIPT_HASH_SDBM) h = 0; while( *p ) // hash*65599 + c h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)(*p++)); #elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash h = 0; while( *p ) { unsigned int g; h = ( h << 4 ) + ((unsigned char)(*p++)); g = h & 0xF0000000; if( g ) { h ^= g >> 24; h &= ~g; } } #else // athena hash h = 0; while( *p ) h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)(*p++); #endif return h % SCRIPT_HASH_SIZE; } /*========================================== * Hashes the input string in a case insensitive way *------------------------------------------*/ static unsigned int calc_hash_ci(const char *p) { unsigned int h = 0; #ifdef ENABLE_CASE_CHECK nullpo_ret(p); #if defined(SCRIPT_HASH_DJB2) h = 5381; while( *p ) // hash*33 + c h = ( h << 5 ) + h + ((unsigned char)TOLOWER(*p++)); #elif defined(SCRIPT_HASH_SDBM) h = 0; while( *p ) // hash*65599 + c h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)TOLOWER(*p++)); #elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash h = 0; while( *p ) { unsigned int g; h = ( h << 4 ) + ((unsigned char)TOLOWER(*p++)); g = h & 0xF0000000; if( g ) { h ^= g >> 24; h &= ~g; } } #else // athena hash h = 0; while( *p ) h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)TOLOWER(*p++); #endif #endif // ENABLE_CASE_CHECK return h % SCRIPT_HASH_SIZE; } /*========================================== * script->str_data manipulation functions *------------------------------------------*/ /// Looks up string using the provided id. static const char *script_get_str(int id) { Assert_retr(NULL, id >= LABEL_START && id < script->str_size); return script->str_buf+script->str_data[id].str; } /// Returns the uid of the string, or -1. static int script_search_str(const char *p) { int i; for( i = script->str_hash[script->calc_hash(p)]; i != 0; i = script->str_data[i].next ) { if( strcmp(script->get_str(i),p) == 0 ) { return i; } } return -1; } static void script_casecheck_clear_sub(struct casecheck_data *ccd) { #ifdef ENABLE_CASE_CHECK nullpo_retv(ccd); if (ccd->str_data) { aFree(ccd->str_data); ccd->str_data = NULL; } ccd->str_data_size = 0; ccd->str_num = 1; if (ccd->str_buf) { aFree(ccd->str_buf); ccd->str_buf = NULL; } ccd->str_pos = 0; ccd->str_size = 0; memset(ccd->str_hash, 0, sizeof(ccd->str_hash)); #endif // ENABLE_CASE_CHECK } static void script_global_casecheck_clear(void) { script_casecheck_clear_sub(&script->global_casecheck); } static void script_local_casecheck_clear(void) { script_casecheck_clear_sub(&script->local_casecheck); } static const char *script_casecheck_add_str_sub(struct casecheck_data *ccd, const char *p) { #ifdef ENABLE_CASE_CHECK int len; int h = script->calc_hash_ci(p); nullpo_retr(NULL, ccd); if (ccd->str_hash[h] == 0) { //empty bucket, add new node here ccd->str_hash[h] = ccd->str_num; } else { int i; for (i = ccd->str_hash[h]; ; i = ccd->str_data[i].next) { const char *s = NULL; Assert_retb(i >= 0 && i < ccd->str_size); s = ccd->str_buf+ccd->str_data[i].str; if (strcasecmp(s,p) == 0) { return s; // string already in list } if (ccd->str_data[i].next == 0) break; // reached the end } // append node to end of list ccd->str_data[i].next = ccd->str_num; } // grow list if neccessary if( ccd->str_num >= ccd->str_data_size ) { ccd->str_data_size += 1280; RECREATE(ccd->str_data,struct str_data_struct,ccd->str_data_size); memset(ccd->str_data + (ccd->str_data_size - 1280), '\0', 1280); } len=(int)strlen(p); // grow string buffer if neccessary while( ccd->str_pos+len+1 >= ccd->str_size ) { ccd->str_size += 10240; RECREATE(ccd->str_buf,char,ccd->str_size); memset(ccd->str_buf + (ccd->str_size - 10240), '\0', 10240); } safestrncpy(ccd->str_buf+ccd->str_pos, p, len+1); ccd->str_data[ccd->str_num].type = C_NOP; ccd->str_data[ccd->str_num].str = ccd->str_pos; ccd->str_data[ccd->str_num].val = 0; ccd->str_data[ccd->str_num].next = 0; ccd->str_data[ccd->str_num].func = NULL; ccd->str_data[ccd->str_num].backpatch = -1; ccd->str_data[ccd->str_num].label = -1; ccd->str_pos += len+1; ccd->str_num++; #endif // ENABLE_CASE_CHECK return NULL; } static const char *script_global_casecheck_add_str(const char *p) { return script_casecheck_add_str_sub(&script->global_casecheck, p); } static const char *script_local_casecheck_add_str(const char *p) { return script_casecheck_add_str_sub(&script->local_casecheck, p); } /// Stores a copy of the string and returns its id. /// If an identical string is already present, returns its id instead. static int script_add_str(const char *p) { int len, h = script->calc_hash(p); #ifdef ENABLE_CASE_CHECK const char *existingentry = NULL; #endif // ENABLE_CASE_CHECK if (script->str_hash[h] == 0) { // empty bucket, add new node here script->str_hash[h] = script->str_num; } else { // scan for end of list, or occurence of identical string int i; for (i = script->str_hash[h]; ; i = script->str_data[i].next) { if( strcmp(script->get_str(i),p) == 0 ) { return i; // string already in list } if( script->str_data[i].next == 0 ) break; // reached the end } // append node to end of list script->str_data[i].next = script->str_num; } #ifdef ENABLE_CASE_CHECK if( (strncmp(p, ".@", 2) == 0) ) { // Local scope vars are checked separately to decrease false positives existingentry = script->local_casecheck.add_str(p); } else { existingentry = script->global_casecheck.add_str(p); if( existingentry ) { if( strcasecmp(p, "disguise") == 0 || strcasecmp(p, "Poison_Spore") == 0 || strcasecmp(p, "PecoPeco_Egg") == 0 || strcasecmp(p, "Soccer_Ball") == 0 || strcasecmp(p, "Horn") == 0 || strcasecmp(p, "Treasure_Box_") == 0 || strcasecmp(p, "Lord_of_Death") == 0 ) // Known duplicates, don't bother warning the user existingentry = NULL; } } if( existingentry ) { DeprecationCaseWarning("script_add_str", p, existingentry, script->parser_current_file); // TODO } #endif // ENABLE_CASE_CHECK // grow list if neccessary if( script->str_num >= script->str_data_size ) { script->str_data_size += 1280; RECREATE(script->str_data,struct str_data_struct,script->str_data_size); memset(script->str_data + (script->str_data_size - 1280), '\0', 1280); } len=(int)strlen(p); // grow string buffer if neccessary while( script->str_pos+len+1 >= script->str_size ) { script->str_size += 10240; RECREATE(script->str_buf,char,script->str_size); memset(script->str_buf + (script->str_size - 10240), '\0', 10240); } safestrncpy(script->str_buf+script->str_pos, p, len+1); script->str_data[script->str_num].type = C_NOP; script->str_data[script->str_num].str = script->str_pos; script->str_data[script->str_num].val = 0; script->str_data[script->str_num].next = 0; script->str_data[script->str_num].func = NULL; script->str_data[script->str_num].backpatch = -1; script->str_data[script->str_num].label = -1; script->str_pos += len+1; return script->str_num++; } static int script_add_variable(const char *varname) { int key = script->search_str(varname); if (key < 0) { key = script->add_str(varname); script->str_data[key].type = C_NAME; } return key; } /** * Appends 1 byte to the script buffer. * * @param a The byte to append. */ static void add_scriptb(int a) { VECTOR_ENSURE(script->buf, 1, SCRIPT_BLOCK_SIZE); VECTOR_PUSH(script->buf, (uint8)a); } /** * Appends a c_op value to the script buffer. * * The value is variable-length encoded into 8-bit blocks. * The encoding scheme is ( 01?????? )* 00??????, LSB first. * All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). * * @param a The value to append. */ static void add_scriptc(int a) { while( a >= 0x40 ) { script->addb((a&0x3f)|0x40); a = (a - 0x40) >> 6; } script->addb(a); } /** * Appends an integer value to the script buffer. * * The value is variable-length encoded into 8-bit blocks. * The encoding scheme is ( 11?????? )* 10??????, LSB first. * All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). * * @param a The value to append. */ static void add_scripti(int a) { while( a >= 0x40 ) { script->addb((a&0x3f)|0xc0); a = (a - 0x40) >> 6; } script->addb(a|0x80); } /** * Appends a script->str_data object (label/function/variable/integer) to the script buffer. * * @param l The id of the script->str_data entry (Maximum up to 16M) */ static void add_scriptl(int l) { int backpatch = script->str_data[l].backpatch; switch(script->str_data[l].type) { case C_POS: case C_USERFUNC_POS: script->addc(C_POS); script->addb(script->str_data[l].label); script->addb(script->str_data[l].label>>8); script->addb(script->str_data[l].label>>16); break; case C_NOP: case C_USERFUNC: // Embedded data backpatch there is a possibility of label script->addc(C_NAME); script->str_data[l].backpatch = VECTOR_LENGTH(script->buf); script->addb(backpatch); script->addb(backpatch>>8); script->addb(backpatch>>16); break; case C_INT: script->addi(abs(script->str_data[l].val)); if( script->str_data[l].val < 0 ) //Notice that this is negative, from jA (Rayce) script->addc(C_NEG); break; default: // assume C_NAME script->addc(C_NAME); script->addb(l); script->addb(l>>8); script->addb(l>>16); break; } } /*========================================== * Resolve the label *------------------------------------------*/ static void set_label(int l, int pos, const char *script_pos) { int i; if(script->str_data[l].type==C_INT || script->str_data[l].type==C_PARAM || script->str_data[l].type==C_FUNC) { //Prevent overwriting constants values, parameters and built-in functions [Skotlex] disp_error_message("set_label: invalid label name",script_pos); return; } if(script->str_data[l].label!=-1) { disp_error_message("set_label: dup label ",script_pos); return; } script->str_data[l].type=(script->str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); script->str_data[l].label=pos; for (i = script->str_data[l].backpatch; i >= 0 && i != 0x00ffffff; ) { int next = GETVALUE(&script->buf, i); VECTOR_INDEX(script->buf, i-1) = (script->str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); SETVALUE(&script->buf, i, pos); i = next; } } /// Skips spaces and/or comments. static const char *script_skip_space(const char *p) { if( p == NULL ) return NULL; for(;;) { while( ISSPACE(*p) ) ++p; if( *p == '/' && p[1] == '/' ) {// line comment while(*p && *p!='\n') ++p; } else if( *p == '/' && p[1] == '*' ) {// block comment p += 2; for(;;) { if( *p == '\0' ) { script->disp_warning_message("script:script->skip_space: end of file while parsing block comment. expected "CL_BOLD"*/"CL_NORM, p); return p; } if( *p == '*' && p[1] == '/' ) {// end of block comment p += 2; break; } ++p; } } else break; } return p; } /// Skips a word. /// A word consists of undercores and/or alphanumeric characters, /// and valid variable prefixes/postfixes. static const char *skip_word(const char *p) { nullpo_retr(NULL, p); // prefix switch( *p ) { case '@':// temporary char variable ++p; break; case '#':// account variable p += ( p[1] == '#' ? 2 : 1 ); break; case '\'':// instance variable ++p; break; case '.':// npc variable p += ( p[1] == '@' ? 2 : 1 ); break; case '$':// global variable p += ( p[1] == '@' ? 2 : 1 ); break; } while (ISALNUM(*p) || *p == '_') ++p; // postfix if( *p == '$' )// string p++; return p; } /// Adds a word to script->str_data. /// @see skip_word /// @see script->add_str static int add_word(const char *p) { size_t len; int i; nullpo_retr(0, p); // Check for a word len = script->skip_word(p) - p; if( len == 0 ) disp_error_message("script:add_word: invalid word. A word consists of undercores and/or alphanumeric characters, and valid variable prefixes/postfixes.", p); // Duplicate the word if( len+1 > script->word_size ) RECREATE(script->word_buf, char, (script->word_size = (len+1))); memcpy(script->word_buf, p, len); script->word_buf[len] = 0; // add the word i = script->add_str(script->word_buf); return i; } /// Parses a function call. /// The argument list can have parenthesis or not. /// The number of arguments is checked. static const char *parse_callfunc(const char *p, int require_paren, int is_custom) { const char *p2; char *arg = NULL; char null_arg = '\0'; int func; bool macro = false; nullpo_retr(NULL, p); // is need add check for arg null pointer below? if (*p == '"') { p2 = ++p; // jump to the start of the word // find the closing quote while (*p2 != '"') { ++p2; } if (p2[1] == ':' && p2[2] == ':') { func = script->add_str("callfunctionofnpc"); arg = "*"; // we already take care of the "vs" part of "vs*" script->syntax.nested_call++; script->syntax.last_func = script->str_data[func].val; script->addl(func); script->addc(C_ARG); script->addc(C_STR); do { script->addb(*p++); // npc name } while (p < p2); script->addb(0); p = p2 + 3; // skip to start of func name p2 = script->skip_word(p); script->addc(C_STR); do { script->addb(*p++); // func name } while (p < p2); script->addb(0); p = p2; // skip to just before the () } else { disp_error_message("script:parse_callfunc: invalid public function call syntax!", p2 + 1); } } else { func = script->add_word(p); if (script->str_data[func].type == C_FUNC) { script->syntax.nested_call++; if (script->syntax.last_func != -1) { if (script->str_data[func].val == script->buildin_lang_macro_offset) { script->syntax.lang_macro_active = true; macro = true; } else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) { script->syntax.lang_macro_fmtstring_active = true; macro = true; } } if (!macro) { // buildin function script->syntax.last_func = script->str_data[func].val; script->addl(func); script->addc(C_ARG); } arg = script->buildin[script->str_data[func].val]; if (script->str_data[func].deprecated == 1) { DeprecationWarning(p); } if (arg == NULL) { arg = &null_arg; // Use a dummy, null string } } else if (script->str_data[func].type == C_USERFUNC || script->str_data[func].type == C_USERFUNC_POS) { // script defined function script->addl(script->buildin_callsub_ref); script->addc(C_ARG); script->addl(func); arg = script->buildin[script->str_data[script->buildin_callsub_ref].val]; if (*arg == 0) { disp_error_message("script:parse_callfunc: callsub has no arguments, please review its definition", p); } if (*arg != '*') { ++arg; // count func as argument } } else { #ifdef SCRIPT_CALLFUNC_CHECK const char *name = script->get_str(func); if (is_custom == 0 && strdb_get(script->userfunc_db, name) == NULL) { #endif disp_error_message("script:parse_callfunc: expect command, missing function name or calling undeclared function", p); #ifdef SCRIPT_CALLFUNC_CHECK } else { script->addl(script->buildin_callfunc_ref); script->addc(C_ARG); script->addc(C_STR); while (*name != '\0') { script->addb(*name++); } script->addb(0); arg = script->buildin[script->str_data[script->buildin_callfunc_ref].val]; if (*arg != '*') { ++ arg; } } #endif } } p = script->skip_word(p); p = script->skip_space(p); script->syntax.curly[script->syntax.curly_count].type = TYPE_ARGLIST; script->syntax.curly[script->syntax.curly_count].count = 0; if (*p == ';') { // ';' script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN; } else if (*p == '(' && *(p2 = script->skip_space(p + 1)) == ')') { // '(' ')' script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN; p = p2; } else { // if (require_paren == 1) { if (*p != '(') { disp_error_message("script:parse_callfunc: need '('", p); } ++p; // skip '(' script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN; } else if( *p == '(' ) { script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_UNDEFINED; } else { script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN; } ++script->syntax.curly_count; while (*arg != '\0') { p2 = script->parse_subexpr(p, -1); if (p == p2) { // not an argument break; } if (*arg != '*') { // next argument ++arg; } p = script->skip_space(p2); if (*arg == 0 || *p != ',') { // no more arguments break; } ++p; // skip comma } --script->syntax.curly_count; } if (arg != NULL && *arg != '\0' && *arg != '?' && *arg != '*') { disp_error_message2("script:parse_callfunc: not enough arguments, expected ','", p, script->config.warn_func_mismatch_paramnum); } if (script->syntax.curly[script->syntax.curly_count].type != TYPE_ARGLIST) { disp_error_message("parse_callfunc: DEBUG last curly is not an argument list", p); } if (script->syntax.curly[script->syntax.curly_count].flag == ARGLIST_PAREN) { if (*p != ')') { disp_error_message("script:parse_callfunc: expected ')' to close argument list", p); } ++p; if (script->str_data[func].val == script->buildin_lang_macro_offset) { script->syntax.lang_macro_active = false; } else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) { script->syntax.lang_macro_fmtstring_active = false; } } if (!macro) { if (0 == --script->syntax.nested_call) { script->syntax.last_func = -1; } script->addc(C_FUNC); } return p; } /// Processes end of logical script line. /// @param first When true, only fix up scheduling data is initialized /// @param p Script position for error reporting in set_label static void parse_nextline(bool first, const char *p) { if( !first ) { script->addc(C_EOL); // mark end of line for stack cleanup script->set_label(LABEL_NEXTLINE, VECTOR_LENGTH(script->buf), p); // fix up '-' labels } // initialize data for new '-' label fix up scheduling script->str_data[LABEL_NEXTLINE].type = C_NOP; script->str_data[LABEL_NEXTLINE].backpatch = -1; script->str_data[LABEL_NEXTLINE].label = -1; } /** * Pushes a variable into stack, processing its array index if needed. * @see parse_variable */ static void parse_variable_sub_push(int word, const char *p2) { if( p2 ) { const char* p3 = NULL; // process the variable index // push the getelementofarray method into the stack script->addl(script->buildin_getelementofarray_ref); script->addc(C_ARG); script->addl(word); // process the sub-expression for this assignment p3 = script->parse_subexpr(p2 + 1, 1); p3 = script->skip_space(p3); if( *p3 != ']' ) {// closing parenthesis is required for this script disp_error_message("Missing closing ']' parenthesis for the variable assignment.", p3); } // push the closing function stack operator onto the stack script->addc(C_FUNC); p3++; } else { // No array index, simply push the variable or value onto the stack script->addl(word); } } /// Parse a variable assignment using the direct equals operator /// @param p script position where the function should run from /// @return NULL if not a variable assignment, the new position otherwise static const char *parse_variable(const char *p) { int word; c_op type = C_NOP; const char *p2 = NULL; const char *var = p; nullpo_retr(NULL, p); if( ( p[0] == '+' && p[1] == '+' && (type = C_ADD_PRE, true) ) // pre ++ || ( p[0] == '-' && p[1] == '-' && (type = C_SUB_PRE, true) ) // pre -- ) { var = p = script->skip_space(&p[2]); } // skip the variable where applicable p = script->skip_word(p); p = script->skip_space(p); if( p == NULL ) { // end of the line or invalid buffer return NULL; } if (*p == '[') { int i, j; // array variable so process the array as appropriate for (p2 = p, i = 0, j = 1; p; ++ i) { if( *p ++ == ']' && --(j) == 0 ) break; if( *p == '[' ) ++ j; } if( !(p = script->skip_space(p)) ) { // end of line or invalid characters remaining disp_error_message("Missing right expression or closing bracket for variable.", p); } } if( type == C_NOP && !( ( p[0] == '=' && p[1] != '=' && (type = C_EQ, true) ) // = || ( p[0] == '+' && p[1] == '=' && (type = C_ADD, true) ) // += || ( p[0] == '-' && p[1] == '=' && (type = C_SUB, true) ) // -= || ( p[0] == '^' && p[1] == '=' && (type = C_XOR, true) ) // ^= || ( p[0] == '|' && p[1] == '=' && (type = C_OR, true) ) // |= || ( p[0] == '&' && p[1] == '=' && (type = C_AND, true) ) // &= || ( p[0] == '*' && p[1] == '=' && (type = C_MUL, true) ) // *= || ( p[0] == '*' && p[1] == '*' && p[2] == '=' && (type = C_POW, true) ) // **= || ( p[0] == '/' && p[1] == '=' && (type = C_DIV, true) ) // /= || ( p[0] == '%' && p[1] == '=' && (type = C_MOD, true) ) // %= || ( p[0] == '+' && p[1] == '+' && (type = C_ADD_POST, true) ) // post ++ || ( p[0] == '-' && p[1] == '-' && (type = C_SUB_POST, true) ) // post -- || ( p[0] == '<' && p[1] == '<' && p[2] == '=' && (type = C_L_SHIFT, true) ) // <<= || ( p[0] == '>' && p[1] == '>' && p[2] == '=' && (type = C_R_SHIFT, true) ) // >>= ) ) {// failed to find a matching operator combination so invalid return NULL; } switch( type ) { case C_ADD_PRE: // pre ++ case C_SUB_PRE: // pre -- // (nothing more to skip) break; case C_EQ: // = p = script->skip_space( &p[1] ); break; case C_L_SHIFT: // <<= case C_R_SHIFT: // >>= case C_POW: // **= p = script->skip_space( &p[3] ); break; default: // everything else p = script->skip_space( &p[2] ); } if( p == NULL ) { // end of line or invalid buffer return NULL; } // push the set function onto the stack script->syntax.nested_call++; script->syntax.last_func = script->str_data[script->buildin_set_ref].val; script->addl(script->buildin_set_ref); script->addc(C_ARG); // always append parenthesis to avoid errors script->syntax.curly[script->syntax.curly_count].type = TYPE_ARGLIST; script->syntax.curly[script->syntax.curly_count].count = 0; script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN; // increment the total curly count for the position in the script ++script->syntax.curly_count; // parse the variable currently being modified word = script->add_word(var); if( script->str_data[word].type == C_FUNC || script->str_data[word].type == C_USERFUNC || script->str_data[word].type == C_USERFUNC_POS ) { // cannot assign a variable which exists as a function or label disp_error_message("Cannot modify a variable which has the same name as a function or label.", p); } parse_variable_sub_push(word, p2); // Push variable onto the stack if( type != C_EQ ) { parse_variable_sub_push(word, p2); // Push variable onto the stack once again (first argument of setr) } if( type == C_ADD_POST || type == C_SUB_POST ) { // post ++ / -- script->addi(1); script->addc(type == C_ADD_POST ? C_ADD : C_SUB); parse_variable_sub_push(word, p2); // Push variable onto the stack (third argument of setr) } else if( type == C_ADD_PRE || type == C_SUB_PRE ) { // pre ++ / -- script->addi(1); script->addc(type == C_ADD_PRE ? C_ADD : C_SUB); } else { // process the value as an expression p = script->parse_subexpr(p, -1); if( type != C_EQ ) { // push the type of modifier onto the stack script->addc(type); } } // decrement the curly count for the position within the script --script->syntax.curly_count; // close the script by appending the function operator script->addc(C_FUNC); if (--script->syntax.nested_call == 0) script->syntax.last_func = -1; // push the buffer from the method return p; } /** * Converts a number expression literal to an actual integer. * Number separators are skipped. * * expects these formats: * 1337 * 0x1337 * 0b1001 * 0o1337 * * example with separating nibbles of a binary literal: * 0b1101_0111_1001_1111 * * @param p - a pointer to the first char of the number literal * @param lli - a pointer to the resulting long long integer * @returns a pointer to the first char after the parsed number */ static const char *parse_number(const char *p, long long *lli) { nullpo_retr(NULL, p); const bool unary_plus = (*p == '+'); const bool unary_minus = (*p == '-'); if (unary_plus || unary_minus) { p++; } if (ISNSEPARATOR(*p)) { disp_error_message("parse_number: number literals cannot begin with a separator", p); } #define PARSENUMBER(skip, func, radix) \ for (p += skip; func(*p) || (ISNSEPARATOR(*p) && (func(p[1]) || ISNSEPARATOR(p[1]))); ++p) { \ if (func(*p)) { \ *lli *= radix; \ *lli += (*p < 'A') ? (*p & 0xF) : (9 + (*p & 0x7)); \ } else if (ISNSEPARATOR(p[1])) { \ disp_error_message("parse_number: number literals cannot contain two separators in a row", p + 1); \ } \ } if (*p == '0' && p[1] == 'x') { PARSENUMBER(2, ISXDIGIT, 16); } else if (*p == '0' && p[1] == 'o') { PARSENUMBER(2, ISODIGIT, 8); } else if (*p == '0' && p[1] == 'b') { PARSENUMBER(2, ISBDIGIT, 2); } else { PARSENUMBER(0, ISDIGIT, 10); } #undef PARSENUMBER if (ISNSEPARATOR(*p)) { disp_error_message("parse_number: number literals cannot end with a separator", p); } if (unary_minus) { // reverse the sign *lli = -(*lli); } // make sure we can't underflow/overflow if (*lli < INT_MIN) { *lli = INT_MIN; script->disp_warning_message("parse_number: underflow detected, capping value to INT_MIN", p); } else if (*lli > INT_MAX) { *lli = INT_MAX; script->disp_warning_message("parse_number: overflow detected, capping value to INT_MAX", p); } return p; } /* * Checks whether the gives string is a number literal * * Mainly necessary to differentiate between number literals and NPC name * constants, since several of those start with a digit. * * All this does is to check if the string begins with an optional + or - sign, * followed by a hexadecimal or decimal number literal literal and is NOT * followed by a underscore or letter. * * @param p Pointer to the string to check * @return Whether the string is a number literal */ static bool is_number(const char *p) { const char *np; nullpo_retr(false, p); if (*p == '-' || *p == '+') { p++; } np = p; if (*p == '0' && p[1] == 'x') { // Hexadecimal: 0xFFFF np = (p += 2); while (ISXDIGIT(*np) || ISNSEPARATOR(*np)) { np++; } } else if (*p == '0' && p[1] == 'b') { // Binary: 0b0001 np = (p += 2); while (ISBDIGIT(*np) || ISNSEPARATOR(*np)) { np++; } } else if (*p == '0' && p[1] == 'o') { // Octal: 0o1500 np = (p += 2); while (ISODIGIT(*np) || ISNSEPARATOR(*np)) { np++; } } else if (ISDIGIT(*p)) { // Decimal: 1234 while (ISDIGIT(*np) || ISNSEPARATOR(*np)) { np++; } } if (p != np && *np != '_' && !ISALPHA(*np)) { // At least one digit, and next isn't a letter or _ return true; } return false; } /** * Duplicates a script string into the script string list. * * Grows the script string list as needed. * * @param str The string to insert. * @return the string position in the script string list. */ static int script_string_dup(char *str) { int len; int pos = script->string_list_pos; nullpo_retr(pos, str); len = (int)strlen(str); while (pos+len+1 >= script->string_list_size) { script->string_list_size += (1024*1024)/2; RECREATE(script->string_list,char,script->string_list_size); } safestrncpy(script->string_list+pos, str, len+1); script->string_list_pos += len+1; return pos; } /*========================================== * Analysis section *------------------------------------------*/ static const char *parse_simpleexpr(const char *p) { p = script->skip_space(p); nullpo_retr(NULL, p); if (*p == ';' || *p == ',') { disp_error_message("script:parse_simpleexpr: unexpected end of expression", p); } if (*p == '(') { return script->parse_simpleexpr_paren(p); } else if (is_number(p)) { return script->parse_simpleexpr_number(p); } else if(*p == '"') { const char *p2 = p + 1; while (*p2 != '"') { ++p2; } if (p2[1] == ':' && p2[2] == ':') { return script->parse_callfunc(p, 1, 0); // XXX: why does callfunc use int for booleans? } return script->parse_simpleexpr_string(p); } else { return script->parse_simpleexpr_name(p); } } static const char *parse_simpleexpr_paren(const char *p) { int i = script->syntax.curly_count - 1; nullpo_retr(NULL, p); if (i >= 0 && script->syntax.curly[i].type == TYPE_ARGLIST) ++script->syntax.curly[i].count; p = script->parse_subexpr(p + 1, -1); p = script->skip_space(p); if ((i = script->syntax.curly_count - 1) >= 0 && script->syntax.curly[i].type == TYPE_ARGLIST && script->syntax.curly[i].flag == ARGLIST_UNDEFINED && --script->syntax.curly[i].count == 0 ) { if (*p == ',') { script->syntax.curly[i].flag = ARGLIST_PAREN; return p; } else { script->syntax.curly[i].flag = ARGLIST_NO_PAREN; } } if (*p != ')') disp_error_message("parse_simpleexpr: unmatched ')'", p); return p + 1; } static const char *parse_simpleexpr_number(const char *p) { long long lli = 0; const char *np = parse_number(p, &lli); script->addi((int)lli); // Cast is safe, as it's already been checked for overflows return np; } static const char *parse_simpleexpr_string(const char *p) { const char *start_point = p; nullpo_retr(NULL, p); do { p++; while (*p != '\0' && *p != '"') { if ((unsigned char)p[-1] <= 0x7e && *p == '\\') { char buf[8]; size_t len = sv->skip_escaped_c(p) - p; size_t n = sv->unescape_c(buf, p, len); if (n != 1) ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf); p += len; VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512); VECTOR_PUSH(script->parse_simpleexpr_strbuf, buf[0]); continue; } if (*p == '\n') { disp_error_message("parse_simpleexpr: unexpected newline @ string", p); } VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512); VECTOR_PUSH(script->parse_simpleexpr_strbuf, *p++); } if (*p == '\0') disp_error_message("parse_simpleexpr: unexpected end of file @ string", p); p++; //'"' p = script->skip_space(p); } while (*p != '\0' && *p == '"'); VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512); VECTOR_PUSH(script->parse_simpleexpr_strbuf, '\0'); script->add_translatable_string(&script->parse_simpleexpr_strbuf, start_point); VECTOR_TRUNCATE(script->parse_simpleexpr_strbuf); return p; } static const char *parse_simpleexpr_name(const char *p) { int l; const char *pv = NULL; // label , register , function etc if (script->skip_word(p) == p) disp_error_message("parse_simpleexpr: unexpected character", p); l = script->add_word(p); if (script->str_data[l].type == C_FUNC || script->str_data[l].type == C_USERFUNC || script->str_data[l].type == C_USERFUNC_POS) { return script->parse_callfunc(p,1,0); #ifdef SCRIPT_CALLFUNC_CHECK } else { const char *name = script->get_str(l); if (strdb_get(script->userfunc_db,name) != NULL) { return script->parse_callfunc(p, 1, 1); } #endif } if ((pv = script->parse_variable(p)) != NULL) { // successfully processed a variable assignment return pv; } if (script->str_data[l].type == C_INT && script->str_data[l].deprecated) { disp_warning_message("This constant is deprecated and it will be removed in a future version. Please see the script documentation and constants.conf for an alternative.\n", p); } p = script->skip_word(p); if (*p == '[') { // array(name[i] => getelementofarray(name,i) ) script->addl(script->buildin_getelementofarray_ref); script->addc(C_ARG); script->addl(l); p = script->parse_subexpr(p + 1, -1); p = script->skip_space(p); if (*p != ']') disp_error_message("parse_simpleexpr: unmatched ']'", p); ++p; script->addc(C_FUNC); } else if (script->str_data[l].type == C_INT) { script->addc(C_NAME); script->addb(l); script->addb(l >> 8); script->addb(l >> 16); } else { script->addl(l); } return p; } static void script_add_translatable_string(const struct script_string_buf *string, const char *start_point) { struct string_translation *st = NULL; nullpo_retv(string); if (script->syntax.translation_db == NULL || (st = strdb_get(script->syntax.translation_db, VECTOR_DATA(*string))) == NULL) { script->addc(C_STR); VECTOR_ENSURE(script->buf, VECTOR_LENGTH(*string), SCRIPT_BLOCK_SIZE); VECTOR_PUSHARRAY(script->buf, VECTOR_DATA(*string), VECTOR_LENGTH(*string)); } else { unsigned char u; int st_cursor = 0; script->addc(C_LSTR); VECTOR_ENSURE(script->buf, (int)(sizeof(st->string_id) + sizeof(st->translations)), SCRIPT_BLOCK_SIZE); VECTOR_PUSHARRAY(script->buf, (void *)&st->string_id, sizeof(st->string_id)); VECTOR_PUSHARRAY(script->buf, (void *)&st->translations, sizeof(st->translations)); for (u = 0; u != st->translations; u++) { struct string_translation_entry *entry = (void *)(st->buf+st_cursor); char *stringptr = &entry->string[0]; st_cursor += sizeof(*entry); VECTOR_ENSURE(script->buf, (int)(sizeof(entry->lang_id) + sizeof(char *)), SCRIPT_BLOCK_SIZE); VECTOR_PUSHARRAY(script->buf, (void *)&entry->lang_id, sizeof(entry->lang_id)); VECTOR_PUSHARRAY(script->buf, (void *)&stringptr, sizeof(stringptr)); st_cursor += sizeof(uint8); // FIXME: What are we skipping here? while (st->buf[st_cursor++] != 0) (void)0; // Skip string st_cursor += sizeof(uint8); // FIXME: What are we skipping here? } } } /*========================================== * Analysis of the expression *------------------------------------------*/ static const char *script_parse_subexpr(const char *p, int limit) { int op,opl,len; nullpo_retr(NULL, p); p=script->skip_space(p); if( *p == '-' ) { const char *tmpp = script->skip_space(p+1); if( *tmpp == ';' || *tmpp == ',' ) { script->addl(LABEL_NEXTLINE); p++; return p; } } if( (p[0]=='+' && p[1]=='+') /* C_ADD_PRE */ || (p[0]=='-'&&p[1]=='-') /* C_SUB_PRE */ ) { // Pre ++ -- operators p=script->parse_variable(p); } else if( (op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~') ) { // Unary - ! ~ operators p=script->parse_subexpr(p+1,11); script->addc(op); } else { p=script->parse_simpleexpr(p); } p=script->skip_space(p); while(( (op=C_OP3, opl=0, len=1,*p=='?') // ?: || (op=C_ADD, opl=9, len=1,*p=='+' && p[1]!='+') // + || (op=C_SUB, opl=9, len=1,*p=='-' && p[1]!='-') // - || (op=C_POW, opl=11,len=2,*p=='*' && p[1]=='*') // ** || (op=C_MUL, opl=10,len=1,*p=='*') // * || (op=C_DIV, opl=10,len=1,*p=='/') // / || (op=C_MOD, opl=10,len=1,*p=='%') // % || (op=C_LAND, opl=2, len=2,*p=='&' && p[1]=='&') // && || (op=C_AND, opl=5, len=1,*p=='&') // & || (op=C_LOR, opl=1, len=2,*p=='|' && p[1]=='|') // || || (op=C_OR, opl=3, len=1,*p=='|') // | || (op=C_XOR, opl=4, len=1,*p=='^') // ^ || (op=C_EQ, opl=6, len=2,*p=='=' && p[1]=='=') // == || (op=C_NE, opl=6, len=2,*p=='!' && p[1]=='=') // != || (op=C_RE_EQ, opl=6, len=2,*p=='~' && p[1]=='=') // ~= || (op=C_RE_NE, opl=6, len=2,*p=='~' && p[1]=='!') // ~! || (op=C_R_SHIFT,opl=8, len=2,*p=='>' && p[1]=='>') // >> || (op=C_GE, opl=7, len=2,*p=='>' && p[1]=='=') // >= || (op=C_GT, opl=7, len=1,*p=='>') // > || (op=C_L_SHIFT,opl=8, len=2,*p=='<' && p[1]=='<') // << || (op=C_LE, opl=7, len=2,*p=='<' && p[1]=='=') // <= || (op=C_LT, opl=7, len=1,*p=='<') // < ) && opl>limit) { p+=len; if(op == C_OP3) { p=script->parse_subexpr(p,-1); p=script->skip_space(p); if( *(p++) != ':') disp_error_message("parse_subexpr: need ':'", p-1); p=script->parse_subexpr(p,-1); } else { p=script->parse_subexpr(p,opl); } script->addc(op); p=script->skip_space(p); } return p; /* return first untreated operator */ } /*========================================== * Evaluation of the expression *------------------------------------------*/ static const char *parse_expr(const char *p) { nullpo_retr(NULL, p); switch(*p) { case ')': case ';': case ':': case '[': case ']': case '}': disp_error_message("parse_expr: unexpected char",p); } p=script->parse_subexpr(p,-1); return p; } /*========================================== * Analysis of the line *------------------------------------------*/ static const char *parse_line(const char *p) { const char* p2; nullpo_retr(NULL, p); p=script->skip_space(p); if(*p==';') { //Close decision for if(); for(); while(); p = script->parse_syntax_close(p + 1); return p; } if(*p==')' && script->parse_syntax_for_flag) return p+1; p = script->skip_space(p); if(p[0] == '{') { script->syntax.curly[script->syntax.curly_count].type = TYPE_NULL; script->syntax.curly[script->syntax.curly_count].count = -1; script->syntax.curly[script->syntax.curly_count].index = -1; script->syntax.curly_count++; return p + 1; } else if(p[0] == '}') { return script->parse_curly_close(p); } // Syntax-related processing p2 = script->parse_syntax(p); if(p2 != NULL) return p2; // attempt to process a variable assignment p2 = script->parse_variable(p); if (p2 != NULL) { // variable assignment processed so leave the method if (script->parse_syntax_for_flag) { if (*p2 != ')') disp_error_message("parse_line: need ')'", p2); } else { if (*p2 != ';') disp_error_message("parse_line: need ';'", p2); } return script->parse_syntax_close(p2 + 1); } p = script->parse_callfunc(p,0,0); p = script->skip_space(p); if(script->parse_syntax_for_flag) { if( *p != ')' ) disp_error_message("parse_line: need ')'",p); } else { if( *p != ';' ) disp_error_message("parse_line: need ';'",p); } //Binding decision for if(), for(), while() p = script->parse_syntax_close(p+1); return p; } /** * parses a local function expression * * expects these formats: * function ; * function {