/* * File: scriptcontext.cpp * * Author: Lukas Zeller (luz@synthesis.ch) * * TScriptContext * Environment to tokenize, prepare and run scripts * * Copyright (c) 2002-2009 by Synthesis AG (www.synthesis.ch) * * 2002-09-11 : luz : created * */ #include "prefix_file.h" #ifdef SCRIPT_SUPPORT // includes #include "scriptcontext.h" #include "platform_exec.h" // for SHELLEXECUTE #include "rrules.h" // for RECURRENCE_COUNT/DATE #include "vtimezone.h" // for SETTIMEZONE #include "mimediritemtype.h" // for AlldayCount/MakeAllday #ifdef REGEX_SUPPORT #include "pcre.h" // for RegEx functions #endif // script debug messages #ifdef SYDEBUG #define SCRIPTDBGMSGX(lvl,x) { if (debugon && fSessionP) { POBJDEBUGPRINTFX(fSessionP,lvl,x); } } #define SCRIPTDBGMSG(x) SCRIPTDBGMSGX(DBG_SCRIPTS,x) #define SCRIPTDBGSTART(nam) { if (debugon && fSessionP) { if (fSessionP->getDbgMask() & DBG_SCRIPTS) { fSessionP->PDEBUGBLOCKFMTCOLL(("ScriptExecute","Start executing Script","name=%s",nam)); } else { POBJDEBUGPRINTFX(fSessionP,DBG_DATA,("Executing Script '%s'",nam)); } } } #define SCRIPTDBGEND() { if (debugon && fSessionP && (fSessionP->getDbgMask() & DBG_SCRIPTS)) { fSessionP->PDEBUGENDBLOCK("ScriptExecute"); } } #define SCRIPTDBGTEST (debugon && fSessionP && (fSessionP->getDbgMask() & DBG_SCRIPTS)) #define EXPRDBGTEST (debugon && fSessionP && ((fSessionP->getDbgMask() & (DBG_SCRIPTS|DBG_SCRIPTEXPR)) == (DBG_SCRIPTS|DBG_SCRIPTEXPR))) #define DBGSTRINGDEF(s) string s #define DBGVALUESHOW(s,v) dbgValueShow(s,v) #define SHOWVARDEFS(t) showVarDefs(t) #else #define SCRIPTDBGMSGX(lvl,x) #define SCRIPTDBGMSG(x) #define SCRIPTDBGSTART(nam) #define SCRIPTDBGEND() #define SCRIPTDBGTEST false #define EXPRDBGTEST false #define DBGSTRINGDEF(s) #define DBGVALUESHOW(s,v) #define SHOWVARDEFS(t) #endif namespace sysync { #ifdef SYDEBUG // show value of a field static void dbgValueShow(string &aString, TItemField *aFieldP) { TItemFieldTypes ty; if (aFieldP) { // value aString = "&html;&html;"; aFieldP->StringObjFieldAppend(aString,200); aString += "&html;&html;"; // add type info ty=aFieldP->getType(); aString += " ("; aString += ItemFieldTypeNames[ty]; aString += ")"; } else { aString=""; } } // dbgValueShow #endif TTokenizeException::TTokenizeException(cAppCharP aScriptName, cAppCharP aMsg1,cAppCharP aScript, uInt16 aIndex, uInt16 aLine) : TConfigParseException("") { cAppCharP p2=aScript+aIndex; cAppCharP p1=p2-(aIndex>5 ? 5 : aIndex); StringObjPrintf(fMessage, "%s: %s at line %hd: '...%-.5s_%-.10s...'", aScriptName ? aScriptName : "", aMsg1, aLine, p1, p2 ); } // TTokenizeException::TTokenizeException TScriptErrorException::TScriptErrorException(cAppCharP aMsg1, uInt16 aLine, cAppCharP aIdent) : TConfigParseException("") { if (aIdent) { StringObjPrintf(fMessage,aMsg1,aIdent); } else { StringObjPrintf(fMessage,"%s",aMsg1); } StringObjAppendPrintf( fMessage, " in script at line %hd", aLine ); } // TScriptErrorException::TScriptErrorException /* * Implementation of TScriptConfig */ // config constructor TScriptConfig::TScriptConfig(TConfigElement *aParentElementP) : TConfigElement("scripting",aParentElementP) { clear(); } // TScriptConfig::TScriptConfig // config destructor TScriptConfig::~TScriptConfig() { clear(); } // TScriptConfig::~TScriptConfig // init defaults void TScriptConfig::clear(void) { // delete options fMaxLoopProcessingTime = #if SYDEBUG>1 60; // 1 min for debugging #else 5; // 5 seconds for real execution #endif // delete functions TUserScriptList::iterator pos; for (pos=fFunctionScripts.begin();pos!=fFunctionScripts.end();++pos) { delete (*pos); } fFunctionScripts.clear(); fScriptMacros.clear(); // clear inherited inherited::clear(); } // TScriptConfig::clear // server config element parsing bool TScriptConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine) { // checking the elements if (strucmp(aElementName,"looptimeout")==0) expectUInt32(fMaxLoopProcessingTime); else if (strucmp(aElementName,"function")==0) { // create new function definition TUserScriptFunction *funcdefP = new TUserScriptFunction; fFunctionScripts.push_back(funcdefP); expectFunction(*funcdefP,aLine); } else if (strucmp(aElementName,"macro")==0) { const char *macroname = getAttr(aAttributes,"name"); if (!macroname) fail(" must have a 'name' attribute"); // create new macro expectRawString(fScriptMacros[macroname]); } // - none known here else return inherited::localStartElement(aElementName,aAttributes,aLine); // ok return true; } // TScriptConfig::localStartElement // resolve void TScriptConfig::localResolve(bool aLastPass) { // resolve inherited inherited::localResolve(aLastPass); } // TScriptConfig::localResolve // get script text string *TScriptConfig::getFunctionScript(sInt16 aFuncIndex) { if (aFuncIndex<0 || aFuncIndex>(sInt16)fFunctionScripts.size()) return NULL; return &(fFunctionScripts[aFuncIndex]->fFuncDef); } // TScriptConfig::getFunctionScript // get index of specific function sInt16 TScriptConfig::getFunctionIndex(cAppCharP aName, size_t aLen) { TUserScriptList::iterator pos; sInt16 i=0; for (pos=fFunctionScripts.begin();pos!=fFunctionScripts.end();++pos) { if (strucmp((*pos)->fFuncName.c_str(),aName)==0) return i; i++; } // unknown return VARIDX_UNDEFINED; } // TScriptConfig::getFunctionIndex // Script variable definition // create new scrip variable definition TScriptVarDef::TScriptVarDef(cAppCharP aName,uInt16 aIdx, TItemFieldTypes aType, bool aIsArray, bool aIsRef, bool aIsOpt) { fVarName=aName; fIdx=aIdx; fVarType=aType; fIsArray=aIsArray; fIsRef=aIsRef; fIsOpt=aIsOpt; } // TScriptVarDef::TScriptVarDef TScriptVarDef::~TScriptVarDef() { } // TScriptVarDef::~TScriptVarDef /* * builtin function definitions */ class TBuiltinStdFuncs { public: // timestamp NOW() // returns current date/time stamp in UTC with timezone set static void func_Now(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aTermP); tsP->setTimestampAndContext( getSystemNowAs(TCTX_UTC, tsP->getGZones()), TCTX_UTC ); }; // func_Now // timestamp SYSTEMNOW() // returns current date/time stamp as system time with timezone set static void func_SystemNow(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aTermP); tsP->setTimestampAndContext( getSystemNowAs(TCTX_SYSTEM, tsP->getGZones()), TCTX_SYSTEM ); }; // func_SystemNow // timestamp DBNOW() // returns database's idea of "now" in UTC with local timezone set static void func_DbNow(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aTermP); tsP->setTimestampAndContext( aFuncContextP->getSession()->getDatabaseNowAs(TCTX_UTC), TCTX_UTC ); }; // func_DbNow // integer ZONEOFFSET(timestamp atime) // returns zone offset for given timestamp. // New in 3.1: Floating timestamps return unassigned static void func_ZoneOffset(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); sInt16 moffs=0; if (tsP->isFloating()) { // floating timestamps do not have an offset aTermP->unAssign(); } else { moffs = tsP->getMinuteOffset(); aTermP->setAsInteger((sInt32)moffs*SecsPerMin); } }; // func_ZoneOffset // helper to get a time context from integer seconds offset or string zone name static timecontext_t contextFromSpec(TItemField *aVariantP, TScriptContext *aFuncContextP) { timecontext_t tctx = TCTX_UNKNOWN; if (aVariantP->isBasedOn(fty_timestamp)) { // just use context of another timestamp tctx = static_cast(aVariantP)->getTimeContext(); } else if (aVariantP->getCalcType()==fty_integer) { // integer specifies a seconds offset tctx = TCTX_MINOFFSET(aVariantP->getAsInteger() / SecsPerMin); } else if (aVariantP->isEmpty()) { // empty non-timestamp and non-integer mean unknown/floating timezone tctx = TCTX_UNKNOWN; } else { // treat as string specifying time zone by name or vTimezone string str; aVariantP->getAsString(str); if (strucmp(str.c_str(),"USERTIMEZONE")==0) { // special case - session's user time zone tctx = aFuncContextP->getSession()->fUserTimeContext; } else if (strucmp(str.c_str(),"SYSTEM")==0) { tctx = TCTX_SYSTEM; } else if (strucmp(str.c_str(),"BEGIN:VTIMEZONE",15)==0) { // is a vTimezone, get it if (!VTIMEZONEtoInternal(str.c_str(), tctx, aFuncContextP->getSession()->getSessionZones())) tctx = TCTX_UNKNOWN; } else { // search for timezone by name if (!TimeZoneNameToContext(str.c_str(), tctx, aFuncContextP->getSession()->getSessionZones())) { // last attempt is parsing it as a ISO8601 offset spec ISO8601StrToContext(str.c_str(), tctx); } } } return tctx; } // contextFromSpec // helper to represent a time context as string static void zoneStrFromContext(timecontext_t aContext, TItemField *aZoneStrFieldP, TScriptContext *aFuncContextP) { string str; if (TCTX_IS_UNKNOWN(aContext)) { // no time zone aZoneStrFieldP->unAssign(); } else if (TCTX_IS_TZ(aContext)) { // symbolic time zone, show name TimeZoneContextToName(aContext,str,aFuncContextP->getSession()->getSessionZones()); aZoneStrFieldP->setAsString(str); } else { // is non-symbolic minute offset, show it in ISO8601 extended form str.erase(); ContextToISO8601StrAppend(str, aContext, true); } } // zoneStrFromContext // string TIMEZONE(timestamp atime) // returns time zone name static void func_Timezone(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); zoneStrFromContext(tsP->getTimeContext(), aTermP, aFuncContextP); }; // func_Timezone // string VTIMEZONE(timestamp atime) // returns time zone in VTIMEZONE format static void func_VTimezone(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); string z; internalToVTIMEZONE(tsP->getTimeContext(),z,aFuncContextP->getSession()->getSessionZones()); aTermP->setAsString(z); }; // func_VTimezone // SETTIMEZONE(timestamp &atime,variant zonespec) // sets time zone for given timestamp field static void func_SetTimezone(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get context from variant spec timecontext_t tctx = contextFromSpec(aFuncContextP->getLocalVar(1), aFuncContextP); // set it tsP->setTimeContext(tctx); }; // func_SetTimezone // SETFLOATING(timestamp &atime) // sets given timestamp to floating (no timezone) // this is an efficient shortform for SETTIMEZONE(atime,"FLOATING") or SETTIMEZONE(atime,"") static void func_SetFloating(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // set it tsP->setTimeContext(TCTX_UNKNOWN); }; // func_SetFloating // timestamp CONVERTTOZONE(timestamp atime, variant zonespec [,boolean doUnfloat]) // returns timestamp converted to specified zone. // - If doUnfloat, floating timestamps will be fixed in the new zone w/o conversion of the timestamp itself. // - timestamps that already have a zone will be converted static void func_ConvertToZone(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get context from variant spec timecontext_t actual,tctx = contextFromSpec(aFuncContextP->getLocalVar(1), aFuncContextP); // convert and get actually resulting context back (can also be floating) lineartime_t ts = tsP->getTimestampAs(tctx,&actual); // unfloat floats if selected if (aFuncContextP->getLocalVar(2)->getAsBoolean() && TCTX_IS_UNKNOWN(actual)) actual=tctx; // unfloat // assign it to result static_cast(aTermP)->setTimestampAndContext(ts,actual); }; // func_ConvertToZone // timestamp CONVERTTOUSERZONE(timestamp atime [,boolean doUnfloat]) // returns timestamp converted to user time zone.(or floating timestamp as-is) // - this is an efficient shortform for CONVERTTOZONE(atime,"USERTIMEZONE") // - If doUnfloat, floating timestamps will be fixed in the new zone w/o conversion of the timestamp itself. // - timestamps that already have a zone will be converted static void func_ConvertToUserZone(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); timecontext_t actual,tctx = aFuncContextP->getSession()->fUserTimeContext; // convert and get actually resulting context back (can also be floating) lineartime_t ts = tsP->getTimestampAs(tctx,&actual); // unfloat floats if selected if (aFuncContextP->getLocalVar(1)->getAsBoolean() && TCTX_IS_UNKNOWN(actual)) actual=tctx; // unfloat // assign it to result static_cast(aTermP)->setTimestampAndContext(ts,actual); }; // func_ConvertToUserZone // string USERTIMEZONE() // returns session user time zone name static void func_UserTimezone(TItemField *&aTermP, TScriptContext *aFuncContextP) { zoneStrFromContext(aFuncContextP->getSession()->fUserTimeContext,aTermP, aFuncContextP); }; // func_UserTimezone // SETUSERTIMEZONE(variant zonespec) // sets session user time zone static void func_SetUserTimezone(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get context from variant spec timecontext_t tctx = contextFromSpec(aFuncContextP->getLocalVar(1), aFuncContextP); aFuncContextP->getSession()->fUserTimeContext = tctx; }; // func_SetUserTimezone // integer ISDATEONLY(timestamp atime) // returns true if given timestamp is a date-only static void func_IsDateOnly(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); aTermP->setAsInteger(TCTX_IS_DATEONLY(tsP->getTimeContext()) ? 1 : 0); }; // func_IsDateOnly // timestamp DATEONLY(timestamp atime) // returns a floating(!) date-only of the given timestamp static void func_DateOnly(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get timestamp as dateonly timecontext_t tctx; lineartime_t ts; ts = tsP->getTimestampAs(TCTX_UNKNOWN | TCTX_DATEONLY, &tctx); // assign it to result (but do NOT pass in tctx, as it might contain a zone // but we need it as floating to make dates comparable static_cast(aTermP)->setTimestampAndContext(ts,TCTX_DATEONLY|TCTX_UNKNOWN); }; // func_DateOnly // timestamp TIMEONLY(timestamp atime) // returns a floating(!) time-only of the given timestamp static void func_TimeOnly(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get timestamp as dateonly timecontext_t tctx; lineartime_t ts; ts = tsP->getTimestampAs(TCTX_UNKNOWN | TCTX_TIMEONLY, &tctx); // assign it to result (but do NOT pass in tctx, as it might contain a zone // but we need it as floating to make dates comparable static_cast(aTermP)->setTimestampAndContext(ts,TCTX_TIMEONLY|TCTX_UNKNOWN); }; // func_TimeOnly // integer ISDURATION(timestamp atime) // returns true if given timestamp is a duration value static void func_IsDuration(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); aTermP->setAsInteger(tsP->isDuration() ? 1 : 0); }; // func_IsDuration // timestamp DURATION(timestamp atime) // returns the timestamp as a duration (floating, duration flag set) static void func_Duration(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get timestamp as-is timecontext_t tctx; lineartime_t ts; ts = tsP->getTimestampAs(TCTX_UNKNOWN, &tctx); // result is floating timestamp with duration flag set (and dateonly/timeonly flags retained) static_cast(aTermP)->setTimestampAndContext(ts,TCTX_UNKNOWN|(tctx&TCTX_RFLAGMASK)|TCTX_DURATION); }; // func_Duration // timestamp POINTINTIME(timestamp atime) // returns the timestamp as a point in time (i.e. not duration and not dateonly/timeonly) static void func_PointInTime(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); // get timestamp as-is timecontext_t tctx; lineartime_t ts; ts = tsP->getTimestampAs(TCTX_UNKNOWN, &tctx); // assign it to result with duration and dateonly flag cleared static_cast(aTermP)->setTimestampAndContext(ts,tctx & (~(TCTX_DURATION+TCTX_DATEONLY+TCTX_TIMEONLY))); }; // func_PointInTime // integer ISFLOATING(timestamp atime) // returns true if given timestamp is floating (i.e. not bound to a time zone) static void func_IsFloating(TItemField *&aTermP, TScriptContext *aFuncContextP) { TTimestampField *tsP = static_cast(aFuncContextP->getLocalVar(0)); aTermP->setAsInteger(tsP->isFloating() ? 1 : 0); }; // func_IsFloating // integer ALLDAYCOUNT(timestamp start, timestamp end [, boolean checkinusercontext [, onlyutcinusercontext]]) // returns number of days for an all-day event // Note: Timestamps must be in the context in which they are to be checked for midnight, 23:59:xx etc. // except if checkinusercontext ist set (then non-floating timestamps are moved into user context before // checking. onlyutcinusercontext limits moving to user context to UTC timestamps only. static void func_AlldayCount(TItemField *&aTermP, TScriptContext *aFuncContextP) { sInt16 c = AlldayCount( aFuncContextP->getLocalVar(0), aFuncContextP->getLocalVar(1), aFuncContextP->getLocalVar(2)->getAsBoolean() ? aFuncContextP->getSession()->fUserTimeContext : TCTX_UNKNOWN, aFuncContextP->getLocalVar(3)->getAsBoolean() ); aTermP->setAsInteger(c); }; // func_AlldayCount // MAKEALLDAY(timestamp &start, timestamp &end [,integer days]) // adjusts timestamps for allday representation, makes them floating // Note: Timestamps must already represent local day times static void func_MakeAllday(TItemField *&aTermP, TScriptContext *aFuncContextP) { sInt16 days=aFuncContextP->getLocalVar(2)->getAsInteger(); // returns 0 if unassigned MakeAllday( aFuncContextP->getLocalVar(0), aFuncContextP->getLocalVar(1), TCTX_UNKNOWN, days ); }; // func_MakeAllday // integer WEEKDAY(timestamp timestamp) // returns weekday (0=sunday, 1=monday ... 6=saturday) from a timestamp static void func_Weekday(TItemField *&aTermP, TScriptContext *aFuncContextP) { lineartime_t lt = static_cast(aFuncContextP->getLocalVar(0))->getTimestampAs(TCTX_UNKNOWN); aTermP->setAsInteger(lineartime2weekday(lt)); }; // func_Weekday // integer SECONDS(integer timeunits) // returns number of seconds from a time unit spec static void func_Seconds(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t v=aFuncContextP->getLocalVar(0)->getAsInteger(); aTermP->setAsInteger(v/secondToLinearTimeFactor); }; // func_Seconds // integer MILLISECONDS(integer timeunits) // returns number of milliseconds from a time unit spec static void func_Milliseconds(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t v=aFuncContextP->getLocalVar(0)->getAsInteger(); if (secondToLinearTimeFactor==1) { v=v*1000; // internal unit is seconds } else if (secondToLinearTimeFactor!=1000) { v=v/secondToLinearTimeFactor; // seconds v=v*1000; // artifical milliseconds } aTermP->setAsInteger(v); }; // func_Milliseconds // void SLEEPMS(integer milliseconds) // sleeps process (or thread) for specified time static void func_SleepMS(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t sl=aFuncContextP->getLocalVar(0)->getAsInteger(); // make nanoseconds, then timeunits sl *= 1000000LL; sl /= nanosecondsPerLinearTime; sleepLineartime(sl); }; // func_SleepMS // integer TIMEUNITS(integer seconds) // returns number of time units from a seconds spec static void func_Timeunits(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t v=aFuncContextP->getLocalVar(0)->getAsInteger(); aTermP->setAsInteger(v*secondToLinearTimeFactor); }; // func_Timeunits // integer DAYUNITS(integer days) // returns number of time units from a seconds spec static void func_Dayunits(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t v=aFuncContextP->getLocalVar(0)->getAsInteger(); aTermP->setAsInteger(v*linearDateToTimeFactor); }; // func_Dayunits // integer MONTHDAYS(timestamp date) // returns number of days of the month date is in static void func_MonthDays(TItemField *&aTermP, TScriptContext *aFuncContextP) { lineartime_t ts = static_cast(aFuncContextP->getLocalVar(0))->getTimestampAs(TCTX_UNKNOWN); aTermP->setAsInteger(getMonthDays(lineartime2dateonly(ts))); }; // func_MonthDays // DEBUGMESSAGE(string msg) // writes debug message to debug log file if debugging is not completely disabled static void func_Debugmessage(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG if (aFuncContextP->getDbgMask()) { // get message string s; aFuncContextP->getLocalVar(0)->getAsString(s); // write it if (aFuncContextP->getSession()) { POBJDEBUGPRINTFX(aFuncContextP->getSession(),DBG_HOT,("Script DEBUGMESSAGE() at Line %hd: %s",aFuncContextP->getScriptLine(),s.c_str())); } else { POBJDEBUGPRINTFX(aFuncContextP->getSyncAppBase(),DBG_HOT,("Script DEBUGMESSAGE() at Line %hd: %s",aFuncContextP->getScriptLine(),s.c_str())); } } #endif }; // func_Debugmessage // DEBUGSHOWVARS() // shows values of all local script variables static void func_DebugShowVars(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG if (aFuncContextP->getDbgMask() && aFuncContextP->fParentContextP) { POBJDEBUGPRINTFX(aFuncContextP->getSession(),DBG_HOT,("Script DEBUGSHOWVARS() at Line %hd:",aFuncContextP->getScriptLine())); // show all local vars (of PARENT!) TScriptContext *showContextP = aFuncContextP->fParentContextP; string fshow; uInt16 i; for (i=0; igetNumLocals(); i++) { StringObjAppendPrintf(fshow,"- %-20s : ",showContextP->getVarDef(i)->fVarName.c_str()); showContextP->getLocalVar(i)->StringObjFieldAppend(fshow,80); fshow += '\n'; } // output preformatted POBJDEBUGPUTSXX(aFuncContextP->getSession(),DBG_HOT,fshow.c_str(),0,true); } #endif }; // func_DebugShowVars // DEBUGSHOWITEM([bool aShowRefItem]) // shows all fields and values of current item static void func_DebugShowItem(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG if (aFuncContextP->getDbgMask() && aFuncContextP->fParentContextP) { POBJDEBUGPRINTFX(aFuncContextP->getSession(),DBG_HOT,("Script DEBUGSHOWITEM() at Line %hd:",aFuncContextP->getScriptLine())); // select item TMultiFieldItem *showItemP = aFuncContextP->getLocalVar(0)->getAsBoolean() ? // optional param to select ref item instead of target aFuncContextP->fParentContextP->fReferenceItemP : aFuncContextP->fParentContextP->fTargetItemP; if (showItemP) { showItemP->debugShowItem(DBG_HOT); } else { POBJDEBUGPRINTFX(aFuncContextP->getSession(),DBG_HOT,("- no item to show")); } } #endif }; // func_DebugShowItem // SETXMLTRANSLATE(bool yesorno) // enables or disables XML translated SyncML message dumping on a per session basis static void func_SetXMLTranslate(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG if (aFuncContextP->getSession()) aFuncContextP->getSession()->fXMLtranslate=aFuncContextP->getLocalVar(0)->getAsBoolean(); #endif }; // func_SetXMLTranslate // SETMSGDUMP(bool yesorno) // enables or disables raw SyncML message dumping on a per session basis static void func_SetMsgDump(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG if (aFuncContextP->getSession()) aFuncContextP->getSession()->fMsgDump=aFuncContextP->getLocalVar(0)->getAsBoolean(); #endif }; // func_SetMsgDump // SETDEBUGOPTIONS(string optionname, boolean set) // sets or clears debug option flags (for the currently running session) static void func_SetDebugOptions(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG TDebugLogger *loggerP=aFuncContextP->getDbgLogger(); if (loggerP) { // get option string string s; aFuncContextP->getLocalVar(0)->getAsString(s); // convert to bitmask sInt16 k; if (StrToEnum(debugOptionNames,numDebugOptions,k,s.c_str())) { // found mask, modify uInt32 currentmask=loggerP->getRealMask(); if (aFuncContextP->getLocalVar(1)->getAsBoolean()) currentmask |= debugOptionMasks[k]; else currentmask &= ~debugOptionMasks[k]; // .. and apply loggerP->setMask(currentmask); } } #endif }; // func_SetDebugOptions // SETDEBUGMASK(integer mask) // sets the debug mask static void func_SetDebugMask(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG TDebugLogger *loggerP=aFuncContextP->getDbgLogger(); if (loggerP) { // get mask value loggerP->setMask(aFuncContextP->getLocalVar(0)->getAsInteger()); } #endif }; // func_SetDebugMask // integer GETDEBUGMASK() // gets the current debug mask static void func_GetDebugMask(TItemField *&aTermP, TScriptContext *aFuncContextP) { uInt32 m=0; // without debug, mask is always 0 #ifdef SYDEBUG TDebugLogger *loggerP=aFuncContextP->getDbgLogger(); if (loggerP) { // get mask value loggerP->getRealMask(); } #endif aTermP->setAsInteger(m); }; // func_GetDebugMask // void REQUESTMAXTIME(integer maxtime_seconds) static void func_RequestMaxTime(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->fRequestMaxTime=aFuncContextP->getLocalVar(0)->getAsInteger(); } } // func_RequestMaxTime // void REQUESTMINTIME(integer maxtime_seconds) // artificial delay for testing static void func_RequestMinTime(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->fRequestMinTime=aFuncContextP->getLocalVar(0)->getAsInteger(); } } // func_RequestMinTime // string SYNCMLVERS() // gets the SyncML version of the current session as string like "1.2" or "1.0" etc. static void func_SyncMLVers(TItemField *&aTermP, TScriptContext *aFuncContextP) { aTermP->setAsString(SyncMLVersionNames[aFuncContextP->getSession()->getSyncMLVersion()]); }; // func_SyncMLVers // integer SHELLEXECUTE(string command, string arguments [,boolean inbackground]) // executes the command line in a shell, returns the exit code of the command static void func_Shellexecute(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string cmd,params; bool inbackground; sInt32 exitcode; aFuncContextP->getLocalVar(0)->getAsString(cmd); aFuncContextP->getLocalVar(1)->getAsString(params); // optional param inbackground=aFuncContextP->getLocalVar(2)->getAsBoolean(); // returns false if not assigned -> not in background // execute now exitcode=shellExecCommand(cmd.c_str(),params.c_str(),inbackground); // return result code aTermP->setAsInteger(exitcode); }; // func_Shellexecute // string REMOTERULENAME() // returns name of applied remote rule, empty if none static void func_Remoterulename(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifndef NO_REMOTE_RULES string r; if (aFuncContextP->getSession()) { // there is a session if (aFuncContextP->getSession()->fAppliedRemoteRuleP) { // there is a rule applied aTermP->setAsString(aFuncContextP->getSession()->fAppliedRemoteRuleP->getName()); return; } } #endif // no remote rule applied aTermP->assignEmpty(); }; // func_Remoterulename // TREATASLOCALTIME(integer flag) static void func_SetTreatAsLocaltime(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fTreatRemoteTimeAsLocal = aFuncContextP->getLocalVar(0)->getAsBoolean(); }; // func_SetTreatAsLocaltime // TREATASUTC(integer flag) static void func_SetTreatAsUTC(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fTreatRemoteTimeAsUTC = aFuncContextP->getLocalVar(0)->getAsBoolean(); }; // func_SetTreatAsUTC // UPDATECLIENTINSLOWSYNC(integer flag) static void func_SetUpdateClientInSlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fUpdateClientDuringSlowsync = aFuncContextP->getLocalVar(0)->getAsBoolean(); }; // func_SetUpdateClientInSlowSync // UPDATESERVERINSLOWSYNC(integer flag) static void func_SetUpdateServerInSlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fUpdateServerDuringSlowsync = aFuncContextP->getLocalVar(0)->getAsBoolean(); }; // func_SetUpdateServerInSlowSync // SHOWCTCAPPROPERTIES(bool yesorno) static void func_ShowCTCapProps(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fShowCTCapProps = aFuncContextP->getLocalVar(0)->getAsBoolean(); } // func_ShowCTCapProps // SHOWTYPESIZEINCTCAP10(bool yesorno) static void func_ShowTypeSizeInCTCap10(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) sessionP->fShowTypeSzInCTCap10 = aFuncContextP->getLocalVar(0)->getAsBoolean(); } // func_ShowTypeSizeInCTCap10 // ENUMDEFAULTPROPPARAMS(bool yesorno) static void func_EnumDefaultPropParams(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); // Note that this is a tristate! if (sessionP) sessionP->fEnumDefaultPropParams = aFuncContextP->getLocalVar(0)->getAsBoolean() ? 1 : 0; } // func_EnumDefaultPropParams // string LOCALURI() // returns local URI as used by client for starting session static void func_LocalURI(TItemField *&aTermP, TScriptContext *aFuncContextP) { string r; if (aFuncContextP->getSession()) { // there is a session aTermP->setAsString(aFuncContextP->getSession()->getInitialLocalURI()); return; } // no session?? aTermP->assignEmpty(); }; // func_LocalURI // string SUBSTR(string, from [, count]) static void func_Substr(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s; aFuncContextP->getLocalVar(0)->getAsString(s); string::size_type i=aFuncContextP->getLocalVar(1)->getAsInteger(); // optional param string::size_type l=s.size(); // default to entire string if (aFuncContextP->getLocalVar(2)->isAssigned()) l=aFuncContextP->getLocalVar(2)->getAsInteger(); // use specified count string r; // adjust params if (i>=s.size()) l=0; else if (i+l>s.size()) l=s.size()-i; // evaluate if (l>0) r.assign(s,i,l); // save result aTermP->setAsString(r); }; // func_Substr // string EXPLODE(string glue, variant &parts[]) static void func_Explode(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string glue,s; aFuncContextP->getLocalVar(0)->getAsString(glue); TItemField *fldP = aFuncContextP->getLocalVar(1); // concatenate array elements with glue aTermP->assignEmpty(); for (uInt16 i=0; iarraySize(); i++) { // get array element as string fldP->getArrayField(i)->getAsString(s); // add to output aTermP->appendString(s); if (i+1arraySize()) { // we have more elements, add glue aTermP->appendString(glue); } } }; // func_Explode #ifdef _MSC_VER static long long V_llabs( long long j ) { return (j < 0 ? -j : j); } #endif // integer ABS(integer val) static void func_Abs(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef _MSC_VER aTermP->setAsInteger(V_llabs(aFuncContextP->getLocalVar(0)->getAsInteger())); #else aTermP->setAsInteger(::llabs(aFuncContextP->getLocalVar(0)->getAsInteger())); #endif }; // func_Abs // integer SIGN(integer val) // i == 0 : 0 // i > 0 : 1 // i < 0 : -1 static void func_Sign(TItemField *&aTermP, TScriptContext *aFuncContextP) { fieldinteger_t i = aFuncContextP->getLocalVar(0)->getAsInteger(); aTermP->setAsInteger(i==0 ? 0 : (i>0 ? 1 : -1)); }; // func_Sign // integer RANDOM(integer range [, integer seed]) // generates random number between 0 and range-1. Seed is optional to init random generator static void func_Random(TItemField *&aTermP, TScriptContext *aFuncContextP) { // seed if second param there if (aFuncContextP->getLocalVar(1)->isAssigned()) { // seed random gen first srand((unsigned int)aFuncContextP->getLocalVar(1)->getAsInteger()); } // now get random value fieldinteger_t r = rand(); fieldinteger_t max = RAND_MAX; // scale to specified range aTermP->setAsInteger(r * aFuncContextP->getLocalVar(0)->getAsInteger() / (max+1)); }; // func_Random // string NUMFORMAT(integer num, integer digits [,string filler=" " [,boolean opts=""]]) static void func_NumFormat(TItemField *&aTermP, TScriptContext *aFuncContextP) { // mandatory params fieldinteger_t i = aFuncContextP->getLocalVar(0)->getAsInteger(); sInt16 numdigits = aFuncContextP->getLocalVar(1)->getAsInteger(); // negative = left justified // optional params string filler = " "; // default to filling with spaces if (aFuncContextP->getLocalVar(2)->isAssigned()) aFuncContextP->getLocalVar(2)->getAsString(filler); // empty: no filling, only truncation string opts; aFuncContextP->getLocalVar(3)->getAsString(opts); // +: show plus sign. space: show space as plus sign // generate raw string string s; // - determine hex mode bool hex = opts.find("x")!=string::npos; // - create sign char sign = 0; if (!hex) { if (i<0) sign = '-'; else { if (opts.find("+")!=string::npos) sign='+'; else if (opts.find(" ")!=string::npos) sign=' '; } } // create raw numeric string if (hex) { StringObjPrintf(s,"%llX",(long long)i); } else { #ifdef _MSC_VER StringObjPrintf(s,"%lld",V_llabs(i)); #else StringObjPrintf(s,"%lld",::llabs(i)); #endif } // adjust char c = *(filler.c_str()); // NUL or filler char if (c!='0' && sign) { s.insert(0,1,sign); // no zero-padding: insert sign before padding sign=0; // done now } sInt32 n,sz = s.size() + (sign ? 1 : 0); // leave room for sign after zero padding if (numdigits>0) { // right aligned field n = numdigits-sz; // empty space in field if (n<0) s.erase(0,-n); // delete at beginning else if (n>0 && c) s.insert(0,n,c); // insert at beginning } else { // left aligned field n = -numdigits-sz; // empty space in field if (n<0) s.erase(sz-n,-n); // delete at end else if (n>0 && c) s.insert(sz,n,c); // insert at end } // insert plus now if filled with zeroes if (sign) s.insert(0,1,sign); // insert sign after zero padding // return string aTermP->setAsString(s); } // func_NumFormat // integer LENGTH(string) static void func_Length(TItemField *&aTermP, TScriptContext *aFuncContextP) { TItemField *fldP = aFuncContextP->getLocalVar(0); fieldinteger_t siz; if (fldP->isBasedOn(fty_string)) { // don't get value to avoid pulling large strings just for size siz=static_cast(fldP)->getStringSize(); } else { // brute force string s; fldP->getAsString(s); siz=s.size(); } // save result aTermP->setAsInteger(siz); }; // func_Length // integer SIZE(&var) static void func_Size(TItemField *&aTermP, TScriptContext *aFuncContextP) { TItemField *fldP = aFuncContextP->getLocalVar(0); fieldinteger_t siz; if (fldP->isArray()) { siz=fldP->arraySize(); } else if (fldP->isBasedOn(fty_string)) { // don't get value to avoid pulling large strings just for size siz=static_cast(fldP)->getStringSize(); } else { // brute force string s; fldP->getAsString(s); siz=s.size(); } // save result aTermP->setAsInteger(siz); }; // func_Size // integer FIND(string, pattern [, startat]) static void func_Find(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat; string::size_type p; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); // optional param uInt32 i=aFuncContextP->getLocalVar(2)->getAsInteger(); // returns 0 if unassigned // find in string if (iunAssign(); else aTermP->setAsInteger(p); }; // func_Find // integer RFIND(string, pattern [, startat]) static void func_RFind(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat; string::size_type p; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); // optional param uInt32 i=aFuncContextP->getLocalVar(2)->getAsInteger(); // returns 0 if unassigned if (i>s.size()) i=s.size(); // find in string p=s.rfind(pat,i); // return UNASSIGNED for "not found" and position otherwise if (p==string::npos) aTermP->unAssign(); else aTermP->setAsInteger(p); }; // func_RFind #ifdef REGEX_SUPPORT // run PCRE regexp // Returns: > 0 => success; value is the number of elements filled in // = 0 => success, but offsets is not big enough // -1 => failed to match // -2 => PCRE_ERROR_NULL => did not compile, error reported to aDbgLogger // < -2 => some kind of unexpected problem static int run_pcre(cAppCharP aRegEx, cAppCharP aSubject, stringSize aSubjLen, stringSize aSubjStart, int *aOutVec, int aOVSize, TDebugLogger *aDbgLogger) { string regexpat; // set default options int options=0; // scan input pattern. If it starts with /, we assume /xxx/opt form cAppCharP p = aRegEx; char c=*p; if (c=='/') { // delimiter found p++; // - now search end while (*p) { if (*p=='\\') { // escaped char p++; if (*p) p++; } else { if (*p==c) { // found end of regex size_t n=p-aRegEx-1; // size of plain regExp // - scan options cAppCharP o = p++; while (*o) { switch (*o) { case 'i' : options |= PCRE_CASELESS; break; case 'm' : options |= PCRE_MULTILINE; break; case 's' : options |= PCRE_DOTALL; break; case 'x' : options |= PCRE_EXTENDED; break; case 'U' : options |= PCRE_UNGREEDY; break; } o++; } // - extract regex itself regexpat.assign(aRegEx+1,n); aRegEx = regexpat.c_str(); break; // done } p++; } } // while chars in regex } // if regex with delimiter // - compile regex pcre *regex; cAppCharP errMsg=NULL; int errOffs=0; regex = pcre_compile(aRegEx, options | PCRE_UTF8, &errMsg, &errOffs, NULL); if (regex==NULL) { // error, display it in log if script logging is on PLOGDEBUGPRINTFX(aDbgLogger,DBG_SCRIPTS+DBG_ERROR,( "RegEx error at pattern pos %d: %s ", errOffs, errMsg ? errMsg : "" )); return PCRE_ERROR_NULL; // -2, regexp did not compile } else { // regExp is ok and can be executed against subject int r = pcre_exec(regex, NULL, aSubject, aSubjLen, aSubjStart, 0, aOutVec, aOVSize); pcre_free(regex); return r; } } // run_pcre // integer REGEX_FIND(string subject, string pattern [, integer startat]) static void func_Regex_Find(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); // optional param sInt16 i=aFuncContextP->getLocalVar(2)->getAsInteger(); // returns 0 if unassigned // use PCRE to find const int ovsize=3; // we need no matches int ov[ovsize]; int rc = run_pcre(pat.c_str(),s.c_str(),s.size(),i,ov,ovsize,aFuncContextP->getDbgLogger()); if (rc>=0) { // return start position aTermP->setAsInteger(ov[0]); } else { // return UNASSIGNED for "not found" and error aTermP->unAssign(); } }; // func_Regex_Find // integer REGEX_MATCH(string subject, string regexp, integer startat, array &matches) static void func_Regex_Match(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); sInt32 i=aFuncContextP->getLocalVar(2)->getAsInteger(); TItemField *matchesP = aFuncContextP->getLocalVar(3); string m; // use PCRE to find const int ovsize=54; // max matches (they say this must be a multiple of 3, no idea why; I'd say 2...) int ov[ovsize]; int rc = run_pcre(pat.c_str(),s.c_str(),s.size(),i,ov,ovsize,aFuncContextP->getDbgLogger()); if (rc>0) { // return start position aTermP->setAsInteger(ov[0]); // return matches int mIdx; TItemField *fldP; for (mIdx=0; mIdxisArray()) fldP = matchesP->getArrayField(mIdx); else { // non-array specified fldP = matchesP; // - if there are no subpatterns, assign the first match (entire pattern) // - if there are subpatterns, assign the first subpattern match if (rc>1) { // there is at least one subpattern mIdx++; // skip the entire pattern match such that 1st subpattern gets assigned } } // assign match (first is entire pattern) fldP->setAsString(s.c_str()+ov[mIdx*2],ov[mIdx*2+1]-ov[mIdx*2]); // assign substring // end if matches is not an array if (!matchesP->isArray()) break; } } else { // return UNASSIGNED for "not found" and all errors aTermP->unAssign(); } }; // func_Regex_Match // integer REGEX_SPLIT(string subject, string separatorregexp, array elements [, boolean emptyElements]) // returns number of elements created in elements array static void func_Regex_Split(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); TItemField *elementsP = aFuncContextP->getLocalVar(2); // optional params bool emptyElements = aFuncContextP->getLocalVar(3)->getAsBoolean(); // skip empty elements by default // find all recurrences of separator uInt32 i = 0; // start at beginning sInt32 rIdx = 0; // no results so far while (igetDbgLogger()); if (rc<=0) { // no further match found // - simulate match at end of string ov[0]=s.size(); ov[1]=ov[0]; } // copy element if (uInt32(ov[0])>i || emptyElements) { TItemField *fldP = elementsP->getArrayField(rIdx); if (ov[0]-i>0) fldP->setAsString(s.c_str()+i,ov[0]-i); else fldP->assignEmpty(); // empty element // next element rIdx++; } // skip separator i = ov[1]; } // while not at end of string // return number of elements found aTermP->setAsInteger(rIdx); }; // func_Regex_Split // string REGEX_REPLACE(string,regexp,replacement [,startat [,repeat]]) static void func_Regex_Replace(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string s,pat,reppat,res; aFuncContextP->getLocalVar(0)->getAsString(s); aFuncContextP->getLocalVar(1)->getAsString(pat); aFuncContextP->getLocalVar(2)->getAsString(reppat); // optional params sInt16 i=aFuncContextP->getLocalVar(3)->getAsInteger(); // returns 0 if unassigned -> start at beginning sInt32 c=aFuncContextP->getLocalVar(4)->getAsInteger(); // returns 0 if unassigned -> replace all // use PCRE to find const int ovsize=54; // max matches (they say this must be a multiple of 3, no idea why; I'd say 2...) int ov[ovsize]; res.assign(s.c_str(),i); // part of string not searched at all do { int rc = run_pcre(pat.c_str(),s.c_str(),s.size(),i,ov,ovsize,aFuncContextP->getDbgLogger()); if (rc<0) break; // error or no more matches found // found an occurrence // - subsititute matches in replacement string cAppCharP p=reppat.c_str(); cAppCharP q=p; string rep; rep.erase(); while (*p) { if (*p=='\\') { p++; if (*p==0) break; // check for escaped backslash if (*p=='\\') { p++; continue; } // get replacement number if (isdigit(*p)) { // is replacement escape sequence, get index uInt16 mIdx = *(p++)-'0'; // append chars before \x rep.append(q,p-q-2); // append match (if there is one) if (mIdx0); // if c==0, replace all, otherwise as many times as c says res.append(s.c_str()+i); // rest // return result aTermP->setAsString(res); }; // func_Regex_Replace #endif // REGEX_SUPPORT // integer COMPARE(value, value) // - returns 0 if equal, 1 if first > second, -1 if first < second, // SYSYNC_NOT_COMPARABLE if not equal and no ordering known or if field // types do not match. static void func_Compare(TItemField *&aTermP, TScriptContext *aFuncContextP) { // compare first field with second one aTermP->setAsInteger( aFuncContextP->getLocalVar(0)->compareWith(*(aFuncContextP->getLocalVar(1))) ); }; // func_Compare // integer CONTAINS(&ref, value [,bool caseinsensitive]) // - returns 1 if value contained in ref, 0 if not static void func_Contains(TItemField *&aTermP, TScriptContext *aFuncContextP) { // check if second is contained in first bool caseinsensitive = aFuncContextP->getLocalVar(2)->getAsBoolean(); // returns false if not specified aTermP->setAsBoolean( aFuncContextP->getLocalVar(0)->contains(*(aFuncContextP->getLocalVar(1)),caseinsensitive) ); }; // func_Contains // APPEND(&ref, value) // - appends value to ref (ref can be array) static void func_Append(TItemField *&aTermP, TScriptContext *aFuncContextP) { // append second to first aFuncContextP->getLocalVar(0)->append(*(aFuncContextP->getLocalVar(1))); }; // func_Append // string UPPERCASE(string) static void func_UpperCase(TItemField *&aTermP, TScriptContext *aFuncContextP) { string s; TItemField *fldP = aFuncContextP->getLocalVar(0); if (fldP->isAssigned()) { fldP->getAsString(s); StringUpper(s); // save result aTermP->setAsString(s); } else { aTermP->unAssign(); } }; // func_UpperCase // string LOWERCASE(string) static void func_LowerCase(TItemField *&aTermP, TScriptContext *aFuncContextP) { string s; TItemField *fldP = aFuncContextP->getLocalVar(0); if (fldP->isAssigned()) { fldP->getAsString(s); StringLower(s); // save result aTermP->setAsString(s); } else { aTermP->unAssign(); } }; // func_LowerCase // string NORMALIZED(variant value) // get as normalized string (trimmed CR/LF/space at both ends, no special chars for telephone numbers, http:// added for URLs w/o protocol spec) static void func_Normalized(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get field reference TItemField *fldP = aFuncContextP->getLocalVar(0); if (fldP->isAssigned()) { // get normalized version string s; fldP->getAsNormalizedString(s); // save it aTermP->setAsString(s); } else { aTermP->unAssign(); } }; // func_Normalized // bool ISAVAILABLE(variant &fieldvar) // check if field is available (supported by both ends) // - returns EMPTY if availability is not known // - fieldvar must be a field contained in the primary item of the caller, else function returns UNASSIGNED static void func_IsAvailable(TItemField *&aTermP, TScriptContext *aFuncContextP) { if (aFuncContextP->fParentContextP) { // get item to find field in TMultiFieldItem *checkItemP = aFuncContextP->fParentContextP->fTargetItemP; // check if this item's type has actually received availability info if (!checkItemP->knowsRemoteFieldOptions()) { aTermP->assignEmpty(); // nothing known about field availability return; } else { // we have availability info // - get index of field by field pointer (passed by reference) sInt16 fid = checkItemP->getIndexOfField(aFuncContextP->getLocalVar(0)); if (fid!=FID_NOT_SUPPORTED) { // field exists, return availability aTermP->setAsBoolean(checkItemP->isAvailable(fid)); return; } } } // no parent context or field not found aTermP->unAssign(); }; // func_IsAvailable // string ITEMDATATYPE() // returns the type's internal name (like "vcard21") static void func_ItemDataType(TItemField *&aTermP, TScriptContext *aFuncContextP) { if (aFuncContextP->fParentContextP) { // get item of which we want to know the type TMultiFieldItem *checkItemP = aFuncContextP->fParentContextP->fTargetItemP; if (checkItemP) { TMultiFieldItemType *mfitP = static_cast(checkItemP->getItemType()); if (mfitP) { aTermP->setAsString(mfitP->getTypeConfig()->getName()); return; } } } // no type associated or no item in current context aTermP->unAssign(); }; // func_ItemDataType // string ITEMTYPENAME() // returns the type's name (like "text/x-vcard") static void func_ItemTypeName(TItemField *&aTermP, TScriptContext *aFuncContextP) { if (aFuncContextP->fParentContextP) { // get item of which we want to know the type TMultiFieldItem *checkItemP = aFuncContextP->fParentContextP->fTargetItemP; if (checkItemP) { TMultiFieldItemType *mfitP = static_cast(checkItemP->getItemType()); if (mfitP) { aTermP->setAsString(mfitP->getTypeName()); return; } } } // no type associated or no item in current context aTermP->unAssign(); }; // func_ItemTypeName // string ITEMTYPEVERS() // returns the type's version string (like "2.1") static void func_ItemTypeVers(TItemField *&aTermP, TScriptContext *aFuncContextP) { if (aFuncContextP->fParentContextP) { // get item of which we want to know the type TMultiFieldItem *checkItemP = aFuncContextP->fParentContextP->fTargetItemP; if (checkItemP) { TMultiFieldItemType *mfitP = static_cast(checkItemP->getItemType()); if (mfitP) { aTermP->setAsString(mfitP->getTypeVers()); return; } } } // no type associated or no item in current context aTermP->unAssign(); }; // func_ItemTypeVers // void SWAP(untyped1,untyped2) static void func_Swap(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params TItemField *p1 = aFuncContextP->getLocalVar(0); TItemField *p2 = aFuncContextP->getLocalVar(1); TItemField *tempP = newItemField(p1->getType(), aFuncContextP->getSessionZones()); // swap (*tempP)=(*p1); (*p1)=(*p2); (*p2)=(*tempP); }; // func_Swap // string TYPENAME(untyped1) static void func_TypeName(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params TItemFieldTypes ty = aFuncContextP->getLocalVar(0)->getType(); // return type name aTermP->setAsString(ItemFieldTypeNames[ty]); }; // func_TypeName // variant SESSIONVAR(string varname) static void func_SessionVar(TItemField *&aTermP, TScriptContext *aFuncContextP) { TItemField *sessionVarP; TScriptVarDef *sessionVarDefP; string varname; TScriptContext *sessionContextP=NULL; // get name aFuncContextP->getLocalVar(0)->getAsString(varname); // get variable from session if (aFuncContextP->getSession()) sessionContextP=aFuncContextP->getSession()->getSessionScriptContext(); if (sessionContextP) { // get definition sessionVarDefP = sessionContextP->getVarDef( varname.c_str(),varname.size() ); if (sessionVarDefP) { // get variable sessionVarP = sessionContextP->getLocalVar(sessionVarDefP->fIdx); if (sessionVarP) { // create result field of appropriate type aTermP = newItemField(sessionVarP->getType(), aFuncContextP->getSessionZones()); // copy value (*aTermP) = (*sessionVarP); } } } if (!aTermP) { // if no such variable found, return unassigned (but not no-value, which would abort script) aTermP=newItemField(fty_none, aFuncContextP->getSessionZones()); aTermP->unAssign(); // make it (already is...) unassigned } }; // func_SessionVar // SETSESSIONVAR(string varname, value) static void func_SetSessionVar(TItemField *&aTermP, TScriptContext *aFuncContextP) { TItemField *sessionVarP; TScriptVarDef *sessionVarDefP; string varname; TScriptContext *sessionContextP=NULL; // get name aFuncContextP->getLocalVar(0)->getAsString(varname); // get variable from session if (aFuncContextP->getSession()) sessionContextP=aFuncContextP->getSession()->getSessionScriptContext(); if (sessionContextP) { // get definition sessionVarDefP = sessionContextP->getVarDef( varname.c_str(),varname.size() ); if (sessionVarDefP) { // get variable sessionVarP = sessionContextP->getLocalVar(sessionVarDefP->fIdx); if (sessionVarP) { // store new value (*sessionVarP) = (*(aFuncContextP->getLocalVar(1))); } } } }; // func_SetSessionVar // void ABORTSESSION(integer statuscode) static void func_AbortSession(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->AbortSession(aFuncContextP->getLocalVar(0)->getAsInteger(),true); // locally caused } }; // func_AbortSession // SETDEBUGLOG(integer enabled) // set debug log output for this sync session static void func_SetDebugLog(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifdef SYDEBUG TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->getDbgLogger()->setEnabled( aFuncContextP->getLocalVar(0)->getAsBoolean() ); /// @todo: remove this // %%% for now, we also need to set this separate flag sessionP->fSessionDebugLogs= aFuncContextP->getLocalVar(0)->getAsBoolean(); } #endif }; // func_SetDebugLog // SETLOG(integer enabled) // set debug log output for this sync session static void func_SetLog(TItemField *&aTermP, TScriptContext *aFuncContextP) { #ifndef MINIMAL_CODE TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->fLogEnabled= aFuncContextP->getLocalVar(0)->getAsBoolean(); } #endif }; // func_SetLog // SETREADONLY(integer readonly) // set readonly option of this sync session static void func_SetReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP) { TSyncSession *sessionP = aFuncContextP->getSession(); if (sessionP) { sessionP->setReadOnly(aFuncContextP->getLocalVar(0)->getAsBoolean()); } }; // func_SetReadOnly // string CONFIGVAR(string varname) static void func_ConfigVar(TItemField *&aTermP, TScriptContext *aFuncContextP) { string varname,value; // get name aFuncContextP->getLocalVar(0)->getAsString(varname); // get value from syncappbase if (!aFuncContextP->getSyncAppBase()->getConfigVar(varname.c_str(),value)) aTermP->setAsString(value); else aTermP->unAssign(); // not found }; // func_ConfigVar // timestamp RECURRENCE_DATE( // timestamp start, // string rr_freq, integer interval, // integer fmask, integer lmask, // boolean occurrencecount, // integer count // ) static void func_Recurrence_Date(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string rr_freq; TTimestampField *startFldP = static_cast(aFuncContextP->getLocalVar(0)); timecontext_t tctx; // - get start timestamp as is along with current context lineartime_t start = startFldP->getTimestampAs(TCTX_UNKNOWN,&tctx); aFuncContextP->getLocalVar(1)->getAsString(rr_freq); char freq = rr_freq.size()>0 ? rr_freq[0] : ' '; char freqmod = rr_freq.size()>1 ? rr_freq[1] : ' '; sInt16 interval = aFuncContextP->getLocalVar(2)->getAsInteger(); fieldinteger_t fmask = aFuncContextP->getLocalVar(3)->getAsInteger(); fieldinteger_t lmask = aFuncContextP->getLocalVar(4)->getAsInteger(); bool occurrencecount = aFuncContextP->getLocalVar(5)->getAsBoolean(); uInt16 count = aFuncContextP->getLocalVar(6)->getAsInteger(); // now calculate lineartime_t occurrence; if(endDateFromCount( occurrence, start, freq,freqmod, interval, fmask,lmask, count, occurrencecount, aFuncContextP->getDbgLogger() )) { // successful, set timestamp in same context as start timestamp had static_cast(aTermP)->setTimestampAndContext(occurrence,tctx); } else { // unsuccessful aTermP->unAssign(); } } // func_Recurrence_Date // integer RECURRENCE_COUNT( // timestamp start, // string rr_freq, integer interval, // integer fmask, integer lmask, // boolean occurrencecount, // timestamp occurrence // ) static void func_Recurrence_Count(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string rr_freq; timecontext_t tctx; // - start time context is used for rule evaluation lineartime_t start = static_cast(aFuncContextP->getLocalVar(0))->getTimestampAs(TCTX_UNKNOWN,&tctx); aFuncContextP->getLocalVar(1)->getAsString(rr_freq); char freq = rr_freq.size()>0 ? rr_freq[0] : ' '; char freqmod = rr_freq.size()>1 ? rr_freq[1] : ' '; sInt16 interval = aFuncContextP->getLocalVar(2)->getAsInteger(); fieldinteger_t fmask = aFuncContextP->getLocalVar(3)->getAsInteger(); fieldinteger_t lmask = aFuncContextP->getLocalVar(4)->getAsInteger(); bool occurrencecount = aFuncContextP->getLocalVar(5)->getAsBoolean(); // - get end date / recurrence to get count for // Note: this is obtained in the same context as start, even if it might be in another context lineartime_t occurrence = static_cast(aFuncContextP->getLocalVar(6))->getTimestampAs(tctx); // now calculate sInt16 count; if(countFromEndDate( count, occurrencecount, start, freq,freqmod, interval, fmask,lmask, occurrence, aFuncContextP->getDbgLogger() )) { // successful aTermP->setAsInteger(count); } else { // unsuccessful aTermP->unAssign(); } } // func_Recurrence_Count // string MAKE_RRULE( // boolean rrule2, // string rr_freq, integer interval, // integer fmask, integer lmask, // timestamp until // ) static void func_Make_RRULE(TItemField *&aTermP, TScriptContext *aFuncContextP) { // get params string rr_freq; timecontext_t untilcontext; // - start time context is used for rule evaluation bool rruleV2 = aFuncContextP->getLocalVar(0)->getAsBoolean(); aFuncContextP->getLocalVar(1)->getAsString(rr_freq); char freq = rr_freq.size()>0 ? rr_freq[0] : ' '; char freqmod = rr_freq.size()>1 ? rr_freq[1] : ' '; sInt16 interval = aFuncContextP->getLocalVar(2)->getAsInteger(); fieldinteger_t fmask = aFuncContextP->getLocalVar(3)->getAsInteger(); fieldinteger_t lmask = aFuncContextP->getLocalVar(4)->getAsInteger(); lineartime_t until = static_cast(aFuncContextP->getLocalVar(5))->getTimestampAs(TCTX_UNKNOWN,&untilcontext); // convert to RRULE string string rrule; bool ok; if (rruleV2) ok = internalToRRULE2(rrule,freq,freqmod,interval,fmask,lmask,until,untilcontext,aFuncContextP->getDbgLogger()); else ok = internalToRRULE1(rrule,freq,freqmod,interval,fmask,lmask,until,untilcontext,aFuncContextP->getDbgLogger()); if (ok) { // successful aTermP->setAsString(rrule); } else { // unsuccessful aTermP->unAssign(); } } // func_Make_RRULE // boolean PARSE_RRULE( // boolean rruleV2, // string rrule, // timestamp start, // string &rr_freq, integer &interval, // integer &fmask, integer &lmask, // timestamp &until // ) static void func_Parse_RRULE(TItemField *&aTermP, TScriptContext *aFuncContextP) { timecontext_t startcontext; char freq[3] = {' ', ' ', 0}; sInt16 interval; fieldinteger_t fmask,lmask; lineartime_t until; timecontext_t untilcontext; // get params // - start time context is used for rule evaluation bool rruleV2 = aFuncContextP->getLocalVar(0)->getAsBoolean(); string rrule; aFuncContextP->getLocalVar(1)->getAsString(rrule); lineartime_t start = static_cast(aFuncContextP->getLocalVar(2))->getTimestampAs(TCTX_UNKNOWN,&startcontext); bool ok; if (rruleV2) ok = RRULE2toInternal(rrule.c_str(),start,startcontext,freq[0],freq[1],interval,fmask,lmask,until,untilcontext,aFuncContextP->getDbgLogger()); else ok = RRULE1toInternal(rrule.c_str(),start,startcontext,freq[0],freq[1],interval,fmask,lmask,until,untilcontext,aFuncContextP->getDbgLogger()); if (ok) { // successful // - save return values aFuncContextP->getLocalVar(3)->setAsString(freq); aFuncContextP->getLocalVar(4)->setAsInteger(interval); aFuncContextP->getLocalVar(5)->setAsInteger(fmask); aFuncContextP->getLocalVar(6)->setAsInteger(lmask); static_cast(aFuncContextP->getLocalVar(7))->setTimestampAndContext(until, untilcontext); } aTermP->setAsBoolean(ok); } // func_Parse_RRULE // integer PARSEEMAILSPEC(string emailspec, string &name, string &email) static void func_ParseEmailSpec(TItemField *&aTermP, TScriptContext *aFuncContextP) { string spec,name,addr; aFuncContextP->getLocalVar(0)->getAsString(spec); cAppCharP e = parseRFC2822AddrSpec(spec.c_str(),name,addr); aTermP->setAsInteger(e-spec.c_str()); // return number of chars parsed aFuncContextP->getLocalVar(1)->setAsString(name); aFuncContextP->getLocalVar(2)->setAsString(addr); } // func_ParseEmailSpec // string MAKEEMAILSPEC(string name, string email) static void func_MakeEmailSpec(TItemField *&aTermP, TScriptContext *aFuncContextP) { string spec,name,addr; aFuncContextP->getLocalVar(0)->getAsString(name); aFuncContextP->getLocalVar(1)->getAsString(addr); makeRFC2822AddrSpec(name.c_str(),addr.c_str(),spec); aTermP->setAsString(spec); // return RFC2822 email address specification } // func_MakeEmailSpec // helper to create profile handler by name // - returns NULL if no such profile name or profile's field list does not match the item's fieldlist static TProfileHandler *newProfileHandlerByName(cAppCharP aProfileName, TMultiFieldItem *aItemP) { // get type registry to find profile config in TMultiFieldDatatypesConfig *mufcP; GET_CASTED_PTR(mufcP,TMultiFieldDatatypesConfig,aItemP->getItemType()->getTypeConfig()->getParentElement(),"PARSETEXTWITHPROFILE/MAKETEXTWITHPROFILE used with non-multifield item"); // get profile config from type registry TProfileConfig *profileConfig = mufcP->getProfile(aProfileName); if (profileConfig) { // create a profile handler for the item type return profileConfig->newProfileHandler(aItemP->getItemType()); } return NULL; // no such profile } // integer PARSETEXTWITHPROFILE(string textformat, string profileName [, int mode = 0 = default [, string remoteRuleName = "" = other]]) static void func_ParseTextWithProfile(TItemField *&aTermP, TScriptContext *aFuncContextP) { bool ok = false; if (aFuncContextP->fParentContextP) { // get the item to work with TMultiFieldItem *itemP = aFuncContextP->fParentContextP->fTargetItemP; // get a handler by name string s; aFuncContextP->getLocalVar(1)->getAsString(s); TProfileHandler *profileHandlerP = newProfileHandlerByName(s.c_str(), itemP); if (profileHandlerP) { // now we can convert // - set the mode code (none = 0 = default) // TODO? Shouldn't this check whether arg #2 was passed at all? If not, // the code will segfault when trying to call getAsInteger() profileHandlerP->setProfileMode(aFuncContextP->getLocalVar(2)->getAsInteger()); profileHandlerP->setRelatedDatastore(NULL); // no datastore in particular is related #ifndef NO_REMOTE_RULES // - try to find remote rule TItemField *field = aFuncContextP->getLocalVar(3); if (field) { field->getAsString(s); if (!s.empty()) profileHandlerP->setRemoteRule(s); } #endif // - convert aFuncContextP->getLocalVar(0)->getAsString(s); ok = profileHandlerP->parseText(s.c_str(), s.size(), *itemP); // - forget delete profileHandlerP; } } aTermP->setAsBoolean(ok); } // func_ParseTextWithProfile // string MAKETEXTWITHPROFILE(string profileName [, int mode [, string remoteRuleName = "" = other] ]) static void func_MakeTextWithProfile(TItemField *&aTermP, TScriptContext *aFuncContextP) { if (aFuncContextP->fParentContextP) { // get the item to work with TMultiFieldItem *itemP = aFuncContextP->fParentContextP->fTargetItemP; // get a handler by name string s; aFuncContextP->getLocalVar(0)->getAsString(s); TProfileHandler *profileHandlerP = newProfileHandlerByName(s.c_str(), itemP); if (profileHandlerP) { // now we can convert // - set the mode code (none = 0 = default) profileHandlerP->setProfileMode(aFuncContextP->getLocalVar(1)->getAsInteger()); profileHandlerP->setRelatedDatastore(NULL); // no datastore in particular is related #ifndef NO_REMOTE_RULES // - try to find remote rule TItemField *field = aFuncContextP->getLocalVar(2); if (field) { field->getAsString(s); if (!s.empty()) profileHandlerP->setRemoteRule(s); } #endif // - convert, after clearing the string (some generateText() implementations // append instead of overwriting) s = ""; profileHandlerP->generateText(*itemP,s); aTermP->setAsString(s); // return text generated according to profile // - forget delete profileHandlerP; } } } // func_MakeTextWithProfile }; // TBuiltinStdFuncs const uInt8 param_oneTimestamp[] = { VAL(fty_timestamp) }; const uInt8 param_oneInteger[] = { VAL(fty_integer) }; const uInt8 param_oneString[] = { VAL(fty_string) }; const uInt8 param_oneVariant[] = { VAL(fty_none) }; const uInt8 param_oneOptInteger[] = { OPTVAL(fty_integer) }; const uInt8 param_Random[] = { VAL(fty_integer), OPTVAL(fty_integer) }; const uInt8 param_SetTimezone[] = { REF(fty_timestamp), VAL(fty_none) }; const uInt8 param_SetFloating[] = { REF(fty_timestamp) }; const uInt8 param_ConvertToZone[] = { VAL(fty_timestamp), VAL(fty_none), OPTVAL(fty_integer) }; const uInt8 param_ConvertToUserZone[] = { VAL(fty_timestamp), OPTVAL(fty_integer) }; const uInt8 param_Shellexecute[] = { VAL(fty_string), VAL(fty_string), OPTVAL(fty_integer) }; const uInt8 param_substr[] = { VAL(fty_string), VAL(fty_integer), OPTVAL(fty_integer) }; const uInt8 param_size[] = { REF(fty_none) }; const uInt8 param_Normalized[] = { REF(fty_none) }; const uInt8 param_find[] = { VAL(fty_string), VAL(fty_string), OPTVAL(fty_integer) }; const uInt8 param_compare[] = { VAL(fty_none), VAL(fty_none) }; const uInt8 param_contains[] = { REF(fty_none), VAL(fty_none), OPTVAL(fty_integer) }; const uInt8 param_append[] = { REF(fty_none), VAL(fty_none) }; const uInt8 param_swap[] = { REF(fty_none), REF(fty_none) }; const uInt8 param_isAvailable[] = { REF(fty_none) }; const uInt8 param_typename[] = { VAL(fty_none) }; const uInt8 param_SetSessionVar[] = { VAL(fty_string), VAL(fty_none) }; const uInt8 param_SetDebugOptions[] = { VAL(fty_string), VAL(fty_integer) }; const uInt8 param_Recurrence_Date[] = { VAL(fty_timestamp), VAL(fty_string), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer) }; const uInt8 param_Recurrence_Count[] = { VAL(fty_timestamp), VAL(fty_string), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer), VAL(fty_timestamp) }; const uInt8 param_Make_RRULE[] = { VAL(fty_integer), VAL(fty_string), VAL(fty_integer), VAL(fty_integer), VAL(fty_integer), VAL(fty_timestamp) }; const uInt8 param_Parse_RRULE[] = { VAL(fty_integer), VAL(fty_string), VAL(fty_timestamp), REF(fty_string), REF(fty_integer), REF(fty_integer), REF(fty_integer), REF(fty_timestamp) }; const uInt8 param_AlldayCount[] = { VAL(fty_timestamp), VAL(fty_timestamp), OPTVAL(fty_integer), OPTVAL(fty_integer) }; const uInt8 param_MakeAllday[] = { REF(fty_timestamp), REF(fty_timestamp), OPTVAL(fty_integer) }; const uInt8 param_NumFormat[] = { VAL(fty_integer), VAL(fty_integer), OPTVAL(fty_string), OPTVAL(fty_string) }; const uInt8 param_Explode[] = { VAL(fty_string), REFARR(fty_none) }; const uInt8 param_parseEmailSpec[] = { VAL(fty_string), REF(fty_string), REF(fty_string) }; const uInt8 param_makeEmailSpec[] = { VAL(fty_string), VAL(fty_string) }; const uInt8 param_parseTextWithProfile[] = { VAL(fty_string), VAL(fty_string), OPTVAL(fty_integer), OPTVAL(fty_string) }; const uInt8 param_makeTextWithProfile[] = { VAL(fty_string), OPTVAL(fty_integer), OPTVAL(fty_string) }; #ifdef REGEX_SUPPORT const uInt8 param_regexfind[] = { VAL(fty_string), VAL(fty_string), OPTVAL(fty_integer) }; const uInt8 param_regexmatch[] = { VAL(fty_string), VAL(fty_string), VAL(fty_integer), REF(fty_none) }; const uInt8 param_regexreplace[] = { VAL(fty_string), VAL(fty_string), VAL(fty_string), OPTVAL(fty_integer), OPTVAL(fty_integer) }; const uInt8 param_regexsplit[] = { VAL(fty_string), VAL(fty_string), REFARR(fty_none), OPTVAL(fty_integer) }; #endif // builtin function table const TBuiltInFuncDef BuiltInFuncDefs[] = { { "ABS", TBuiltinStdFuncs::func_Abs, fty_integer, 1, param_oneInteger }, { "SIGN", TBuiltinStdFuncs::func_Sign, fty_integer, 1, param_oneInteger }, { "RANDOM", TBuiltinStdFuncs::func_Random, fty_integer, 2, param_Random }, { "NUMFORMAT", TBuiltinStdFuncs::func_NumFormat, fty_string, 4, param_NumFormat }, { "NORMALIZED", TBuiltinStdFuncs::func_Normalized, fty_string, 1, param_Normalized }, { "ISAVAILABLE", TBuiltinStdFuncs::func_IsAvailable, fty_integer, 1, param_isAvailable }, { "ITEMDATATYPE", TBuiltinStdFuncs::func_ItemDataType, fty_string, 0, NULL }, { "ITEMTYPENAME", TBuiltinStdFuncs::func_ItemTypeName, fty_string, 0, NULL }, { "ITEMTYPEVERS", TBuiltinStdFuncs::func_ItemTypeVers, fty_string, 0, NULL }, { "EXPLODE", TBuiltinStdFuncs::func_Explode, fty_string, 2, param_Explode }, { "SUBSTR", TBuiltinStdFuncs::func_Substr, fty_string, 3, param_substr }, { "LENGTH", TBuiltinStdFuncs::func_Length, fty_integer, 1, param_oneString }, { "SIZE", TBuiltinStdFuncs::func_Size, fty_integer, 1, param_size }, { "FIND", TBuiltinStdFuncs::func_Find, fty_integer, 3, param_find }, { "RFIND", TBuiltinStdFuncs::func_RFind, fty_integer, 3, param_find }, #ifdef REGEX_SUPPORT { "REGEX_FIND", TBuiltinStdFuncs::func_Regex_Find, fty_integer, 3, param_regexfind }, { "REGEX_MATCH", TBuiltinStdFuncs::func_Regex_Match, fty_integer, 4, param_regexmatch }, { "REGEX_SPLIT", TBuiltinStdFuncs::func_Regex_Split, fty_integer, 4, param_regexsplit }, { "REGEX_REPLACE", TBuiltinStdFuncs::func_Regex_Replace, fty_string, 5, param_regexreplace }, #endif { "COMPARE", TBuiltinStdFuncs::func_Compare, fty_integer, 2, param_compare }, { "CONTAINS", TBuiltinStdFuncs::func_Contains, fty_integer, 3, param_contains }, { "APPEND", TBuiltinStdFuncs::func_Append, fty_none, 2, param_append }, { "UPPERCASE", TBuiltinStdFuncs::func_UpperCase, fty_string, 1, param_oneString }, { "LOWERCASE", TBuiltinStdFuncs::func_LowerCase, fty_string, 1, param_oneString }, { "SWAP", TBuiltinStdFuncs::func_Swap, fty_none, 2, param_swap }, { "TYPENAME", TBuiltinStdFuncs::func_TypeName, fty_string, 1, param_oneVariant }, { "REMOTERULENAME", TBuiltinStdFuncs::func_Remoterulename, fty_string, 0, NULL }, { "LOCALURI", TBuiltinStdFuncs::func_LocalURI, fty_string, 0, NULL }, { "NOW", TBuiltinStdFuncs::func_Now, fty_timestamp, 0, NULL }, { "SYSTEMNOW", TBuiltinStdFuncs::func_SystemNow, fty_timestamp, 0, NULL }, { "DBNOW", TBuiltinStdFuncs::func_DbNow, fty_timestamp, 0, NULL }, { "ZONEOFFSET", TBuiltinStdFuncs::func_ZoneOffset, fty_integer, 1, param_oneTimestamp }, { "TIMEZONE", TBuiltinStdFuncs::func_Timezone, fty_string, 1, param_oneTimestamp }, { "VTIMEZONE", TBuiltinStdFuncs::func_VTimezone, fty_string, 1, param_oneTimestamp }, { "SETTIMEZONE", TBuiltinStdFuncs::func_SetTimezone, fty_none, 2, param_SetTimezone }, { "SETFLOATING", TBuiltinStdFuncs::func_SetFloating, fty_none, 1, param_SetFloating }, { "CONVERTTOZONE", TBuiltinStdFuncs::func_ConvertToZone, fty_timestamp, 3, param_ConvertToZone }, { "CONVERTTOUSERZONE", TBuiltinStdFuncs::func_ConvertToUserZone, fty_timestamp, 2, param_ConvertToUserZone }, { "USERTIMEZONE", TBuiltinStdFuncs::func_UserTimezone, fty_string, 0, NULL }, { "SETUSERTIMEZONE", TBuiltinStdFuncs::func_SetUserTimezone, fty_none, 1, param_oneVariant }, { "ISDATEONLY", TBuiltinStdFuncs::func_IsDateOnly, fty_integer, 1, param_oneTimestamp }, { "DATEONLY", TBuiltinStdFuncs::func_DateOnly, fty_timestamp, 1, param_oneTimestamp }, { "TIMEONLY", TBuiltinStdFuncs::func_TimeOnly, fty_timestamp, 1, param_oneTimestamp }, { "ISDURATION", TBuiltinStdFuncs::func_IsDuration, fty_integer, 1, param_oneTimestamp }, { "DURATION", TBuiltinStdFuncs::func_Duration, fty_timestamp, 1, param_oneTimestamp }, { "POINTINTIME", TBuiltinStdFuncs::func_PointInTime, fty_timestamp, 1, param_oneTimestamp }, { "ISFLOATING", TBuiltinStdFuncs::func_IsFloating, fty_integer, 1, param_oneTimestamp }, { "WEEKDAY", TBuiltinStdFuncs::func_Weekday, fty_integer, 1, param_oneTimestamp }, { "SECONDS", TBuiltinStdFuncs::func_Seconds, fty_integer, 1, param_oneInteger }, { "MILLISECONDS", TBuiltinStdFuncs::func_Milliseconds, fty_integer, 1, param_oneInteger }, { "SLEEPMS", TBuiltinStdFuncs::func_SleepMS, fty_none, 1, param_oneInteger }, { "TIMEUNITS", TBuiltinStdFuncs::func_Timeunits, fty_integer, 1, param_oneInteger }, { "DAYUNITS", TBuiltinStdFuncs::func_Dayunits, fty_integer, 1, param_oneInteger }, { "MONTHDAYS", TBuiltinStdFuncs::func_MonthDays, fty_integer, 1, param_oneTimestamp }, { "DEBUGMESSAGE", TBuiltinStdFuncs::func_Debugmessage, fty_none, 1, param_oneString }, { "DEBUGSHOWVARS", TBuiltinStdFuncs::func_DebugShowVars, fty_none, 0, NULL }, { "DEBUGSHOWITEM", TBuiltinStdFuncs::func_DebugShowItem, fty_none, 1, param_oneOptInteger }, { "SETDEBUGOPTIONS", TBuiltinStdFuncs::func_SetDebugOptions, fty_none, 2, param_SetDebugOptions }, { "SETDEBUGMASK", TBuiltinStdFuncs::func_SetDebugMask, fty_none, 1, param_oneInteger }, { "SETXMLTRANSLATE", TBuiltinStdFuncs::func_SetXMLTranslate, fty_none, 1, param_oneInteger }, { "SETMSGDUMP", TBuiltinStdFuncs::func_SetMsgDump, fty_none, 1, param_oneInteger }, { "GETDEBUGMASK", TBuiltinStdFuncs::func_GetDebugMask, fty_integer, 0, NULL }, { "REQUESTMAXTIME", TBuiltinStdFuncs::func_RequestMaxTime, fty_none, 1, param_oneInteger }, { "REQUESTMINTIME", TBuiltinStdFuncs::func_RequestMinTime, fty_none, 1, param_oneInteger }, { "SHELLEXECUTE", TBuiltinStdFuncs::func_Shellexecute, fty_integer, 3, param_Shellexecute }, { "SESSIONVAR", TBuiltinStdFuncs::func_SessionVar, fty_none, 1, param_oneString }, { "SETSESSIONVAR", TBuiltinStdFuncs::func_SetSessionVar, fty_none, 2, param_SetSessionVar }, { "ABORTSESSION", TBuiltinStdFuncs::func_AbortSession, fty_none, 1, param_oneInteger }, { "SETDEBUGLOG", TBuiltinStdFuncs::func_SetDebugLog, fty_none, 1, param_oneInteger }, { "SETLOG", TBuiltinStdFuncs::func_SetLog, fty_none, 1, param_oneInteger }, { "SETREADONLY", TBuiltinStdFuncs::func_SetReadOnly, fty_none, 1, param_oneInteger }, { "CONFIGVAR", TBuiltinStdFuncs::func_ConfigVar, fty_string, 1, param_oneString }, { "TREATASLOCALTIME", TBuiltinStdFuncs::func_SetTreatAsLocaltime, fty_none, 1, param_oneInteger }, { "TREATASUTC", TBuiltinStdFuncs::func_SetTreatAsUTC, fty_none, 1, param_oneInteger }, { "UPDATECLIENTINSLOWSYNC", TBuiltinStdFuncs::func_SetUpdateClientInSlowSync, fty_none, 1, param_oneInteger }, { "UPDATESERVERINSLOWSYNC", TBuiltinStdFuncs::func_SetUpdateServerInSlowSync, fty_none, 1, param_oneInteger }, { "SHOWCTCAPPROPERTIES", TBuiltinStdFuncs::func_ShowCTCapProps, fty_none, 1, param_oneInteger }, { "SHOWTYPESIZEINCTCAP10", TBuiltinStdFuncs::func_ShowTypeSizeInCTCap10, fty_none, 1, param_oneInteger }, { "ENUMDEFAULTPROPPARAMS", TBuiltinStdFuncs::func_EnumDefaultPropParams, fty_none, 1, param_oneInteger }, { "RECURRENCE_DATE", TBuiltinStdFuncs::func_Recurrence_Date, fty_timestamp, 7, param_Recurrence_Date }, { "RECURRENCE_COUNT", TBuiltinStdFuncs::func_Recurrence_Count, fty_integer, 7, param_Recurrence_Count }, { "MAKE_RRULE", TBuiltinStdFuncs::func_Make_RRULE, fty_string, 6, param_Make_RRULE }, { "PARSE_RRULE", TBuiltinStdFuncs::func_Parse_RRULE, fty_integer, 8, param_Parse_RRULE }, { "PARSEEMAILSPEC", TBuiltinStdFuncs::func_ParseEmailSpec, fty_integer, 3, param_parseEmailSpec }, { "MAKEEMAILSPEC", TBuiltinStdFuncs::func_MakeEmailSpec, fty_string, 2, param_makeEmailSpec }, { "PARSETEXTWITHPROFILE", TBuiltinStdFuncs::func_ParseTextWithProfile, fty_integer, 4, param_parseTextWithProfile }, { "MAKETEXTWITHPROFILE", TBuiltinStdFuncs::func_MakeTextWithProfile, fty_string, 3, param_makeTextWithProfile }, { "SYNCMLVERS", TBuiltinStdFuncs::func_SyncMLVers, fty_string, 0, NULL }, { "ALLDAYCOUNT", TBuiltinStdFuncs::func_AlldayCount, fty_integer, 4, param_AlldayCount }, { "MAKEALLDAY", TBuiltinStdFuncs::func_MakeAllday, fty_integer, 3, param_MakeAllday }, }; const TFuncTable BuiltInFuncTable = { sizeof(BuiltInFuncDefs) / sizeof(TBuiltInFuncDef), // size of table BuiltInFuncDefs, // table pointer NULL // no chain func }; /* * Implementation of TScriptContext */ /* public TScriptContext members */ TScriptContext::TScriptContext(TSyncAppBase *aAppBaseP, TSyncSession *aSessionP) : fAppBaseP(aAppBaseP), // save syncappbase link, must always exist fSessionP(aSessionP), // save session, can be NULL fNumVars(0), // number of instantiated vars fNumParams(0), fFieldsP(NULL), // no field contents yet scriptname(NULL), // no script name known yet linesource(NULL), executing(false), debugon(false), fTargetItemP(NULL), fReferenceItemP(NULL), fParentContextP(NULL) { fVarDefs.clear(); } // TScriptContext::TScriptContext TScriptContext::~TScriptContext() { clear(); } // TScriptContext::~TScriptContext // Reset context (clear all variables and definitions) void TScriptContext::clear(void) { // clear actual fields clearFields(); // clear definitions TVarDefs::iterator pos; for (pos=fVarDefs.begin(); pos!=fVarDefs.end(); pos++) { if (*pos) delete (*pos); } fVarDefs.clear(); } // TScriptContext::clear GZones *TScriptContext::getSessionZones(void) { return fSessionP ? fSessionP->getSessionZones() : NULL; } // TScriptContext::getSessionZones #ifdef SYDEBUG // get debug logger TDebugLogger *TScriptContext::getDbgLogger(void) { // use session logger if linked to a session if (fSessionP) return fSessionP->getDbgLogger(); // otherwise, use global logger return fAppBaseP ? fAppBaseP->getDbgLogger() : NULL; } // TScriptContext::getDbgLogger uInt32 TScriptContext::getDbgMask(void) { // use session logger if linked to a session if (fSessionP) return fSessionP->getDbgMask(); // otherwise, use global logger return fAppBaseP ? fAppBaseP->getDbgMask() : 0; } // TScriptContext::getDbgMask #endif // Reset context (clear all variables and definitions) void TScriptContext::clearFields(void) { if (fFieldsP) { // clear local vars (fields), but not references TVarDefs::iterator pos; for (pos=fVarDefs.begin(); pos!=fVarDefs.end(); pos++) { sInt16 i=(*pos)->fIdx; if (i>=fNumVars) break; // all instantiated vars done, stop even if more might be defined if (fFieldsP[i]) { if (!(*pos)->fIsRef) delete fFieldsP[i]; // delete field object (but only if not reference) fFieldsP[i]=NULL; } } // clear array of field pointers delete [] fFieldsP; fFieldsP=NULL; fNumVars=0; } } // TScriptContext::clearFields // check for identifier static bool isidentchar(appChar c) { return isalnum(c) || c=='_'; } // isidentchar // adds source line (including source text, if selected) to token stream static void addSourceLine(uInt16 aLine, const char *aText, string &aScript, bool aIncludeSource, uInt16 &aLastIncludedLine) { aScript+=TK_SOURCELINE; // token #ifdef SYDEBUG sInt16 linelen=0; // line #0 is script/function name and must be included anyway if (aIncludeSource && (aLine>aLastIncludedLine || aLine==0)) { const char *tt=aText; while (*tt && *tt!=0x0D && *tt!=0x0A) ++tt; linelen=tt-aText+1; // room for the terminator if (linelen>250) linelen=250; // limit size (2 chars needed for number, 3 reserved) // Note: even empty line will have at least one char (the terminator) aScript+=(appChar)(2+linelen); // length of additional data } else { // Note: not-included line will have no extra data (not even the terminator), so // it can be distinguished from empty line aIncludeSource=false; aScript+=(appChar)(2); // length of additional data } #else aScript+=(appChar)(2); // length of additional data #endif // add source line number aScript+=(appChar)(aLine>>8); // source line aScript+=(appChar)(aLine & 0xFF); // add source itself #ifdef SYDEBUG if (aIncludeSource) { aScript.append(aText,linelen-1); aScript+=(appChar)(0); // add terminator aLastIncludedLine = aLine; } #endif } // addSourceLine // Tokenize input string void TScriptContext::Tokenize(TSyncAppBase *aAppBaseP, cAppCharP aScriptName, sInt32 aLine, cAppCharP aScriptText, string &aTScript, const TFuncTable *aContextFuncs, bool aFuncHeader, bool aNoDeclarations, TMacroArgsArray *aMacroArgsP) { string itm; string macro; appChar c,c2; appChar token,lasttoken=0; cAppCharP text = aScriptText; cAppCharP p; uInt16 line=aLine; // script starts here sInt16 enu; // clear output aTScript.erase(); // debug info if script debugging is enabled in configuration bool includesource = #ifdef SYDEBUG aAppBaseP->getRootConfig()->fDebugConfig.fDebug & DBG_SCRIPTS; #else false; #endif uInt16 lastincludedline = 0; if (*text) { #ifdef SYDEBUG // insert script name as line #0 in all but completely empty scripts (or functions) if (aScriptName) addSourceLine(0,aScriptName,aTScript,true,lastincludedline); #endif // insert source line identification token for start of script for all but completely empty scripts addSourceLine(line,text,aTScript,includesource,lastincludedline); } // marco argument expansion // Note: $n (with n=1,2,3...9) is expanded before any other processing. To insert e.g. $2 literally, use $$2. // $n macros that can't be expanded will be left in the text AS IS string itext; if (aMacroArgsP) { itext = text; // we need a string to substitute macro args in size_t i = 0; while (i=0 && argidxsize()) { // found macro argument, replace in input string itext.replace(i-1, 2, (*aMacroArgsP)[argidx]); // no nested macro argument eval, just advance pointer behind replacement text i += (*aMacroArgsP)[argidx].size()-1; // check next char continue; } } } } // now use expanded version of text for tokenizing text = itext.c_str(); } // actual tokenisation SYSYNC_TRY { // process text while (*text) { // get next token token=0; // none yet // - skip spaces (but not line ends) while (*text==' ' || *text=='\t') text++; // - dispatch different types of tokens c=*text++; if (c==0) break; // done with script // - check input now if (isdigit(c)) { // numeric literal p=text-1; // beginning of literal while (isalnum(*text)) text++; // - p=start, text=past end of numeric literal itm.assign(p,text-p); // code literal into token string aTScript+=TK_NUMERIC_LITERAL; // token aTScript+=(appChar)(itm.size()); // length of additional data aTScript.append(itm); } else if (c=='"') { // string literal, parse itm.erase(); while ((c=*text)) { text++; if (c=='"') { break; } // done if (c=='\\') { // escape char c2=*text++; if (!c2) break; // escape without anything following -> done else if (c2=='n') c='\n'; // internal line end else if (c2=='t') c='\t'; // internal tab else if (c2=='x') { // hex char spec uInt16 sh; text+=HexStrToUShort(text,sh,2); c=(appChar)sh; } else c=c2; // simply use char following the escape char } // now add if (c) itm+=c; } // code literal into token string aTScript+=TK_STRING_LITERAL; // token aTScript+=(appChar)(itm.size()); // length of additional data aTScript.append(itm); } else if (isalpha(c)) { // identifier // - get identifier p=text-1; while (isidentchar(*text)) text++; // - now p=start of identified, text=end uInt16 il=text-p; // - skip whitespace following identifier while (*text==' ' || *text=='\t') text++; // - check language keywords if (strucmp(p,"IF",il)==0) token=TK_IF; else if (strucmp(p,"ELSE",il)==0) token=TK_ELSE; else if (strucmp(p,"LOOP",il)==0) token=TK_LOOP; else if (strucmp(p,"WHILE",il)==0) token=TK_WHILE; else if (strucmp(p,"BREAK",il)==0) token=TK_BREAK; else if (strucmp(p,"CONTINUE",il)==0) token=TK_CONTINUE; else if (strucmp(p,"RETURN",il)==0) token=TK_RETURN; // - check special constants else if (strucmp(p,"EMPTY",il)==0) token=TK_EMPTY; else if (strucmp(p,"UNASSIGNED",il)==0) token=TK_UNASSIGNED; else if (strucmp(p,"TRUE",il)==0) token=TK_TRUE; else if (strucmp(p,"FALSE",il)==0) token=TK_FALSE; // - check types else if (StrToEnum(ItemFieldTypeNames,numFieldTypes,enu,p,il)) { // check if declaration and if allowed if (aNoDeclarations && lasttoken!=TK_OPEN_PARANTHESIS) SYSYNC_THROW(TTokenizeException(aScriptName, "no local variable declarations allowed in this script",aScriptText,text-aScriptText,line)); // code type into token aTScript+=TK_TYPEDEF; // token aTScript+=1; // length of additional data aTScript+=enu; // type } // - check function calls if in body else if (*text=='(' && !aFuncHeader) { // identifier followed by ( must be function call (if not in header of function itself) // - check for built-in function sInt16 k=0; while (k=0) { // no built-in base function, could be context-related function k=0; // function index (may span several chain links) // - start with passed functable TFuncTable *functableP = (TFuncTable *)aContextFuncs; while(functableP) { // get the function table properties sInt16 fidx,numfuncs = functableP->numFuncs; const TBuiltInFuncDef *funcs = functableP->funcDefs; // process this func table for (fidx=0; fidxchainFunc) { // obtain next function table (caller context pointer is irrelevant here) k+=numfuncs; // index for next chained table starts at end of indexes for current table void *ctx=NULL; functableP=(TFuncTable *)functableP->chainFunc(ctx); } else { functableP=NULL; // end chaining loop } } // while if (k>=0) { // no built-in nor context-built-in found, assume user-defined aTScript+=TK_USERFUNCTION; // token aTScript+=(appChar)(il+1); // length of additional data aTScript+=VARIDX_UNDEFINED; // no function index defined yet aTScript.append(p,il); // identifier name } } } // - check object qualifiers else if (*text=='.') { // must be qualifier uInt8 objidx=OBJ_AUTO; if (strucmp(p,"LOCAL",il)==0) objidx=OBJ_LOCAL; else if (strucmp(p,"OLD",il)==0) objidx=OBJ_REFERENCE; else if (strucmp(p,"LOOSING",il)==0) objidx=OBJ_REFERENCE; else if (strucmp(p,"REFERENCE",il)==0) objidx=OBJ_REFERENCE; else if (strucmp(p,"NEW",il)==0) objidx=OBJ_TARGET; else if (strucmp(p,"WINNING",il)==0) objidx=OBJ_TARGET; else if (strucmp(p,"TARGET",il)==0) objidx=OBJ_TARGET; else SYSYNC_THROW(TTokenizeException(aScriptName,"unknown object name",aScriptText,text-aScriptText,line)); text++; // skip object qualifier aTScript+=TK_OBJECT; // token aTScript+=1; // length of additional data aTScript+=objidx; // object index } else { // generic identifier, must be some kind of variable reference aTScript+=TK_IDENTIFIER; // token aTScript+=(appChar)(il+1); // length of additional data aTScript+=VARIDX_UNDEFINED; // no variable index defined yet aTScript.append(p,il); // identifier name } } // if identifier else { // get next char for double-char tokens c2=*text; // check special single chars switch (c) { // - macro case '$': { // get macro name p=text; while (isidentchar(*text)) text++; if (text==p) SYSYNC_THROW(TTokenizeException(aScriptName,"missing macro name after $",aScriptText,text-aScriptText,line)); itm.assign(p,text-p); // see if we have such a macro TScriptConfig *cfgP = aAppBaseP->getRootConfig()->fScriptConfigP; TStringToStringMap::iterator pos = cfgP->fScriptMacros.find(itm); if (pos==cfgP->fScriptMacros.end()) SYSYNC_THROW(TTokenizeException(aScriptName,"unknown macro",aScriptText,p-1-aScriptText,line)); TMacroArgsArray macroArgs; // check for macro arguments if (*text=='(') { // Macro has Arguments text++; string arg; // Note: closing brackets and commas must be escaped when used as part of a macro argument while (*text) { c=*text++; if (c==',' || c==')') { // end of argument macroArgs.push_back(arg); // save it in array arg.erase(); if (c==')') break; // end of macro continue; // skip comma, next arg } else if (c=='\\') { if (*text==0) break; // end of string // escaped - use next char w/o testing for , or ) c=*text++; } // add to argument string arg += c; } } // continue tokenizing with macro text TScriptContext::Tokenize( aAppBaseP, itm.c_str(), // pass macro name as "script" name 1, // line number relative to beginning of macro (*pos).second.c_str(), // use macro text as script text macro, // produce tokenized macro here aContextFuncs, // same context false, // not in function header aNoDeclarations, // same condition ¯oArgs // macro arguments ); // append tokenized macro to current script aTScript+=macro; // continue with normal text break; } // - grouping case '(': token=TK_OPEN_PARANTHESIS; break; // open subexpression/argument paranthesis case ')': token=TK_CLOSE_PARANTHESIS; break; // close subexpression/argument paranthesis case ',': token=TK_LIST_SEPARATOR; break; // comma for separating arguments case '{': token=TK_BEGIN_BLOCK; aFuncHeader=false; break; // begin block (and start of function body) case '}': token=TK_END_BLOCK; break; // end block case ';': token=TK_END_STATEMENT; break; // end statement case '[': token=TK_OPEN_ARRAY; break; // open array paranthesis case ']': token=TK_CLOSE_ARRAY; break; // close array paranthesis // line ends case 0x0D: if (c2==0x0A) text++; // skip LF of CRLF sequence as well to make sure it is not counted twice // otherwise treat like LF case 0x0A: // new line begins : insert source line identification token (and source of next line, if any) line++; addSourceLine(line,text,aTScript,includesource,lastincludedline); break; // possible multi-char tokens case '/': if (c2=='/') { text++; // end-of-line comment, skip it do { c=*text; if (c==0 || c==0x0D || c==0x0A) break; text++; } while(true); } else if (c2=='*') { // C-style comment, skip until next '*/' text++; do { c=*text; if (c==0) break; text++; // next if (c=='*' && *text=='/') { // end of comment text++; // skip / break; // end of comment } else if (c==0x0D || c==0x0A) { if (*text==0x0A) text++; // skip LF of CRLF sequence as well to make sure it is not counted twice // new line begins : insert source line identification token (and source of next line, if any) line++; addSourceLine(line,text,aTScript,includesource,lastincludedline); } } while(true); } else token=TK_DIVIDE; // simple division break; case '*': token=TK_MULTIPLY; break; // multiply case '%': token=TK_MODULUS; break; // modulus case '+': token=TK_PLUS; break; // add case '-': token=TK_MINUS; break; // subtract/unary minus case '^': token=TK_BITWISEXOR; break; // bitwise XOR case '~': token=TK_BITWISENOT; break; // bitwise not (one's complement) case '!': if (c2=='=') { token=TK_NOTEQUAL; text++; } // != else token=TK_LOGICALNOT; // ! break; case '=': if (c2=='=') { token=TK_EQUAL; text++; } // == else token=TK_ASSIGN; // = break; case '>': if (c2=='=') { token=TK_GREATEREQUAL; text++; } // >= else if (c2=='>') { token=TK_SHIFTRIGHT; text++; } // >> else token=TK_GREATERTHAN; // > break; case '<': if (c2=='=') { token=TK_LESSEQUAL; text++; } // <= else if (c2=='<') { token=TK_SHIFTLEFT; text++; } // << else if (c2=='>') { token=TK_NOTEQUAL; text++; } // <> else token=TK_LESSTHAN; // < break; case '&': if (c2=='&') { token=TK_LOGICALAND; text++; } // && else token=TK_BITWISEAND; // & break; case '|': if (c2=='|') { token=TK_LOGICALOR; text++; } // || else token=TK_BITWISEOR; // | break; default: SYSYNC_THROW(TTokenizeException(aScriptName,"Syntax Error",aScriptText,text-aScriptText,line)); } } // add token if simple token found if (token) aTScript+=token; lasttoken=token; // save for differentiating casts from declarations etc. } // while more script text } SYSYNC_CATCH (...) // make sure that script with errors is not stored aTScript.erase(); SYSYNC_RETHROW; SYSYNC_ENDCATCH } // TScriptContext::Tokenize // tokenize and resolve user-defined function void TScriptContext::TokenizeAndResolveFunction(TSyncAppBase *aAppBaseP, sInt32 aLine, cAppCharP aScriptText, TUserScriptFunction &aFuncDef) { TScriptContext *resolvecontextP=NULL; Tokenize(aAppBaseP, NULL, aLine,aScriptText,aFuncDef.fFuncDef,NULL,true); // parse as function SYSYNC_TRY { // resolve identifiers resolvecontextP=new TScriptContext(aAppBaseP,NULL); resolvecontextP->ResolveIdentifiers( aFuncDef.fFuncDef, NULL, // no fields false, // not rebuild &aFuncDef.fFuncName // store name here ); delete resolvecontextP; } SYSYNC_CATCH (exception &e) delete resolvecontextP; SYSYNC_RETHROW; SYSYNC_ENDCATCH } // TScriptContext::TokenizeAndResolveFunction // resolve identifiers in a script, if there is a context passed at all void TScriptContext::resolveScript(TSyncAppBase *aAppBaseP, string &aTScript,TScriptContext *&aCtxP, TFieldListConfig *aFieldListConfigP) { if (aTScript.empty()) return; // no resolving needed if (!aCtxP) { // we need a context, create one aCtxP = new TScriptContext(aAppBaseP, NULL); } aCtxP->ResolveIdentifiers( aTScript, aFieldListConfigP, false ); } // TScriptContext::ResolveScript // link a script into a context with already instantiated variables. // This is for "late bound" scripts (such as rulescript) that cannot be bound at config // but are determined only later, when their context is already instantiated. void TScriptContext::linkIntoContext(string &aTScript,TScriptContext *aCtxP, TSyncSession *aSessionP) { if (aTScript.empty() || !aCtxP) return; // no resolving needed (no script or no context) // resolve identfiers (no new declarations possible) aCtxP->ResolveIdentifiers( aTScript, NULL, // no field list false, // do not rebuild NULL, // no function name false // no declarations allowed ); } // TScriptContext::linkIntoContext // rebuild a script context for a script, if the script is not empty // - Script must already be resolved with ResolveIdentifiers // - If context already exists, adds new locals to existing ones // - If aBuildVars is set, buildVars() will be called after rebuilding variable definitions void TScriptContext::rebuildContext(TSyncAppBase *aAppBaseP, string &aTScript,TScriptContext *&aCtxP, TSyncSession *aSessionP, bool aBuildVars) { // Optimization: Nop if script is empty and NOT build var requested for already existing context (=vars from other scripts!) if (aTScript.empty() && !(aBuildVars && aCtxP)) return; // Create context if there isn't one yet if (!aCtxP) { // we need a context, create one aCtxP = new TScriptContext(aAppBaseP,aSessionP); } SYSYNC_TRY { aCtxP->ResolveIdentifiers( aTScript, NULL, true ); if (aBuildVars) { // call this one, too buildVars(aCtxP); } } SYSYNC_CATCH (TScriptErrorException &e) // show error in log, but otherwise ignore it POBJDEBUGPRINTFX(aSessionP,DBG_ERROR,("Failed rebuilding script context: %s",e.what())); SYSYNC_ENDCATCH SYSYNC_CATCH (...) SYSYNC_ENDCATCH } // TScriptContext::rebuildContext // Builds the local variables according to definitions (clears existing vars first) void TScriptContext::buildVars(TScriptContext *&aCtxP) { if (aCtxP) { // instantiate the variables aCtxP->PrepareLocals(); } } // TScriptContext::buildVars // init parsing vars void TScriptContext::initParse(const string &aTScript, bool aExecuting) { executing=aExecuting; bp=(cUInt8P)aTScript.c_str(); // start of script ep=bp+aTScript.size(); // end of script p=bp; // cursor, start at beginning np=NULL; // no next token yet linesource=NULL; // no source yet scriptname=NULL; // no name yet inComment=false; // not in comment yet // try to get start line if (ep>bp && *p!=TK_SOURCELINE) SYSYNC_THROW(TScriptErrorException(DEBUGTEXT("Script does not start with TK_SOURCELINE","scri2"),line)); do { // get script name or first source code line line = (((uInt8)*(p+2))<<8) + (uInt8)(*(p+3)); nextline=line; // assume same #ifdef SYDEBUG sInt16 n=(uInt8)*(p+1)-2; if (n>0) { linesource=(const char *)(p+4); // source for this line starts here if (line==0) { // this is the script name scriptname=linesource; // remember linesource=NULL; } } p+=2+2+n; #else p+=2+2; // script starts here #endif if (line==0) { // we have the name continue; // get first line now } break; } while(true); } // TScriptContext::initParse #ifdef SYDEBUG // colorize source line (comments only so far) static void colorizeSourceLine(cAppCharP aSource, string &aColorSource, bool &aComment, bool skipping) { appChar c; bool start=true; bool incomment=aComment; bool lineendcomment=false; aColorSource.erase(); while ((c=*aSource++)) { // check for comment start if (!aComment && !skipping) { // not in comment if (c=='/') { if (*aSource=='*') { aComment=true; } else if (*aSource=='/') { aComment=true; lineendcomment=true; } } } // switch to new color if changes at this point if (start || incomment!=aComment) { aColorSource += "&html;"; if (!start) aColorSource += ""; // not start aColorSource += "&html;"; incomment = aComment; } // output current char if (c==' ') aColorSource += "&sp;"; // will convert to   in HTML else aColorSource += c; // check for end of comment after this char if (aComment && !lineendcomment && !skipping) { // in C-style comment if (c=='/' && !start && *(aSource-2)=='*') { // this was end of C-comment aComment=false; } } // not start any more start=false; } // end of color aColorSource += "&html;&html;"; // end of line terminates //-style comment if (lineendcomment) aComment=false; } // colorizeSourceLine #endif // get token at p if not end of script, updates np to point to next token uInt8 TScriptContext::gettoken(void) { uInt8 tk; DBGSTRINGDEF(s); if (np) p=np; // advance to next token line=nextline; // advance to next line if (p>=ep) return 0; // end of script do { // get token and search next tk=*p; // get token if (tk>TK_MAX_MULTIBYTE) np=p+1; // single byte token, next is next char else np=p+2+*(p+1); // use length to find next token // show current line with source if we have it #ifdef SYDEBUG // delay display of line when processing a end block statement to make sure // end-skipping decision is made BEFORE showing the line if (SCRIPTDBGTEST && linesource && executing && tk!=TK_END_STATEMENT && tk!=TK_END_BLOCK) { bool sk = skipping>0; colorizeSourceLine(linesource,s,inComment,sk); SCRIPTDBGMSG(("Line %4hd: %s",line,s.c_str())); linesource=NULL; // prevent showing same line twice } #endif // check if it is line token, which would be processed invisibly if (tk==TK_SOURCELINE) { // get source code line number line = (((uInt8)*(p+2))<<8) + (uInt8)(*(p+3)); // get source code itself, if present #ifdef SYDEBUG sInt16 n=(uInt8)*(p+1)-2; if (n>0) linesource=(const char *)(p+4); // source for this line starts here #endif // go to next token p=np; continue; // fetch next } nextline=line; // by default, assume next token is on same line do { // check if next is line token, if yes, skip it as well if (*np==TK_SOURCELINE) { // get next token's source code line #ifdef SYDEBUG uInt16 showline = nextline; // current line is next that must be shown #endif nextline = (((uInt8)*(np+2))<<8) + (uInt8)(*(np+3)); #ifdef SYDEBUG // - show last line if we'll skip another line if (linesource && executing) { colorizeSourceLine(linesource,s,inComment,skipping>0); SCRIPTDBGMSG(("Line %4hd: %s",showline,s.c_str())); linesource=NULL; // prevent showing same line twice } // - get next line's number and text sInt16 n=(uInt8)*(np+1)-2; if (n>0) linesource=(const char *)(np+4); // source for this line starts here np=np+2+2+n; #else np=np+2+2; // skip token #endif continue; // test again } break; } while(true); break; } while(true); return tk; } // TScriptContext::gettoken // re-use last token fetched with gettoken() void TScriptContext::reusetoken(void) { // set pointer such that next gettoken will fetch the same token again np=p; nextline=line; } // TScriptContext::reusetoken // Resolve local variable declarations, references and field references // Note: does not clear the context, so multiple scripts can // share the same context // Note: modifies the aTScript passed (inserts identifier IDs) void TScriptContext::ResolveIdentifiers(string &aTScript,TFieldListConfig *aFieldListConfigP, bool aRebuild, string *aFuncNameP, bool aNoNewLocals) { uInt8 tk; // current token TItemFieldTypes ty=fty_none; bool deftype=false; bool refdecl=false; bool funcparams=false; string ident; sInt16 objidx; // init parsing initParse(aTScript); objidx=OBJ_AUTO; while ((tk=gettoken())) { // check declaration syntax if (deftype && tk!=TK_IDENTIFIER) SYSYNC_THROW(TScriptErrorException("Bad declaration",line)); if (!funcparams && tk==TK_OPEN_PARANTHESIS) { // could be typecast if (*np==TK_TYPEDEF) { // is a typecase gettoken(); // swallow type identifier // next must be closing paranthesis if (gettoken()!=TK_CLOSE_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("Invalid typecast",line)); } } // process token else if (tk==TK_TYPEDEF) { // type definition (for variable declaration or function definition) ty = (TItemFieldTypes)(*(p+2)); deftype=true; // we are in type definition mode now // check if we are declaring a variable reference if (*np==TK_BITWISEAND) { gettoken(); refdecl=true; // defining a reference if (!funcparams) SYSYNC_THROW(TScriptErrorException("Field reference declaration allowed for function parameters",line)); } } else if (tk==TK_OBJECT) { objidx=*(p+2); continue; // avoid resetting to OBJ_AUTO at end of loop } else if (tk==TK_CLOSE_PARANTHESIS) { if (funcparams) { // end of function parameters fNumParams=fVarDefs.size(); // locals declared up to here are parameters funcparams=false; // done with parameters } } else if (tk==TK_IDENTIFIER) { // get identifier ident.assign((cAppCharP)(p+3),(size_t)(*(p+1)-1)); // check for function definition if (!funcparams && *np==TK_OPEN_PARANTHESIS) { // this is a function declaration if (!aRebuild) { // - check if allowed if (!aFuncNameP) SYSYNC_THROW(TScriptErrorException("cannot declare function here",line)); // - save name of function aFuncNameP->assign(ident); } // - determine return type if (deftype) fFuncType=ty; // save function return type else fFuncType=fty_none; // void function // - switch to function param parsing gettoken(); // get opening paranthesis funcparams=true; deftype=false; } else if (deftype) { // defining new local variable or parameter if (objidx!=OBJ_AUTO && objidx!=OBJ_LOCAL) SYSYNC_THROW(TScriptErrorException("cannot declare non-local variable '%s'",line,ident.c_str())); bool arr=false; // define new identifier(s) cUInt8P idp=p; // remember identifier token pointer // - check if this is an array definition if (*np==TK_OPEN_ARRAY) { arr=true; gettoken(); if (*np!=TK_CLOSE_ARRAY) SYSYNC_THROW(TScriptErrorException("Invalid array declaration for '%s'",line,ident.c_str())); #ifndef ARRAYFIELD_SUPPORT SYSYNC_THROW(TScriptErrorException("Arrays not available in this version",line)); #endif gettoken(); // swallow closing bracket } // - check if variable already defined TScriptVarDef *vardefP=NULL; #if SYDEBUG>1 // always get it by name and compare index vardefP = getVarDef(ident.c_str(),ident.size()); #else if (!aRebuild || (sInt8)(*(idp+2))==VARIDX_UNDEFINED) // first time build or index not yet set for another reason: get by name vardefP = getVarDef(ident.c_str(),ident.size()); else // on rebuild, get definition by already known index vardefP = getVarDef(-((sInt8)(*(idp+2))+1)); #endif #ifndef RELEASE_VERSION DEBUGPRINTFX(DBG_SCRIPTS+DBG_EXOTIC,( "ident=%s, vardefP=0x%lX, vardefP->fIdx=%d, stored idx=%d (=vardef idx %d)", ident.c_str(), (long)vardefP, vardefP ? (int)vardefP->fIdx : VARIDX_UNDEFINED, (int)((sInt8)(*(idp+2))), (int)(-((sInt8)(*(idp+2))+1)) )); #endif // check for match if (vardefP) { // check if type matching if (ty!=vardefP->fVarType || arr!=vardefP->fIsArray || refdecl!=vardefP->fIsRef) SYSYNC_THROW(TScriptErrorException("Redefined '%s' to different type",line,ident.c_str())); } else { // not existing yet if (aNoNewLocals) { // Note: aNoNewLocals is useful when resolving a script into a context // with already existing variables (such as RuleScript). We cannot // add new variables once the VarDefs have been instantiated! SYSYNC_THROW(TScriptErrorException("Cannot declare variables in this script",line)); } else { // create new variable definition vardefP = new TScriptVarDef(ident.c_str(),fVarDefs.size(),ty,arr,refdecl,false); fVarDefs.push_back(vardefP); #ifndef RELEASE_VERSION DEBUGPRINTFX(DBG_SCRIPTS+DBG_EXOTIC,( "created new vardef, ident=%s, type=%s, vardefP=0x%lX, vardefs.size()=%ld", ident.c_str(), ItemFieldTypeNames[ty], (long)vardefP, (long)fVarDefs.size() )); #endif } } if (!aRebuild) { // creating defs for the first time, set index aTScript[idp-bp+2]= -(sInt8)(vardefP->fIdx)-1; } #if SYDEBUG>1 else { // check existing index against new one if ((sInt8)(*(idp+2))!=-(sInt8)(vardefP->fIdx)-1) SYSYNC_THROW(TScriptErrorException(DEBUGTEXT("Rebuilding defs gives different index","scri1"),line)); } #endif // - check what follows deftype=false; // default to nothing refdecl=false; if (funcparams) { if (*np==TK_LIST_SEPARATOR) gettoken(); // ok, next param } else { if (*np==TK_LIST_SEPARATOR) { deftype=true; // continue with more definitions of same type gettoken(); // consume separator } else if (*np!=TK_END_STATEMENT) SYSYNC_THROW(TScriptErrorException("Missing ';' after declaration of '%s'",line,ident.c_str())); } } else if (!aRebuild) { // refer to identifier sInt16 i=getIdentifierIndex(objidx,aFieldListConfigP,ident.c_str(),ident.size()); if (i==VARIDX_UNDEFINED) SYSYNC_THROW(TScriptErrorException("Undefined identifier '%s'",line,ident.c_str())); // save field/var index in script aTScript[p-bp+2]= (sInt8)i; } } else { // Other non-declaration tokens // - if this is function param def, no other tokens are allowed if (funcparams) { SYSYNC_THROW(TScriptErrorException("Invalid function parameter declaration",line)); } else { // non-declaration stuff if (tk==TK_USERFUNCTION && !aRebuild) { // resolve function ident.assign((cAppCharP)(p+3),(size_t)(*(p+1)-1)); sInt16 i=getSyncAppBase()->getRootConfig()->fScriptConfigP->getFunctionIndex(ident.c_str(),ident.size()); if (i==VARIDX_UNDEFINED) SYSYNC_THROW(TScriptErrorException("Undefined function '%s'",line,ident.c_str())); // save function index in script aTScript[p-bp+2]= (sInt8)i; } } } objidx=OBJ_AUTO; // default to auto-select // process next } SHOWVARDEFS(aRebuild ? "Rebuilding" : "Resolving"); } // TScriptContext::ResolveIdentifiers // create local variable definitions for calling a built-in function // This is called for built-in functions before calling PrepareLocals, and // builds up local var definitions like ResolveIdentifiers does for // script code. void TScriptContext::defineBuiltInVars(const TBuiltInFuncDef *aFuncDefP) { // get information and save it in the function's context fNumParams=aFuncDefP->fNumParams; fFuncType=aFuncDefP->fReturntype; // get params sInt16 i=0; while (ifParamTypes[i]; TItemFieldTypes ty= (TItemFieldTypes)(paramdef & PARAM_TYPEMASK); bool isref = paramdef & PARAM_REF; bool isarr = paramdef & PARAM_ARR; bool isopt = paramdef & PARAM_OPT; TScriptVarDef *vardefP = new TScriptVarDef("", fVarDefs.size(), ty, isarr, isref, isopt); fVarDefs.push_back(vardefP); i++; } } // TScriptContext::defineBuiltInVars // execute built-in function void TScriptContext::executeBuiltIn(TItemField *&aTermP, const TBuiltInFuncDef *aFuncDefP) { TItemField *resultP=NULL; // pre-create result field if type is known // Note: multi-typed functions might create result themselves depending on result type if (fFuncType!=fty_none) resultP = newItemField(fFuncType, getSessionZones()); // call actual function routine (aFuncDefP->fFuncProc)(resultP,this); // return result if (aTermP) { // copy value to exiting result field if (resultP) { (*aTermP)=(*resultP); delete resultP; } else aTermP=NULL; // no result } else { // just pass back result field (or NULL) aTermP=resultP; } } // TScriptContext::executeBuiltIn #ifdef SYDEBUG void TScriptContext::showVarDefs(cAppCharP aTxt) { if (DEBUGTEST(DBG_SCRIPTS+DBG_EXOTIC)) { // Show var defs DEBUGPRINTFX(DBG_SCRIPTS+DBG_EXOTIC+DBG_HOT,("%s - %s, ctx=0x%lX, VarDefs:",aTxt,scriptname ? scriptname : "",(long)this)); TVarDefs::iterator pos; for (pos=fVarDefs.begin(); pos!=fVarDefs.end(); pos++) { DEBUGPRINTFX(DBG_SCRIPTS+DBG_EXOTIC,( "%d: %s %s%s%s", (*pos)->fIdx, ItemFieldTypeNames[(*pos)->fVarType], (*pos)->fIsRef ? "&" : "", (*pos)->fVarName.c_str(), (*pos)->fIsArray ? "[]" : "" )); } } } // TScriptContext::showVarDefs #endif // Prepare local variables (create local fields), according to definitions (re-)built with ResolveIdentifiers() // Note: This is called after ResolveIdentifiers() for each script of that context // have been called. Normally ResolveIdentifiers() was called already at config parse // with an anonymous context that is lost when PrepareLocals needs to be called. // Therefore, ResolveIdentifiers(aRebuild=true) can be called to rebuild the definitions from // the script(s) involved. Care must be taken to call ResolveIdentifiers() for each script // in the same order as at config parse to make sure already resolved indexes will match the variables (again). // Note: Field references will have a NULL pointer (which must be filled when the function is // called. bool TScriptContext::PrepareLocals(void) { // make sure old array is cleared SHOWVARDEFS("PrepareLocals"); clearFields(); // create array of appropriate size for locals fNumVars=fVarDefs.size(); SYSYNC_TRY { if (fNumVars) { fFieldsP=new TItemFieldP[fNumVars]; if (fFieldsP==NULL) return false; // - init it with null pointers to make sure we can survive when field instantiation fails for (sInt16 i=0; ifIsRef && ((*pos)->fVarType!=fty_none)) { // this is not a reference and not an untyped parameter: // instantiate a locally owned field fFieldsP[(*pos)->fIdx]=sysync::newItemField((*pos)->fVarType,getSessionZones(),(*pos)->fIsArray); } } } } SYSYNC_CATCH (...) // failed, remove fields again clearFields(); return false; SYSYNC_ENDCATCH return true; } // TScriptContext::PrepareLocals // execute a script returning a boolean bool TScriptContext::executeTest( bool aDefaultAnswer, // result if no script is there or script returns no value TScriptContext *aCtxP, const string &aTScript, const TFuncTable *aFuncTableP, // context's function table, NULL if none void *aCallerContext, // free pointer eventually having a meaning for context functions and chain function TMultiFieldItem *aTargetItemP, // target (or "loosing") item bool aTargetWritable, // if set, target item may be modified TMultiFieldItem *aReferenceItemP, // reference for source (or "old" or "winning") item bool aRefWritable // if set, reference item may also be written ) { // if no script (no context), return default answer if (!aCtxP) return aDefaultAnswer; // now call and evaluate boolean result TItemField *resP; bool res; res=aCtxP->ExecuteScript( aTScript, &resP, // we want a result false, // not a function aFuncTableP, // context function table aCallerContext, // context data aTargetItemP, // target (or "loosing") item aTargetWritable, // if set, target item may be modified aReferenceItemP, // reference for source (or "old" or "winning") item aRefWritable // if set, reference item may also be written ); if (!res) { // script execution failed, do as if there was no script res=aDefaultAnswer; } else { // evaluate result if (resP) { res=resP->getAsBoolean(); // get rid of result delete resP; } else { // no result, return default res=aDefaultAnswer; } } // return result return res; } // TScriptContext::executeTest // execute a script returning a result if there is a context for it bool TScriptContext::executeWithResult( TItemField *&aResultField, // can be default result or NULL, will contain result or NULL if no result TScriptContext *aCtxP, const string &aTScript, const TFuncTable *aFuncTableP, // context's function table, NULL if none void *aCallerContext, // free pointer eventually having a meaning for context functions TMultiFieldItem *aTargetItemP, // target (or "loosing") item bool aTargetWritable, // if set, target item may be modified TMultiFieldItem *aReferenceItemP, // reference for source (or "old" or "winning") item bool aRefWritable, // if set, reference item may also be written bool aNoDebug // if set, debug output is suppressed even if DBG_SCRIPTS is generally on ) { // if no script (no context), return default answer if (!aCtxP) return true; // ok, leave aResultFieldP unmodified // now call and evaluate result return aCtxP->ExecuteScript( aTScript, &aResultField, // we want a result, if we pass a default it will be modified if script has a result false, // not a function aFuncTableP, // context function table aCallerContext, // context data aTargetItemP, // target (or "loosing") item aTargetWritable, // if set, target item may be modified aReferenceItemP, // reference for source (or "old" or "winning") item aRefWritable, // if set, reference item may also be written aNoDebug // if set, debug output is suppressed even if DBG_SCRIPTS is generally on ); } // TScriptContext::executeTest // execute a script if there is a context for it bool TScriptContext::execute( TScriptContext *aCtxP, const string &aTScript, const TFuncTable *aFuncTableP, // context's function table, NULL if none void *aCallerContext, // free pointer eventually having a meaning for context functions TMultiFieldItem *aTargetItemP, // target (or "loosing") item bool aTargetWritable, // if set, target item may be modified TMultiFieldItem *aReferenceItemP, // reference for source (or "old" or "winning") item bool aRefWritable, // if set, reference item may also be written bool aNoDebug // if set, debug output is suppressed even if DBG_SCRIPTS is generally on ) { if (!aCtxP) return true; // no script, success // execute script in given context SYSYNC_TRY { bool r=aCtxP->ExecuteScript( aTScript, NULL, // we don't need a result false, // not a function aFuncTableP, // context specific function table aCallerContext, // caller's context private data pointer aTargetItemP, // target (or "loosing") item aTargetWritable, // if set, target item may be modified aReferenceItemP, // reference for source (or "old" or "winning") item aRefWritable, // if set, reference item may also be written aNoDebug // if set, debug output is suppressed even if DBG_SCRIPTS is generally on ); return r; } SYSYNC_CATCH (...) SYSYNC_RETHROW; SYSYNC_ENDCATCH } // TScriptContext::execute void TScriptContext::pushState(TScriptState aNewState, cUInt8P aBegin, uInt16 aLine) { if (fStackEntries>=maxstackentries) SYSYNC_THROW(TScriptErrorException("too many IF/ELSE/WHILE/LOOP nested",line)); // use defaults if (aBegin==NULL) { aBegin = np; // next token after current token aLine = nextline; } // push current state fScriptstack[fStackEntries].state=aNewState; // new state fScriptstack[fStackEntries].begin=aBegin; // start of block fScriptstack[fStackEntries].line=aLine; // line where block starts fStackEntries++; } // TScriptContext::pushState // pop state from flow control state stack void TScriptContext::popState(TScriptState aCurrentStateExpected) { if (fScriptstack[fStackEntries-1].state!=aCurrentStateExpected) SYSYNC_THROW(TScriptErrorException("bad block/IF/ELSE/WHILE/LOOP nesting",line)); // remove entry fStackEntries--; // remove stack entry if (fStackEntries<1) SYSYNC_THROW(TScriptErrorException(DEBUGTEXT("unbalanced control flow stack pop","scri3"),line)); } // TScriptContext::popState // get variable specification, returns true if writable bool TScriptContext::getVarField(TItemField *&aItemFieldP) { sInt16 objnum, varidx, arridx; TMultiFieldItem *itemP=NULL; bool writeable=false; bool fidoffs=false; uInt8 tk; aItemFieldP=NULL; // none by default tk=gettoken(); objnum=OBJ_AUTO; // default to automatic object selection // check for object specifier if (tk==TK_OBJECT) { // object qualifier objnum=*(p+2); tk=gettoken(); // must be followed by identifier if (tk!=TK_IDENTIFIER) SYSYNC_THROW(TScriptErrorException("bad variable/field reference",line)); } // check for object itself if (tk==TK_IDENTIFIER) { #ifdef SYDEBUG cAppCharP idnam=(cAppCharP)(p+3); int idlen=*(p+1)-1; #endif // get index varidx=(sInt8)(*(p+2)); if (varidx==VARIDX_UNDEFINED) SYSYNC_THROW(TScriptErrorException("Undefined identifier",line)); // check eventual array index arridx=-1; // default to non-array if (*np==TK_OPEN_ARRAY) { tk=gettoken(); // consume open bracket // check special field-offset access mode if (*np==TK_PLUS) { fidoffs=true; gettoken(); // consume the plus } TItemField *fldP = new TIntegerField; SYSYNC_TRY { evalExpression(fldP,EXPRDBGTEST,NULL); // do not show array index expression evaluation arridx=fldP->getAsInteger(); delete fldP; } SYSYNC_CATCH (...) delete fldP; SYSYNC_RETHROW; SYSYNC_ENDCATCH // check closing array bracket tk=gettoken(); if (tk!=TK_CLOSE_ARRAY) SYSYNC_THROW(TScriptErrorException("missing ']' for array reference",line)); } // now access field #ifdef SYDEBUG sInt16 oai=arridx; #endif // - determine object if not explicitly specified if (objnum==OBJ_AUTO) { if (varidx<0) objnum=OBJ_LOCAL; else objnum=OBJ_TARGET; } // - now access (arridx==-1 if we don't want to access leaf element) if (objnum==OBJ_LOCAL) { if (fidoffs) { varidx-=arridx; arridx=-1; } // use array index as offset within local fields (negative indices!) aItemFieldP=getFieldOrVar(NULL,varidx,arridx); writeable=true; // locals are always writeable } else { // prepare index/arrayindex to access if (fidoffs) { varidx+=arridx; arridx=-1; } // use array index as offset within field list // get item to access if (objnum==OBJ_TARGET) { itemP=fTargetItemP; writeable=fTargetWritable; } else if (objnum==OBJ_REFERENCE) { itemP=fReferenceItemP; writeable=fRefWritable; } if (itemP) aItemFieldP=getFieldOrVar(itemP,varidx,arridx); else SYSYNC_THROW(TScriptErrorException("field not accessible in this context",line)); } // error if no field DBGSTRINGDEF(s); DBGSTRINGDEF(sa); DBGVALUESHOW(s,aItemFieldP); if (EXPRDBGTEST) { if (oai>=0) { if (arridx!=-1) StringObjPrintf(sa,"[%hd]",oai); else StringObjPrintf(sa,"[+%hd]",oai); } else { // no array access sa.erase(); } SCRIPTDBGMSG(( "- %s: %.*s%s = %s", objnum==OBJ_LOCAL ? "Local Variable" : (objnum==OBJ_REFERENCE ? "OLD/LOOSING Field " : "Field"), idlen,idnam, sa.c_str(), s.c_str() )); } if (!aItemFieldP) SYSYNC_THROW(TScriptErrorException("undefined identifier, bad array index or offset",line)); // return field pointer we've found, if any return writeable; } else SYSYNC_THROW(TScriptErrorException("expected identifier",line)); } // TScriptContext::getVarField // evaluate function parameters void TScriptContext::evalParams(TScriptContext *aFuncContextP) { uInt8 tk; sInt16 paramidx; TScriptVarDef *vardefP; TItemField *fldP; tk=gettoken(); if (tk!=TK_OPEN_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing '(' of function parameter list",line)); paramidx=0; // special check for function with only optional params if (aFuncContextP->getNumParams()>0 && aFuncContextP->getVarDef((short)0)->fIsOpt) { if (*np==TK_CLOSE_PARANTHESIS) goto noparams; // first is optional already -> ok to see closing paranthesis here } // at least one param while(paramidxgetNumParams()) { vardefP=aFuncContextP->getVarDef(paramidx); // check what param is expected next if (vardefP->fIsRef) { // by reference, we must have a writable lvalue here fldP=NULL; if (*np==TK_IDENTIFIER || *np==TK_OBJECT) { // get writable field/var of caller if (!getVarField(fldP)) SYSYNC_THROW(TScriptErrorException("expected writable by-reference parameter",line)); // type must match (or destination must be untyped) if (vardefP->fVarType!=fldP->getType() && vardefP->fVarType!=fty_none) SYSYNC_THROW(TScriptErrorException("expected by-reference parameter of type '%s'",line,ItemFieldTypeNames[vardefP->fVarType])); // type must match (or destination must be untyped) if (vardefP->fIsArray && !(fldP->isArray())) SYSYNC_THROW(TScriptErrorException("expected array as by-reference parameter",line)); // assign field to function context's local var list aFuncContextP->setLocalVar(paramidx,fldP); } else SYSYNC_THROW(TScriptErrorException("expected by-reference parameter",line)); } else { // by value // - get field where to assign value if (vardefP->fVarType!=fty_none) { // already prepared typed parameter fldP=aFuncContextP->getLocalVar(paramidx); // - evaluate expression into local var evalExpression(fldP,EXPRDBGTEST); // do not show expression, as we will show param } else { // untyped param, let evaluation create correct type fldP=NULL; evalExpression(fldP,EXPRDBGTEST); // do not show expression, as we will show param // untyped params are not yet instantiated, assign expression result now aFuncContextP->setLocalVar(paramidx,fldP); } } DBGSTRINGDEF(s); DBGVALUESHOW(s,fldP); SCRIPTDBGMSG(( "- Parameter #%d (by %s) = %s", paramidx+1, vardefP->fIsRef ? "reference" : "value", s.c_str() )); // got param paramidx++; // check parameter delimiters if (paramidxgetNumParams()) { // function potentially has more params tk=gettoken(); if (tk!=TK_LIST_SEPARATOR) { // - check if next is an optional parameter if (aFuncContextP->getVarDef(paramidx)->fIsOpt) goto endofparams; // yes, optional -> check for end of parameter list else SYSYNC_THROW(TScriptErrorException("expected ',' (more parameters)",line)); // non-optional parameter missing } } } // check end of parameter list noparams: tk=gettoken(); endofparams: if (tk!=TK_CLOSE_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing ')' of function parameter list",line)); } // TScriptContext::evalParams // evaluate term, return caller-owned field containing term data TItemField *TScriptContext::evalTerm(TItemFieldTypes aResultType) { TItemFieldTypes termtype=aResultType; // default to result type TItemField *termP=NULL; uInt8 tk; TScriptContext *funccontextP; string *funcscript; const char *funcname; uInt16 funcnamelen; // Evaluate term. A term is // - a subexpression in paranthesis // - a variable reference // - a builtin function call // - a user defined function call // - a literal, including the special EMPTY and UNASSIGNED ones // A term can be preceeded by a typecast do { tk=gettoken(); if (tk==TK_OPEN_PARANTHESIS) { // typecast or subexpression if (*np==TK_TYPEDEF) { // this is a typecast gettoken(); // actually get type termtype=(TItemFieldTypes)(*(p+2)); // set new term target type if (gettoken()!=TK_CLOSE_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing ')' after typecast",line)); if (aResultType!=fty_none && aResultType!=termtype) SYSYNC_THROW(TScriptErrorException("invalid typecast",line)); continue; // now get term } else { // process subexpression into term if (termtype!=fty_none) { // prepare distinct result type field termP=newItemField(termtype, getSessionZones()); } // puts result in a caller-owned termP (creates new one ONLY if caller passes NULL) evalExpression(termP,EXPRDBGTEST); // do not show subexpression results if (gettoken()!=TK_CLOSE_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing ')' after expression",line)); } } else if (tk==TK_FUNCTION || tk==TK_CONTEXTFUNCTION) { // get function definition table reference const TFuncTable *functableP = (tk==TK_FUNCTION) ? &BuiltInFuncTable : // globally available functions fFuncTableP; // context specific functions // get the function index sInt16 funcid=*(p+2); // now find the function definition record in the chain of function tables void *callerContext = fCallerContext; // start with caller context of current script while(functableP && funcid>=functableP->numFuncs) { // function is in a chained table // - adjust id to get offset based on next table funcid-=functableP->numFuncs; // - get next table and next caller context if (functableP->chainFunc) functableP = (TFuncTable *)functableP->chainFunc(callerContext); else functableP = NULL; } if (!functableP) SYSYNC_THROW(TScriptErrorException("undefined context function",line)); // found function table and caller context, now get funcdef const TBuiltInFuncDef *funcdefP = &(functableP->funcDefs[funcid]); // execute built-in function call funcname=funcdefP->fFuncName; funcnamelen=100; // enough SCRIPTDBGMSG(("- %s() built-in function call:",funcname)); // - get context for built-in function funccontextP=new TScriptContext(fAppBaseP,fSessionP); SYSYNC_TRY { // define built-in parameters of function context funccontextP->defineBuiltInVars(funcdefP); // prepare local variables of function context funccontextP->PrepareLocals(); // prepare parameters evalParams(funccontextP); // copy current line for reference (as builtins have no own line number funccontextP->line=line; // copy caller's context pointer (eventually modified by function table chaining) funccontextP->fCallerContext=callerContext; funccontextP->fParentContextP=this; // link to calling script context // copy target and reference item vars funccontextP->fTargetItemP = fTargetItemP; funccontextP->fTargetWritable = fTargetWritable; funccontextP->fReferenceItemP = fReferenceItemP; funccontextP->fRefWritable = fRefWritable; // execute function funccontextP->executeBuiltIn(termP,funcdefP); // show by-ref parameters after call if (SCRIPTDBGTEST) { // show by-ref variables for (uInt16 i=0; ifNumParams; i++) { if (funcdefP->fParamTypes[i] & PARAM_REF) { // is a parameter passed by reference, possibly changed by function call DBGSTRINGDEF(s); DBGVALUESHOW(s,funccontextP->getLocalVar(i)); SCRIPTDBGMSG(( "- return value of by-ref parameter #%d = %s", i+1, s.c_str() )); } } } // done delete funccontextP; } SYSYNC_CATCH (...) if (funccontextP) delete funccontextP; SYSYNC_RETHROW; SYSYNC_ENDCATCH goto funcresult; } else if (tk==TK_USERFUNCTION) { // user defined function call funcname=(const char *)p+3; funcnamelen=*(p+1)-1; SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- User-defined function %.*s call:",funcnamelen,funcname)); // - get function text funcscript=getSyncAppBase()->getRootConfig()->fScriptConfigP->getFunctionScript(*(p+2)); if (!funcscript) SYSYNC_THROW(TSyncException(DEBUGTEXT("invalid user function index","scri7"))); // %%% add caching of function contexts here eventually. // Now we rebuild a context for every function call. Not extremely efficient... funccontextP=NULL; rebuildContext(fAppBaseP,*funcscript,funccontextP,fSessionP,true); if (!funccontextP) SYSYNC_THROW(TSyncException(DEBUGTEXT("no context for user-defined function call","scri5"))); SYSYNC_TRY { // prepare parameters evalParams(funccontextP); // pass parent context funccontextP->fParentContextP=this; // link to calling script context // process sub-script into term if (termtype!=fty_none) termP=newItemField(termtype, getSessionZones()); // we want a specific type, pre-define the field // execute function if (!funccontextP->ExecuteScript( *funcscript, &termP, // receives result, if any true, // is a function NULL, NULL, // no context functions/datapointer NULL, false, // no target fields NULL, false // no reference fields )) { SCRIPTDBGMSG(("- User-defined function failed to execute")); SYSYNC_THROW(TSyncException("User-defined function failed to execute properly")); } // done if (funccontextP) delete funccontextP; } SYSYNC_CATCH (...) if (funccontextP) delete funccontextP; SYSYNC_RETHROW; SYSYNC_ENDCATCH funcresult: DBGSTRINGDEF(s); DBGVALUESHOW(s,termP); SCRIPTDBGMSG(( "- %.*s() function result = %s", funcnamelen,funcname, s.c_str() )); } else if (tk==TK_EMPTY) { termP=newItemField(fty_none, getSessionZones()); termP->assignEmpty(); // make it EMPTY (that is, assigned) //SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Literal: EMPTY")); } else if (tk==TK_UNASSIGNED) { termP=newItemField(fty_none, getSessionZones()); termP->unAssign(); // make it (already is...) unassigned //SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Literal: UNASSIGNED")); } else if (tk==TK_TRUE || tk==TK_FALSE) { termP=newItemField(fty_integer, getSessionZones()); termP->setAsInteger(tk==TK_TRUE ? 1 : 0); // set 0 or 1 //SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Literal BOOLEAN: %d",tk==TK_TRUE ? 1 : 0)); } else if (tk==TK_NUMERIC_LITERAL) { // %%% add fty_float later eventually // set type to integer if not another type requested //SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Literal number: %0.*s",*(p+1),p+2)); if (termtype==fty_none) termtype=fty_integer; goto literalterm; } else if (tk==TK_STRING_LITERAL) { if (termtype==fty_none) termtype=fty_string; //SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Literal string: \"%0.*s\"",*(p+1),p+2)); literalterm: termP = newItemField(termtype, getSessionZones()); termP->setAsString((cAppCharP)(p+2),*(p+1)); } else { // must be identifier reusetoken(); TItemField *varP=NULL; getVarField(varP); // copy and convert if (termtype==fty_none) termtype=varP->getType(); termP=newItemField(termtype, getSessionZones()); (*termP)=(*varP); // simply assign (automatically converts if needed) } break; } while(true); // repeat only for typecast return termP; // return caller-owned field } // TScriptContext::evalTerm // evaluate expression, creates new or fills passed caller-owned field with result void TScriptContext::evalExpression( TItemField *&aResultFieldP, // result (created new if passed NULL, modified and casted if passed a field) bool aShowResult, // if set, this is the main expression (and we want to see the result in DBG) TItemField *aLeftTermP, // if not NULL, chain-evaluate rest of expression according to aBinaryOp and aPreviousOp. WILL BE CONSUMED uInt8 *aBinaryOpP, // operator to be applied between term passed in aLeftTermP and next term, will receive next operator that has same or lower precedence than aPreviousOp uInt8 aPreviousOp // if an operator of same or lower precedence than this is found, expression evaluation ends ) { TItemFieldTypes termtype; // type of term TItemField *termP; // next term TItemField *resultP; // intermediate result string s; // temp string bool retainType=false; uInt8 unaryop,binaryop,nextbinaryop; fieldinteger_t a,b; // defaults binaryop=0; // none by default if (aBinaryOpP) binaryop=*aBinaryOpP; // get one if one was passed termtype=fty_none; // first term can be anything // init first term/operation if there is one if (binaryop && !aLeftTermP) binaryop=0; // security // process expression #ifdef SYDEBUG // - determine if subexpression if (!binaryop) { // starting new evaluation SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Starting expression evaluation")); } #endif SYSYNC_TRY { // Note: if binaryop!=0, we have a valid aLeftTermP do { resultP=NULL; termP=NULL; // Get next term unaryop=0; // default to none // - check for unaries if (*np==TK_BITWISENOT || *np==TK_LOGICALNOT || *np==TK_MINUS) { unaryop=gettoken(); // evaluate to integer, as unary ops all only make sense with integers termtype=fty_integer; } // - get next term termP=evalTerm(termtype); // Apply unaries now if (unaryop) { // all unaries are integer ops if (termP->getCalcType()!=fty_integer) SYSYNC_THROW(TScriptErrorException("unary operator applied to non-integer",line)); fieldinteger_t ival = termP->getAsInteger(); // apply op switch (unaryop) { case TK_MINUS: ival=-ival; break; case TK_BITWISENOT: ival= ~ival; break; case TK_LOGICALNOT: ival= !termP->getAsBoolean(); break; } // store back into term field termP->setAsInteger(ival); } // now we have a new term in termP (or NULL if term had no result) // - check for a next operator nextbinaryop=0; if (*np>=TK_BINOP_MIN && *np <=TK_BINOP_MAX) { // this is a binary operator nextbinaryop=gettoken(); } // - check for non-existing term if (!termP && (binaryop || nextbinaryop)) SYSYNC_THROW(TScriptErrorException("non-value cannot be used in expression",line)); // - check if there is a previous operation pending if (binaryop) { fieldinteger_t intres; // There is an operation to perform between aLeftTermP and current term (or expression with // higher precedence) // - get precedences (make them minus to have lowerpreccurrentprec) { // next operator has higher precedence, evaluate everything up to next operator with same or // lower precedence as current one BEFORE applying binaryop evalExpression( resultP, // we'll receive the result here EXPRDBGTEST, // do not normally show subexpression results termP, // left term of new expression, WILL BE CONSUMED &nextbinaryop, // operator, will be updated to receive next operator to apply binaryop // evaluation stops when expression reaches operator with same or lower precedence ); // nextbinaryop now has next operation to perform AFTER doing binaryop termP=resultP; // original termP has been consumed, use result as new termP resultP=NULL; } // Now we can apply binaryop between lasttermP and termP // Note: this must CONSUME termP AND aLeftTermP and CREATE resultP // - check for operators that work with multiple types if (binaryop==TK_PLUS) { if (aLeftTermP->getCalcType()!=fty_integer) { // treat plus as general append (defaults to string append for non-arrays) aLeftTermP->append(*termP); resultP=aLeftTermP; // we can simply pass the pointer aLeftTermP=NULL; // consume goto opdone; // operation done, get rid of termP } } else if (binaryop>=TK_LESSTHAN && binaryop<=TK_NOTEQUAL) { // comparison operators sInt16 cmpres=-3; // will never happen bool neg=false; switch (binaryop) { // - comparison case TK_LESSTHAN : cmpres=-1; break; case TK_GREATERTHAN : cmpres=1; break; case TK_LESSEQUAL : cmpres=1; neg=true; break; case TK_GREATEREQUAL : cmpres=-1; neg=true; break; // - equality case TK_EQUAL : cmpres=0; break; case TK_NOTEQUAL : cmpres=0; neg=true; break; } // do comparison if (termP->getType()==fty_none) { // EMPTY or UNASSIGNED comparison if (termP->isUnassigned()) { intres = aLeftTermP->isUnassigned() ? 0 : 1; // if left is assigned, it is greater } else { intres = aLeftTermP->isEmpty() ? 0 : 1; // if left is not empty, it is greater } } else { // normal comparison intres=aLeftTermP->compareWith(*termP); } if (intres==SYSYNC_NOT_COMPARABLE) intres=0; // false else { intres=cmpres==intres; if (neg) intres=!intres; } retainType= aLeftTermP->getType()==fty_integer;; // comparisons must always have plain integer result goto intresult; } // - integer operators a=aLeftTermP->getAsInteger(); b=termP->getAsInteger(); // for integer math operators, retain type of left term if it can calculate as integer // (such that expressions like: "timestamp + integer" will have a result type of timestamp) retainType = aLeftTermP->getCalcType()==fty_integer; // now perform integer operation switch (binaryop) { // - multiply, divide case TK_MULTIPLY : intres=a*b; break; case TK_DIVIDE : intres=a/b; break; case TK_MODULUS : intres=a%b; break; // - add, subtract case TK_PLUS : intres=a+b; break; case TK_MINUS : intres=a-b; break; // - shift case TK_SHIFTLEFT : intres=a<>b; break; // - bitwise AND case TK_BITWISEAND : intres=a&b; break; // - bitwise XOR case TK_BITWISEXOR : intres=a^b; break; // - bitwise OR case TK_BITWISEOR : intres=a|b; break; // - logical AND case TK_LOGICALAND : intres=a&&b; break; // - logical OR case TK_LOGICALOR : intres=a||b; break; default: SYSYNC_THROW(TScriptErrorException("operator not implemented",line)); } intresult: // save integer result (optimized, generate new field only if aLeftTermP is not already integer-calc-type) if (retainType) resultP=aLeftTermP; // retain original left-side type (including extra information like time zone context and rendering type) else { // create new integer field resultP = newItemField(fty_integer, getSessionZones()); delete aLeftTermP; } aLeftTermP=NULL; resultP->setAsInteger(intres); opdone: // check for special conditions when operating on timestamps if (resultP->isBasedOn(fty_timestamp) && termP->isBasedOn(fty_timestamp)) { // both based on timestamp. if (binaryop==TK_MINUS && !static_cast(resultP)->isDuration() && !static_cast(termP)->isDuration()) { // subtracted two points in time -> result is duration static_cast(resultP)->makeDuration(); } } // get rid of termP delete termP; termP=NULL; } // if binary op was pending else { // no binary op pending, simply pass term as result resultP=termP; termP=NULL; } // Now termP and aLeftTermP are consumed, resultP is alive // - determine next operation if (nextbinaryop) { // check precedence, if we can pass back to previous sInt8 lastprec=-(aPreviousOp & TK_OP_PRECEDENCE_MASK); sInt8 thisprec=-(nextbinaryop & TK_OP_PRECEDENCE_MASK); if (aPreviousOp && thisprec<=lastprec) break; // force exit, resultP is assigned } // result is going to be left term for next operation aLeftTermP=resultP; binaryop=nextbinaryop; } while(nextbinaryop); // as long as expression continues // show result #ifdef SYDEBUG // show final result of main expression only if (!nextbinaryop && aShowResult) { DBGVALUESHOW(s,resultP); SCRIPTDBGMSG(( "- Expression result: %s", s.c_str() )); } #endif // convert result to desired type if (!aResultFieldP) { // no return field passed, just pass pointer (and ownership) of our resultP aResultFieldP=resultP; } else { // return field already exists, assign value (will convert type if needed) if (resultP) { (*aResultFieldP)=(*resultP); delete resultP; // not passed to caller, so we must get rid of it } else { // no result, unAssign aResultFieldP->unAssign(); } } // return nextbinaryop if requested if (aBinaryOpP) *aBinaryOpP=nextbinaryop; // this is how we ended } SYSYNC_CATCH (...) // delete terms if (resultP) delete resultP; if (aLeftTermP) delete aLeftTermP; if (termP) delete termP; SYSYNC_RETHROW; SYSYNC_ENDCATCH } // TScriptContext:evalExpression // execute script // returns false if script execution was not successful bool TScriptContext::ExecuteScript( const string &aTScript, TItemField **aResultPP, // if not NULL, a result field will be returned here (must be deleted by caller) bool aAsFunction, // if set, this is a function call const TFuncTable *aFuncTableP, // context's function table, NULL if none void *aCallerContext, // free pointer eventually having a meaning for context functions TMultiFieldItem *aTargetItemP, // target (or "loosing") item bool aTargetWritable, // if set, target item may be modified TMultiFieldItem *aReferenceItemP, // reference for source (or "old" or "winning") item bool aRefWritable, // if set, reference item may also be written bool aNoDebug // if set, debug output is suppressed even if DBG_SCRIPTS is generally on ) { if (aResultPP) *aResultPP=NULL; // no result yet TItemField *resultP = NULL; // none yet uInt8 tk; // current token TScriptState sta; // status skipping=0; // skipping level bool funchdr=aAsFunction; // flag if processing function header first if (funchdr) skipping=1; // skip stuff // endless loop protection lineartime_t loopmaxtime=0; // not in a loop // save context parameters fTargetItemP=aTargetItemP; // target (or "loosing") item fTargetWritable=aTargetWritable; // if set, target item may be modified fReferenceItemP=aReferenceItemP; // reference for source (or "old" or "winning") item fRefWritable=aRefWritable; // if set, reference item may also be written fFuncTableP=aFuncTableP; // context's function table, NULL if none fCallerContext=aCallerContext; #ifdef SYDEBUG debugon = !aNoDebug; #endif // init parsing (for execute) initParse(aTScript,true); // test if there's something to execute at all if (ep>bp) { #ifdef SYDEBUG if (aAsFunction) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,("* Starting execution of user-defined function")); } else { SCRIPTDBGSTART(scriptname ? scriptname : ""); } #endif SYSYNC_TRY { // init state stack fStackEntries=0; // stack is empty pushState(ssta_statement); // start with statement // execute while ((tk=gettoken())) { resultP=NULL; // start of statement // - check for block if (tk==TK_BEGIN_BLOCK) { // push current state to stack and start new block pushState(ssta_block); if (funchdr) { // start of first block = start of function body, stop skipping funchdr=false; skipping=0; } continue; // process next } else if (tk==TK_END_BLOCK) { // pop block start, throw error if none there popState(ssta_block); // treat like end-of-statement, check for IF/ELSE/LOOP etc. goto endstatement; } else if (tk==TK_END_STATEMENT) { goto endstatement; } // - handle skipping of conditionally excluded blocks if (skipping!=0) { // only IF and ELSE are of any relevance if (tk==TK_IF) { pushState(ssta_if); // nested IF skipping++; // skip anyway } else if (tk==TK_ELSE) { // %%% this case probably never happens, as it is pre-fetched at end-of-statement // only push ELSE if this is not a chained ELSE IF if (*np!=TK_IF) pushState(ssta_else); // nested ELSE } // all others are irrelevant during skip continue; } else { // really executing // - check empty statement if (tk==TK_END_STATEMENT) goto endstatement; // - check IF statement else if (tk==TK_IF || tk==TK_WHILE) { // IF or WHILE conditional // - remember for WHILE case as condition must be re-evaluated for every loop cUInt8P condBeg = p; uInt16 condLine = line; // - process boolean expression tk=gettoken(); if (tk!=TK_OPEN_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing '(' after IF or WHILE",line)); SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- IF or WHILE, evaluating condition...")); evalExpression(resultP,EXPRDBGTEST); tk=gettoken(); if (tk!=TK_CLOSE_PARANTHESIS) SYSYNC_THROW(TScriptErrorException("missing ')' in IF or WHILE",line)); // - determine which branch to process if (!(resultP && resultP->getAsBoolean())) skipping=1; // enter skip level 1 SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- %s condition is %s", *condBeg==TK_WHILE ? "WHILE" : "IF", skipping ? "false" : "true")); delete resultP; resultP=NULL; if (*condBeg==TK_WHILE) { if (skipping) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,("- WHILE condition is false -> skipping WHILE body")); } // for every subsequent loop, WHILE must be reprocessed pushState(ssta_while,condBeg,condLine); goto startloop; // limit loop execution time } else { // process statement (block) after IF pushState(ssta_if); } continue; } // Note: ELSE is checked at end of statement only // Check loop beginning else if (tk==TK_LOOP) { // initiate loop pushState(ssta_loop); SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- LOOP entered")); startloop: // - remember entry time of outermost loop if (loopmaxtime==0) { uInt32 loopSecs = getSyncAppBase()->getRootConfig()->fScriptConfigP->fMaxLoopProcessingTime; if (loopSecs>0) { loopmaxtime= getSystemNowAs(TCTX_UTC, getSessionZones())+ secondToLinearTimeFactor * loopSecs; } else { loopmaxtime=maxLinearTime; // virtually unlimited time } } continue; } else if (tk==TK_CONTINUE || tk==TK_BREAK) { // see if there is a LOOP or WHILE on the stack bool inloop=false; bool inwhile=false; sInt16 sp=fStackEntries; while (sp>0) { sta = fScriptstack[sp-1].state; if (sta==ssta_loop) { inloop=true; break; } // we are in a LOOP if (sta==ssta_while) { inwhile=true; break; } // we are in a WHILE sp--; } // no loop found, error if (!inloop && !inwhile) SYSYNC_THROW(TScriptErrorException("BREAK or CONTINUE without enclosing LOOP or WHILE",line)); if (tk==TK_BREAK) { // make sure we are skipping everything (including any number of nested if/else // and blocks up to reaching next end-of-loop). Note that if a LOOP or WHILE is in the // skipped part, it will not cause troubles because it is not recognized as // such while skipping. skipping=maxstackentries; } else { // continue, remove stack entries down to LOOP or WHILE and jump to beginning of loop // - same as if we had reached the bottom of the loop, but pop // open blocks, if's and else's first // - sta is ssta_loop or ssta_while to decide if we must pop the status or not goto loopcontinue; } } else if (tk==TK_TYPEDEF) { // declaration, skip it do { if (*np!=TK_IDENTIFIER) SYSYNC_THROW(TScriptErrorException("Invalid declaration",line)); tk=gettoken(); // swallow identifier if (*np==TK_OPEN_ARRAY) { // must be array declaration tk=gettoken(); // swallow [ if (*np==TK_CLOSE_ARRAY) gettoken(); // swallow ] } if (*np!=TK_LIST_SEPARATOR) break; gettoken(); // swallow separator } while(true); // end of statement should follow here, will be checked below. } else if (tk==TK_IDENTIFIER || tk==TK_OBJECT) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Starting assignment/unstored expression")); // could be assignment if identifier is followed by TK_ASSIGN // otherwise, it could be a simple expression evaluation cUInt8P ts=p; // remember start of statement uInt16 tl=line; // remember line as well TItemField *fldP; reusetoken(); bool writeable=getVarField(fldP); // now check if this is an assignment if (*np==TK_ASSIGN) { gettoken(); // swallow TK_ASSIGN // must be writeable if (!writeable) SYSYNC_THROW(TScriptErrorException("Not allowed to assign to this field/variable",line)); // evaluate expression into given field evalExpression(fldP,false); // we show the result ourselves DBGSTRINGDEF(s); DBGVALUESHOW(s,fldP); SCRIPTDBGMSG(("- Assigned expression result = %s",s.c_str())); } else { // must be plain expression np=ts; // back to start of statement; nextline=tl; evalExpression(resultP,true); // we always want to see the result SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Evaluated unstored expression")); // - if nothing follows, script ends here with result of expression if (*np==0) break; // - otherwise, forget result delete resultP; resultP=NULL; } } else if (tk==TK_RETURN) { // simply return if no expression follows resultP=NULL; if (*np==TK_END_STATEMENT) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,("- RETURN: ending %s without result",aAsFunction ? "function" : "script")); break; } // evaluate expression first SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- RETURN: evaluating result expression...")); evalExpression(resultP,false); // we always show the result ourselves DBGSTRINGDEF(s); DBGVALUESHOW(s,resultP); SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,( "- RETURN: ending %s with result = %s", aAsFunction ? "function" : "script", s.c_str() )); // now end script break; } else { // everything else must be plain expression (e.g. function calls etc.) SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Starting evaluating unstored expression")); reusetoken(); evalExpression(resultP,false); // we always show the result ourselves SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC+DBG_SCRIPTEXPR,("- Evaluated unstored expression")); /* %%% wrong, prevents script from returning NOTHING! to return a value, RETURN *must* be used! // - if nothing follows, script ends here with result of expression if (*np==0) { DBGSTRINGDEF(s); DBGVALUESHOW(s,resultP); SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,( "- Script ends with result = %s", s.c_str() )); break; } */ // - otherwise, forget result delete resultP; resultP=NULL; } } // not skipping // Statement executed, next must be end-of-statement tk=gettoken(); if (tk!=TK_END_STATEMENT) SYSYNC_THROW(TScriptErrorException("missing ';' after statement",line)); // Statement executed and properly terminated endstatement: // check state sta=fScriptstack[fStackEntries-1].state; // check end of IF block if (sta==ssta_if) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- End of %s IF",skipping ? "skipped" : "executed")); popState(sta); // end this state if (skipping) { // IF branch was not executed, check for else if (*np==TK_ELSE) { // else follows // - swallow TK_ELSE gettoken(); // - check for ELSE IF chain // (in this case, we must NOT push a state, but let chained IF push TK_IF again // and also increment skipping again, after it is decremented by one below) if (*np!=TK_IF) { pushState(ssta_else); // end-of-chain ELSE: enter else state // keep skipping (compensate for decrement below) ONLY if end-of-chain ELSE // is not to be executed due to surrounding skip. if (skipping>1) skipping++; } } // no ELSE, just continue with next statement skipping--; // reduce skip level continue; } else { // IF branch was executed, check for else if (*np==TK_ELSE) { // else follows, skip it (including all chained IFs) skipping=1; gettoken(); // swallow ELSE // - check for ELSE IF chain if (*np==TK_IF) { // chained if while skipping gettoken(); // consume it (avoid regular parsing to see it and increment skipping) pushState(ssta_chainif); // chained IF, make sure nothing is executed up to and including end-of-chain else } else pushState(ssta_else); // process as skipped ELSE part } // no else, simply continue execution continue; } } else if (sta==ssta_chainif) { // only entered while skipping rest of chain SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- End of skipped ELSE IF")); if (*np==TK_ELSE) { gettoken(); // consume it if (*np==TK_IF) { // chained if while skipping gettoken(); // consume it (avoid regular parsing to see it and increment skipping) // stay in ssta_chainif } else { popState(ssta_chainif); // end of ELSE IF chain pushState(ssta_else); // rest is a normal skipped ELSE part } } else { // end of skipped chained ifs popState(ssta_chainif); skipping--; } } else if (sta==ssta_else) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- End of %s ELSE",skipping ? "skipped" : "executed")); popState(ssta_else); // end of else state if (skipping) skipping--; } // check end of LOOP block else if (sta==ssta_loop || sta==ssta_while) { // Note: we'll never see that for a completely skipped loop/while, as // no ssta_loop/ssta_while is pushed while skipping. if (!skipping) goto loopcontinue; // not end of while or breaking out of loop, repeat // - end of loop reached popState(sta); skipping=0; // end of loop found after BREAK, continue normal execution SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_EXOTIC,("- End of WHILE or LOOP")); } // nothing special, just execute next statement continue; loopcontinue: // continue at beginning of loop which is at top of state stack if (getSystemNowAs(TCTX_UTC, getSessionZones())>loopmaxtime) SYSYNC_THROW(TScriptErrorException("loop execution aborted because reached (endless loop?)",line)); // go to beginning of loop: restore position of beginning of loop (including condition in case of WHILE) np=fScriptstack[fStackEntries-1].begin; // next token after current token nextline=fScriptstack[fStackEntries-1].line; // line of next token after current token linesource=NULL; // prevent showing source (which is that of NEXT line, not that of jump target's // for while, we must pop the stack entry as it will be recreated by re-excution of the WHILE statement if (sta==ssta_while) popState(sta); SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,("- Starting next iteration of LOOP/WHILE -> jumping to line %hd",nextline)); continue; } // while more tokens } // try SYSYNC_CATCH (exception &e) // make sure field is deleted if (resultP) delete resultP; // show error message SCRIPTDBGMSGX(DBG_ERROR,("Warning: TERMINATING SCRIPT WITH ERROR: %s",e.what())); if (!aAsFunction) SCRIPTDBGEND(); return false; SYSYNC_ENDCATCH #ifdef SYDEBUG if (aAsFunction) { SCRIPTDBGMSGX(DBG_SCRIPTS+DBG_HOT,("* Successfully finished execution of user-defined function")); } else { SCRIPTDBGEND(); } #endif } // if something to execute at all // return most recent evaluated expression (normally RETURN expression) if (aResultPP) { // caller wants to see result, pass it back if (*aResultPP && resultP) { // script result field already exists, assign value (**aResultPP)=(*resultP); delete resultP; } else { // script result field does not yet exist, just pass result (even if it is NULL) *aResultPP=resultP; } } else if (resultP) delete resultP; // nobody needs the result, delete it // execution successful return true; } // TScriptContext::ExecuteScript // get variable definition by name, NULL if none defined yet TScriptVarDef *TScriptContext::getVarDef(cAppCharP aVarName,size_t len) { TVarDefs::iterator pos; for (pos=fVarDefs.begin(); pos!=fVarDefs.end(); pos++) { if (strucmp((*pos)->fVarName.c_str(),aVarName,0,len)==0) return (*pos); } return NULL; } // TScriptContext::getVarDef // get variable definition by index, NULL if none defined yet TScriptVarDef *TScriptContext::getVarDef(sInt16 aLocalVarIdx) { if (aLocalVarIdx<0 || uInt32(aLocalVarIdx)>=fVarDefs.size()) return NULL; return fVarDefs[aLocalVarIdx]; } // TScriptContext::getVarDef // - get identifier index (>=0: field index, <0: local var) // returns VARIDX_UNDEFINED for unknown identifier sInt16 TScriptContext::getIdentifierIndex(sInt16 aObjIndex, TFieldListConfig *aFieldListConfigP, cAppCharP aIdentifier,size_t aLen) { // first look for local variable if (aObjIndex==OBJ_AUTO || aObjIndex==OBJ_LOCAL) { TScriptVarDef *vardefP = getVarDef(aIdentifier,aLen); if (vardefP) { return - (sInt16)vardefP->fIdx-1; } } if (aObjIndex==OBJ_AUTO || aObjIndex==OBJ_TARGET || aObjIndex==OBJ_REFERENCE) { // look for field with that name if (!aFieldListConfigP) return VARIDX_UNDEFINED; // no field list available here return aFieldListConfigP->fieldIndex(aIdentifier,aLen); } else return VARIDX_UNDEFINED; // unknown object, unknown index } // TScriptContext::getIdentifierIndex // get field by fid, can also be field of aItem, also resolves arrays TItemField *TScriptContext::getFieldOrVar(TMultiFieldItem *aItemP, sInt16 aFid, sInt16 aArrIdx) { TItemField *fldP = getFieldOrVar(aItemP,aFid); if (!fldP) return NULL; #ifdef ARRAYFIELD_SUPPORT if (aArrIdx>=0) return fldP->getArrayField(aArrIdx); else return fldP; // return field without array resolution #else if (aArrIdx>0) return NULL; return fldP; #endif } // TScriptContext::getFieldOrVar // get field by fid, can be field of aItem (positive aFid) or local variable (negative fid) TItemField *TScriptContext::getFieldOrVar(TMultiFieldItem *aItemP, sInt16 aFid) { if (aFid<0) return getLocalVar(-aFid-1); if (!aItemP) return NULL; return aItemP->getField(aFid); } // TScriptContext::getFieldOrVar // get local var by local index TItemField *TScriptContext::getLocalVar(sInt16 aVarIdx) { if (aVarIdx<0 || aVarIdx>=fNumVars) return NULL; return fFieldsP[aVarIdx]; } // TScriptContext::getLocalVar // set local var by local index (used for passing references) void TScriptContext::setLocalVar(sInt16 aVarIdx, TItemField *aFieldP) { if (aVarIdx<0 || aVarIdx>=fNumVars) return; fFieldsP[aVarIdx]=aFieldP; // set new } // TScriptContext::setLocalVar /* end of TScriptContext implementation */ #ifdef ENGINEINTERFACE_SUPPORT // TScriptVarKey // ============= // get FID for specified name sInt16 TScriptVarKey::getFidFor(cAppCharP aName, stringSize aNameSz) { if (fScriptContext==NULL) return VARIDX_UNDEFINED; // check for iterator commands first if (strucmp(aName,VALNAME_FIRST)==0) { fIterator=0; if (fIterator>=sInt32(fScriptContext->fVarDefs.size())) return VARIDX_UNDEFINED; return -fIterator-1; } else if (strucmp(aName,VALNAME_NEXT)==0) { fIterator++; if (fIterator>=sInt32(fScriptContext->fVarDefs.size())) return VARIDX_UNDEFINED; return -fIterator-1; } else return fScriptContext->getIdentifierIndex(OBJ_LOCAL, NULL, aName, aNameSz); } // TScriptVarKey::getFidFor // get base field from FID TItemField *TScriptVarKey::getBaseFieldFromFid(sInt16 aFid) { if (fScriptContext==NULL) return NULL; return fScriptContext->getFieldOrVar(NULL, aFid); } // TScriptVarKey::getBaseFieldFromFid // get field name from FID bool TScriptVarKey::getFieldNameFromFid(sInt16 aFid, string &aFieldName) { if (fScriptContext==NULL) return false; TScriptVarDef *vardefP = fScriptContext->getVarDef(-aFid-1); if (vardefP) { aFieldName = vardefP->fVarName; return true; } // none found return false; } // TScriptVarKey::getFieldNameFromFid #endif // ENGINEINTERFACE_SUPPORT } // namespace sysync #endif // SCRIPT_SUPPORT // eof