/**
* 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 {