// This file is a part of the Framsticks GDK. // Copyright (C) 1999-2014 Maciej Komosinski and Szymon Ulatowski. See LICENSE.txt for details. // Refer to http://www.framsticks.com/ for further information. #include "extvalue.h" #include #include "sstringutils.h" #include #include #include #include #include #ifndef NO_BARRIER #include #include #endif #ifdef MULTITHREADED #include //this lock only protects against ref.counter corruption caused by concurrent reads. //read/write conficts and nonatomicity are handled by BarrierObject (at least in theory ;-)) static pthread_mutex_t extobject_ref_lock=PTHREAD_MUTEX_INITIALIZER; #define REF_LOCK pthread_mutex_lock(&extobject_ref_lock) #define REF_UNLOCK pthread_mutex_unlock(&extobject_ref_lock) #else #define REF_LOCK #define REF_UNLOCK #endif void ExtObject::incref() const { if (subtype&1) { REF_LOCK; dbobject->refcount++; REF_UNLOCK; } } void ExtObject::decref() const { if (subtype&1) { REF_LOCK; bool destroy=!--dbobject->refcount; REF_UNLOCK; //another thread can now access the object while we are deleting it //but this is not a bug since we only guarantee read/read safety if (destroy) delete dbobject; } } bool ExtObject::makeUnique() { if (!(subtype&1)) return false; if (dbobject->refcount==1) return false; VectorObject* v=VectorObject::fromObject(*this,false); if (v) { VectorObject* n=new VectorObject; n->data.setSize(n->data.size()); for(int i=0;idata.size();i++) { ExtValue *x=(ExtValue*)v->data(i); n->data.set(i,x?new ExtValue(*x):NULL); } operator=(n->makeObject()); return true; } return false; } void* ExtObject::getTarget(const char* classname, bool through_barrier, bool warn) const { if (!strcmp(interfaceName(),classname)) return getTarget(); #ifndef NO_BARRIER if (through_barrier) { BarrierObject *bo=BarrierObject::fromObject(*this); if (bo) return bo->getSourceObject().getTarget(classname,true,warn); } #endif if (warn) { FMprintf("ExtValue","getObjectTarget",FMLV_WARN,"%s object expected, %s found",classname,interfaceName()); } return NULL; } SString ExtObject::toString() const { if (isEmpty()) return SString(""); Param tmp_param; ParamInterface *p=getParamInterface(tmp_param); int tostr=p->findId("toString"); if (tostr>=0) { return SString(p->getString(tostr)); } else { SString tmp("<"); tmp+=p->getName(); tmp+=SString::sprintf(" object at %p>",object?object:paraminterface); return tmp; } } THREAD_LOCAL_DEF(ExtObject::Serialization,ExtObject::serialization); void ExtObject::Serialization::begin() { if (level==0) refs.clear(); level++; } int ExtObject::Serialization::add(const ExtObject &o) { if (o.isEmpty()) return -1; for(int i=0;i<(int)refs.size();i++) { ExtObject& r=refs[i]; if (r==o) return i; } refs.push_back(o); return -1; } void ExtObject::Serialization::replace(const ExtObject& o,const ExtObject& other) { if (o.isEmpty()) return; for(int i=0;i<(int)refs.size();i++) { ExtObject& r=refs[i]; if (r==o) { r=other; return; } } } void ExtObject::Serialization::remove(const ExtObject& o) { if (o.isEmpty()) return; for(int i=0;i<(int)refs.size();i++) { ExtObject& r=refs[i]; if (o==r) refs.erase(refs.begin()+i); } } const ExtObject* ExtObject::Serialization::get(int ref) { if (ref<0) return NULL; if (ref>=(int)refs.size()) return NULL; return &refs[ref]; } void ExtObject::Serialization::end() { level--; if (level==0) refs.clear(); } SString ExtObject::serialize_inner() const { int ref=tlsGetRef(serialization).add(*this); if (ref>=0) return SString::sprintf("^%d",ref); if (isEmpty()) return SString(); VectorObject *vec=VectorObject::fromObject(*this,false); if (vec) return vec->serialize(); DictionaryObject *dic=DictionaryObject::fromObject(*this,false); if (dic) return dic->serialize(); Param tmp_param; ParamInterface *p=getParamInterface(tmp_param); int m=p->findId("toVector"); if (m<0) m=p->findId("toDictionary"); if (m>=0) { ExtObject o(p->getObject(m)); SString ret=SString(interfaceName())+o.serialize(); return ret; } m=p->findId("toString"); if (m>=0) { SString str=p->getString(m); sstringQuote(str); SString ret=SString(interfaceName())+"\""+str+"\""; return ret; } tlsGetRef(serialization).remove(*this);//undo nonserializable reference SString ret=interfaceName(); ret+=SString::sprintf("<%p>",object?object:paraminterface); return ret; } SString ExtObject::serialize() const { tlsGetRef(serialization).begin(); SString ret=serialize_inner(); tlsGetRef(serialization).end(); return ret; } /////////////////////////////////////// void *ExtValue::getObjectTarget(const char* classname,bool warn) const { if (type!=TObj) { if (warn) { SString tmp=getString(); if (tmp.len()>30) tmp=tmp.substr(0,30)+"..."; if (type==TString) tmp=SString("\"")+tmp+SString("\""); FMprintf("ExtValue","getObjectTarget",FMLV_WARN,"%s object expected, %s found",classname,(const char*)tmp); } return NULL; } return getObject().getTarget(classname,true,warn); } void ExtValue::set(const ExtValue& src) { switch(src.type) { case TString: sets(src.sdata()); break; case TInt: seti(src.idata()); break; case TDouble: setd(src.ddata()); break; case TObj: seto(src.odata()); break; default:type=src.type; break; } } void ExtValue::setEmpty() { switch(type) { #ifdef EXTVALUEUNION case TString: sdata().~SString(); break; case TObj: odata().~ExtObject(); break; #else case TString: delete s; break; case TObj: delete o; break; #endif default:; } type=TUnknown; } static long longsign(long x) { if (x<0) return -1; if (x>0) return 1; return 0; } static long compareNull(const ExtValue& v) { switch(v.type) { case TDouble: return v.getDouble()!=0.0; case TInt: return v.getInt()?1:0; case TString: return 1; default: return !v.isNull(); } } long ExtValue::compare(const ExtValue& src) const { if (type==TUnknown) return compareNull(src); else if (src.type==TUnknown) return compareNull(*this); switch(type) { case TInt: { long t=src.getInt(); if (idata()>0) {if (t>0) return longsign(idata()-t); else return +1;} else {if (t<=0) return longsign(idata()-t); else return -1;} } case TDouble: { double t=ddata()-src.getDouble(); if (t<0) return -1; else if (t>0) return 1; return 0; } case TString: { SString t=src.getString(); SString& t2=sdata(); const char* s1=(const char*)t2; const char* s2=(const char*)t; return longsign(strcmp(s1,s2)); } case TObj: { if (src.type==TObj) return !(odata()==src.odata()); return 1; } default:; } return 1; } int ExtValue::operator==(const ExtValue& src) const { if (type!=src.type) return 0; switch(type) { case TInt: return idata()==src.idata(); case TDouble: return ddata()==src.ddata(); case TString: return sdata()==src.sdata(); case TObj: return odata()==src.odata(); default:; } return 1; } void ExtValue::operator+=(const ExtValue& src) { switch(type) { case TInt: idata()+=src.getInt(); break; case TDouble: ddata()+=src.getDouble(); break; case TString: sdata()+=src.getString(); break; case TObj: { VectorObject *vec=VectorObject::fromObject(getObject(),false); VectorObject *vec2=VectorObject::fromObject(src.getObject(),false); if (vec && vec2) { for(int i=0;idata.size();i++) { ExtValue *s=(ExtValue*)vec2->data(i); ExtValue *d=s?new ExtValue(*s):NULL; vec->data+=d; } } } default:; } } void ExtValue::operator-=(const ExtValue& src) { switch(type) { case TInt: idata()-=src.getInt(); break; case TDouble: ddata()-=src.getDouble(); break; default:; } } void ExtValue::operator*=(const ExtValue& src) { switch(type) { case TInt: idata()*=src.getInt(); break; case TDouble: ddata()*=src.getDouble(); break; case TString: { SString t; for(int n=src.getInt();n>0;n--) t+=getString(); setString(t); break; } case TObj: { VectorObject *vec=VectorObject::fromObject(getObject(),false); if (vec) { int n=src.getInt(); int orig_size=vec->data.size(); if (n<=0) {vec->clear();return;} for(;n>1;n--) { for(int i=0;idata(i); ExtValue *d=s?new ExtValue(*s):NULL; vec->data+=d; } } } break; } default:; } } #include /*#include "fpu_control.h" #include static int fpuexception; void mathhandler(int sig) { printf("fpu exception!\n"); fpuexception=1; signal(SIGFPE,SIG_IGN); } */ void ExtValue::operator/=(const ExtValue& src) { switch(type) { case TInt: { int a=src.getInt(); // idata()/=src.getInt(); if (a) idata()/=a; else {FMprintf("ExtValue","divide",FMLV_CRITICAL,"%d/0",idata()); setInvalid();} } break; case TDouble: { double d=src.getDouble(); if (d==0.0) { FMprintf("ExtValue","divide",FMLV_CRITICAL,"%s/0.0",(const char*)getString()); setInvalid(); } else { fpExceptDisable(); double tmp=ddata()/d; if (!finite(tmp)) { FMprintf("ExtValue","divide",FMLV_CRITICAL,"overflow %s/%s",(const char*)getString(),(const char*)src.getString()); setInvalid(); } else ddata()=tmp; // niby dobrze ale lepiej byloby to robic bardziej systematycznie a nie tylko w dzieleniu? //if (isnan(ddata())) //http://www.digitalmars.com/d/archives/c++/Traping_divide_by_zero_5728.html // { FMprintf("ExtValue","divide",FMLV_ERROR,"not-a-number",(const char*)getString()); setInvalid(); } fpExceptEnable(); } } break; default:; } } SString ExtValue::format(SString& fmt,const ExtValue **values,int count) { SString ret; // "..........%.........%..........%........" // ^_cur ^_next // ^^^^^^^^^^___sub // // "..........%.........%..........%........" // ^-cur ^-next // ^^^^^^^^^^___sub const char* begin=(const char*)fmt, *end=begin+fmt.len(), *curr=begin; int type=0; class Args { const ExtValue **values; int count; int arg; public: Args(const ExtValue **v,int c):values(v),count(c),arg(0) {} bool finished() {return arg>=count;} const ExtValue *getNext() {const ExtValue *ret=NULL; if ((argbegin)) {next=strchr(next+1,'%'); if (!next) next=end;} type=0; if (curr>begin) { type=0; for(const char* t=curr;tbegin) {type=*t; t=next;} break; } } if (curr>begin) curr--; const ExtValue *a; if (args.finished() && (type!=0) && (type!='%')) { ret+=fmt.substr(curr-begin); break; } SString sub=fmt.substr(curr-begin,next-curr); switch(type) { case 'd': a=args.getNext(); ret+=SString::sprintf((const char*)sub,a?a->getInt():0); break; case 'f': a=args.getNext(); ret+=SString::sprintf((const char*)sub,a?a->getDouble():0); break; case 's': {a=args.getNext(); SString tmp; if (a) tmp=a->getString(); ret+=SString::sprintf((const char*)sub,(const char*)tmp);} break; case 't': case 'T': { a=args.getNext(); time_t ti=a?a->getInt():0; struct tm tm=Convert::localtime(ti); SString timtxt; if (type=='T') timtxt=SString::sprintf("%04d-%02d-%02d %02d:%02d:%02d",1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec); else timtxt=Convert::asctime(tm).c_str(); ret+=timtxt; ret+=sub.substr(2); } break; case '%': ret+='%'; ret+=sub.substr(2); break; case 0: ret+=sub; break; } curr=next+1; } return ret; } void ExtValue::operator%=(const ExtValue& src) { switch(type) { case TInt: idata()=idata()%src.getInt(); break; case TDouble: ddata()=fmod(ddata(),src.getDouble()); break; case TString: { VectorObject *vec=VectorObject::fromObject(src.getObject(),false); if (vec) sdata()=format(sdata(),(const ExtValue**)&vec->data.getref(0),vec->data.size()); else {const ExtValue *ptr=&src; sdata()=ExtValue::format(sdata(),&ptr,1);} } break; default:; } } long ExtValue::getInt(const char* s) { if ((s[0]=='0')&&(s[1]=='x')) { long val; sscanf(s+2,"%lx",&val); return val; } else { if (strchr(s,'e')||(strchr(s,'E'))) return (long)atof(s); else return atol(s); } } double ExtValue::getDouble(const char* s) { if ((s[0]=='0')&&(s[1]=='x')) { long val; sscanf(s+2,"%lx",&val); return val; } else return atof(s); } long ExtValue::getInt() const { switch(type) { case TInt: return idata(); case TDouble: return (int)ddata(); case TString: return getInt((const char*)sdata()); case TObj: FMprintf("ExtValue","getInt",FMLV_WARN,"Getting integer value from object reference (%s)",(const char*)getString()); return (long)odata().param; default:; } return 0; } double ExtValue::getDouble() const { switch(type) { case TDouble: return ddata(); case TInt: return (double)idata(); case TString: return getDouble((const char*)sdata()); case TObj: FMprintf("ExtValue","getDouble",FMLV_WARN,"Getting floating point value from object reference (%s)",(const char*)getString()); return (double)(long)odata().param; default:; } return 0.0; } SString ExtValue::getString() const { switch(type) { case TString: return sdata(); case TInt: return SString::valueOf(idata()); case TDouble: return SString::valueOf(ddata()); case TObj: return odata().toString(); case TInvalid: return SString("invalid"); default: return SString("null"); } } const SString* ExtValue::getStringPtr() const { if (type==TString) return &sdata(); return NULL; } SString ExtValue::serialize() const { switch(type) { case TString: { SString q=sdata(); sstringQuote(q); return SString("\"")+q+SString("\""); } case TInt: return SString::valueOf(idata()); case TDouble: return SString::valueOf(ddata()); case TObj: return odata().serialize(); case TInvalid: return SString("invalid"); default: return SString("null"); } } //returns the first character after the parsed number or NULL if not a number const char* ExtValue::parseNumber(const char* in) { if (isdigit(*in)||((*in=='-')&&(isdigit(in[1])))) { const char* p=in; if (*p=='-') p++; while(isdigit(*p)) p++; bool fp=false; if ((*p=='.') && isdigit(p[1])) { p++; while(isdigit(*p)) p++; fp=true; } if (((*p=='e')||(*p=='E')) && (isdigit(p[1]) || (((p[1]=='-') || (p[1]=='+')) && isdigit(p[2])))) { p++; if ((*p=='-')||(*p=='+')) p++; while(isdigit(*p)) p++; fp=true; } if (fp) { setDouble(atof(in)); return p; } else { setInt(atol(in)); return p; } } return NULL; } PtrListTempl ExtValue::deserializable_classes; void ExtValue::initDeserializableClasses() { deserializable_classes+=&Pt3D_Ext::getStaticParam(); deserializable_classes+=&Orient_Ext::getStaticParam(); } ParamInterface *ExtValue::findDeserializableClass(const char* name) { FOREACH(ParamInterface*,cls,deserializable_classes) if (!strcmp(cls->getName(),name)) return cls; return NULL; } static const char* skipWord(const char* in) { while(isalpha(*in)||(*in=='_')) in++; return in; } //returns the first character after the parsed portion or NULL if invalid format const char* ExtValue::deserialize_inner(const char* in) { const char* ret=parseNumber(in); if (ret) return ret; else if (*in=='\"') { ret=skipQuoteString(in+1,NULL); SString s(in+1,ret-(in+1)); sstringUnquote(s); setString(s); if (*ret=='\"') return ret+1; else return NULL; } else if (*in=='[') { VectorObject *vec=new VectorObject; ExtObject o(&VectorObject::par,vec); tlsGetRef(ExtObject::serialization).add(o); const char* p=in+1; ExtValue tmp; while(*p) { if (*p==']') {p++;break;} ret=tmp.deserialize(p); if (ret) { vec->data+=new ExtValue(tmp); p=ret; if (*p==',') p++; } else { p=NULL; break; } } setObject(o); return p; } else if (*in=='{') { DictionaryObject *dic=new DictionaryObject; ExtObject o(&DictionaryObject::par,dic); tlsGetRef(ExtObject::serialization).add(o); const char* p=in+1; ExtValue args[2]/*={value,key}*/, dummy_ret; while(*p) { if (*p=='}') {p++;break;} ret=args[1].deserialize(p); if ((!ret)||(args[1].getType()!=TString)) {p=NULL;break;} p=ret; if (*p!=':') {p=NULL;break;} p++; ret=args[0].deserialize(p); if (!ret) {p=NULL;break;} p=ret; dic->p_set(args,&dummy_ret); if (*p==',') p++; } setObject(o); return p; } else if (!strncmp(in,"null",4)) { setEmpty(); return in+4; } else if (!strncmp(in,"invalid",9)) { setInvalid(); return in+9; } else if (*in=='<') { //unserializable object setInvalid(); while(*in) if (*in=='>') return in+1; else in++; return in; } else if (*in=='^') { in++; ExtValue ref; ret=ref.parseNumber(in); if (ret && (ref.getType()==TInt)) { const ExtObject* o=tlsGetRef(ExtObject::serialization).get(ref.getInt()); if (o) { setObject(*o); return ret; } } return NULL; } else if ((ret=skipWord(in))&&(ret!=in)) { SString clsname(in,ret-in); ExtValue tmp; ret=tmp.deserialize(ret); ParamInterface *cls=findDeserializableClass(clsname); if (cls && (tmp.getType()!=TUnknown) && (tmp.getType()!=TInvalid)) { VectorObject *vec=VectorObject::fromObject(tmp.getObject(),false); if (vec) { int m=cls->findId("newFromVector"); if (m>=0) { cls->call(m,&tmp,this); tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject()); return ret; } } DictionaryObject *dic=DictionaryObject::fromObject(tmp.getObject(),false); if (dic) { int m=cls->findId("newFromDictionary"); if (m>=0) { cls->call(m,&tmp,this); tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject()); return ret; } } if (tmp.getType()==TString) { int m=cls->findId("newFromString"); if (m>=0) { cls->call(m,&tmp,this); tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject()); return ret; } } tlsGetRef(ExtObject::serialization).remove(tmp.getObject()); setEmpty(); } setEmpty(); FMprintf("ExtValue","deserialize",FMLV_WARN,"object of class \"%s\" could not be deserialized",(const char*)clsname); return ret; } setEmpty(); return NULL; } const char* ExtValue::deserialize(const char* in) { tlsGetRef(ExtObject::serialization).begin(); const char* ret=deserialize_inner(in); tlsGetRef(ExtObject::serialization).end(); return ret; } ExtObject ExtValue::getObject() const { if (type==TObj) return odata(); return ExtObject(); } ExtValue ExtValue::getExtType() { if (getType()!=TObj) return ExtValue((long)getType()); ExtObject& o=odata(); return ExtValue(SString(o.isEmpty()?"":o.interfaceName())); } SString SString::valueOf(const ExtValue& v) { return v.getString(); } SString SString::valueOf(const ExtObject& v) { return v.toString(); }