#ifndef __EEL__STRINGS_H__ #define __EEL__STRINGS_H__ #include "ns-eel-int.h" #include "../wdlcstring.h" #include "../wdlstring.h" // required for context // #define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((sInst *)opaque)->m_eel_string_state) /* // writeable user-strings are 0..1023 (EEL_STRING_MAX_USER_STRINGS-1), and can be up to about EEL_STRING_MAXUSERSTRING_LENGTH_HINT bytes long printf("string %d blah"); -- output to log, allows %d %u %f etc, if host implements formats [1] sprintf(str,"string %d blah"); -- output to str [1] strlen(str); -- returns string length match("*test*", "this is a test") -- search for first parameter regex-style in second parameter matchi("*test*", "this is a test") -- search for first parameter regex-style in second parameter (case insensitive) // %s means 1 or more chars // %0s means 0 or more chars // %5s means exactly 5 chars // %5-s means 5 or more chars // %-10s means 1-10 chars // %3-5s means 3-5 chars. // %0-5s means 0-5 chars. // %S (uppercase) indicates lazy match (%D, %F, %X, etc) strcpy(str, srcstr); -- replaces str with srcstr strcat(str, srcstr); -- appends srcstr to str strcmp(str, str2) -- compares strings stricmp(str, str2) -- compares strings (ignoring case) strncmp(str, str2, maxlen) -- compares strings up to maxlen bytes strnicmp(str, str2, maxlen) -- compares strings (ignoring case) up to maxlen bytes strncpy(str, srcstr, maxlen); -- replaces str with srcstr, up to maxlen (-1 for unlimited) strncat(str, srcstr, maxlen); -- appends up to maxlen of srcstr to str (-1 for unlimited) strcpy_from(str,srcstr, offset); -- copies srcstr to str, but starts reading srcstr at offset offset strcpy_substr(str, srcstr, offs, ml) -- php-style (start at offs, offs<0 means from end, ml for maxlen, ml<0 = reduce length by this amt) str_getchar(str, offset[, type]); -- returns value at offset offset, type can be omitted or 0 or 'c', 's', 'S', 'i', 'I', 'f', 'F', 'd', 'D', 'uc', 'us', 'US', 'ui', 'UI' -- negative offset is offset from end of string str_setchar(str, offset, val[, type]); - sets value at offset offset, type optional. offset must be [0,length], if length it can lengthen string, if >length then call fails -- negative offset is offset from end of string str_setlen(str, len); -- sets length of string (if increasing, will be space-padded) str_delsub(str, pos, len); -- deletes len chars at pos str_insert(str, srcstr, pos); -- inserts srcstr at pos [1]: note: printf/sprintf are NOT binary safe when using %s with modifiers (such as %100s etc) -- the source string being formatted will terminate at the first NULL character ); */ // define this to allow modifying literal strings via code, i.e. foobar = strcat("foo","bar"); // disabled by default, so literals can be pooled/reused/etc // #define EEL_STRINGS_MUTABLE_LITERALS #ifndef EEL_STRING_MAXUSERSTRING_LENGTH_HINT #define EEL_STRING_MAXUSERSTRING_LENGTH_HINT 16384 #endif #ifndef EEL_STRING_MAX_USER_STRINGS // strings 0...x-1 #define EEL_STRING_MAX_USER_STRINGS 1024 #endif #ifndef EEL_STRING_LITERAL_BASE // strings defined by "xyz" #define EEL_STRING_LITERAL_BASE 10000 #endif // base for named mutable strings (#xyz) #ifndef EEL_STRING_NAMED_BASE #define EEL_STRING_NAMED_BASE 90000 #endif // base for unnamed mutable strings (#) #ifndef EEL_STRING_UNNAMED_BASE #define EEL_STRING_UNNAMED_BASE 190000 #endif // define EEL_STRING_MUTEXLOCK_SCOPE for custom, otherwise EEL_STRING_WANT_MUTEX for builtin locking #ifndef EEL_STRING_MUTEXLOCK_SCOPE #ifdef EEL_STRING_WANT_MUTEX #include "../mutex.h" #define EEL_STRING_MUTEXLOCK_SCOPE WDL_MutexLock __lock(&(EEL_STRING_GET_CONTEXT_POINTER(opaque)->m_mutex)); #else #define EEL_STRING_MUTEXLOCK_SCOPE #endif #endif // allow overriding behavior #ifndef EEL_STRING_GET_FOR_INDEX #define EEL_STRING_GET_FOR_INDEX(x, wr) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetStringForIndex(x, wr, false)) #endif #ifndef EEL_STRING_GET_FOR_WRITE #define EEL_STRING_GET_FOR_WRITE(x, wr) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetStringForIndex(x, wr, true)) #endif #ifndef EEL_STRING_GETFMTVAR #define EEL_STRING_GETFMTVAR(x) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetVarForFormat(x)) #endif #ifndef EEL_STRING_GETNAMEDVAR #define EEL_STRING_GETNAMEDVAR(x,createOK,altOut) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetNamedVar(x,createOK,altOut)) #endif #ifndef EEL_STRING_STORAGECLASS #define EEL_STRING_STORAGECLASS WDL_FastString #endif class eel_string_context_state { public: static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } eel_string_context_state() : m_named_strings_names(false), m_varname_cache(cmpistr) { m_vm=0; memset(m_user_strings,0,sizeof(m_user_strings)); } ~eel_string_context_state() { clear_state(true); } void clear_state(bool full) { if (full) { int x; for (x=0;x=0 && idx < EEL_STRING_MAX_USER_STRINGS) { if (stringContainerOut) { if (!m_user_strings[idx]) m_user_strings[idx] = new EEL_STRING_STORAGECLASS; *stringContainerOut = m_user_strings[idx]; } return m_user_strings[idx]?m_user_strings[idx]->Get():""; } EEL_STRING_STORAGECLASS *s; s = m_unnamed_strings.Get(idx - EEL_STRING_UNNAMED_BASE); if (!s) s= m_named_strings.Get(idx - EEL_STRING_NAMED_BASE); if (s) { // mutable string if (stringContainerOut) *stringContainerOut=s; } else { s = m_literal_strings.Get(idx - EEL_STRING_LITERAL_BASE); #ifdef EEL_STRINGS_MUTABLE_LITERALS if (stringContainerOut) *stringContainerOut=s; #else if (stringContainerOut) *stringContainerOut=is_for_write ? NULL : s; #endif } return s ? s->Get() : NULL; } WDL_PtrList m_literal_strings; // "this kind", normally immutable WDL_PtrList m_unnamed_strings; // # WDL_PtrList m_named_strings; // #xyz by index, but stringkeyed below for names WDL_StringKeyedArray m_named_strings_names; // #xyz->index EEL_STRING_STORAGECLASS *m_user_strings[EEL_STRING_MAX_USER_STRINGS]; // indices 0-1023 (etc) WDL_AssocArray m_varname_cache; // cached pointers when using %{xyz}s, %{#xyz}s bypasses NSEEL_VMCTX m_vm; #ifdef EEL_STRING_WANT_MUTEX WDL_Mutex m_mutex; #endif static EEL_F addNamedStringCallback(void *opaque, const char *name) { if (!opaque) return -1.0; eel_string_context_state *_this = EEL_STRING_GET_CONTEXT_POINTER(opaque); if (!_this) return -1.0; #ifdef EEL_STRING_NAMEDSTRINGCALLBACK_HOOK EEL_STRING_NAMEDSTRINGCALLBACK_HOOK #endif EEL_STRING_MUTEXLOCK_SCOPE if (!name || !name[0]) { _this->m_unnamed_strings.Add(new EEL_STRING_STORAGECLASS); return (EEL_F) (_this->m_unnamed_strings.GetSize()-1 + EEL_STRING_UNNAMED_BASE); } int a = _this->m_named_strings_names.Get(name); if (a) return (EEL_F)a; a = _this->m_named_strings.GetSize() + EEL_STRING_NAMED_BASE; _this->m_named_strings.Add(new EEL_STRING_STORAGECLASS); _this->m_named_strings_names.Insert(name,a); return (EEL_F)a; } static EEL_F addStringCallback(void *opaque, struct eelStringSegmentRec *list) { if (!opaque) return -1.0; eel_string_context_state *_this = EEL_STRING_GET_CONTEXT_POINTER(opaque); if (!_this) return -1.0; EEL_STRING_STORAGECLASS *ns = new EEL_STRING_STORAGECLASS; // could probably do a faster implementation using AddRaw() etc but this should also be OK int sz=nseel_stringsegments_tobuf(NULL,0,list); ns->SetLen(sz+32); sz=nseel_stringsegments_tobuf((char *)ns->Get(),sz,list); ns->SetLen(sz); EEL_STRING_MUTEXLOCK_SCOPE return (EEL_F)_this->AddString(ns); } int AddString(EEL_STRING_STORAGECLASS *ns) { #ifdef EEL_STRINGS_MUTABLE_LITERALS m_literal_strings.Add(ns); return m_literal_strings.GetSize()-1+EEL_STRING_LITERAL_BASE; #else const int l = ns->GetLength(); const int sz=m_literal_strings.GetSize(); int x; for (x=0;xGetLength() == l && !strcmp(s->Get(),ns->Get())) break; } if (xm_varname_cache.AddUnsorted(name,val); return 1; } }; static int eel_validate_format_specifier(const char *fmt_in, char *typeOut, char *fmtOut, int fmtOut_sz, char *varOut, int varOut_sz, int *varOut_used ) { const char *fmt = fmt_in+1; int state=0; if (fmt_in[0] != '%') return 0; // ugh passed a non-specifier *varOut_used = 0; *varOut = 0; if (fmtOut_sz-- < 2) return 0; *fmtOut++ = '%'; while (*fmt) { const char c = *fmt++; if (fmtOut_sz < 2) return 0; if (c == 'f'|| c=='e' || c=='E' || c=='g' || c=='G' || c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'c' || c == 'C' || c =='s' || c=='S' || c=='i') { *typeOut = c; fmtOut[0] = c; fmtOut[1] = 0; return (int) (fmt - fmt_in); } else if (c == '.') { *fmtOut++ = c; fmtOut_sz--; if (state&(2)) break; state |= 2; } else if (c == '+') { *fmtOut++ = c; fmtOut_sz--; if (state&(32|16|8|4)) break; state |= 8; } else if (c == '-' || c == ' ') { *fmtOut++ = c; fmtOut_sz--; if (state&(32|16|8|4)) break; state |= 16; } else if (c >= '0' && c <= '9') { *fmtOut++ = c; fmtOut_sz--; state|=4; } else if (c == '{') { if (state & 64) break; state|=64; if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or . while (*fmt != '}') { if ((*fmt >= 'a' && *fmt <= 'z') || (*fmt >= 'A' && *fmt <= 'Z') || (*fmt >= '0' && *fmt <= '9') || *fmt == '_' || *fmt == '.' || *fmt == '#') { if (varOut_sz < 2) return 0; *varOut++ = *fmt++; varOut_sz -- ; } else { return 0; // bad character in variable name } } fmt++; *varOut = 0; *varOut_used=1; } else { break; } } return 0; } int eel_format_strings(void *opaque, const char *fmt, const char *fmt_end, char *buf, int buf_sz, int num_fmt_parms, EEL_F **fmt_parms) { int fmt_parmpos = 0; char *op = buf; while ((fmt_end ? fmt < fmt_end : *fmt) && op < buf+buf_sz-128) { if (fmt[0] == '%' && fmt[1] == '%') { *op++ = '%'; fmt+=2; } else if (fmt[0] == '%') { char ct=0; char fs[128]; char varname[128]; int varname_used=0; const int l=eel_validate_format_specifier(fmt,&ct,fs,sizeof(fs),varname,sizeof(varname),&varname_used); if (!l || !ct) { *op=0; return -1; } EEL_F vv=0.0; const EEL_F *varptr = NULL; if (varname_used) { #ifdef EEL_STRING_GETNAMEDVAR if (varname[0]) varptr=EEL_STRING_GETNAMEDVAR(varname,0,&vv); #endif } else { if (fmt_parmpos < num_fmt_parms) varptr = fmt_parms[fmt_parmpos]; #ifdef EEL_STRING_GETFMTVAR if (!varptr) varptr = EEL_STRING_GETFMTVAR(fmt_parmpos); #endif fmt_parmpos++; } double v = varptr ? (double)*varptr : 0.0; if (ct == 's' || ct=='S') { EEL_STRING_STORAGECLASS *wr=NULL; const char *str = EEL_STRING_GET_FOR_INDEX(v,&wr); const int maxl=(int) (buf+buf_sz - 2 - op); if (wr && !fs[2]) // %s or %S -- todo: implement padding modes for binary compat too? { int wl = wr->GetLength(); if (wl > maxl) wl=maxl; memcpy(op,wr->Get(),wl); op += wl; *op=0; } else { snprintf(op,maxl,fs,str ? str : ""); } } else { if (varptr == &vv) // passed %{#str}d etc, convert to float { const char *str = EEL_STRING_GET_FOR_INDEX(v,NULL); v = str ? atof(str) : 0.0; } if (ct == 'x' || ct == 'X' || ct == 'd' || ct == 'u' || ct=='i') { snprintf(op,64,fs,(int) (v)); } else if (ct == 'c') { *op++=(char) (int)v; *op=0; } else if (ct == 'C') { const unsigned int iv = (unsigned int) v; int bs = 0; if (iv & 0xff000000) bs=24; else if (iv & 0x00ff0000) bs=16; else if (iv & 0x0000ff00) bs=8; while (bs>=0) { const char c=(char) (iv>>bs); *op++=c?c:' '; bs-=8; } *op=0; } else { snprintf(op,64,fs,v); } } while (*op) op++; fmt += l; } else { *op++ = *fmt++; } } *op=0; return (int) (op - buf); } static int eel_string_match(void *opaque, const char *fmt, const char *msg, int match_fmt_pos, int ignorecase, const char *fmt_endptr, const char *msg_endptr, int num_fmt_parms, EEL_F **fmt_parms) { // check for match, updating EEL_STRING_GETFMTVAR(*) as necessary // %d=12345 // %f=12345[.678] // %c=any nonzero char, ascii value // %x=12354ab // %*, %?, %+, %% literals // * ? + match minimal groups of 0+,1, or 1+ chars for (;;) { if (fmt>=fmt_endptr) { if (msg>=msg_endptr) return 1; return 0; // format ends before matching string } // if string ends and format is not on a wildcard, early-out to 0 if (msg>=msg_endptr && *fmt != '*' && *fmt != '%') return 0; switch (*fmt) { case '*': case '+': // if last char of search pattern, we're done! if (fmt+1>=fmt_endptr || (fmt[1] == '?' && fmt+2>=fmt_endptr)) return *fmt == '*' || msg= 0 && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len--; return len >= 0; } break; case '?': fmt++; msg++; break; case '%': { fmt++; unsigned short fmt_minlen = 1, fmt_maxlen = 0; if (*fmt >= '0' && *fmt <= '9') { fmt_minlen = *fmt++ - '0'; while (*fmt >= '0' && *fmt <= '9') fmt_minlen = fmt_minlen * 10 + (*fmt++ - '0'); fmt_maxlen = fmt_minlen; } if (*fmt == '-') { fmt++; fmt_maxlen = 0; while (*fmt >= '0' && *fmt <= '9') fmt_maxlen = fmt_maxlen * 10 + (*fmt++ - '0'); } const char *dest_varname=NULL; if (*fmt == '{') { dest_varname=++fmt; while (*fmt && fmt < fmt_endptr && *fmt != '}') fmt++; if (fmt >= fmt_endptr-1 || *fmt != '}') return 0; // malformed %{var}s fmt++; // skip '}' } char fmt_char = *fmt++; if (!fmt_char) return 0; // malformed if (fmt_char == '*' || fmt_char == '?' || fmt_char == '+' || fmt_char == '%') { if (*msg++ != fmt_char) return 0; } else if (fmt_char == 'c') { EEL_F *varOut = NULL; EEL_F vv=0.0; if (!dest_varname) { if (match_fmt_pos < num_fmt_parms) varOut = fmt_parms[match_fmt_pos]; #ifdef EEL_STRING_GETFMTVAR if (!varOut) varOut = EEL_STRING_GETFMTVAR(match_fmt_pos); #endif match_fmt_pos++; } else { #ifdef EEL_STRING_GETNAMEDVAR char tmp[128]; int idx=0; while (dest_varname < fmt_endptr && *dest_varname && *dest_varname != '}' && idx<(int)sizeof(tmp)-1) tmp[idx++] = *dest_varname++; tmp[idx]=0; if (idx>0) varOut = EEL_STRING_GETNAMEDVAR(tmp,1,&vv); #endif } if (msg >= msg_endptr) return 0; // out of chars if (varOut) { if (varOut == &vv) // %{#foo}c { EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(vv, &wr); if (wr) wr->Set(msg,1); } else { *varOut = (EEL_F)*(unsigned char *)msg; } } msg++; } else { int len=0; int lazy=0; if (fmt_char>='A'&&fmt_char<='Z') { lazy=1; fmt_char += 'a' - 'A'; } if (fmt_char == 's') { len = (int) (msg_endptr-msg); } else if (fmt_char == 'x') { while ((msg[len] >= '0' && msg[len] <= '9') || (msg[len] >= 'A' && msg[len] <= 'F') || (msg[len] >= 'a' && msg[len] <= 'f')) len++; } else if (fmt_char == 'f') { if (msg[len] == '-') len++; while (msg[len] >= '0' && msg[len] <= '9') len++; if (msg[len] == '.') { len++; while (msg[len] >= '0' && msg[len] <= '9') len++; } } else if (fmt_char == 'd' || fmt_char == 'u' || fmt_char == 'i') { if (fmt_char != 'u' && msg[len] == '-') len++; while (msg[len] >= '0' && msg[len] <= '9') len++; } else { // bad format return 0; } if (fmt_maxlen>0 && len > fmt_maxlen) len = fmt_maxlen; if (!dest_varname) match_fmt_pos++; if (lazy) { if (fmt_maxlen<1 || fmt_maxlen>len) fmt_maxlen=len; len=fmt_minlen; while (len <= fmt_maxlen && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len++; if (len > fmt_maxlen) return 0; } else { while (len >= fmt_minlen && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len--; if (len < fmt_minlen) return 0; } EEL_F vv=0.0; EEL_F *varOut = NULL; if (!dest_varname) { if (match_fmt_pos>0 && match_fmt_pos-1 < num_fmt_parms) varOut = fmt_parms[match_fmt_pos-1]; #ifdef EEL_STRING_GETFMTVAR if (!varOut) varOut = EEL_STRING_GETFMTVAR(match_fmt_pos-1); #endif } else { #ifdef EEL_STRING_GETNAMEDVAR char tmp[128]; int idx=0; while (dest_varname < fmt_endptr && *dest_varname && *dest_varname != '}' && idx<(int)sizeof(tmp)-1) tmp[idx++] = *dest_varname++; tmp[idx]=0; if (idx>0) varOut = EEL_STRING_GETNAMEDVAR(tmp,1,&vv); #endif } if (varOut) { if (fmt_char == 's') { EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(*varOut, &wr); if (wr) { if (msg_endptr >= wr->Get() && msg_endptr <= wr->Get() + wr->GetLength()) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("match: destination specifier passed is also haystack, will not update"); #endif } else if (fmt_endptr >= wr->Get() && fmt_endptr <= wr->Get() + wr->GetLength()) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("match: destination specifier passed is also format, will not update"); #endif } else { wr->SetRaw(msg,len); } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("match: bad destination specifier passed as %d: %f",match_fmt_pos,*varOut); #endif } } else { char tmp[128]; lstrcpyn_safe(tmp,msg,wdl_min(len+1,(int)sizeof(tmp))); if (varOut == &vv) { EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(vv, &wr); if (wr) wr->Set(tmp); } else { char *bl=(char*)msg; if (fmt_char == 'u') *varOut = (EEL_F)strtoul(tmp,&bl,10); else if (fmt_char == 'x') *varOut = (EEL_F)strtoul(msg,&bl,16); else *varOut = (EEL_F)atof(tmp); } } } return 1; } } break; default: if (ignorecase ? (toupper(*fmt) != toupper(*msg)) : (*fmt!= *msg)) return 0; fmt++; msg++; break; } } } static EEL_F NSEEL_CGEN_CALL _eel_sprintf(void *opaque, INT_PTR num_param, EEL_F **parms) { if (num_param<2) return 0.0; if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(*(parms[0]), &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("sprintf: bad destination specifier passed %f",*(parms[0])); #endif } else { EEL_STRING_STORAGECLASS *wr_src=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&wr_src); if (fmt) { char buf[16384]; const int fmt_len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()):NULL,buf,(int)sizeof(buf), (int)num_param-2, parms+2); if (fmt_len>=0) { wr->SetRaw(buf,fmt_len); } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("sprintf: bad format string %s",fmt); #endif } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("sprintf: bad format specifier passed %f",*(parms[1])); #endif } } } return *(parms[0]); } static EEL_F NSEEL_CGEN_CALL _eel_strncat(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *maxlen) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scat: bad destination specifier passed %f",maxlen ? "n":"",*strOut); #endif } else { const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); if (fmt||wr_src) { if (wr->GetLength() > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scat: will not grow string since it is already %d bytes",maxlen ? "n":"",wr->GetLength()); #endif } else { int ml=0; if (maxlen && *maxlen > 0) ml = (int)*maxlen; if (wr_src) { EEL_STRING_STORAGECLASS tmp; if (wr_src == wr) *(wr_src=&tmp) = *wr; wr->AppendRaw(wr_src->Get(), ml > 0 && ml < wr_src->GetLength() ? ml : wr_src->GetLength()); } else wr->Append(fmt, ml); } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scat: bad format specifier passed %f",maxlen ? "n":"",*fmt_index); #endif } } } return *strOut; } static EEL_F NSEEL_CGEN_CALL _eel_strcpysubstr(void *opaque, INT_PTR nparm, EEL_F **parms) //EEL_F *strOut, EEL_F *fmt_index, EEL_F *offs { if (opaque && nparm>=3) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; EEL_STRING_GET_FOR_WRITE(parms[0][0], &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("strcpy_substr: bad destination specifier passed %f",parms[0][0]); #endif } else { const char *fmt = EEL_STRING_GET_FOR_INDEX(parms[1][0],&wr_src); if (fmt) { const int fmt_len = wr_src ? wr_src->GetLength() : (int) strlen(fmt); int maxlen, o = (int) parms[2][0]; if (o < 0) { o = fmt_len + o; if (o < 0) o=0; } maxlen = fmt_len - o; if (nparm >= 4) { const int a = (int) parms[3][0]; if (a<0) maxlen += a; else if (a= fmt_len) { wr->Set(""); } else { if (wr_src==wr) { wr->DeleteSub(0,o); if (wr->GetLength() > maxlen) wr->SetLen(maxlen); } else { wr->SetRaw(fmt+o,maxlen); } } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("strcpy_substr: bad format specifier passed %f",parms[1][0]); #endif } } return parms[0][0]; } return 0.0; } static EEL_F NSEEL_CGEN_CALL _eel_strncpy(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *maxlen) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scpy: bad destination specifier passed %f",maxlen ? "n":"",*strOut); #endif } else { const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); if (fmt) { int ml=-1; if (maxlen && *maxlen >= 0) ml = (int)*maxlen; if (wr_src == wr) { if (ml>=0 && ml < wr->GetLength()) wr->SetLen(ml); // shorten string if strncpy(x,x,len) and len >=0 return *strOut; } if (wr_src) wr->SetRaw(fmt, ml>0 && ml < wr_src->GetLength() ? ml : wr_src->GetLength()); else wr->Set(fmt,ml); } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scpy: bad format specifier passed %f",maxlen ? "n":"",*fmt_index); #endif } } } return *strOut; } static EEL_F _eel_strcmp_int(const char *a, int a_len, const char *b, int b_len, int ml, bool ignorecase) { // binary-safe comparison (at least if a_len>=0 etc) int pos = 0; for (;;) { if (ml > 0 && pos == ml) return 0.0; const bool a_end = a_len >= 0 ? pos == a_len : !a[pos]; const bool b_end = b_len >= 0 ? pos == b_len : !b[pos]; if (a_end || b_end) { if (!b_end) return -1.0; // b[pos] is nonzero, a[pos] is zero if (!a_end) return 1.0; return 0.0; } char av = a[pos]; char bv = b[pos]; if (ignorecase) { av=toupper(av); bv=toupper(bv); } if (bv > av) return -1.0; if (av > bv) return 1.0; pos++; } } static EEL_F NSEEL_CGEN_CALL _eel_strncmp(void *opaque, EEL_F *aa, EEL_F *bb, EEL_F *maxlen) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr_a=NULL,*wr_b=NULL; const char *a = EEL_STRING_GET_FOR_INDEX(*aa,&wr_a); const char *b = EEL_STRING_GET_FOR_INDEX(*bb,&wr_b); if (!a || !b) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%scmp: bad specifier(s) passed %f/%f",maxlen ? "n" : "",*aa,*bb); #endif } else { const int ml = maxlen ? (int) *maxlen : -1; if (!ml || a==b) return 0; // strncmp(x,y,0) == 0 return _eel_strcmp_int(a,wr_a ? wr_a->GetLength() : -1,b,wr_b ? wr_b->GetLength() : -1, ml, false); } } return -1.0; } static EEL_F NSEEL_CGEN_CALL _eel_strnicmp(void *opaque, EEL_F *aa, EEL_F *bb, EEL_F *maxlen) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr_a=NULL,*wr_b=NULL; const char *a = EEL_STRING_GET_FOR_INDEX(*aa,&wr_a); const char *b = EEL_STRING_GET_FOR_INDEX(*bb,&wr_b); if (!a || !b) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str%sicmp: bad specifier(s) passed %f/%f",maxlen ? "n" : "",*aa,*bb); #endif } else { const int ml = maxlen ? (int) *maxlen : -1; if (!ml || a==b) return 0; // strncmp(x,y,0) == 0 return _eel_strcmp_int(a,wr_a ? wr_a->GetLength() : -1,b,wr_b ? wr_b->GetLength() : -1, ml, true); } } return -1.0; } static EEL_F NSEEL_CGEN_CALL _eel_strcat(void *opaque, EEL_F *strOut, EEL_F *fmt_index) { return _eel_strncat(opaque,strOut,fmt_index,NULL); } static EEL_F NSEEL_CGEN_CALL _eel_strcpy(void *opaque, EEL_F *strOut, EEL_F *fmt_index) { return _eel_strncpy(opaque,strOut,fmt_index,NULL); } static EEL_F NSEEL_CGEN_CALL _eel_strcmp(void *opaque, EEL_F *strOut, EEL_F *fmt_index) { return _eel_strncmp(opaque,strOut,fmt_index,NULL); } static EEL_F NSEEL_CGEN_CALL _eel_stricmp(void *opaque, EEL_F *strOut, EEL_F *fmt_index) { return _eel_strnicmp(opaque,strOut,fmt_index,NULL); } static EEL_F NSEEL_CGEN_CALL _eel_strgetchar(void *opaque, EEL_F *strOut, EEL_F *idx) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*strOut, &wr); if (!fmt) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_getchar: bad specifier passed %f",*strOut); #endif } else { const int wl=(wr?wr->GetLength():(int)strlen(fmt)); int l = (int) *idx; if (*idx < 0.0) l+=wl; if (l>=0 && l < wl) return ((unsigned char *)fmt)[l]; } } return 0; } #define EEL_GETCHAR_FLAG_ENDIANSWAP 0x10 #define EEL_GETCHAR_FLAG_UNSIGNED 0x20 #define EEL_GETCHAR_FLAG_FLOAT 0x40 static int eel_getchar_flag(int type) { #ifdef __ppc__ int ret=EEL_GETCHAR_FLAG_ENDIANSWAP; // default to LE #else int ret=0; #endif if (toupper((type>>8)&0xff) == 'U') ret|=EEL_GETCHAR_FLAG_UNSIGNED; else if (type>255 && toupper(type&0xff) == 'U') { ret|=EEL_GETCHAR_FLAG_UNSIGNED; type>>=8; } type&=0xff; if (isupper(type)) ret^=EEL_GETCHAR_FLAG_ENDIANSWAP; else type += 'A'-'a'; switch (type) { case 'F': return ret|4|EEL_GETCHAR_FLAG_FLOAT; case 'D': return ret|8|EEL_GETCHAR_FLAG_FLOAT; case 'S': return ret|2; case 'I': return ret|4; } return ret|1; } static void eel_setchar_do(int flag, char *dest, EEL_F val) { union { char buf[8]; float asFloat; double asDouble; int asInt; short asShort; char asChar; unsigned int asUInt; unsigned short asUShort; unsigned char asUChar; } a; const int type_sz=flag&0xf; if (flag & EEL_GETCHAR_FLAG_FLOAT) { if (type_sz==8) a.asDouble=val; else a.asFloat=(float)val; } else if (flag & EEL_GETCHAR_FLAG_UNSIGNED) { if (type_sz==4) a.asUInt=(unsigned int)val; else if (type_sz==2) a.asUShort=(unsigned short)val; else a.asUChar=(unsigned char)val; } else if (type_sz==4) a.asInt=(int)val; else if (type_sz==2) a.asShort=(short)val; else a.asChar=(char)val; if (flag & EEL_GETCHAR_FLAG_ENDIANSWAP) { dest += type_sz; int x; for(x=0;x=3) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(parms[0][0], &wr); if (!fmt) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_getchar: bad specifier passed %f",parms[0][0]); #endif } else { const int wl=(wr?wr->GetLength():(int)strlen(fmt)); const int flags=eel_getchar_flag((int) parms[2][0]); int l = (int) parms[1][0]; if (parms[1][0] < 0.0) l+=wl; if (l>=0 && l <= wl-(flags&0xf)) { return eel_getchar_do(flags,fmt+l); } } } return 0; } static EEL_F NSEEL_CGEN_CALL _eel_strsetchar2(void *opaque, INT_PTR np, EEL_F **parms) { if (opaque && np>=4) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(parms[0][0], &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setchar: bad destination specifier passed %f",parms[0][0]); #endif } else { const int wl=wr->GetLength(); int l = (int) parms[1][0]; if (parms[1][0] < 0.0) l+=wl; if (l>=0 && l <= wl) { const int flags=eel_getchar_flag((int) parms[3][0]); if (l==wl) { if (wl > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setchar: will not grow string since it is already %d bytes",wl); #endif } else { char buf[32]; eel_setchar_do(flags,buf,parms[2][0]); wr->AppendRaw(buf,flags&0xf); } } else eel_setchar_do(flags,(char*)wr->Get()+l,parms[2][0]); } } } return parms[0][0]; } static EEL_F NSEEL_CGEN_CALL _eel_strsetchar(void *opaque, EEL_F *strOut, EEL_F *idx, EEL_F *val) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setchar: bad destination specifier passed %f",*strOut); #endif } else { const int wl=wr->GetLength(); int l = (int) *idx; if (*idx < 0.0) l+=wl; if (l>=0 && l <= wl) { const unsigned char v=((int)*val)&255; if (l==wl) { if (wl > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setchar: will not grow string since it is already %d bytes",wl); #endif } else wr->AppendRaw((const char*)&v,1); } else ((unsigned char *)wr->Get())[l]=v; // allow putting nulls in string, strlen() will still get the full size } } } return *strOut; } static EEL_F NSEEL_CGEN_CALL _eel_strinsert(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *pos) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_insert: bad destination specifier passed %f",*strOut); #endif } else { const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); if (fmt) { EEL_STRING_STORAGECLASS tmp; if (wr_src == wr) *(wr_src=&tmp) = *wr; // insert from copy // if wr_src, fmt is guaranteed to be wr_src.Get() int p = (int)*pos; int insert_l = wr_src ? wr_src->GetLength() : (int)strlen(fmt); if (p < 0) { insert_l += p; // decrease insert_l fmt -= p; // advance fmt -- if fmt gets advanced past NULL term, insert_l will be < 0 anyway p=0; } if (insert_l>0) { if (wr->GetLength() > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_insert: will not grow string since it is already %d bytes",wr->GetLength()); #endif } else { if (wr_src) { wr->InsertRaw(fmt,p, insert_l); } else { wr->Insert(fmt,p); } } } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_insert: bad source specifier passed %f",*fmt_index); #endif } } } return *strOut; } static EEL_F NSEEL_CGEN_CALL _eel_strdelsub(void *opaque, EEL_F *strOut, EEL_F *pos, EEL_F *len) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_delsub: bad destination specifier passed %f",*strOut); #endif } else { int p=(int)*pos; int l=(int)*len; if (p<0) { l+=p; p=0; } if (l>0) wr->DeleteSub(p,l); } } return *strOut; } static EEL_F NSEEL_CGEN_CALL _eel_strsetlen(void *opaque, EEL_F *strOut, EEL_F *newlen) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; EEL_STRING_GET_FOR_WRITE(*strOut, &wr); if (!wr) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setlen: bad destination specifier passed %f",*strOut); #endif } else { int l = (int) *newlen; if (l < 0) l=0; if (l > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("str_setlen: clamping requested length of %d to %d",l,EEL_STRING_MAXUSERSTRING_LENGTH_HINT); #endif l=EEL_STRING_MAXUSERSTRING_LENGTH_HINT; } wr->SetLen(l); } } return *strOut; } static EEL_F NSEEL_CGEN_CALL _eel_strlen(void *opaque, EEL_F *fmt_index) { if (opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr); if (wr) return (EEL_F) wr->GetLength(); if (fmt) return (EEL_F) strlen(fmt); } return 0.0; } static EEL_F NSEEL_CGEN_CALL _eel_printf(void *opaque, INT_PTR num_param, EEL_F **parms) { if (num_param>0 && opaque) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *wr_src=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&wr_src); if (fmt) { char buf[16384]; const int len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()):NULL,buf,(int)sizeof(buf), (int)num_param-1, parms+1); if (len >= 0) { #ifdef EEL_STRING_STDOUT_WRITE EEL_STRING_STDOUT_WRITE(buf,len); #endif return 1.0; } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("printf: bad format string %s",fmt); #endif } } else { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("printf: bad format specifier passed %f",*(parms[0])); #endif } } return 0.0; } static EEL_F NSEEL_CGEN_CALL _eel_match(void *opaque, INT_PTR num_parms, EEL_F **parms) { if (opaque && num_parms >= 2) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *fmt_wr=NULL, *msg_wr=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&fmt_wr); const char *msg = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&msg_wr); if (fmt && msg) return eel_string_match(opaque,fmt,msg,0,0, fmt + (fmt_wr?fmt_wr->GetLength():strlen(fmt)), msg + (msg_wr?msg_wr->GetLength():strlen(msg)),(int)num_parms-2,parms+2) ? 1.0 : 0.0; } return 0.0; } static EEL_F NSEEL_CGEN_CALL _eel_matchi(void *opaque, INT_PTR num_parms, EEL_F **parms) { if (opaque && num_parms >= 2) { EEL_STRING_MUTEXLOCK_SCOPE EEL_STRING_STORAGECLASS *fmt_wr=NULL, *msg_wr=NULL; const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&fmt_wr); const char *msg = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&msg_wr); if (fmt && msg) return eel_string_match(opaque,fmt,msg,0,1, fmt + (fmt_wr?fmt_wr->GetLength():strlen(fmt)), msg + (msg_wr?msg_wr->GetLength():strlen(msg)),(int)num_parms-2,parms+2) ? 1.0 : 0.0; } return 0.0; } void EEL_string_register() { NSEEL_addfunc_retval("strlen",1,NSEEL_PProc_THIS,&_eel_strlen); NSEEL_addfunc_retval("strcat",2,NSEEL_PProc_THIS,&_eel_strcat); NSEEL_addfunc_retval("strcpy",2,NSEEL_PProc_THIS,&_eel_strcpy); NSEEL_addfunc_retval("strcmp",2,NSEEL_PProc_THIS,&_eel_strcmp); NSEEL_addfunc_retval("stricmp",2,NSEEL_PProc_THIS,&_eel_stricmp); NSEEL_addfunc_retval("strncat",3,NSEEL_PProc_THIS,&_eel_strncat); NSEEL_addfunc_retval("strncpy",3,NSEEL_PProc_THIS,&_eel_strncpy); NSEEL_addfunc_retval("strncmp",3,NSEEL_PProc_THIS,&_eel_strncmp); NSEEL_addfunc_retval("strnicmp",3,NSEEL_PProc_THIS,&_eel_strnicmp); NSEEL_addfunc_retval("str_setlen",2,NSEEL_PProc_THIS,&_eel_strsetlen); NSEEL_addfunc_exparms("strcpy_from",3,NSEEL_PProc_THIS,&_eel_strcpysubstr); // alias NSEEL_addfunc_exparms("strcpy_substr",3,NSEEL_PProc_THIS,&_eel_strcpysubstr); // only allow 3 or 4 parms NSEEL_addfunc_exparms("strcpy_substr",4,NSEEL_PProc_THIS,&_eel_strcpysubstr); NSEEL_addfunc_retval("str_getchar",2,NSEEL_PProc_THIS,&_eel_strgetchar); NSEEL_addfunc_retval("str_setchar",3,NSEEL_PProc_THIS,&_eel_strsetchar); NSEEL_addfunc_exparms("str_getchar",3,NSEEL_PProc_THIS,&_eel_strgetchar2); NSEEL_addfunc_exparms("str_setchar",4,NSEEL_PProc_THIS,&_eel_strsetchar2); NSEEL_addfunc_retval("str_insert",3,NSEEL_PProc_THIS,&_eel_strinsert); NSEEL_addfunc_retval("str_delsub",3,NSEEL_PProc_THIS,&_eel_strdelsub); NSEEL_addfunc_varparm("sprintf",2,NSEEL_PProc_THIS,&_eel_sprintf); NSEEL_addfunc_varparm("printf",1,NSEEL_PProc_THIS,&_eel_printf); NSEEL_addfunc_varparm("match",2,NSEEL_PProc_THIS,&_eel_match); NSEEL_addfunc_varparm("matchi",2,NSEEL_PProc_THIS,&_eel_matchi); } void eel_string_initvm(NSEEL_VMCTX vm) { NSEEL_VM_SetStringFunc(vm, eel_string_context_state::addStringCallback, eel_string_context_state::addNamedStringCallback); } #ifdef EEL_WANT_DOCUMENTATION static const char *eel_strings_function_reference = #ifdef EEL_STRING_STDOUT_WRITE "printf\t\"format\"[, ...]\tOutput formatted string to system-specific destination, see sprintf() for more information\0" #endif "sprintf\t#dest,\"format\"[, ...]\tFormats a string and stores it in #dest. Format specifiers begin with %, and may include:\3\n" "\4 %% = %\n" "\4 %s = string from parameter\n" "\4 %d = parameter as integer\n" "\4 %i = parameter as integer\n" "\4 %u = parameter as unsigned integer\n" "\4 %x = parameter as hex (lowercase) integer\n" "\4 %X = parameter as hex (uppercase) integer\n" "\4 %c = parameter as character\n" "\4 %f = parameter as floating point\n" "\4 %e = parameter as floating point (scientific notation, lowercase)\n" "\4 %E = parameter as floating point (scientific notation, uppercase)\n" "\4 %g = parameter as floating point (shortest representation, lowercase)\n" "\4 %G = parameter as floating point (shortest representation, uppercase)\n" "\2\n" "Many standard C printf() modifiers can be used, including:\3\n" "\4 %.10s = string, but only print up to 10 characters\n" "\4 %-10s = string, left justified to 10 characters\n" "\4 %10s = string, right justified to 10 characters\n" "\4 %+f = floating point, always show sign\n" "\4 %.4f = floating point, minimum of 4 digits after decimal point\n" "\4 %10d = integer, minimum of 10 digits (space padded)\n" "\4 %010f = integer, minimum of 10 digits (zero padded)\n" "\2\n" "Values for format specifiers can be specified as additional parameters to sprintf, or within {} in the format specifier (such as %{varname}d, in that case a global variable is always used).\0" "matchi\t\"needle\",\"haystack\"[, ...]\tCase-insensitive version of match().\0" "match\t\"needle\",\"haystack\"[, ...]\tSearches for the first parameter in the second parameter, using a simplified regular expression syntax.\3\n" "\4* = match 0 or more characters\n" "\4*? = match 0 or more characters, lazy\n" "\4+ = match 1 or more characters\n" "\4+? = match 1 or more characters, lazy\n" "\4? = match one character\n" "\2\n" "You can also use format specifiers to match certain types of data, and optionally put that into a variable:\3\n" "\4%s means 1 or more chars\n" "\4%0s means 0 or more chars\n" "\4%5s means exactly 5 chars\n" "\4%5-s means 5 or more chars\n" "\4%-10s means 1-10 chars\n" "\4%3-5s means 3-5 chars\n" "\4%0-5s means 0-5 chars\n" "\4%x, %d, %u, and %f are available for use similarly\n" "\4%c can be used, but can't take any length modifiers\n" "\4Use uppercase (%S, %D, etc) for lazy matching\n" "\2\n" "See also sprintf() for other notes, including specifying direct variable references via {}.\0" "strlen\t\"str\"\tReturns the length of the string passed as a parameter\0" "strcpy\t#str,\"srcstr\"\tCopies the contents of srcstr to #str, and returns #str\0" "strcat\t#str,\"srcstr\"\tAppends srcstr to #str, and returns #str\0" "strcmp\t\"str\",\"str2\"\tCompares strings, returning 0 if equal\0" "stricmp\t\"str\",\"str2\"\tCompares strings ignoring case, returning 0 if equal\0" "strncmp\t\"str\",\"str2\",maxlen\tCompares strings giving up after maxlen characters, returning 0 if equal\0" "strnicmp\t\"str\",\"str2\",maxlen\tCompares strings giving up after maxlen characters, ignoring case, returning 0 if equal\0" "strncpy\t#str,\"srcstr\",maxlen\tCopies srcstr to #str, stopping after maxlen characters. Returns #str.\0" "strncat\t#str,\"srcstr\",maxlen\tAppends srcstr to #str, stopping after maxlen characters of srcstr. Returns #str.\0" "strcpy_from\t#str,\"srcstr\",offset\tCopies srcstr to #str, but starts reading srcstr at offset offset\0" "strcpy_substr\t#str,\"srcstr\",offs,ml)\tPHP-style (start at offs, offs<0 means from end, ml for maxlen, ml<0 = reduce length by this amt)\0" "str_getchar\t\"str\",offset[,type]\tReturns the data at byte-offset offset of str. If offset is negative, position is relative to end of string." "type defaults to signed char, but can be specified to read raw binary data in other formats (note the single quotes, these are single/multi-byte characters):\3\n" "\4'c' - signed char\n" "\4'cu' - unsigned char\n" "\4's' - signed short\n" "\4'S' - signed short, big endian\n" "\4'su' - unsigned short\n" "\4'Su' - unsigned short, big endian\n" "\4'i' - signed int\n" "\4'I' - signed int, big endian\n" "\4'iu' - unsigned int\n" "\4'Iu' - unsigned int, big endian\n" "\4'f' - float\n" "\4'F' - float, big endian\n" "\4'd' - double\n" "\4'D' - double, big endian\n" "\2\0" "str_setchar\t#str,offset,val[,type])\tSets value at offset offset, type optional. offset may be negative to refer to offset relative to end of string, or between 0 and length, inclusive, and if set to length it will lengthen string. See str_getchar() for more information on types.\0" "str_setlen\t#str,len\tSets length of #str (if increasing, will be space-padded), and returns #str.\0" "str_delsub\t#str,pos,len\tDeletes len characters at offset pos from #str, and returns #str.\0" "str_insert\t#str,\"srcstr\",pos\tInserts srcstr into #str at offset pos. Returns #str\0" ; #endif #endif